diff --git a/README.md b/README.md index ab2637e8..2b3b4f33 100644 --- a/README.md +++ b/README.md @@ -416,6 +416,22 @@ correct key is provided set in `git_crypt_key`. the repository. + + revision
Optional + + If true, clone the repository using git clone + --revision=<ref>, which fetches only the history leading to + the specific version.ref commit. HEAD is detached at that + commit and no remote-tracking branches are created. This is more + efficient than the default clone strategy for large repositories when + the exact commit SHA is known, as it avoids fetching the branch tip and + the deepening loop. Can be combined with depth to further + limit ancestry. Incompatible with branch. +

+ Note: Requires a git version that supports the + --revision flag. + + fetch
Optional diff --git a/assets/in b/assets/in index 7c50dda4..44ead8c6 100755 --- a/assets/in +++ b/assets/in @@ -73,6 +73,7 @@ timestamp_format=$(jq -r '(.params.timestamp_format // "iso8601")' <<< "$payload describe_ref_options=$(jq -r '(.params.describe_ref_options // "--always --dirty --broken")' <<< "$payload") search_remote_refs_flag=$(jq -r '(.source.search_remote_refs // false)' <<< "$payload") all_branches=$(jq -r '(.params.all_branches // false)' <<< "$payload") +revision=$(jq -r '(.params.revision // false)' <<< "$payload") # If params not defined, get it from source if [ -z "$fetch_tags" ] || [ "$fetch_tags" == "null" ] ; then @@ -97,6 +98,13 @@ if [ -n "$override_branch" ]; then branchflag="--branch $override_branch" fi +if [ "$revision" == "true" ]; then + if [ -n "$branchflag" ]; then + echo "error: revision param is incompatible with branch" >&2 + exit 1 + fi +fi + depthflag="" if test "$depth" -gt 0 2> /dev/null; then depthflag="--depth $depth" @@ -124,7 +132,16 @@ if [ "${all_branches,,}" == "true" ]; then singlebranchflag="" fi -git clone --progress $singlebranchflag $depthflag $uri $branchflag $destination $tagflag $nocheckoutflag +revisionflag="" +if [ "$revision" == "true" ]; then + revisionflag="--revision=$ref" +fi + +if [ "$revision" == "true" ]; then + git clone --progress $depthflag $revisionflag $uri $destination $tagflag $nocheckoutflag +else + git clone --progress $singlebranchflag $depthflag $uri $branchflag $destination $tagflag $nocheckoutflag +fi cd $destination @@ -137,7 +154,10 @@ fi git fetch origin refs/notes/*:refs/notes/* $tagflag -if [ "$depth" -gt 0 ]; then +if [ "$revision" == "true" ]; then + # --revision already detaches HEAD at $ref, no checkout needed + : +elif [ "$depth" -gt 0 ]; then "$bin_dir"/deepen_shallow_clone_until_ref_is_found_then_check_out "$depth" "$ref" "$tagflag" else if [ "$search_remote_refs_flag" == "true" ] && ! [ -z "$branchflag" ] && ! git rev-list -1 $ref 2> /dev/null > /dev/null; then diff --git a/assets/in_schema.json b/assets/in_schema.json index 0eacd84c..7aba0067 100644 --- a/assets/in_schema.json +++ b/assets/in_schema.json @@ -11,5 +11,6 @@ "timestamp_format": "", "describe_ref_options": "", "all_branches": "", - "debug": "" + "debug": "", + "revision": "" } diff --git a/test/get.sh b/test/get.sh index 9875622a..c4446b09 100755 --- a/test/get.sh +++ b/test/get.sh @@ -1118,6 +1118,53 @@ it_can_get_from_url_at_branch_with_search_remote_refs() { test "$(git -C $dest rev-parse HEAD)" = $ref2 } +it_can_get_with_revision() { + local repo=$(init_repo) + local ref1=$(make_commit $repo) + local ref2=$(make_commit $repo) + local ref3=$(make_commit $repo) + + local dest=$TMPDIR/destination + + get_uri_with_revision "file://$repo" $ref2 $dest | jq -e " + .version == {ref: $(echo $ref2 | jq -R .)} + " + + test "$(git -C $dest rev-parse HEAD)" = $ref2 + test -z "$(git -C $dest branch -r)" +} + +it_can_get_with_revision_and_depth() { + local repo=$(init_repo) + local ref1=$(make_commit $repo) + local ref2=$(make_commit $repo) + local ref3=$(make_commit $repo) + + local dest=$TMPDIR/destination + + get_uri_with_revision_at_depth "file://$repo" 1 $ref2 $dest | jq -e " + .version == {ref: $(echo $ref2 | jq -R .)} + " + + test "$(git -C $dest rev-parse HEAD)" = $ref2 + test -e $dest/.git/shallow + test "$(git -C $dest rev-list --all --count)" = 1 +} + +it_errors_with_revision_and_branch() { + local repo=$(init_repo) + local ref=$(make_commit $repo) + local dest=$TMPDIR/destination + + set +e + output=$(get_uri_with_revision_and_branch "file://$repo" "master" $ref $dest 2>&1) + exit_code=$? + set -e + + test "${exit_code}" != 0 + echo "${output}" | grep "error: revision param is incompatible with branch" +} + it_errors_when_there_are_unknown_keys_in_params() { local failed_output=$TMPDIR/get-unknown-keys-output if get_uri_unknown_keys "some-uri" "some-dest" 2>"$failed_output"; then @@ -1179,4 +1226,7 @@ run it_retains_tags_with_clean_tags_param run it_returns_list_without_tags_in_metadata run it_returns_list_of_all_tags_in_metadata run it_can_get_from_url_at_branch_with_search_remote_refs +run it_can_get_with_revision +run it_can_get_with_revision_and_depth +run it_errors_with_revision_and_branch run it_errors_when_there_are_unknown_keys_in_params diff --git a/test/helpers.sh b/test/helpers.sh index c3325328..eff429bf 100644 --- a/test/helpers.sh +++ b/test/helpers.sh @@ -816,6 +816,50 @@ get_uri_at_depth_at_ref() { }" | ${resource_dir}/in "$4" | tee -a /dev/stderr } +get_uri_with_revision() { + jq -n "{ + source: { + uri: $(echo $1 | jq -R .) + }, + params: { + revision: \"true\" + }, + version: { + ref: $(echo $2 | jq -R .) + } + }" | ${resource_dir}/in "$3" | tee /dev/stderr +} + +get_uri_with_revision_at_depth() { + jq -n "{ + source: { + uri: $(echo $1 | jq -R .) + }, + params: { + revision: \"true\", + depth: $(echo $2 | jq -R .) + }, + version: { + ref: $(echo $3 | jq -R .) + } + }" | ${resource_dir}/in "$4" | tee /dev/stderr +} + +get_uri_with_revision_and_branch() { + jq -n "{ + source: { + uri: $(echo $1 | jq -R .), + branch: $(echo $2 | jq -R .) + }, + params: { + revision: \"true\" + }, + version: { + ref: $(echo $3 | jq -R .) + } + }" | ${resource_dir}/in "$4" | tee /dev/stderr +} + get_uri_with_submodules_at_depth() { jq -n "{ source: {