Sadly, this can't quite be an alias (which would be difficult to automatically set up anyways), since we want to check if an r/number is part of the (upstream) canon branch. The test script for the subcommand doubles up as a soundness check for our pipelines ref creation. Change-Id: I840af6556e50187c69490668bd8a18dd7dc25a86 Reviewed-on: https://cl.tvl.fyi/c/depot/+/8844 Tested-by: BuildkiteCI Autosubmit: sterni <sternenseemann@systemli.org> Reviewed-by: flokli <flokli@flokli.de>
		
			
				
	
	
		
			138 lines
		
	
	
	
		
			4.1 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
			
		
		
	
	
			138 lines
		
	
	
	
		
			4.1 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
| # Git subcommand loaded into the depot direnv via //tools/depot-deps that can
 | |
| # display the r/number for (a) given commit(s) in depot. The r/number is a
 | |
| # monotonically increasing number assigned to each commit which correspond to
 | |
| # refs/r/* as created by `//ops/pipelines/static-pipeline.yaml`. They can also
 | |
| # be used as TVL shortlinks and are supported by //web/atward.
 | |
| { pkgs, lib, ... }:
 | |
| 
 | |
| pkgs.writeTextFile {
 | |
|   name = "git-r";
 | |
|   destination = "/bin/git-r";
 | |
|   executable = true;
 | |
|   text = ''
 | |
|       set -euo pipefail
 | |
| 
 | |
|       PROG_NAME="$0"
 | |
| 
 | |
|       CANON_BRANCH="canon"
 | |
|       CANON_REMOTE="$(git config "branch.$CANON_BRANCH.remote" || echo "origin")"
 | |
|       CANON_HEAD="$CANON_REMOTE/$CANON_BRANCH"
 | |
| 
 | |
|       usage() {
 | |
|         cat <<EOF
 | |
|     Usage: git r [-h | --usage] [<git commit> ...]
 | |
| 
 | |
|       Display the r/number for the given git commit(s). If none is given,
 | |
|       HEAD is used as a default. The r/number is a monotonically increasing
 | |
|       number assigned to each commit on the $CANON_BRANCH branch in depot
 | |
|       equivalent  to the revcount ignoring merged in branches (using
 | |
|       git-rev-list(1) internally).
 | |
| 
 | |
|       The r/numbers displayed by \`git r\` correspond to refs created by CI
 | |
|       in depot, so they can be used as monotonically increasing commit
 | |
|       identifiers that can be used instead of a commit hash. To have
 | |
|       \`refs/r/*\` available locally (which is not necessary for the operation
 | |
|       of \`git r\`), you may have to enable fetching them like this:
 | |
| 
 | |
|           git config --add remote.origin.fetch '+refs/r/*:refs/r/*'
 | |
| 
 | |
|       They are created the next time you run `git fetch origin`.
 | |
| 
 | |
|     EOF
 | |
|         exit "''${1:-0}"
 | |
|       }
 | |
| 
 | |
|       eprintf() {
 | |
|         printf "$@" 1>&2
 | |
|       }
 | |
| 
 | |
|       revs=()
 | |
| 
 | |
|       if [[ $# -le 0 ]]; then
 | |
|         revs+=("HEAD")
 | |
|       fi
 | |
| 
 | |
|       for arg in "$@"; do
 | |
|         # No flags supported at the moment
 | |
|         case "$arg" in
 | |
|           # --help is mapped to `man git-r` by git(1)
 | |
|           # TODO(sterni): git-r man page
 | |
|           -h | --usage)
 | |
|             usage
 | |
|             ;;
 | |
|           -*)
 | |
|             eprintf 'error: unknown flag %s\n' "$PROG_NAME" "$arg"
 | |
|             usage 100 1>&2
 | |
|             ;;
 | |
|           *)
 | |
|             revs+=("$arg")
 | |
|             ;;
 | |
|         esac
 | |
|       done
 | |
| 
 | |
|       for rev in "''${revs[@]}"; do
 | |
|         # Make sure $rev is well formed
 | |
|         git rev-parse "$rev" -- > /dev/null
 | |
| 
 | |
|         if git merge-base --is-ancestor "$rev" "$CANON_HEAD"; then
 | |
|           printf 'r/'
 | |
|           git rev-list --count --first-parent "$rev"
 | |
|         else
 | |
|           eprintf 'error: refusing to calculate r/number: %s is not an ancestor of %s\n' \
 | |
|             "$rev" "$CANON_HEAD" 1>&2
 | |
|           exit 100
 | |
|         fi
 | |
|       done
 | |
|   '';
 | |
| 
 | |
|   # Test case, assumes that it is executed in a checkout of depot
 | |
|   meta.ci.extraSteps.matches-refs = {
 | |
|     needsOutput = true;
 | |
|     label = "Verify `git r` output matches refs/r/*";
 | |
|     command = pkgs.writeShellScript "git-r-matches-refs" ''
 | |
|       set -euo pipefail
 | |
| 
 | |
|       export PATH="${lib.makeBinPath [ pkgs.git pkgs.findutils ]}"
 | |
|       revs=("origin/canon" "origin/canon~1" "93a746aaaa092ffc3e7eb37e1df30bfd3a28435f")
 | |
| 
 | |
|       failed=false
 | |
| 
 | |
|       # assert_eq DESCRIPTION EXPECTED GIVEN
 | |
|       assert_eq() {
 | |
|         desc="$1"
 | |
|         exp="$2"
 | |
|         given="$3"
 | |
| 
 | |
|         if [[ "$exp" != "$given" ]]; then
 | |
|           failed=true
 | |
|           printf 'error: case "%s" failed\n\texp:\t%s\n\tgot:\t%s\n' "$desc" "$exp" "$given" 1>&2
 | |
|         fi
 | |
|       }
 | |
| 
 | |
|       git fetch origin '+refs/r/*:refs/r/*'
 | |
| 
 | |
|       for rev in "''${revs[@]}"; do
 | |
|         assert_eq \
 | |
|           "r/number ref for $rev points at that rev" \
 | |
|           "$(git rev-parse "$rev")" \
 | |
|           "$(git rev-parse "$(./result/bin/git-r "$rev")")"
 | |
|       done
 | |
| 
 | |
|       for rev in "''${revs[@]}"; do
 | |
|         assert_eq \
 | |
|           "r/number for matches ref pointing at $rev" \
 | |
|           "$(git for-each-ref --points-at="$rev" --format="%(refname:short)" 'refs/r/*')" \
 | |
|           "$(./result/bin/git-r "$rev")"
 | |
|       done
 | |
| 
 | |
|       assert_eq \
 | |
|         "Passing multiple revs to git r works as expected" \
 | |
|         "$(git rev-parse "''${revs[@]}")" \
 | |
|         "$(./result/bin/git-r "''${revs[@]}" | xargs git rev-parse)"
 | |
| 
 | |
|       if $failed; then
 | |
|         exit 1
 | |
|       fi
 | |
|     '';
 | |
|   };
 | |
| }
 |