mirror of
https://gerrit.googlesource.com/git-repo
synced 2026-09-26 16:50:37 +00:00
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>
127 lines
5.3 KiB
Markdown
127 lines
5.3 KiB
Markdown
# Reproject Command Contract
|
|
|
|
The `repo.reprojectcmd` configuration names a command that `repo sync` runs
|
|
instead of Git to move a project's index and worktree to the tree of the
|
|
target commit. It is the checkout-side counterpart of `repo.fetchcmd` (see
|
|
`docs/fetch-cmd.md`): together they let an external tool take over both the
|
|
network fetch and the materialization of a project. This is useful on
|
|
virtualized filesystems that address content by hash, where a tree can be
|
|
materialized far faster than `git checkout` can write every file.
|
|
|
|
The command only materializes the tree. `repo` then makes the ref write that
|
|
Git would have made, using `git update-ref`.
|
|
|
|
## Configuration
|
|
|
|
To use this feature, set the following in `.repo/manifests.git/config`:
|
|
```ini
|
|
[repo]
|
|
reprojectcmd = "your custom command here"
|
|
uselocalgitdirs = true
|
|
```
|
|
Setting `repo.reprojectcmd` **requires** `repo.uselocalgitdirs` to be set to
|
|
`true`.
|
|
|
|
For reference, this command does with Git what `repo` would otherwise do
|
|
itself:
|
|
```ini
|
|
[repo]
|
|
reprojectcmd = "git -C $REPO_PATH read-tree -m -u $REPO_TREV"
|
|
uselocalgitdirs = true
|
|
```
|
|
The one-tree merge applies the change to the target, keeps local changes to
|
|
every other path, and refuses to overwrite a modified or untracked file, so it
|
|
enforces the preconditions below by itself. It also works for a project that
|
|
has nothing checked out yet.
|
|
|
|
## Environment Variables
|
|
|
|
The command is executed in a subshell, from the root of the client, populated
|
|
with standard project-context environment variables. For details on standard
|
|
variables (such as `REPO_PROJECT`, `REPO_PATH`, `REPO_REMOTE`, etc.), see the
|
|
Environment section in `repo help forall` or `subcmds/forall.py`.
|
|
|
|
The variables the command typically needs are:
|
|
|
|
* `REPO_PATH`: The project path relative to the root of the client.
|
|
* `REPO_TREV`: The target revision resolved to a full commit hash. Match this
|
|
commit's tree.
|
|
|
|
There is no force mode: a project that would need one never reaches the
|
|
command (see the preconditions below).
|
|
|
|
## When the command runs
|
|
|
|
`repo sync` already classifies each project and picks a Git operation. The
|
|
command replaces the three that are a materialization of a target tree:
|
|
|
|
1. The checkout that detaches HEAD at the target. This is the common case: a
|
|
project on a detached HEAD, a project on a branch that does not track
|
|
upstream, and `repo sync -d`.
|
|
2. The fast-forward of the checked out branch to the target.
|
|
3. The hard reset of the checked out branch to the target, when the commits
|
|
it carried were dropped upstream.
|
|
|
|
After the command exits 0, `repo` writes the ref itself: it detaches `HEAD` at
|
|
`REPO_TREV`, or moves the checked out branch to `REPO_TREV`.
|
|
|
|
The command is **not** run:
|
|
|
|
* When `HEAD` already names `REPO_TREV`.
|
|
* At the fast-forward step when `HEAD` is ahead of `REPO_TREV`, where Git's
|
|
merge would be a no-op.
|
|
* For a rebase. A branch carrying local commits has them replayed onto the
|
|
target by `git rebase`, which is not a materialization of a target tree.
|
|
* For `MetaProject`s (i.e. the internal `repo` repository itself at
|
|
`.repo/repo` and the `manifests` repository at `.repo/manifests`).
|
|
|
|
## Contract
|
|
|
|
### Preconditions
|
|
|
|
Before invoking the command, `repo` ensures that:
|
|
|
|
* The index has no staged changes (the index matches `HEAD`, or is empty on an unborn `HEAD`).
|
|
* No rebase, cherry-pick, merge, or revert is in progress.
|
|
|
|
Detecting collisions with untracked files or unstaged working-tree modifications is the responsibility of the reproject command itself (e.g. via `git read-tree -m -u $REPO_TREV` or a custom virtual filesystem checkout tool). If local changes collide with the target tree, the command must abort with a non-zero exit code. Local modifications and untracked files outside the diff between `HEAD` and `REPO_TREV` must be preserved.
|
|
|
|
### Postconditions on exit 0
|
|
|
|
After the command exits with status 0, `repo` expects the following
|
|
postconditions to be met:
|
|
|
|
1. `git diff-index --quiet --cached REPO_TREV^{tree}` exits 0 (the index
|
|
matches the target tree).
|
|
2. `HEAD` still names what it did before the command, and its resolved commit
|
|
object ID has not changed.
|
|
|
|
### Invariants
|
|
|
|
The command may modify the worktree and the index, and may write project-local
|
|
Git config. The command must:
|
|
|
|
* Apply the change from `HEAD`'s tree to `REPO_TREV`'s tree and leave every
|
|
other path alone. Local modifications and untracked files outside that
|
|
change must survive: the command applies a diff, it does not reset the
|
|
tree.
|
|
* Not write any ref, including `HEAD` and `ORIG_HEAD`. `repo` owns every ref
|
|
write.
|
|
* Not create or replace `.git/`, and not touch anything under `.repo/`.
|
|
* Not require the Git remote, to preserve `repo sync --local-only`.
|
|
* Be idempotent. Running it twice on the same target is a no-op.
|
|
|
|
### Failure
|
|
|
|
* A non-zero exit status, a failed precondition or a failed postcondition
|
|
fails that project's sync, and the command's or Git's output is surfaced
|
|
to the user.
|
|
* Other projects continue, and `repo sync` exits non-zero.
|
|
|
|
## Limitations
|
|
|
|
Nested projects are out of scope: a project whose path lies inside another
|
|
project's path, a `<project>` nested in another `<project>` in the manifest,
|
|
and a submodule discovered with `sync-s` or `--recurse-submodules`. `repo sync`
|
|
fails if the manifest has one while `repo.reprojectcmd` is set.
|