Skip to content

Commit 8a7b31a

Browse files
Merge pull request #10 from Awesome-Embedded-Learning-Studio/fix/issue-8-rkbin-submodule-init
fix: clean up the some bugs
2 parents a3f2936 + 4f0be45 commit 8a7b31a

9 files changed

Lines changed: 258 additions & 42 deletions

File tree

QUICK_START.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,24 @@
88
./scripts/doctor.sh
99
```
1010

11-
探测交叉工具链`arm-linux-gnueabihf`)和构建依赖、检测 WSL2,缺什么就打印精确的 `sudo apt install ...`**绝不自动安装**(保持脚本可被 Python 包裹)。
11+
探测 host 构建依赖 + 交叉工具链`arm-none-linux-gnueabihf`,即 Arm GNU Toolchain)、检测 WSL2,缺什么就打印精确的 `sudo apt install ...`**绝不自动安装**(保持脚本可被 Python 包裹)。
1212

13-
全新 WSL2 环境通常需要:
13+
> **交叉工具链不是 apt 包装的**:本项目的交叉编译器是 **Arm GNU Toolchain 15.2.Rel1**(前缀 `arm-none-linux-gnueabihf-`),手动装到 `/opt`(路径以 [config/toolchain.conf](config/toolchain.conf) 为准,从 <https://developer.arm.com/downloads> 下载)。Debian 的 `gcc-arm-linux-gnueabihf`**另一把**工具链(前缀不同、gcc 版本不同),装了也不满足。下面的 apt 行只管 **host** 构建依赖。
14+
15+
全新 WSL2 环境通常需要(**仅 host 依赖**):
1416

1517
```bash
16-
sudo apt install gcc-arm-linux-gnueabihf device-tree-compiler bison flex cpio \
17-
u-boot-tools libssl-dev libncurses-dev python3-pyelftools
18+
sudo apt install device-tree-compiler bison flex cpio libssl-dev libncurses-dev \
19+
python3-pyelftools mtd-utils gdisk
20+
# mtd-utils → pack-ubifs 的 mkfs.ubifs/ubinize;gdisk → pack-sd 的 sgdisk
1821
```
1922

2023
> 本项目深度 WSL2 友好(Mirrored 网络模式可直通开发板,USB 设备直通用于烧录/串口)。
2124
2225
## 2. 导出工具链环境(每个 shell)
2326

2427
```bash
25-
source scripts/env-setup.sh # 设置 ARCH=arm, CROSS_COMPILE=arm-linux-gnueabihf-
28+
source scripts/env-setup.sh # 设置 ARCH=arm, CROSS_COMPILE=arm-none-linux-gnueabihf-
2629
```
2730

2831
## 3. 一条命令构建
@@ -34,7 +37,7 @@ bash scripts/forge.sh all # setup → build → pack → assemble(默认
3437
`forge` 是单一入口编排器,按 DAG 跑各 stage,输入没变的 stage 用内容哈希跳过。常用子命令:
3538

3639
```bash
37-
bash scripts/forge.sh setup # 拉源码树 + WiFi 驱动 + 应用补丁库(git am)
40+
bash scripts/forge.sh setup # 初始化 rkbin submodule + 拉源码树 + WiFi 驱动 + 应用补丁库(git am)
3841
bash scripts/forge.sh build # 编 kernel(uboot / buildroot 单独触发,会打印命令)
3942
bash scripts/forge.sh pack # 打 loader + FIT + stage/ubifs rootfs
4043
bash scripts/forge.sh pack-sd # 打可启动 SD 卡镜像(复用 NAND pack 产物)

README.en.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ The pre-U-Boot stages (DDR / secure) still borrow the `rkbin` blob — a hard re
6464

6565
```bash
6666
./scripts/doctor.sh # check host deps + the armhf cross toolchain (prints the apt line if missing)
67-
source scripts/env-setup.sh # export ARCH=arm / CROSS_COMPILE=arm-linux-gnueabihf-
67+
source scripts/env-setup.sh # export ARCH=arm / CROSS_COMPILE=arm-none-linux-gnueabihf-
6868
bash scripts/forge.sh all # setup → build → pack → assemble → board/aes/out/update.img
6969
```
7070

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ rk-forge 服务**没人服务的 RK3506**:把最新主线 Linux 跑起来,
6464

