summaryrefslogtreecommitdiffstats
path: root/subcmds
diff options
context:
space:
mode:
authorGavin Mak <gavinmak@google.com>2026-09-03 22:55:57 +0000
committergerrit-scoped@luci-project-accounts.iam.gserviceaccount.com <gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com>2026-09-15 12:53:01 -0700
commita468ea7752cddd67fa2242ef038ac96f53c35a6a (patch)
tree42dd5085036f44d3559a992ee12b465850e5c095 /subcmds
parentc2c330ba4a92c915f0180457f0d7ee29031bb253 (diff)
downloadgit-repo-a468ea7752cddd67fa2242ef038ac96f53c35a6a.tar.gz
sync: implement reprojectcmd for the local half
Add `repo.reprojectcmd`, the checkout counterpart to `repo.fetchcmd`. When set, `repo sync` runs this command instead of Git to materialize a project's index and worktree at the target tree, while `repo` handles ref updates directly via `git update-ref`. This replaces Git's tree materialization steps: detaching HEAD, fast-forwarding, and hard-resetting. Rebasing is not delegated, and the command is skipped if HEAD is already at the target, if HEAD is ahead of the target during fast-forward, or for MetaProjects. The command runs in a subshell with project environment variables (such as `REPO_TREV`). Before running, `repo` ensures no operation is in progress and no staged changes exist. Worktree collision detection is delegated to the command (preserving benign unstaged/untracked edits). Afterward, `repo` verifies that HEAD was untouched and that the index matches the target tree. Like `repo.fetchcmd`, this requires `repo.uselocalgitdirs`. Nested projects and submodules are unsupported; `repo sync` fails if the manifest contains any while `repo.reprojectcmd` is enabled. Verified end-to-end with repo init using local-gitdirs, repo.fetchcmd, and repo.reprojectcmd ('git -C $REPO_PATH read-tree -m -u $REPO_TREV'): * Verified detached HEAD checkout and correct reflog generation across projects. * Verified benign unstaged edits and untracked files survive checkout. * Verified conflicting untracked files fail with exit 128 without clobbering worktree. * Verified staged changes fail upfront before reprojectcmd is executed. Bug: 513329573 Change-Id: I964d24d22dccffc05a9b991ad69f3a7e93268c01 Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/626281 Tested-by: Gavin Mak <gavinmak@google.com> Reviewed-by: Brian Gan <brgan@google.com> Commit-Queue: Gavin Mak <gavinmak@google.com>
Diffstat (limited to 'subcmds')
-rw-r--r--subcmds/sync.py47
1 files changed, 47 insertions, 0 deletions
diff --git a/subcmds/sync.py b/subcmds/sync.py
index d88ec883e..c51ecf385 100644
--- a/subcmds/sync.py
+++ b/subcmds/sync.py
@@ -72,6 +72,7 @@ from git_refs import HEAD
72from git_refs import R_HEADS 72from git_refs import R_HEADS
73import git_superproject 73import git_superproject
74from hooks import RepoHook 74from hooks import RepoHook
75from manifest_xml import XmlManifest
75import platform_utils 76import platform_utils
76from progress import elapsed_str 77from progress import elapsed_str
77from progress import jobs_str 78from progress import jobs_str
@@ -156,6 +157,11 @@ def _SafeCheckoutOrder(checkouts: List[Project]) -> List[List[Project]]:
156 return res 157 return res
157 158
158 159
160def _NestedProjects(projects: List[Project]) -> List[Project]:
161 """Return the projects in |projects| living inside another one's path."""
162 return [p for level in _SafeCheckoutOrder(projects)[1:] for p in level]
163
164
159def _ParentFirstBatches(projects: List[Project]) -> List[List[Project]]: 165def _ParentFirstBatches(projects: List[Project]) -> List[List[Project]]:
160 """Group |projects| so that a parent is fetched before its submodules. 166 """Group |projects| so that a parent is fetched before its submodules.
161 167
@@ -2478,6 +2484,7 @@ later is required to fix a server side protocol bug.
2478 manifest=manifest, 2484 manifest=manifest,
2479 all_manifests=not opt.this_manifest_only, 2485 all_manifests=not opt.this_manifest_only,
2480 ) 2486 )
2487 self._CheckReprojectCmdNesting(opt, args, manifest, all_projects)
2481 2488
2482 # Log the repo projects by existing and new. 2489 # Log the repo projects by existing and new.
2483 existing = [x for x in all_projects if x.Exists] 2490 existing = [x for x in all_projects if x.Exists]
@@ -2541,6 +2548,46 @@ later is required to fix a server side protocol bug.
2541 if not opt.quiet: 2548 if not opt.quiet:
2542 print("repo sync has finished successfully.") 2549 print("repo sync has finished successfully.")
2543 2550
2551 def _CheckReprojectCmdNesting(
2552 self,
2553 opt: optparse.Values,
2554 args: List[str],
2555 manifest: XmlManifest,
2556 all_projects: List[Project],
2557 ) -> None:
2558 """Fail when repo.reprojectcmd is used on a manifest nesting projects.
2559
2560 The command materializes a project's tree without Git, so nothing
2561 keeps it from clobbering a project or submodule checked out inside
2562 that tree. See docs/reproject-cmd.md.
2563 """
2564 if not any(p.UseReprojectCmd for p in all_projects):
2565 return
2566 projects = all_projects
2567 if args:
2568 # Nesting is a property of the manifest, not of the projects
2569 # picked on the command line.
2570 projects = self.GetProjects(
2571 [],
2572 groups=opt.groups,
2573 missing_ok=True,
2574 submodules_ok=opt.recurse_submodules,
2575 manifest=manifest,
2576 all_manifests=not opt.this_manifest_only,
2577 )
2578 nested = _NestedProjects(projects)
2579 if not nested:
2580 return
2581 e = SyncError(
2582 "error: repo.reprojectcmd does not support nested projects or "
2583 "submodules; found:\n"
2584 + "\n".join(
2585 f" - {p.RelPath(local=opt.this_manifest_only)}" for p in nested
2586 )
2587 )
2588 logger.error(e)
2589 raise e
2590
2544 def _CreateSyncProgressThread( 2591 def _CreateSyncProgressThread(
2545 self, pm: Progress, stop_event: _threading.Event 2592 self, pm: Progress, stop_event: _threading.Event
2546 ) -> _threading.Thread: 2593 ) -> _threading.Thread: