Details
-
Task
-
Resolution: Unresolved
-
P3: Somewhat important
-
None
-
None
-
None
Description
Add a new tool to qtrepotools to facilitate pushing a group of changes ("patchset") to Gerrit and maintaining it.
Command-line
git gp [options] [reviewers] commits
where:
- options are:
- -t / --to / -b / --branch: the target branch. Default: config "branch.branchname.gerrit-upstream"; if empty, config "branch.branchname.merge" (i.e., upstream of the current branch). [the tool inserts {{refs/for}}].
- --full: always pushes all commits in the series. Default: attempt to push only commits that are modified.
- --base: commit ID to base the commits on; implies --full. Default: determine current base from Gerrit.
- --rebase: rebase the patchset on top of the current tip of the target branch. Equivalent to --base @{upstream}.
- -r / --remote: remote to push to. Default: config "branch.branchname.gerrit-remote"; if empty, remote called gerrit, if it exists, or config "branch.branchname.remote" (i.e., the current branch's upstream).
- -n / --dry-run: do everything except actually pushing.
- -v / --verbose: be verbose, explaining what it's doing and print Git and SSH commands being issued.
- -digit(s): limit number of commits in the set (passed to git rev-list).
- --map: the alias map file. Default: a file called .git-gpush-aliases in the same dir as the executable.
- reviewers are:
- +id: Gerrit username, alias or email address to be added as a reviewer
- =id: Gerrit username, alias or email address to be cc'ed in this change, but not added as a reviewer.
- commits are one or more Git committishes or ranges to be pushed (passed to git rev-parse)
If a single commit is provided, query the Gerrit server to verify whether this commit is part of a patch series. If so, determine the range of the series and operate as if that range had been passed; otherwise, operate on exactly the range passed.
Behaviour
- For each commit in the patchset, determine if the current commit in the range given by the user has been updated in any way (parent commit, commit message, author name, email or date, patch contents, including context changes that would prevent Gerrit from applying the patch).
- Then push each commit that is new or was determined to be updated
- If the patchset is being rebased, then all commits need to be pushed.
Operation
- Parse the command-line and separate the arguments to be passed to git rev-list
- Determine the target remote and target branch; extract the SSH server from the target remote
- Resolve aliases
- Determine repository top dir and chdir there (required for some commands below)
- Get the commit list: git rev-list --no-walk arguments
- Get the Change-Id for each of the commits in the list (will probably also get the rest of the metadata)
- Query gerrit about each of the commits: ssh gerritserver gerrit query --format JSON --current-patch-set change-ids separated by OR
- Determine from this which changes have not yet been pushed
- Obtain the commits as previously pushed
- (Optimisation) First, check if all of the previously pushed commits are available locally already by git rev-parse or git show them
- git fetch gerritremote reflist
- If the command-line contained one single commit entry and it had been previously pushed, verify whether the given commit is the tip of a patchset: git rev-list commit-from-server --not --remotes=gerrit-remote/* --remotes=current-branch-upstream/*
- if the above results in more than one commit, use the commit range and restart from step 2.
- note: the current branch's upstream remote is listed because the Gerrit remote is likely to be stale and not have been updated recently
- If rebasing (--rebase or --base options), mark all commits in the range as in need of update
- If not rebasing, for each commit in the current patchset and in the user's range, determine if the user has modified the commit
- The first commit in the patchset has no parent; all other commits have the previous commit as the parent (parent is the Change-Id, not comimt SHA-1)
- Compare: parent Change-Id, author name, email and date, commit message, commit diff
- (Optimisation) If the commit metadata has changed, then there's no need to get the patch's content and compare
- Diff comparison is fuzzy. It might be possible to skip it here and instead do tests with git apply --cached.
- Each commit that fails the comparison is added to the list of commits to be re-pushed
- For each commit, create the new commits using git apply --cached (with -C1 or -C2)
- Read in the base commit into the temporary index using git read-tree
- If we're rebasing, for the first commit it's the specified new base
- If the parent change has been applied again, use that (in other words, no git read-tree is necessary)
- If not rebasing and the parent change has not changed, the base is the parent commit of this commit's previous iteration. This may be the first commit after a gap.
- Apply
- If the application failed and there is a gap in the commit list, remove it
- This is necessary when one later commit depends on changes from a previous one
- The gap is removed by marking all the commits in the gap as modified and rewinding the application process to the first commit in the gap (its parent commit has been successfully applied, so we'll use that commit we've recently created)
- If the application failed and there is no gap, then report failure to the user, suggest rebasing, and exit.
- If the application was done on top of a new commit, replace that in the list of commits to be pushed.
- If not, add this commit to the list of commits to be pushed.
- Read in the base commit into the temporary index using git read-tree
- Push to Gerrit