6565
```bash
6666
./scripts/doctor.sh # 检查 host 依赖 + armhf 交叉工具链(缺啥会给 apt 命令)
67-
source scripts/env-setup.sh # 导出 ARCH=arm / CROSS_COMPILE=arm-linux-gnueabihf-
67+
source scripts/env-setup.sh # 导出 ARCH=arm / CROSS_COMPILE=arm-none-linux-gnueabihf-
6868
bash scripts/forge.sh all # setup → build → pack → assemble → board/aes/out/update.img
6969
```
7070

board/aes/initramfs/README.md

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,26 @@
1-
# initramfs(主线 Linux 交互 shell 用)
1+
# initramfs(首启置备 ramdisk)
22

3-
最小 initramfs:静态 busybox + /init,塞进 kernel FIT 当 ramdisk 节点。主线 Linux
4-
7.0.12 在 RK3506B 上跑 `/init` 进交互 shell(见 logs/boot-sdl-202606151234.txt,
5-
结尾 `~ #`)。无 rootfs/mmc/nand 依赖,纯 ramdisk。
3+
首启置备 initramfs:静态 busybox + `/init` + `ubiprog`,塞进 kernel FIT 当 ramdisk
4+
节点。首启时 `/init` 用 ubiprog 经内核可靠写路径重写 SPI-NAND rootfs 分区(绕开 rkbin
5+
loader 对部分擦除块的弱写),打 marker 后续启动直接 switch_root 到真正 buildroot
6+
rootfs。详见 `init` 注释 + document/notes/26。
67

7-
## 构建 busybox(静态,armhf)
8+
## 生成(真相源 = forge stage)
9+
10+
**不要再手敲。** `scripts/build-initramfs.sh` 是这个 cpio 的唯一生成器,`forge pack`
11+
会作为 `build-initramfs` stage 自动跑(pack-fit 之前)。从 tracked/pinned 源全可复现:
12+
13+
- busybox ← `pins/busybox`(上游 1.36.1 tarball,sha256 锁),用 forge 工具链(gcc 15.2)静态编
14+
- ubiprog ← `board/aes/rootfs/ubiprog.c`(tracked),静态编
15+
- /init ← `board/aes/initramfs/init`(tracked)
16+
17+
输出 `board/aes/fit/initramfs.cpio.gz`(pack-fit 读这个路径;.gitignore 正确忽略这个
18+
**生成产物**)。单独跑:`bash scripts/build-initramfs.sh`
19+
20+
下面的手敲食谱是**历史参考**(早先靠 vendor-sdk 的 busybox + ATK gcc 10.3 手搓),
21+
现在被生成器取代——别照它做,它漏了 ubiprog 且依赖 gitignored 的 vendor-sdk。
22+
23+
## 构建 busybox(静态,armhf)——历史参考,勿用
824

925
源码用 vendor buildroot 自带的 `busybox-1.36.1.tar.bz2`(无需外部下载):
1026

pins/busybox

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# pins/busybox — source tarball for the provisioning initramfs's static busybox.
2+
# Downloaded + sha256-verified by scripts/build-initramfs.sh (the initramfs
3+
# generator). Format: <url> <sha256> (# = comment).
4+
#
5+
# 1.36.1 is the version the board-verified initramfs.cpio.gz was built from.
6+
# It builds CLEAN + static under the forge toolchain (Arm GNU gcc 15.2,
7+
# arm-none-linux-gnueabihf) — verified to produce a byte-size-identical busybox
8+
# (1414692 B) to the hand-built original, so the generator reproduces the
9+
# provisioning ramdisk without the ATK vendor-sdk gcc 10.3 the old manual
10+
# recipe (board/aes/initramfs/README.md) depended on.
11+
https://busybox.net/downloads/busybox-1.36.1.tar.bz2 b8cc24c9574d809e7279c3be349795c5d5ceb6fdf19ca709f80cde50e47de314

scripts/build-initramfs.sh

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
#!/usr/bin/env bash
2+
# build-initramfs.sh — generate the first-boot provisioning initramfs.cpio.gz
3+
# from tracked/pinned sources (the in-forge generator; replaces the hand-built,
4+
# never-committed blob that was issue-class with #6/#8).
5+
#
6+
# The provisioning initramfs is what boot.img's ramdisk node carries: a static
7+
# busybox shell + /init + ubiprog. On FIRST boot /init rewrites the SPI-NAND
8+
# rootfs partition through the kernel's reliable write path (ubiprog), working
9+
# around the rkbin loader's weak programming of some erase blocks (PEBs 3/4…).
10+
# A marker file makes later boots skip the rewrite and switch_root to the real
11+
# buildroot rootfs. See board/aes/initramfs/init + document/notes/26.
12+
#
13+
# Reproducibility: everything is built from source under the forge toolchain
14+
# (Arm GNU gcc 15.2, arm-none-linux-gnueabihf) — NO ATK vendor-sdk dependency:
15+
# busybox ← pins/busybox (upstream 1.36.1 tarball, sha256-pinned) — static
16+
# ubiprog ← board/aes/rootfs/ubiprog.c (tracked) — static
17+
# /init ← board/aes/initramfs/init (tracked)
18+
# gcc 15.2 builds busybox 1.36.1 clean + static and reproduces the hand-built
19+
# original byte-for-byte on SIZE (busybox 1414692 B, ubiprog 357400 B).
20+
#
21+
# Output: ${BRINGUP}/fit/initramfs.cpio.gz (the path pack-fit.sh reads; correctly
22+
# gitignored as a regenerated artifact, NOT a missing input). Idempotent: skips
23+
# the cpio repack when busybox + ubiprog + /init are unchanged.
24+
#
25+
# Usage:
26+
# scripts/build-initramfs.sh [--out <cpio.gz>] [--clean]
27+
# --out <path> output cpio.gz (default: board/aes/fit/initramfs.cpio.gz)
28+
# --clean force a full rebuild (rm busybox build + cpio)
29+
# Seam: bash-first; arg parsing leaves a Python seam for later.
30+
set -euo pipefail
31+
_SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
32+
# shellcheck disable=SC1091
33+
source "${_SCRIPT_DIR}/lib/env.sh" # PROJECT_ROOT + BRINGUP + SRC_DIR
34+
# shellcheck disable=SC1091
35+
source "${_SCRIPT_DIR}/lib/log.sh"
36+
# shellcheck disable=SC1091
37+
source "${_SCRIPT_DIR}/lib/toolchain.sh" # CROSS_COMPILE / ARCH / TOOLCHAIN_BIN_DIR
38+
39+
OUT_CPIO="${BRINGUP}/fit/initramfs.cpio.gz"
40+
CLEAN=0
41+
while [[ $# -gt 0 ]]; do
42+
case "$1" in
43+
--out) OUT_CPIO="$2"; shift 2;;
44+
--clean) CLEAN=1; shift;;
45+
-h|--help) sed -n '2,30p' "$0"; exit 0;;
46+
*) die "unknown arg: $1";;
47+
esac
48+
done
49+
50+
check_toolchain || die "toolchain not on PATH. Run: source scripts/env-setup.sh"
51+
CC="${CROSS_COMPILE}gcc"
52+
# Reproducibility: pin the build timestamp so two builds converge (busybox embeds
53+
# a build date; gcc respects SOURCE_DATE_EPOCH). gzip -n (below) drops the gzip
54+
# header mtime. Not byte-identical to the hand-built original (that used ATK gcc
55+
# 10.3) but functionally + size-equivalent and rebuildable from source.
56+
export SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:-0}"
57+
58+
# --- inputs (all tracked/pinned in-repo) ---
59+
PIN="${PROJECT_ROOT}/pins/busybox"
60+
[[ -f "$PIN" ]] || die "busybox pin missing: $PIN"
61+
read -r BB_URL BB_SHA256 < <(grep -vE '^[[:space:]]*#' "$PIN" | grep -vE '^[[:space:]]*$' | head -1)
62+
[[ -n "$BB_URL" && -n "$BB_SHA256" ]] || die "busybox pin malformed (want '<url> <sha256>'): $PIN"
63+
BB_VER="busybox-1.36.1" # matches the pinned tarball's top-level dir
64+
BB_SRC="${PROJECT_ROOT}/${SRC_DIR:-third_party/src}/${BB_VER}"
65+
UBIPROG_SRC="${BRINGUP}/rootfs/ubiprog.c"
66+
INIT_SRC="${BRINGUP}/initramfs/init"
67+
for f in "$UBIPROG_SRC" "$INIT_SRC"; do
68+
[[ -f "$f" ]] || die "initramfs source missing: $f"
69+
done
70+
71+
# --- ensure busybox source: download + sha256-verify + extract (idempotent) ---
72+
ensure_busybox_source() {
73+
[[ -d "$BB_SRC" ]] && return 0
74+
local tarball="${BB_SRC}.tar.bz2"
75+
mkdir -p "$(dirname "$BB_SRC")"
76+
log_info "downloading $BB_VER ($BB_URL)"
77+
# forge standard: curl first (web-access-via-curl memory), wget fallback
78+
if ! curl -fL --retry 3 --connect-timeout 30 -o "$tarball" "$BB_URL" 2>/dev/null; then
79+
wget -q --tries=3 --timeout=30 -O "$tarball" "$BB_URL"
80+
fi
81+
log_info "verifying sha256 ($BB_SHA256)"
82+
echo "$BB_SHA256 $tarball" | sha256sum -c - >/dev/null \
83+
|| { rm -f "$tarball"; die "busybox tarball sha256 mismatch ($BB_URL). pin may be wrong."; }
84+
log_info "extracting"
85+
tar xf "$tarball" -C "$(dirname "$BB_SRC")"
86+
rm -f "$tarball"
87+
[[ -d "$BB_SRC" ]] || die "busybox extract did not create $BB_SRC"
88+
}
89+
90+
# --- build static busybox (in-tree; incremental) ---
91+
build_busybox() {
92+
local bb="$BB_SRC/busybox"
93+
# force-rebuild path (--clean or missing binary)
94+
if [[ "$CLEAN" == 1 ]]; then rm -f "$bb" "$BB_SRC/.config"; fi
95+
[[ -x "$bb" ]] && return 0
96+
( cd "$BB_SRC"
97+
[[ -f .config ]] || make ARCH="$ARCH" CROSS_COMPILE="$CROSS_COMPILE" defconfig >/dev/null
98+
# static link (no libc in the ramdisk). sed is idempotent.
99+
sed -i 's/^# CONFIG_STATIC is not set$/CONFIG_STATIC=y/' .config
100+
grep -q 'CONFIG_STATIC=y' .config || echo 'CONFIG_STATIC=y' >> .config
101+
log_info "building static busybox (gcc 15.2)…"
102+
make ARCH="$ARCH" CROSS_COMPILE="$CROSS_COMPILE" -j"$(nproc)" >/dev/null
103+
)
104+
[[ -x "$bb" ]] || die "busybox build produced no binary ($BB_SRC)"
105+
file "$bb" | grep -q 'statically linked' || die "busybox is NOT static (CONFIG_STATIC=y?)"
106+
}
107+
108+
# --- assemble the ramdisk tree + pack cpio.gz ---
109+
pack_cpio() {
110+
local root
111+
root=$(mktemp -d)
112+
trap 'rm -rf "$root"' RETURN
113+
# dir layout (matches the board-verified cpio)
114+
mkdir -p "$root"/{bin,sbin,proc,sys,dev,etc,usr/bin,usr/sbin,tmp,root}
115+
# static binaries
116+
cp "$BB_SRC/busybox" "$root/bin/busybox"; chmod +x "$root/bin/busybox"
117+
log_info "building static ubiprog (ubiprog.c)…"
118+
"$CC" -static -O2 -s "$UBIPROG_SRC" -o "$root/bin/ubiprog"
119+
# minimal pre-created applet symlinks (/init calls the rest via /bin/busybox
120+
# full-path or after `busybox --install -s` at boot). Matches the original.
121+
local a
122+
for a in sh mount umount ls cat echo uname mkdir ps dmesg pwd vi; do
123+
ln -sf busybox "$root/bin/$a"
124+
done
125+
# /init (the provisioning script)
126+
cp "$INIT_SRC" "$root/init"; chmod +x "$root/init"
127+
128+
mkdir -p "$(dirname "$OUT_CPIO")"
129+
log_info "packing cpio.gz → $OUT_CPIO"
130+
( cd "$root" && find . | cpio -o -H newc --quiet ) | gzip -9 -n > "$OUT_CPIO"
131+
log_ok "initramfs.cpio.gz → $OUT_CPIO ($(stat -c%s "$OUT_CPIO") B)"
132+
log_ok " busybox=$(stat -c%s "$root/bin/busybox") B ubiprog=$(stat -c%s "$root/bin/ubiprog") B"
133+
}
134+
135+
ensure_busybox_source
136+
build_busybox
137+
pack_cpio
138+
log_info "next: scripts/forge.sh pack (pack-fit incbin's it into boot.img)"

