Skip to content

Commit a47e794

Browse files
committed
feat(clone): reuse repository cache for branch pulls
1 parent f961baa commit a47e794

5 files changed

Lines changed: 111 additions & 29 deletions

File tree

.kanban/changes/ABC-5/tasks.md

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,28 @@
22

33
## Implementation
44

5-
- [ ] Update `packages/lib/src/core/templates-entrypoint/tasks.ts` clone-cache flow.
6-
- [ ] Apply the same template update to `packages/app/src/lib/core/templates-entrypoint/tasks.ts`.
7-
- [ ] Keep cache roots under `/home/<sshUser>/.docker-git/.cache`.
8-
- [ ] Preserve existing auth-label and token handling.
9-
- [ ] Preserve existing fork remote behavior after clone.
10-
- [ ] Preserve existing clone completion markers.
11-
- [ ] Ensure cache paths are ignored by state repo sync if new cache directories are added.
5+
- [x] Update `packages/lib/src/core/templates-entrypoint/tasks.ts` clone-cache flow.
6+
- [x] Apply the same template update to `packages/app/src/lib/core/templates-entrypoint/tasks.ts`.
7+
- [x] Keep cache roots under `/home/<sshUser>/.docker-git/.cache`.
8+
- [x] Preserve existing auth-label and token handling.
9+
- [x] Preserve existing fork remote behavior after clone.
10+
- [x] Preserve existing clone completion markers.
11+
- [x] Ensure cache paths are ignored by state repo sync if new cache directories are added.
1212

1313
## Tests
1414

15-
- [ ] Add or extend template tests for cache initialization, refresh, and reuse markers.
16-
- [ ] Add or extend template tests for branch, issue branch, GitHub PR ref, and GitLab MR ref behavior.
17-
- [ ] Extend `scripts/e2e/clone-cache.sh` to verify same repository cache reuse across two clones.
18-
- [ ] Verify second clone uses cache and ends on the requested branch.
19-
- [ ] Verify cache artifacts are not tracked by the state repository.
15+
- [x] Add or extend template tests for cache initialization, refresh, and reuse markers.
16+
- [x] Add or extend template tests for branch, issue branch, GitHub PR ref, and GitLab MR ref behavior.
17+
- [x] Extend `scripts/e2e/clone-cache.sh` to verify same repository cache reuse across two clones.
18+
- [x] Verify second clone uses cache and ends on the requested branch.
19+
- [x] Verify cache artifacts are not tracked by the state repository.
2020

2121
## Verification Commands
2222

23-
- [ ] `bun run --filter @effect-template/lib test`
24-
- [ ] `bun run --filter @prover-coder-ai/docker-git test`
25-
- [ ] `bun run typecheck`
26-
- [ ] `bun run e2e:clone-cache`
23+
- [x] `bun run --filter @effect-template/lib test`
24+
- [x] `bun run --filter @prover-coder-ai/docker-git test`
25+
- [x] `bun run typecheck`
26+
- [ ] `bun run e2e:clone-cache` (blocked locally: docker-git host CLI cannot auto-discover controller over remote `DOCKER_HOST=tcp://host.docker.internal:2375`)
2727

2828
## Out of Scope
2929

packages/app/src/lib/core/templates-entrypoint/tasks.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ const cloneCacheRefreshRefspecs = "'+refs/heads/*:refs/heads/*' '+refs/tags/*:re
130130

