Compare commits

...

7 Commits
v2.56 ... v2.57

Author SHA1 Message Date
Gavin Mak
25858c8b16 sync: Default to interleaved mode
The previous default, "phased" sync (separate network and checkout
phases), can now be selected with `--no-interleaved`.

Bug: 421935613
Bug: 432082000
Change-Id: Ia8624daa609a28ea2f87f8ea4b42138d8b3e9269
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/489681
Reviewed-by: Scott Lee <ddoman@google.com>
Tested-by: Gavin Mak <gavinmak@google.com>
Commit-Queue: Gavin Mak <gavinmak@google.com>
2025-07-21 14:51:36 -07:00
Gavin Mak
52bab0ba27 project: Use git rev-parse to read HEAD
Don't directly read `.git/HEAD`, git already has a command for this.

Bug: 432200791
Change-Id: Iba030650224143eb07c44da1fa56341d9deb4288
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/492941
Reviewed-by: Scott Lee <ddoman@google.com>
Commit-Queue: Gavin Mak <gavinmak@google.com>
Tested-by: Gavin Mak <gavinmak@google.com>
2025-07-21 14:50:46 -07:00
Gavin Mak
2e6d0881d9 sync: Improve UI and error reporting for interleaved mode
This fixes two issues:
1. the progress bar could show a count greater than the total if new projects were discovered mid-sync. Update the progress bar total dynamically
2. Make "Stall detected" error message more actionable

Bug: 432206932
Change-Id: Ie2a4ada5b1770cae0302fb06590641c522cbb7e7
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/491941
Tested-by: Gavin Mak <gavinmak@google.com>
Reviewed-by: Scott Lee <ddoman@google.com>
Commit-Queue: Gavin Mak <gavinmak@google.com>
2025-07-17 17:30:33 -07:00
Gavin Mak
74edacd8e5 project: Use plumbing commands to manage HEAD
Don't directly manipulate `.git/HEAD` since it bypasses Git's internal
state management.

Bug: 432200791
Change-Id: I1c9264bcf107d34574a82b60a22ea2c83792951b
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/491841
Commit-Queue: Gavin Mak <gavinmak@google.com>
Reviewed-by: Scott Lee <ddoman@google.com>
Tested-by: Gavin Mak <gavinmak@google.com>
2025-07-17 15:41:59 -07:00
Gavin Mak
5d95ba8d85 progress: Make end() idempotent
This fixes the double "done" text on successful interleaved sync.

Bug: 421935613
Change-Id: I4f01418cb0340129a8f0a2a5835f7e3fa6a6b119
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/487081
Reviewed-by: Scott Lee <ddoman@google.com>
Tested-by: Gavin Mak <gavinmak@google.com>
Commit-Queue: Gavin Mak <gavinmak@google.com>
2025-07-02 13:11:23 -07:00
Kenny Cheng
82d500eb7a sync: support post-sync hook in <repo-hooks>
Add support for a new hook type "post-sync" declared in the manifest using
<repo-hooks>. This allows executing a script automatically after a successful
`repo sync`.

This is useful for initializing developer environments, installing project-wide
Git hooks, generating configs, and other post-sync automation tasks.

Example manifest usage:

  <project name="myorg/repo-hooks" path="hooks" revision="main" />
  <repo-hooks in-project="myorg/repo-hooks" enabled-list="post-sync">
    <hook name="post-sync" />
  </repo-hooks>

The hook script must be named `post-sync.py` and located at the root of the
hook project.

The post-sync hook does not block `repo sync`; if the script fails, the sync
still completes successfully with a warning.

Test: Added `post-sync.py` in hook project and verified it runs after `repo sync`

Bug: b/421694721
Change-Id: I69f3158f0fc319d73a85028d6e90fea02c1dc8c8
Signed-off-by: Kenny Cheng <chao.shun.cheng.tw@gmail.com>
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/480581
Reviewed-by: Scott Lee <ddoman@google.com>
Reviewed-by: Gavin Mak <gavinmak@google.com>
2025-07-01 16:11:50 -07:00
Matt Moeller
21269c3eed init: Add environment variable for git-lfs
Convenient way to always enable or disable git-lfs without having to
remember to put on the command line.

Useful if you want to ALWAYS have git-lfs enabled on your system when
you 'init' a new project.

