| # 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. |