131131
const renderCloneCacheInit = (config: TemplateConfig): string =>
132132
` CLONE_CACHE_ARGS=""
133+
CLONE_SOURCE_REPO="$AUTH_REPO_URL"
134+
CLONE_USED_CACHE=0
133135
CACHE_REPO_DIR=""
134136
CACHE_ROOT="/home/${config.sshUser}/.docker-git/.cache/git-mirrors"
135137
if command -v sha256sum >/dev/null 2>&1; then
@@ -150,6 +152,8 @@ const renderCloneCacheInit = (config: TemplateConfig): string =>
150152
echo "[clone-cache] mirror refresh failed for $REPO_URL"
151153
fi
152154
CLONE_CACHE_ARGS="--reference-if-able '$CACHE_REPO_DIR' --dissociate"
155+
CLONE_SOURCE_REPO="$CACHE_REPO_DIR"
156+
CLONE_USED_CACHE=1
153157
echo "[clone-cache] using mirror: $CACHE_REPO_DIR"
154158
else
155159
echo "[clone-cache] invalid mirror removed: $CACHE_REPO_DIR"
@@ -170,19 +174,19 @@ const renderCloneBodyRef = (config: TemplateConfig): string =>
170174
String.raw` if [[ -n "$REPO_REF" ]]; then
171175
if [[ "$REPO_REF" == refs/pull/* || "$REPO_REF" == refs/merge-requests/* ]]; then
172176
REF_BRANCH="$(printf "%s" "$REPO_REF" | sed -E 's#^refs/pull/([^/]+)/head$#pr-\1#; s#^refs/merge-requests/([^/]+)/head$#mr-\1#')"
173-
if ! su - ${config.sshUser} -c "GIT_TERMINAL_PROMPT=0 git clone --progress $CLONE_CACHE_ARGS '$AUTH_REPO_URL' '$TARGET_DIR'"; then
177+
if ! su - ${config.sshUser} -c "GIT_TERMINAL_PROMPT=0 git clone --progress $CLONE_CACHE_ARGS '$CLONE_SOURCE_REPO' '$TARGET_DIR'"; then
174178
echo "[clone] git clone failed for $REPO_URL"
175179
CLONE_OK=0
176180
else
177-
if ! su - ${config.sshUser} -c "cd '$TARGET_DIR' && GIT_TERMINAL_PROMPT=0 git fetch --progress origin '$REPO_REF':'$REF_BRANCH' && git checkout '$REF_BRANCH'"; then
181+
if ! su - ${config.sshUser} -c "cd '$TARGET_DIR' && git remote set-url origin '$AUTH_REPO_URL' && GIT_TERMINAL_PROMPT=0 git fetch --progress origin '$REPO_REF':'$REF_BRANCH' && git checkout '$REF_BRANCH'"; then
178182
echo "[clone] git fetch failed for $REPO_REF"
179183
CLONE_OK=0
180184
fi
181185
fi
182186
else
183-
if ! su - ${config.sshUser} -c "GIT_TERMINAL_PROMPT=0 git clone --progress $CLONE_CACHE_ARGS --branch '$REPO_REF' '$AUTH_REPO_URL' '$TARGET_DIR'"; then
187+
if ! su - ${config.sshUser} -c "GIT_TERMINAL_PROMPT=0 git clone --progress $CLONE_CACHE_ARGS --branch '$REPO_REF' '$CLONE_SOURCE_REPO' '$TARGET_DIR'"; then
184188
echo "[clone] branch '$REPO_REF' missing; retrying without --branch"
185-
if ! su - ${config.sshUser} -c "GIT_TERMINAL_PROMPT=0 git clone --progress $CLONE_CACHE_ARGS '$AUTH_REPO_URL' '$TARGET_DIR'"; then
189+
if ! su - ${config.sshUser} -c "GIT_TERMINAL_PROMPT=0 git clone --progress $CLONE_CACHE_ARGS '$CLONE_SOURCE_REPO' '$TARGET_DIR'"; then
186190
echo "[clone] git clone failed for $REPO_URL"
187191
CLONE_OK=0
188192
elif [[ "$REPO_REF" == issue-* ]]; then
@@ -191,12 +195,26 @@ const renderCloneBodyRef = (config: TemplateConfig): string =>
191195
CLONE_OK=0
192196
fi
193197
fi
198+
elif [[ "$CLONE_USED_CACHE" == "1" ]]; then
199+
if ! su - ${config.sshUser} -c "cd '$TARGET_DIR' && git remote set-url origin '$AUTH_REPO_URL' && GIT_TERMINAL_PROMPT=0 git pull --ff-only origin '$REPO_REF'"; then
200+
echo "[clone-cache] git pull failed for $REPO_REF"
201+
CLONE_OK=0
202+
else
203+
echo "[clone-cache] pulled branch: $REPO_REF"
204+
fi
194205
fi
195206
fi
196207
else
197-
if ! su - ${config.sshUser} -c "GIT_TERMINAL_PROMPT=0 git clone --progress $CLONE_CACHE_ARGS '$AUTH_REPO_URL' '$TARGET_DIR'"; then
208+
if ! su - ${config.sshUser} -c "GIT_TERMINAL_PROMPT=0 git clone --progress $CLONE_CACHE_ARGS '$CLONE_SOURCE_REPO' '$TARGET_DIR'"; then
198209
echo "[clone] git clone failed for $REPO_URL"
199210
CLONE_OK=0
211+
elif [[ "$CLONE_USED_CACHE" == "1" ]]; then
212+
if ! su - ${config.sshUser} -c "cd '$TARGET_DIR' && git remote set-url origin '$AUTH_REPO_URL' && GIT_TERMINAL_PROMPT=0 git pull --ff-only"; then
213+
echo "[clone-cache] git pull failed for default branch"
214+
CLONE_OK=0
215+
else
216+
echo "[clone-cache] pulled default branch"
217+
fi
200218
fi
201219
fi`
202220

packages/lib/src/core/templates-entrypoint/tasks.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ const cloneCacheRefreshRefspecs = "'+refs/heads/*:refs/heads/*' '+refs/tags/*:re
130130

131131
const renderCloneCacheInit = (config: TemplateConfig): string =>
132132
` CLONE_CACHE_ARGS=""
133+
CLONE_SOURCE_REPO="$AUTH_REPO_URL"
134+
CLONE_USED_CACHE=0
133135
CACHE_REPO_DIR=""
134136
CACHE_ROOT="/home/${config.sshUser}/.docker-git/.cache/git-mirrors"
135137
if command -v sha256sum >/dev/null 2>&1; then
@@ -150,6 +152,8 @@ const renderCloneCacheInit = (config: TemplateConfig): string =>
150152
echo "[clone-cache] mirror refresh failed for $REPO_URL"
151153
fi
152154
CLONE_CACHE_ARGS="--reference-if-able '$CACHE_REPO_DIR' --dissociate"
155+
CLONE_SOURCE_REPO="$CACHE_REPO_DIR"
156+
CLONE_USED_CACHE=1
153157
echo "[clone-cache] using mirror: $CACHE_REPO_DIR"
154158
else
155159
echo "[clone-cache] invalid mirror removed: $CACHE_REPO_DIR"
@@ -170,19 +174,19 @@ const renderCloneBodyRef = (config: TemplateConfig): string =>
170174
String.raw` if [[ -n "$REPO_REF" ]]; then
171175
if [[ "$REPO_REF" == refs/pull/* || "$REPO_REF" == refs/merge-requests/* ]]; then
172176
REF_BRANCH="$(printf "%s" "$REPO_REF" | sed -E 's#^refs/pull/([^/]+)/head$#pr-\1#; s#^refs/merge-requests/([^/]+)/head$#mr-\1#')"
173-
if ! su - ${config.sshUser} -c "GIT_TERMINAL_PROMPT=0 git clone --progress $CLONE_CACHE_ARGS '$AUTH_REPO_URL' '$TARGET_DIR'"; then
177+
if ! su - ${config.sshUser} -c "GIT_TERMINAL_PROMPT=0 git clone --progress $CLONE_CACHE_ARGS '$CLONE_SOURCE_REPO' '$TARGET_DIR'"; then
174178
echo "[clone] git clone failed for $REPO_URL"
175179
CLONE_OK=0
176180
else
177-
if ! su - ${config.sshUser} -c "cd '$TARGET_DIR' && GIT_TERMINAL_PROMPT=0 git fetch --progress origin '$REPO_REF':'$REF_BRANCH' && git checkout '$REF_BRANCH'"; then
181+
if ! su - ${config.sshUser} -c "cd '$TARGET_DIR' && git remote set-url origin '$AUTH_REPO_URL' && GIT_TERMINAL_PROMPT=0 git fetch --progress origin '$REPO_REF':'$REF_BRANCH' && git checkout '$REF_BRANCH'"; then
178182
echo "[clone] git fetch failed for $REPO_REF"
179183
CLONE_OK=0
180184
fi
181185
fi
182186
else
183-
if ! su - ${config.sshUser} -c "GIT_TERMINAL_PROMPT=0 git clone --progress $CLONE_CACHE_ARGS --branch '$REPO_REF' '$AUTH_REPO_URL' '$TARGET_DIR'"; then
187+
if ! su - ${config.sshUser} -c "GIT_TERMINAL_PROMPT=0 git clone --progress $CLONE_CACHE_ARGS --branch '$REPO_REF' '$CLONE_SOURCE_REPO' '$TARGET_DIR'"; then
184188
echo "[clone] branch '$REPO_REF' missing; retrying without --branch"
185-
if ! su - ${config.sshUser} -c "GIT_TERMINAL_PROMPT=0 git clone --progress $CLONE_CACHE_ARGS '$AUTH_REPO_URL' '$TARGET_DIR'"; then
189+
if ! su - ${config.sshUser} -c "GIT_TERMINAL_PROMPT=0 git clone --progress $CLONE_CACHE_ARGS '$CLONE_SOURCE_REPO' '$TARGET_DIR'"; then
186190
echo "[clone] git clone failed for $REPO_URL"
187191
CLONE_OK=0
188192
elif [[ "$REPO_REF" == issue-* ]]; then
@@ -191,12 +195,26 @@ const renderCloneBodyRef = (config: TemplateConfig): string =>
191195
CLONE_OK=0
192196
fi
193197
fi
198+
elif [[ "$CLONE_USED_CACHE" == "1" ]]; then
199+
if ! su - ${config.sshUser} -c "cd '$TARGET_DIR' && git remote set-url origin '$AUTH_REPO_URL' && GIT_TERMINAL_PROMPT=0 git pull --ff-only origin '$REPO_REF'"; then
200+
echo "[clone-cache] git pull failed for $REPO_REF"
201+
CLONE_OK=0
202+
else
203+
echo "[clone-cache] pulled branch: $REPO_REF"
204+
fi
194205
fi
195206
fi
196207
else
197-
if ! su - ${config.sshUser} -c "GIT_TERMINAL_PROMPT=0 git clone --progress $CLONE_CACHE_ARGS '$AUTH_REPO_URL' '$TARGET_DIR'"; then
208+
if ! su - ${config.sshUser} -c "GIT_TERMINAL_PROMPT=0 git clone --progress $CLONE_CACHE_ARGS '$CLONE_SOURCE_REPO' '$TARGET_DIR'"; then
198209
echo "[clone] git clone failed for $REPO_URL"
199210
CLONE_OK=0
211+
elif [[ "$CLONE_USED_CACHE" == "1" ]]; then
212+
if ! su - ${config.sshUser} -c "cd '$TARGET_DIR' && git remote set-url origin '$AUTH_REPO_URL' && GIT_TERMINAL_PROMPT=0 git pull --ff-only"; then
213+
echo "[clone-cache] git pull failed for default branch"
214+
CLONE_OK=0
215+
else
216+
echo "[clone-cache] pulled default branch"
217+
fi
200218
fi
201219
fi`
202220

packages/lib/tests/core/templates.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,50 @@ describe("renderEntrypoint clone cache", () => {
306306
expect(entrypoint).not.toContain("'+refs/merge-requests/*:refs/merge-requests/*'")
307307
})
308308

309+
it("uses refreshed mirrors as clone source and pulls cached branch clones", () => {
310+
const entrypoint = renderEntrypoint(makeTemplateConfig())
311+
312+
expectContainsAll(entrypoint, [
313+
'CLONE_SOURCE_REPO="$AUTH_REPO_URL"',
314+
"CLONE_SOURCE_REPO=\"$CACHE_REPO_DIR\"",
315+
"CLONE_USED_CACHE=1",
316+
"git clone --progress $CLONE_CACHE_ARGS --branch '$REPO_REF' '$CLONE_SOURCE_REPO' '$TARGET_DIR'",
317+
"git remote set-url origin '$AUTH_REPO_URL' && GIT_TERMINAL_PROMPT=0 git pull --ff-only origin '$REPO_REF'",
318+
'echo "[clone-cache] pulled branch: $REPO_REF"'
319+
])
320+
})
321+
322+
it("keeps issue refs as local branches after fallback clone", () => {
323+
const entrypoint = renderEntrypoint(makeTemplateConfig())
324+
325+
expectContainsAll(entrypoint, [
326+
'elif [[ "$REPO_REF" == issue-* ]]; then',
327+
"cd '$TARGET_DIR' && git checkout -B '$REPO_REF'",
328+
'echo "[clone] failed to create local branch \'$REPO_REF\'"'
329+
])
330+
})
331+
332+
it("keeps PR and merge request refs as explicit fetches after cached clone", () => {
333+
const entrypoint = renderEntrypoint(makeTemplateConfig())
334+
335+
expect(entrypoint).toContain(
336+
"git clone --progress $CLONE_CACHE_ARGS '$CLONE_SOURCE_REPO' '$TARGET_DIR'"
337+
)
338+
expect(entrypoint).toContain(
339+
"git remote set-url origin '$AUTH_REPO_URL' && GIT_TERMINAL_PROMPT=0 git fetch --progress origin '$REPO_REF':'$REF_BRANCH' && git checkout '$REF_BRANCH'"
340+
)
341+
})
342+
343+
it("pulls the remote default branch when cloning from a warm cache without repoRef", () => {
344+
const entrypoint = renderEntrypoint(makeTemplateConfig())
345+
346+
expectContainsAll(entrypoint, [
347+
"git clone --progress $CLONE_CACHE_ARGS '$CLONE_SOURCE_REPO' '$TARGET_DIR'",
348+
"git remote set-url origin '$AUTH_REPO_URL' && GIT_TERMINAL_PROMPT=0 git pull --ff-only",
349+
'echo "[clone-cache] pulled default branch"'
350+
])
351+
})
352+
309353
it("preserves branch/tag-only clone-cache refspecs for generated configs", () => {
310354
fc.assert(
311355
fc.property(generatedTemplateConfigArbitrary, (config) => {

scripts/e2e/clone-cache.sh

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ export DOCKER_GIT_PROJECTS_ROOT="$ROOT"
2626
export DOCKER_GIT_STATE_AUTO_PULL=0
2727
export DOCKER_GIT_STATE_AUTO_SYNC=0
2828

29-
REPO_URL="https://github.com/octocat/Hello-World/issues/1"
30-
TARGET_DIR="/home/dev/workspaces/octocat/hello-world/issue-1"
29+
REPO_URL="https://github.com/octocat/Hello-World/tree/master"
30+
TARGET_DIR="/home/dev/workspaces/octocat/hello-world"
3131
MIRROR_PREFIX="/home/dev/.docker-git/.cache/git-mirrors"
3232

3333
ACTIVE_OUT_DIR=""
@@ -174,7 +174,7 @@ EOF_ENV
174174

175175
local branch
176176
branch="$(dg_project_docker exec -u dev "$container_name" bash -lc "cd '$TARGET_DIR' && git rev-parse --abbrev-ref HEAD")"
177-
[[ "$branch" == "issue-1" ]] || fail "expected branch issue-1, got: $branch"
177+
[[ "$branch" == "master" ]] || fail "expected branch master, got: $branch"
178178

179179
if [[ "$expect_cache_use" == "1" ]]; then
180180
if [[ -n "$expected_mirror_name" ]]; then
@@ -184,6 +184,8 @@ EOF_ENV
184184
grep -Fq -- "[clone-cache] using mirror: $MIRROR_PREFIX/" "$log_path" \
185185
|| fail "expected cache reuse log in second clone"
186186
fi
187+
grep -Fq -- "[clone-cache] pulled branch: master" "$log_path" \
188+
|| fail "expected branch pull from warm cache in second clone"
187189
else
188190
grep -Fq -- "[clone-cache] mirror created: $MIRROR_PREFIX/" "$log_path" \
189191
|| grep -Fq -- "[clone-cache] using mirror: $MIRROR_PREFIX/" "$log_path" \

0 commit comments

Comments
 (0)