From 2bd8d93b1cd7080d9e14d1628691f190757f895e Mon Sep 17 00:00:00 2001 From: Arc Date: Sun, 2 Aug 2026 18:06:32 +0000 Subject: [PATCH] fix(CSM-353): avoid full-history fetch for specific commit + branch When a commit and a branch were both provided, the branch was fetched without --depth, pulling its whole history. Try a shallow fetch of the commit itself first, then deepen the branch progressively, and only fall back to the full fetch when the commit is still not reachable. Co-authored-by: burako --- README.md | 5 +++++ git_clone.sh | 41 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 24b1c32..e07a695 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,11 @@ You can use this component with the following options: - `AC_GIT_SUBMODULE`: Used to specify whether the submodule should be cloned. - `AC_GIT_CACHE_CREDENTIALS`: If this set to true, the credentials will be cached to memory. This can be useful if the same credentials are used for multiple repositories. - `AC_GIT_EXTRA_PARAMS`: If this set, sends extra parameter for git requests. +- `AC_GIT_COMMIT_FETCH_DEEPEN_STEPS`: Advanced. Space separated fetch depths that are tried, in order, when a specific commit has to be reached through its branch because the Git server does not serve arbitrary commits. Defaults to `50 500 5000`. + +## Fetching a Specific Commit + +When both a branch and a commit are given, the component first tries to fetch only that commit with `--depth=1`. This works when the Git server allows it (`uploadpack.allowReachableSHA1InWant` / `uploadpack.allowAnySHA1InWant`). If the server refuses, the branch is fetched with progressively larger depths (`AC_GIT_COMMIT_FETCH_DEEPEN_STEPS`), and only if the commit is still not reachable, the full history of the branch is fetched. The requested commit is always checked out. ## Output Variables diff --git a/git_clone.sh b/git_clone.sh index 48d3150..bc9b105 100644 --- a/git_clone.sh +++ b/git_clone.sh @@ -11,6 +11,8 @@ LFS=true REFERENCE='' IS_SPECIFIC_COMMIT=false GIT_EXTRA_PARAMS='' +# Depths tried, in order, when a specific commit has to be reached through its branch. +COMMIT_FETCH_DEEPEN_STEPS="${AC_GIT_COMMIT_FETCH_DEEPEN_STEPS:-50 500 5000}" for i in "$@" do case $i in @@ -102,6 +104,43 @@ fi "$@" } + # Succeeds when the requested commit object already exists in the local repository. + function hasCommit(){ + git cat-file -e "${COMMIT}^{commit}" 2>/dev/null + } + + # Fetches only what is needed to check out ${COMMIT} instead of the whole history of + # ${BRANCH}. Three stages, cheapest first, each one guaranteeing the commit is present + # before it returns. + function fetchSpecificCommit(){ + # 1) Ask the server for the single commit. Supported when the remote enables + # uploadpack.allowReachableSHA1InWant / uploadpack.allowAnySHA1InWant. + if runCommand git fetch --prune --progress --no-recurse-submodules --depth=1 origin "${COMMIT}" && hasCommit; then + return 0 + fi + + echo "Remote did not serve ${COMMIT} directly, deepening ${BRANCH} progressively." + + # 2) Deepen the branch step by step. Builds almost always target a recent commit, + # so a few hundred commits are enough and still far cheaper than full history. + for depth in ${COMMIT_FETCH_DEEPEN_STEPS}; do + if ! runCommand git fetch --prune --progress --no-recurse-submodules --depth="${depth}" origin "${BRANCH}"; then + break + fi + if hasCommit; then + return 0 + fi + done + + # 3) Last resort: the full history fetch of the branch, the previous behavior. + echo "${COMMIT} is not within the deepened history, fetching the full history of ${BRANCH}." + if [ -f .git/shallow ]; then + runCommand git fetch --unshallow origin "${BRANCH}" + else + runCommand git fetch origin "${BRANCH}" + fi + } + runCommand git --version runCommand git lfs --version runCommand git init @@ -135,7 +174,7 @@ fi if [ "$IS_SPECIFIC_COMMIT" = true ]; then if [ ! -z "${BRANCH}" ]; then - runCommand git fetch origin "${BRANCH}" + fetchSpecificCommit else runCommand git fetch fi