Skip to content

Commit ebacf96

Browse files
authored
feat: drop python dependency; make gha-timer opt-out (#62)
* feat: drop python dependency; make gha-timer opt-out Port src/scripts/git_ungraft.py to a bash equivalent (src/scripts/git_ungraft.sh) and drop the actions/setup-python step from action.yml. The composite action now has no interpreter-setup prerequisites beyond bash itself. The port uses `git cat-file -p` to discover parent commits, not shallow-aware commands such as `git show --format=%P` (which returns an empty parent list for commits listed in .git/shallow — exactly the subset the ungraft logic needs to re-evaluate). The awk parser reads only the header block (up to the first blank line), so commit-message body contents cannot be misparsed as "parent <sha>" lines. Add an `enable-timing` input (default: true) that controls whether fulcrumgenomics/gha-timer is installed. A shared shell wrapper (src/scripts/gha_timer_wrapper.sh) transparently falls back to emitting `::group::` / `::endgroup::` workflow commands when gha-timer is absent, so the action log remains collapsible in the GH Actions UI for users who opt out. Add a matrix test case exercising the enable-timing=false path. * fix: git_fetch_parents was a no-op on shallow-boundary commits; add shellcheck Extract `git_fetch_parents` from `src/scripts/fetch_through_merge_base.sh` into its own sourceable helper `src/scripts/git_fetch_parents.sh`, add a regression test (`tests/test_git_fetch_parents.sh`), and fix the shallow-aware-parent bug it surfaced. Before this change the function used `git show --no-patch --format='%P'` to discover parents. That command (like every other shallow-aware git porcelain: `git log --pretty=%P`, `git rev-list --parents`, etc.) returns an EMPTY parent list for commits listed in `.git/shallow` — which is exactly the subset for which the function is meant to do useful work (pre-deepen parent fetching on PR merge commits that sit at the shallow boundary). Consequence: the optimization was a silent no-op whenever it mattered; the deepen loop masked the miss, so CI stayed green. The fix reads parent SHAs from the raw commit object via `git cat-file -p`, parsed by an awk filter that emits only header-block `parent <sha>` lines (up to the first blank line), so commit-message body contents cannot be misparsed — preserving the protection added in e4d95ef for the parent-in-body regression test. CI changes: - Add a `shellcheck` job that runs `shellcheck -x --severity=style --source-path=SCRIPTDIR` on every shell script in the repo. Severity is the strictest setting; no rules are excluded. - Add a `shell-tests` matrix job (ubuntu/macos) that runs the new unit test. The step installs `fulcrumgenomics/gha-timer` and sources `src/scripts/gha_timer_wrapper.sh` so it dogfoods the action's own default-on timing path. Cleanup surfaced by shellcheck: - Switch `fetch_through_merge_base.sh` shebang from `/bin/sh` to `/usr/bin/env bash` (the script has always used `[[`, `BASH_SOURCE`, arrays, etc.). - Quote variables in `git rev-parse --verify` arguments and in `bash ${SCRIPT_DIR}/...` invocations. - Replace `let FAIL_AFTER="FAIL_AFTER-1"` (which trips `set -e` when the result is zero, hence the surrounding `set +e`/`set -e` dance) with `FAIL_AFTER=$((FAIL_AFTER - 1))`. * docs: flesh out enable-timing description in the testing wrapper Match the convention of the other inputs in .github/actions/testing/ action.yml, which carry the same description as the corresponding input on the action under test instead of pointing at external docs. * refactor: apply review feedback - fetch_through_merge_base.sh: normalize `set -eou pipefail` to the idiomatic `set -euo pipefail`; fix `Deepend` typo. - git_ungraft.sh: use `git rev-parse --absolute-git-dir` instead of the cd/cd/pwd dance so a GIT_DIR pointing outside the work tree is handled correctly; add an arity check for -C/--git-dir so `set -u` doesn't produce an unhelpful "$2: unbound variable"; add a comment noting that the rewrite predicate is equivalent to the Python original's `set(grafted) != set(remaining)`. - gha_timer_wrapper.sh: document that the fallback is best-effort log grouping only — it emits no timing data and silently drops `gha_timer stop`. - action.yml (both): quote `default: 'true'` for enable-timing to make the string contract explicit and document accepted values; fix a double-space in the description. * test: add unit tests for git_ungraft.sh and gha_timer_wrapper.sh test_git_ungraft.sh builds a hermetic shallow clone from a local bare repo (no network) and asserts: - No-candidate case leaves .git/shallow untouched (mtime preserved). - --dry-run prints "Would ungraft <sha>" without rewriting the file. - Default run rewrites .git/shallow, removing the ungraftable entry. test_gha_timer_wrapper.sh exercises both branches of the wrapper: - When gha-timer is on PATH, argv is forwarded verbatim to a shim binary and no ::group::/::endgroup:: markers are emitted. - When gha-timer is absent, the fallback emits the expected ::group::/::endgroup:: workflow commands and drops `stop`. Both tests are picked up by the existing `tests/test_*.sh` loop in the shell-tests job, so the wrapper fallback is now covered in CI without needing a separate matrix entry. * test: pin bare-repo default branch to main in test_git_ungraft On ubuntu-22.04 runners, `git init --bare` without an explicit initial-branch flag creates a repo whose HEAD points to `master`. The test then pushes `main`, so the subsequent shallow clone hits an unborn HEAD and lands as an empty clone — no `.git/shallow` file — failing the first assertion. Pass `-b main` to the bare init so HEAD tracks the branch we actually push. * test: use inode identity instead of mtime to detect shallow rewrite The test's case 1 and case 2 want to verify that git_ungraft.sh does not rewrite `.git/shallow` when there are no candidates (case 1) or when run with `--dry-run` (case 2). The previous implementation compared `stat %Y` (seconds-precision mtime) before and after, which proved flaky on CI: git's shallow-handling appears to touch the file in place during read-only sub-commands run by the script, leaving the inode unchanged but bumping mtime into a new second. Switch to inode identity. The script's rewrite path is a tempfile + `mv`, which atomically replaces the file and therefore changes its inode. The inode check precisely targets that rewrite and ignores incidental in-place touches by unrelated git machinery.
1 parent bad50f4 commit ebacf96

12 files changed

Lines changed: 780 additions & 253 deletions

.github/actions/testing/action.yml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,17 @@ inputs:
4949
default: false
5050
required: false
5151
description: >
52-
True to fetch all commits if number of attempts to deepen reaches
52+
True to fetch all commits if number of attempts to deepen reaches
5353
its limit, false to fail the action.
54+
enable-timing:
55+
default: 'true'
56+
required: false
57+
description: >
58+
True to emit per-step timings via fulcrumgenomics/gha-timer,
59+
false to skip the gha-timer setup step entirely. Set this to
60+
false in environments where installing gha-timer is undesirable
61+
(e.g. self-hosted runners with restricted egress). Accepted
62+
values are the strings 'true' and 'false'.
5463
ancestor-ref:
5564
default: ""
5665
required: false
@@ -79,6 +88,7 @@ runs:
7988
fail-after: ${{ inputs.fail-after }}
8089
working-directory: ./__testing_dir__
8190
fallback-fetch-all: ${{ inputs.fallback-fetch-all }}
91+
enable-timing: ${{ inputs.enable-timing }}
8292
- name: cleanup the testing directory
8393
shell: bash
8494
run: rm -rf __testing_dir__

.github/workflows/tests.yml

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,49 @@ jobs:
3838
echo "::error::Unstaged changes detected. Locally try running: git clean -ffdx && npm ci && npm run format && npm run build"
3939
exit 1
4040
fi
41+
shellcheck:
42+
runs-on: ubuntu-24.04
43+
steps:
44+
- name: Checkout
45+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
46+
- name: Install shellcheck
47+
run: sudo apt-get update && sudo apt-get install -y shellcheck
48+
- name: Run shellcheck
49+
run: |
50+
shellcheck \
51+
-x \
52+
--severity=style \
53+
--source-path=SCRIPTDIR \
54+
src/scripts/*.sh \
55+
tests/*.sh
56+
shell-tests:
57+
strategy:
58+
matrix:
59+
os: [ubuntu-24.04, ubuntu-22.04, macos-15, macos-14]
60+
runs-on: ${{ matrix.os }}
61+
steps:
62+
- name: Checkout
63+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
64+
- name: Setup gha-timer
65+
uses: fulcrumgenomics/gha-timer@d0d886fa587c7f4f714e64d84aa6e274e818d48b # v1.1.1
66+
with:
67+
skip-banner: true
68+
- name: Run shell unit tests
69+
shell: bash
70+
run: |
71+
set -euo pipefail
72+
# shellcheck disable=SC1091
73+
source src/scripts/gha_timer_wrapper.sh
74+
for t in tests/test_*.sh; do
75+
gha_timer start --name "${t}"
76+
if bash "${t}"; then
77+
gha_timer elapsed --outcome success --name "${t}"
78+
else
79+
status=$?
80+
gha_timer elapsed --outcome failure --name "${t}"
81+
exit "${status}"
82+
fi
83+
done
4184
testing:
4285
strategy:
4386
matrix:
@@ -115,3 +158,13 @@ jobs:
115158
base-ref: "main"
116159
head-ref: "test-0002-parent-in-body"
117160
ancestor-ref: "6eb785672d2c6770af9e985d36ddcd1b3a285f8e"
161+
# Exercises the enable-timing=false opt-out: the setup-gha-timer step is
162+
# skipped and the shared wrapper falls back to ::group::/::endgroup::
163+
# workflow commands. The merge-base result must be unchanged.
164+
- name: Test enable-timing opt-out
165+
uses: ./.github/actions/testing
166+
with:
167+
head-ref: "test-0001-head-ref"
168+
base-ref: "test-0001-base-ref"
169+
enable-timing: false
170+
ancestor-ref: "6eb785672d2c6770af9e985d36ddcd1b3a285f8e"

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,13 @@ A GitHub Action for fetching PR commits through the merge-base
7676
# false to fail the action.
7777
# Default: false
7878
fallback-fetch-all: ''
79+
80+
# True to emit per-step timings via fulcrumgenomics/gha-timer, false to skip the
81+
# gha-timer setup step entirely. Set this to false in environments where
82+
# installing gha-timer is undesirable (e.g. self-hosted runners with restricted
83+
# egress). Accepted values are the strings 'true' and 'false'.
84+
# Default: true
85+
enable-timing: ''
7986
```
8087
8188
### Outputs

action.yml

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,17 @@ inputs:
5454
default: false
5555
required: false
5656
description: >
57-
True to fetch all commits if number of attempts to deepen reaches
57+
True to fetch all commits if number of attempts to deepen reaches
5858
its limit, false to fail the action.
59+
enable-timing:
60+
default: 'true'
61+
required: false
62+
description: >
63+
True to emit per-step timings via fulcrumgenomics/gha-timer,
64+
false to skip the gha-timer setup step entirely. Set this to
65+
false in environments where installing gha-timer is undesirable
66+
(e.g. self-hosted runners with restricted egress). Accepted
67+
values are the strings 'true' and 'false'.
5968
outputs:
6069
base-ref:
6170
description: >
@@ -82,12 +91,16 @@ runs:
8291
shell: bash
8392
run: |
8493
cat ${GITHUB_ACTION_PATH}/src/misc/banner.txt
85-
- id: setup-python
86-
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
87-
with:
88-
python-version: "3.12"
89-
update-environment: false
94+
- id: validate-enable-timing
95+
if: ${{ inputs.enable-timing != 'true' && inputs.enable-timing != 'false' }}
96+
shell: bash
97+
env:
98+
ENABLE_TIMING: ${{ inputs.enable-timing }}
99+
run: |
100+
echo "enable-timing must be 'true' or 'false'; got '${ENABLE_TIMING}'" >&2
101+
exit 2
90102
- id: setup-gha-timer
103+
if: ${{ inputs.enable-timing == 'true' }}
91104
uses: fulcrumgenomics/gha-timer@d0d886fa587c7f4f714e64d84aa6e274e818d48b # v1.1.1
92105
with:
93106
skip-banner: true
@@ -104,7 +117,8 @@ runs:
104117
WORKING_DIRECTORY: ${{ inputs.working-directory }}
105118
FALLBACK_FETCH_ALL: ${{ inputs.fallback-fetch-all }}
106119
run: |
107-
gha-timer start --name "Setting up environment variables...🚦"
120+
source "${{ github.action_path }}/src/scripts/gha_timer_wrapper.sh"
121+
gha_timer start --name "Setting up environment variables...🚦"
108122
cd $WORKING_DIRECTORY;
109123
if [ -z "$BASE_REF" ] ; then
110124
export GITHUB_BASE_REF="$FALLBACK_BASE_REF"
@@ -124,9 +138,9 @@ runs:
124138
echo "GITHUB_HEAD_REF=$GITHUB_HEAD_REF"
125139
export GITHUB_BASE_REF
126140
export GITHUB_HEAD_REF
127-
gha-timer elapsed --outcome success --name "Setting up environment variables...🚦"
141+
gha_timer elapsed --outcome success --name "Setting up environment variables...🚦"
128142
set -euo pipefail
129-
bash ${{ github.action_path }}/src/scripts/fetch_through_merge_base.sh
143+
bash "${{ github.action_path }}/src/scripts/fetch_through_merge_base.sh"
130144
# Note: `outcome` does not consider `continue-on-error`
131145
# Note: fallback-fetch-all is a string, not a boolean, since type is not supported
132146
# for inputs on composite actions (only workflows).
@@ -146,12 +160,13 @@ runs:
146160
HEAD_REF: ${{ steps.fetch_through_merge_base.outputs.head-ref }}
147161
WORKING_DIRECTORY: ${{ inputs.working-directory }}
148162
run: |
149-
gha-timer start --name "Falling back to fetching all...🚦"
163+
source "${{ github.action_path }}/src/scripts/gha_timer_wrapper.sh"
164+
gha_timer start --name "Falling back to fetching all...🚦"
150165
cd $WORKING_DIRECTORY;
151166
git fetch --all --unshallow --verbose;
152-
"${{ steps.setup-python.outputs.python-path }}" ${{ github.action_path }}/src/scripts/git_ungraft.py;
167+
bash "${{ github.action_path }}/src/scripts/git_ungraft.sh";
153168
echo -n "Verifying merge-base: "
154-
gha-timer elapsed --outcome success --name "Falling back to fetching all...🚦"
169+
gha_timer elapsed --outcome success --name "Falling back to fetching all...🚦"
155170
git merge-base $BASE_REF $HEAD_REF
156171
- id: finalize
157172
if: always()
@@ -161,12 +176,13 @@ runs:
161176
HEAD_REF: ${{ steps.fetch_through_merge_base.outputs.head-ref }}
162177
WORKING_DIRECTORY: ${{ inputs.working-directory }}
163178
run: |
164-
gha-timer start --name "Getting merge-base (ancestor-ref)...🚦"
179+
source "${{ github.action_path }}/src/scripts/gha_timer_wrapper.sh"
180+
gha_timer start --name "Getting merge-base (ancestor-ref)...🚦"
165181
cd $WORKING_DIRECTORY;
166182
ancestor_ref="$(git merge-base $BASE_REF $HEAD_REF)";
167183
echo "ancestor-ref=${ancestor_ref}" >> $GITHUB_OUTPUT
168-
gha-timer elapsed --outcome success --name "Getting merge-base (ancestor-ref)...🚦"
169-
gha-timer stop
184+
gha_timer elapsed --outcome success --name "Getting merge-base (ancestor-ref)...🚦"
185+
gha_timer stop
170186
echo "Merge base is:"
171187
echo ${ancestor_ref}
172188
echo "Action succeeded! ✅"
Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
1-
#!/bin/sh
1+
#!/usr/bin/env bash
22

3+
set -euo pipefail
34

45
DEEPEN_LENGTH=${DEEPEN_LENGTH:-10}
56
FAIL_AFTER=${FAIL_AFTER:-1000}
67

7-
SCRIPT_DIR=$(dirname ${BASH_SOURCE[0]});
8+
SCRIPT_DIR=$(dirname "${BASH_SOURCE[0]}");
89

9-
gha-timer start --name "Attempts remaining: ${FAIL_AFTER} 🚦"
10+
# shellcheck source=./gha_timer_wrapper.sh
11+
. "${SCRIPT_DIR}/gha_timer_wrapper.sh"
12+
# shellcheck source=./git_fetch_parents.sh
13+
. "${SCRIPT_DIR}/git_fetch_parents.sh"
1014

11-
if [ -z "${GITHUB_HEAD_REF}" ]; then
15+
gha_timer start --name "Attempts remaining: ${FAIL_AFTER} 🚦"
16+
17+
if [ -z "${GITHUB_HEAD_REF:-}" ]; then
1218
echo "Empty GITHUB_HEAD_REF!";
1319
exit 1;
1420
fi
15-
if [ -z "${GITHUB_BASE_REF}" ]; then
21+
if [ -z "${GITHUB_BASE_REF:-}" ]; then
1622
echo "Empty GITHUB_BASE_REF!";
1723
exit 1;
1824
fi
@@ -25,21 +31,11 @@ if [ -z "${FAIL_AFTER}" ]; then
2531
exit 1;
2632
fi
2733

28-
set -eou pipefail
29-
30-
function git_fetch_parents() {
31-
local sha1=${1};
32-
for parent_sha1 in $(git show --no-patch --format='%P' "${sha1}"); do
33-
git fetch --update-head-ok --update-shallow --progress --quiet --depth=1 origin "${parent_sha1}:__github_parent__";
34-
git branch -D __github_parent__;
35-
done
36-
}
37-
3834
# Fetch a branch or tag, and track it. Do not fetch if a commit (yet).
39-
if [[ "${GITHUB_BASE_REF}" != "$(git rev-parse --verify ${GITHUB_BASE_REF})" ]]; then
35+
if [[ "${GITHUB_BASE_REF}" != "$(git rev-parse --verify "${GITHUB_BASE_REF}")" ]]; then
4036
git fetch --update-head-ok --update-shallow --progress --quiet --depth=1 origin "$GITHUB_BASE_REF:$GITHUB_BASE_REF";
4137
fi
42-
if [[ "${GITHUB_HEAD_REF}" != "$(git rev-parse --verify ${GITHUB_HEAD_REF})" ]]; then
38+
if [[ "${GITHUB_HEAD_REF}" != "$(git rev-parse --verify "${GITHUB_HEAD_REF}")" ]]; then
4339
git fetch --update-head-ok --update-shallow --progress --quiet --depth=1 origin "$GITHUB_HEAD_REF:$GITHUB_HEAD_REF";
4440
fi
4541

@@ -52,31 +48,29 @@ GITHUB_HEAD_REF=$(git rev-parse "__github_head_ref__");
5248
# For merge commits we need to fetch both parents (e.g. from GitHub PRs)
5349
git_fetch_parents "${GITHUB_BASE_REF}";
5450
git_fetch_parents "${GITHUB_HEAD_REF}";
55-
python ${SCRIPT_DIR}/git_ungraft.py;
51+
bash "${SCRIPT_DIR}/git_ungraft.sh";
5652

5753
# keep fetching deeper until we find the common ancestor reference
5854
while [ -z "$( git merge-base "__github_base_ref__" "__github_head_ref__" )" ]; do
5955
# check if we are done iterating
60-
set +e;
61-
let FAIL_AFTER="FAIL_AFTER-1";
62-
set -e;
56+
FAIL_AFTER=$((FAIL_AFTER - 1))
6357
if [ "$FAIL_AFTER" -le 0 ]; then
64-
gha-timer elapsed --outcome failure
58+
gha_timer elapsed --outcome failure
6559
echo "Failed to find the common ancestors of GITHUB_BASE_REF=${GITHUB_BASE_REF} and GITHUB_HEAD_REF=${GITHUB_HEAD_REF}";
6660
echo "Failure! ❌"
6761
exit 1;
6862
fi
6963
# fetch deeper
7064
git fetch --quiet --update-shallow --deepen="$DEEPEN_LENGTH" origin "$GITHUB_HEAD_REF";
7165
git fetch --quiet --update-shallow --deepen="$DEEPEN_LENGTH" origin "$GITHUB_BASE_REF";
72-
python ${SCRIPT_DIR}/git_ungraft.py;
73-
echo "Deepend search by ${DEEPEN_LENGTH}‼️";
74-
gha-timer elapsed --outcome skipped
75-
gha-timer start --name "Attempts remaining: ${FAIL_AFTER} 🚦"
66+
bash "${SCRIPT_DIR}/git_ungraft.sh";
67+
echo "Deepened search by ${DEEPEN_LENGTH}‼️";
68+
gha_timer elapsed --outcome skipped
69+
gha_timer start --name "Attempts remaining: ${FAIL_AFTER} 🚦"
7670
done
7771

7872

7973
# cleanup
8074
git branch -D __github_base_ref__ __github_head_ref__;
81-
gha-timer elapsed --outcome success
75+
gha_timer elapsed --outcome success
8276
echo "Success! ✅"

src/scripts/gha_timer_wrapper.sh

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# shellcheck shell=bash
2+
#
3+
# Defines a `gha_timer` shell function that forwards to the
4+
# fulcrumgenomics/gha-timer binary when it is present on PATH, and
5+
# otherwise falls back to emitting GitHub Actions workflow commands
6+
# (`::group::` / `::endgroup::`) so the log remains collapsible in the
7+
# workflow UI.
8+
#
9+
# The fallback is strictly best-effort log grouping: it emits no
10+
# timing data and silently drops `gha_timer stop` (the cumulative
11+
# summary the real binary prints has no analogue in workflow
12+
# commands). Users who need per-step timings must leave
13+
# `enable-timing` on so the real binary is on PATH.
14+
#
15+
# This file is intended to be `source`d from bash scripts and from
16+
# inline `run:` blocks in action.yml. It is NOT a standalone script.
17+
18+
gha_timer() {
19+
if command -v gha-timer >/dev/null 2>&1; then
20+
gha-timer "$@"
21+
return
22+
fi
23+
local subcommand="${1:-}"
24+
[ $# -gt 0 ] && shift
25+
local name=""
26+
while [ $# -gt 0 ]; do
27+
case "$1" in
28+
--name)
29+
name="${2:-}"
30+
shift
31+
[ $# -gt 0 ] && shift
32+
;;
33+
--outcome)
34+
shift
35+
[ $# -gt 0 ] && shift
36+
;;
37+
*)
38+
shift
39+
;;
40+
esac
41+
done
42+
case "$subcommand" in
43+
start) echo "::group::${name}" ;;
44+
elapsed) echo "::endgroup::" ;;
45+
*) : ;;
46+
esac
47+
}

src/scripts/git_fetch_parents.sh

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# shellcheck shell=bash
2+
#
3+
# Defines a `git_fetch_parents` shell function.
4+
#
5+
# Given a commit SHA (typically one at the shallow boundary), this
6+
# function fetches each of the commit's parents into the local
7+
# repository at `--depth=1`, so that the merge-commit case of a PR
8+
# has both sides represented locally. The fetched tip is landed
9+
# under the private ref namespace
10+
# `refs/fetch-through-merge-base/__github_parent__` (rather than a
11+
# branch name that could collide with a user's branch) and is
12+
# deleted immediately afterward via `git update-ref -d`.
13+
#
14+
# Parent discovery reads the raw commit object via `git cat-file -p`
15+
# rather than a shallow-aware command such as `git show --format=%P`.
16+
# Shallow-aware commands treat commits recorded in `.git/shallow` as
17+
# if they had no parents — which is exactly the subset for which this
18+
# function is meant to do useful work. The awk filter emits only
19+
# header-block `parent <sha>` lines (up to the first blank line),
20+
# so commit-message body contents cannot be misparsed.
21+
#
22+
# This file is intended to be `source`d from other shell scripts;
23+
# running it standalone does nothing.
24+
25+
git_fetch_parents() {
26+
local sha1="${1}"
27+
local parent_sha1
28+
local tmp_ref="refs/fetch-through-merge-base/__github_parent__"
29+
# awk sets `in_body` at the first blank line terminating the commit
30+
# header and then consumes the rest of the stream without printing.
31+
# Do NOT use `awk '/^$/ { exit }'`: under `set -o pipefail`, an
32+
# early-exit awk closes the read end of the pipe and causes
33+
# `git cat-file` to die of SIGPIPE when the commit body exceeds the
34+
# pipe buffer (~64 KB on Linux), aborting the script.
35+
for parent_sha1 in $(
36+
git cat-file -p "${sha1}" \
37+
| awk '
38+
in_body { next }
39+
/^$/ { in_body = 1; next }
40+
$1 == "parent" { print $2 }
41+
'
42+
); do
43+
git fetch --update-head-ok --update-shallow --progress --quiet \
44+
--depth=1 origin "${parent_sha1}:${tmp_ref}"
45+
git update-ref -d "${tmp_ref}"
46+
done
47+
}

0 commit comments

Comments
 (0)