Also useful if you are using the Jenkins repo plugin as it doesn't
provide an option for enabling git-lfs in its UI.

Change-Id: Ieb1bbe83de9c21523ab69b30fc5047c257d02731
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/437661
Commit-Queue: Scott Lee <ddoman@google.com>
Reviewed-by: Gavin Mak <gavinmak@google.com>
Reviewed-by: Fatahillah Wk <fatahillahwkwk@gmail.com>
Reviewed-by: Scott Lee <ddoman@google.com>
Tested-by: Matt Moeller <moeller.matt@gmail.com>
Reviewed-by: Yingchun Li <sword.l.dragon@gmail.com>
2025-06-30 15:27:26 -07:00
6 changed files with 103 additions and 28 deletions

View File

@@ -133,3 +133,43 @@ def main(project_list, worktree_list=None, **kwargs):
kwargs: Leave this here for forward-compatibility.
"""
```
### post-sync
This hook runs when `repo sync` completes without errors.
Note: This includes cases where no actual checkout may occur. The hook will still run.
For example:
- `repo sync -n` performs network fetches only and skips the checkout phase.
- `repo sync <project>` only updates the specified project(s).
- Partial failures may still result in a successful exit.
This hook is useful for post-processing tasks such as setting up git hooks,
bootstrapping configuration files, or running project initialization logic.
The hook is defined using the existing `<repo-hooks>` manifest block and is
optional. If the hook script fails or is missing, `repo sync` will still
complete successfully, and the error will be printed as a warning.
Example:
```xml
<project name="myorg/dev-tools" path="tools" revision="main" />
<repo-hooks in-project="myorg/dev-tools" enabled-list="post-sync">
<hook name="post-sync" />
</repo-hooks>
```
The `post-sync.py` file should be defined like:
```py
def main(repo_topdir=None, **kwargs):
"""Main function invoked directly by repo.
We must use the name "main" as that is what repo requires.
Args:
repo_topdir: The absolute path to the top-level directory of the repo workspace.
kwargs: Leave this here for forward-compatibility.
"""
```

View File

@@ -25,6 +25,7 @@ from git_refs import HEAD
# The API we've documented to hook authors. Keep in sync with repo-hooks.md.
_API_ARGS = {
"pre-upload": {"project_list", "worktree_list"},
"post-sync": {"repo_topdir"},
}

View File

@@ -101,6 +101,7 @@ class Progress:
self._units = units
self._elide = elide and _TTY
self._quiet = quiet
self._ended = False
# Only show the active jobs section if we run more than one in parallel.
self._show_jobs = False
@@ -118,6 +119,11 @@ class Progress:
if not quiet and show_elapsed:
self._update_thread.start()
def update_total(self, new_total):
"""Updates the total if the new total is larger."""
if new_total > self._total:
self._total = new_total
def _update_loop(self):
while True:
self.update(inc=0)
@@ -211,6 +217,10 @@ class Progress:
self.update(inc=0)
def end(self):
if self._ended:
return
self._ended = True
self._update_event.set()
if not _TTY or IsTraceToStderr() or self._quiet:
return

View File

@@ -2061,10 +2061,7 @@ class Project:
if head == revid:
# Same revision; just update HEAD to point to the new
# target branch, but otherwise take no other action.
_lwrite(
self.work_git.GetDotgitPath(subpath=HEAD),
f"ref: {R_HEADS}{name}\n",
)
self.work_git.SetHead(R_HEADS + name)
return True
GitCommand(
@@ -2100,9 +2097,7 @@ class Project:
revid = self.GetRevisionId(all_refs)
if head == revid:
_lwrite(
self.work_git.GetDotgitPath(subpath=HEAD), "%s\n" % revid
)
self.work_git.DetachHead(revid)
else:
self._Checkout(revid, quiet=True)
GitCommand(
@@ -3492,9 +3487,7 @@ class Project:
self._createDotGit(dotgit)
if init_dotgit:
_lwrite(
os.path.join(self.gitdir, HEAD), f"{self.GetRevisionId()}\n"
)
self.work_git.UpdateRef(HEAD, self.GetRevisionId(), detach=True)
# Finish checking out the worktree.
cmd = ["read-tree", "--reset", "-u", "-v", HEAD]
@@ -3841,19 +3834,11 @@ class Project:
def GetHead(self):
"""Return the ref that HEAD points to."""
path = self.GetDotgitPath(subpath=HEAD)
try:
with open(path) as fd:
line = fd.readline()
except OSError as e:
return self.rev_parse("--symbolic-full-name", HEAD)
except GitError as e:
path = self.GetDotgitPath(subpath=HEAD)
raise NoManifestException(path, str(e))
try:
line = line.decode()
except AttributeError:
pass
if line.startswith("ref: "):
return line[5:-1]
return line[:-1]
def SetHead(self, ref, message=None):
cmdv = []

View File

@@ -127,6 +127,7 @@ to update the working directory files.
return {
"REPO_MANIFEST_URL": "manifest_url",
"REPO_MIRROR_LOCATION": "reference",
"REPO_GIT_LFS": "git_lfs",
}
def _SyncManifest(self, opt):

View File

@@ -68,6 +68,7 @@ from git_config import GetUrlCookieFile
from git_refs import HEAD
from git_refs import R_HEADS
import git_superproject
from hooks import RepoHook
import platform_utils
from progress import elapsed_str
from progress import jobs_str
@@ -411,16 +412,18 @@ later is required to fix a server side protocol bug.
type=int,
metavar="JOBS",
help="number of network jobs to run in parallel (defaults to "
"--jobs or 1). Ignored when --interleaved is set",
"--jobs or 1). Ignored unless --no-interleaved is set",
)
p.add_option(
"--jobs-checkout",
default=None,
type=int,
metavar="JOBS",
help="number of local checkout jobs to run in parallel (defaults "
f"to --jobs or {DEFAULT_LOCAL_JOBS}). Ignored when --interleaved "
"is set",
help=(
"number of local checkout jobs to run in parallel (defaults "
f"to --jobs or {DEFAULT_LOCAL_JOBS}). Ignored unless "
"--no-interleaved is set"
),
)
p.add_option(
@@ -479,7 +482,14 @@ later is required to fix a server side protocol bug.
p.add_option(
"--interleaved",
action="store_true",
help="fetch and checkout projects in parallel (experimental)",
default=True,
help="fetch and checkout projects in parallel (default)",
)
p.add_option(
"--no-interleaved",
dest="interleaved",
action="store_false",
help="fetch and checkout projects in phases",
)
p.add_option(
"-n",
@@ -623,6 +633,7 @@ later is required to fix a server side protocol bug.
action="store_true",
help=optparse.SUPPRESS_HELP,
)
RepoHook.AddOptionGroup(p, "post-sync")
def _GetBranch(self, manifest_project):
"""Returns the branch name for getting the approved smartsync manifest.
@@ -1847,6 +1858,21 @@ later is required to fix a server side protocol bug.
except (KeyboardInterrupt, Exception) as e:
raise RepoUnhandledExceptionError(e, aggregate_errors=errors)
# Run post-sync hook only after successful sync
self._RunPostSyncHook(opt)
def _RunPostSyncHook(self, opt):
"""Run post-sync hook if configured in manifest <repo-hooks>."""
hook = RepoHook.FromSubcmd(
hook_type="post-sync",
manifest=self.manifest,
opt=opt,
abort_if_user_denies=False,
)
success = hook.Run(repo_topdir=self.client.topdir)
if not success:
print("Warning: post-sync hook reported failure.")
def _ExecuteHelper(self, opt, args, errors):
manifest = self.outer_manifest
if not opt.outer_manifest:
@@ -2488,11 +2514,22 @@ later is required to fix a server side protocol bug.
pending_relpaths = {p.relpath for p in projects_to_sync}
if previously_pending_relpaths == pending_relpaths:
stalled_projects_str = "\n".join(
f" - {path}"
for path in sorted(list(pending_relpaths))
)
logger.error(
"Stall detected in interleaved sync, not all "
"projects could be synced."
"The following projects failed and could not "
"be synced:\n%s",
stalled_projects_str,
)
err_event.set()
# Include these in the final error report.
self._interleaved_err_checkout = True
self._interleaved_err_checkout_results.extend(
list(pending_relpaths)
)
break
previously_pending_relpaths = pending_relpaths
@@ -2553,6 +2590,7 @@ later is required to fix a server side protocol bug.
manifest=manifest,
all_manifests=not opt.this_manifest_only,
)
pm.update_total(len(project_list))
finally:
sync_event.set()
sync_progress_thread.join()