git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: [Bug] Git subtree regression

From
Ggeorge@mail.dietrich.pub <george@mail.dietrich.pub>
Date
Jan 4, 2026, 14:27 UTC
Message-ID
<20260104142733.2334796-1-george@mail.dietrich.pub>
In-Reply-To
<e25b4d76-c1b5-4b6b-ba77-e1e2f7243ce9@howdoi.land>
---

Ahh, yes. It seems you also need to add `clock` as a remote and fetch it: ``` $ git clone git@github.com:athena-framework/athena.git $ cd athena $ git remote add clock git@github.com:athena-framework/clock.git $ git fetch clock $ git subtree split --prefix="src/components/clock" 0efb3d9858e3bfee65165508aeeacc50417c9a99 ```

I wasn't able to use _exactly_ 2.43.7, but I was able to use 2.43.0 which would still be before that other change. It also produced the expected commit hash, unlike 2.52.0. It, also was significantly faster, took ~9s vs 2.51.1 which was ~26s. 2.52.0 was better at ~14s, but of course produces the wrong hash.

This reproduces the issue quite well, and what the root cause likely. It does seem one component was added differently, as a non-merge commit, which seems break things. Looking at the Athena monorepo, this can somewhat be confirmed via https://github.com/athena-framework/athena/commits/master/?after=ee21a41e9dfc969e759b532d45c0c0faa21876d6+0. How the first two commits show up as verified, unlike the other times when I normally do `git subtree add --squash` and push directly to main, they show up as unverified.

``` #!/bin/bash # # THE BUG: # When a commit's direct parent is a squash commit for a DIFFERENT subtree, # and that squash commit's ancestry includes OUR subtree's squash commit, # the split breaks. # # Old code: `git log -1 --grep` searches ancestry, finds our marker → don't ignore # New code: only checks parent's own trailers → ignores → breaks parent chain # # This pattern occurs when subtree squash commits are cherry-picked or rebased # into a linear history (instead of the normal merge structure).

set -e
KEEP_TMPDIR="${KEEP_TMPDIR:-}"

TMPDIR=$(mktemp -d) echo "Working directory: $TMPDIR"

cleanup() {
    if [ -n "$KEEP_TMPDIR" ]; then
        echo "Preserving temp directory: $TMPDIR"
    else
        rm -rf "$TMPDIR"
    fi
}
trap cleanup EXIT
create_repo() {
    local repo="$1"
    git init -b main "$repo"
    git -C "$repo" config user.email "test@test.com"
    git -C "$repo" config user.name "Test User"
    git -C "$repo" config log.date relative
}
create_commit() {
    local repo="$1"
    local name="$2"
    (
        cd "$repo"
        mkdir -p "$(dirname "$name")"
        echo "$name" > "$name"
        git add "$name"
        git commit -m "$name"
    )
}
cd "$TMPDIR"
echo "=== Creating repositories ==="

create_repo monorepo create_repo subA create_repo subB

echo "=== Creating upstream commits ==="

create_commit subA subA1 create_commit subA subA2 create_commit subB subB1 create_commit subB subB2

echo "=== Setting up monorepo with linear squash structure ==="

# Initial commit create_commit monorepo main1

# Add subA with --squash (normal way - creates merge) git -C monorepo fetch ../subA HEAD git -C monorepo subtree add --prefix=subA --squash FETCH_HEAD

# Make a change in subA create_commit monorepo subA/change1

# Now we simulate cherry-picking JUST the squash commit for subB # (This is what seems to have happened in the athena repo) # First, get subB ready git -C monorepo fetch ../subB HEAD

# Create a LINEAR squash commit for subB (simulating cherry-pick of just the squash commit)
# This is the key pattern that triggers the bug - a squash commit as a regular linear commit
(
    cd monorepo
    mkdir -p subB
    git -C ../subB archive HEAD | tar -x -C subB
    git add subB
    # Create a squash-style commit with subtree trailers but as a LINEAR commit
    # Trailers must be in the last paragraph, separated by blank line
    subB_short=$(git -C ../subB rev-parse --short HEAD)
    subB_full=$(git -C ../subB rev-parse HEAD)
    git commit -F - <<EOF
Squashed 'subB/' content from commit $subB_short
git-subtree-dir: subB
git-subtree-split: $subB_full
EOF
)

echo "" echo "=== Key structure: subB squash is a LINEAR commit, not a merge ===" git -C monorepo log -1 --format='%H %s' HEAD echo "Parent count: $(git -C monorepo cat-file -p HEAD | grep -c '^parent')"

# Now make a commit that touches subA # This commit's parent is the subB squash commit (linear) create_commit monorepo subA/change2

echo "" echo "=== Repository structure ===" git -C monorepo log --oneline --graph

# Verify the squash commit's ancestry includes subA's marker
subB_squash=$(git -C monorepo rev-parse HEAD^)
echo ""
echo "=== Checking ancestry of subB squash commit ($subB_squash) ==="
echo "Looking for subA marker in ancestry..."
if git -C monorepo log -1 --grep="git-subtree-dir: subA" "$subB_squash" --oneline 2>/dev/null; then
    echo "  FOUND - old code would search this and NOT ignore"
else
    echo "  NOT FOUND - test setup may be incomplete"
fi

echo "" echo "=== Running subtree split on subA ==="

split_hash=$(git -C monorepo subtree split --prefix=subA 2>/dev/null) echo "Split hash: $split_hash"

split_count=$(git -C monorepo rev-list --count "$split_hash") echo "Commits in split: $split_count"

echo "" echo "=== Split history ===" git -C monorepo log --oneline "$split_hash"

echo "" echo "=== Result ==="

# Expected: 4 commits (2 upstream + 2 local changes)
if [ "$split_count" -ge 4 ]; then
    echo "PASS: Split produced connected history ($split_count commits)"
    exit 0
else
    echo "FAIL: Split produced disconnected history (only $split_count commits, expected >= 4)"
    exit 1
fi
```
Previous: Colin StagnerNext: Colin Stagner
Message 4 of 11 in “[Bug] Git subtree regression”
  1. dev@dietrich.pubDec 26, 2025
  2. george@mail.dietrich.pubDec 30, 2025
  3. Colin StagnerJan 4, 2026
  4. george@mail.dietrich.pubJan 4, 2026
  5. Colin StagnerJan 5, 2026
  6. george@mail.dietrich.pubJan 6, 2026
  7. Colin StagnerJan 10, 2026
  8. george@mail.dietrich.pubJan 10, 2026
  9. Colin StagnerFeb 15, 2026
  10. D. Ben KnobleFeb 16, 2026
  11. george@mail.dietrich.pubFeb 18, 2026

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.