scripts/doctor.sh

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,21 @@ check_cmd() { command -v "$1" >/dev/null 2>&1; }
1717

1818
printf '== rk-forge doctor ==\n' >&2
1919

20-
# host build essentials
21-
for c in git make gcc bc bison flex dtc cpio qemu-system-arm mkimage; do
20+
# host build essentials actually used by forge (mkimage comes from the U-Boot
21+
# tree, NOT the host; qemu-system-arm isn't used — both were over-cautious
22+
# carryovers). mkfs.ubifs/ubinize (pack-ubifs) + sgdisk (pack-sd) ARE needed.
23+
for c in git make gcc bc bison flex dtc cpio mkfs.ubifs ubinize sgdisk; do
2224
if check_cmd "$c"; then log_ok "$c"; else log_warn "missing: $c"; MISSING+=("$c"); fi
2325
done
2426

25-
# cross toolchain
27+
# cross toolchain (Arm GNU Toolchain — NOT an apt package; don't add to the apt
28+
# MISSING list, print a separate remediation instead)
29+
TC_MISSING=0
2630
if check_toolchain; then log_ok "toolchain: ${CROSS_COMPILE}gcc"; else
27-
log_warn "missing cross toolchain: ${CROSS_COMPILE}gcc"; MISSING+=("gcc-arm-linux-gnueabihf")
31+
log_warn "missing cross toolchain: ${CROSS_COMPILE}gcc (Arm GNU Toolchain — NOT apt)"
32+
log_warn " install Arm GNU 15.2.Rel1 so \${TOOLCHAIN_BIN_DIR} (config/toolchain.conf) exists"
33+
log_warn " download: https://developer.arm.com/downloads → GNU Toolchain"
34+
TC_MISSING=1
2835
fi
2936

3037
# python helper for kernel build scripts
@@ -37,28 +44,31 @@ if grep -qi microsoft /proc/version 2>/dev/null; then
3744
log_info "WSL2 detected: USB flashing (rkdeveloptool) needs usbipd-win on Windows; SD-card flashing works directly."
3845
fi
3946

40-
if [[ ${#MISSING[@]} -eq 0 ]]; then
47+
if [[ ${#MISSING[@]} -eq 0 && "$TC_MISSING" == 0 ]]; then
4148
log_ok "all dependencies present"
4249
exit 0
4350
fi
4451

45-
# command -> Debian/Ubuntu package mapping
52+
# command -> Debian/Ubuntu package mapping (host deps only; the cross toolchain
53+
# is Arm GNU, handled separately above — not apt-installable)
4654
declare -A PKG=(
47-
[dtc]=device-tree-compiler [mkimage]=u-boot-tools [qemu-system-arm]=qemu-system-arm
55+
[dtc]=device-tree-compiler [mkfs.ubifs]=mtd-utils [ubinize]=mtd-utils [sgdisk]=gdisk
4856
[bison]=bison [flex]=flex [cpio]=cpio [bc]=bc [git]=git [make]=make [gcc]=gcc
4957
)
5058
APT=(); seen=()
51-
for m in "${MISSING[@]}"; do
52-
pkg="${PKG[$m]:-$m}"
53-
for s in "${seen[@]:-}"; do [[ "$s" == "$pkg" ]] && continue 2; done
54-
seen+=("$pkg"); APT+=("$pkg")
55-
done
56-
# libs the kernel/u-boot builds always want
57-
for extra in libssl-dev libncurses-dev; do
58-
for s in "${seen[@]:-}"; do [[ "$s" == "$extra" ]] && continue 2; done
59-
APT+=("$extra")
60-
done
61-
62-
printf '\nFix with:\n' >&2
63-
printf 'sudo apt install %s\n' "${APT[*]}" # to stdout — copy-pasteable / Python-capturable
59+
if [[ ${#MISSING[@]} -gt 0 ]]; then
60+
for m in "${MISSING[@]}"; do
61+
pkg="${PKG[$m]:-$m}"
62+
for s in "${seen[@]:-}"; do [[ "$s" == "$pkg" ]] && continue 2; done
63+
seen+=("$pkg"); APT+=("$pkg")
64+
done
65+
# libs the kernel/u-boot builds always want
66+
for extra in libssl-dev libncurses-dev; do
67+
for s in "${seen[@]:-}"; do [[ "$s" == "$extra" ]] && continue 2; done
68+
APT+=("$extra")
69+
done
70+
printf '\nFix host deps with:\n' >&2
71+
printf 'sudo apt install %s\n' "${APT[*]}" # to stdout — copy-pasteable / Python-capturable
72+
fi
73+
[[ "$TC_MISSING" == 1 ]] && printf '\n(cross toolchain: see the Arm GNU note above, not apt)\n' >&2
6474
exit 1

scripts/fetch-deps.sh

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,21 @@ declare -A TARGET=(
3030
[buildroot]="$BUILDROOT"
3131
)
3232

33+
# retry a git clone across transient network failures (huge repos over flaky
34+
# networks: observed the linux-stable full clone die mid-transfer with
35+
# `curl 56 GnuTLS recv error` / `fetch-pack: unexpected disconnect`). Retries
36+
# with linear backoff; returns the clone's exit code on final failure.
37+
git_clone_retry() { # <clone-args...>
38+
local tries=3 i=1
39+
while (( i <= tries )); do
40+
if git clone "$@" 2>/dev/null; then return 0; fi
41+
(( i < tries )) && log_warn "git clone failed (attempt $i/$tries); retrying in $(( i * 3 ))s…"
42+
(( i < tries )) && sleep $(( i * 3 ))
43+
(( i++ ))
44+
done
45+
return 1
46+
}
47+
3348
fetch_one() { # <name>
3449
local name="$1" pin_file url ref target
3550
pin_file="${PROJECT_ROOT}/pins/${name}"
@@ -43,14 +58,18 @@ fetch_one() { # <name>
4358
return 0
4459
fi
4560
mkdir -p "$(dirname "$target")"
46-
# --branch works for tags/branches; for a bare SHA (not a named ref) it fails
47-
# and we fall back to a default-branch clone + checkout.
48-
if git clone --branch "$ref" "$url" "$target" 2>/dev/null; then
61+
# --branch works for tags/branches → clone shallow (--depth 1): a full clone
62+
# of linux-stable is multi-GB and routinely dies on flaky networks, and we
63+
# don't need history (only the pinned ref the patch series applies onto).
64+
# For a bare SHA (not a named ref) --branch fails → fall back to a full clone
65+
# + checkout (also retried). Either path: no history is fine — apply-series
66+
# `git am`s patches onto the resolved HEAD, which is present in a shallow clone.
67+
if git_clone_retry --depth 1 --branch "$ref" "$url" "$target"; then
4968
:
5069
else
51-
log_info "$name: '$ref' is not a named ref — full clone + checkout"
52-
git clone "$url" "$target"
53-
git -C "$target" checkout "$ref"
70+
log_info "$name: '$ref' not a named ref (or shallow clone failed) — full clone + checkout (retried)"
71+
git_clone_retry "$url" "$target" || die "$name: git clone failed after retries ($url)"
72+
git -C "$target" checkout "$ref" || die "$name: checkout $ref failed (pin wrong?)"
5473
fi
5574
log_ok "$name @ $ref$target"
5675
}

0 commit comments

Comments
 (0)