Skip to content

Commit 88adeb8

Browse files
committed
TO-SPLIT
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
1 parent 3329724 commit 88adeb8

File tree

2 files changed

+114
-36
lines changed

2 files changed

+114
-36
lines changed

.github/workflows/rebase-shears.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ jobs:
8787
exit 0
8888
fi
8989
90-
if ./ci/rebase-branch.sh "shears/$BRANCH" "$UPSTREAM" ./ci; then
90+
if ./ci/rebase-branch.sh "shears/$BRANCH" "$UPSTREAM" "$PWD/ci"; then
9191
echo "to_push=shears/$BRANCH" >>"$GITHUB_OUTPUT"
9292
else
9393
echo "failed_worktrees=$PWD/rebase-worktree-$BRANCH" >>"$GITHUB_OUTPUT"
@@ -121,7 +121,7 @@ jobs:
121121
fi
122122
123123
worktree="$PWD/rebase-worktree-$BRANCH"
124-
if ./ci/rebase-branch.sh "shears/$BRANCH" "$UPSTREAM" ./ci; then
124+
if ./ci/rebase-branch.sh "shears/$BRANCH" "$UPSTREAM" "$PWD/ci"; then
125125
to_push="${to_push:+$to_push }shears/$BRANCH"
126126
else
127127
echo "::error::Rebase failed for shears/$BRANCH"

ci/rebase-branch.sh

Lines changed: 112 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -37,24 +37,29 @@ usage () {
3737
exit 1
3838
}
3939

40-
# Function to check if patch was trivially upstreamed (exact range-diff match)
41-
check_trivial_skip () {
42-
# Try range-diff with default creation factor to find exact matches
43-
# range-diff may fail if there's no common base, which is fine - just means no trivial match
44-
if ! local range_diff_output=$(git range-diff --no-color REBASE_HEAD^! REBASE_HEAD..); then
45-
# range-diff failed (no common ancestor, etc.) - not a trivial skip
46-
return 1
47-
fi
40+
# Function to generate a correspondence map between two commit ranges
41+
# Usage: generate_correspondence_map <our-range> <their-range> <output-file>
42+
# The map file contains range-diff output that can be searched for correspondences
43+
generate_correspondence_map () {
44+
local our_range=$1 their_range=$2 output_file=$3
45+
git -c core.abbrev=false range-diff --no-color "$our_range" "$their_range" >"$output_file" 2>/dev/null || :
46+
}
4847

49-
# Look for exact match: "1: abc123 = 1: def456"
50-
# The "=" means identical patches
51-
if echo "$range_diff_output" | grep -qE '^[0-9]+: [0-9a-f]+ = [0-9]+: [0-9a-f]+'; then
52-
# Extract the upstream OID (right side of =)
53-
echo "$range_diff_output" | grep -oE '^[0-9]+: [0-9a-f]+ = [0-9]+: ([0-9a-f]+)' | \
54-
sed 's/.*= [0-9]*: //' | head -1
55-
return 0
56-
fi
57-
return 1
48+
# Function to find a corresponding commit in a map file
49+
# Usage: find_correspondence <oid> <map-file>
50+
# Returns: corresponding OID on stdout, exit 0 if found (= or ! match), 1 if not
51+
# Sets CORRESPONDENCE_TYPE to "=" (identical) or "!" (modified)
52+
find_correspondence () {
53+
local oid=$1 map_file=$2
54+
test -s "$map_file" || return 1
55+
56+
# Look for this OID in the left side of range-diff output
57+
# Format: "N: <oid> = M: <oid>" (identical) or "N: <oid> ! M: <oid>" (modified)
58+
local match=$(sed -n "s/^[0-9]*: $oid \([!=]\) [0-9]*: \([0-9a-f]*\).*/\1 \2/p" "$map_file" | head -1)
59+
test -n "$match" || return 1
60+
61+
CORRESPONDENCE_TYPE=${match% *}
62+
echo "${match#* }"
5863
}
5964

6065
# Generate git log -L commands for conflicting hunks
@@ -82,7 +87,10 @@ generate_log_l_commands () {
8287
}
8388

8489
# Function to resolve a single conflict with AI
90+
# Usage: resolve_conflict_with_ai [<tried-correspondences>]
8591
resolve_conflict_with_ai () {
92+
local tried_correspondences=$1
93+
8694
# Get REBASE_HEAD info once
8795
local rebase_head_oid rebase_head_ref rebase_head_oneline
8896
rebase_head_oid=$(git rev-parse REBASE_HEAD)
@@ -101,11 +109,20 @@ resolve_conflict_with_ai () {
101109
log_l_commands="${log_l_commands}$(generate_log_l_commands "$file")"
102110
done
103111

112+
# Add context about tried correspondences
113+
local correspondence_context=""
114+
if test -n "$tried_correspondences"; then
115+
correspondence_context="
116+
Note: We found corresponding commits from previous/sibling rebases but they did not apply cleanly:
117+
$tried_correspondences
118+
You may want to examine these with 'git show <oid>' for hints on how to resolve."
119+
fi
120+
104121
# Build the prompt - minimal, letting LLM discover context
105122
local prompt="Resolve merge conflict during rebase of commit REBASE_HEAD.
106123
107124
Conflicting files: $conflicting_files
108-
125+
$correspondence_context
109126
Investigation commands:
110127
- See the patch: git show REBASE_HEAD
111128
- See conflict markers: view <file>
@@ -297,15 +314,16 @@ run_rebase_with_ai () {
297314
rebase_head_ref=$(git show --no-patch --format=reference REBASE_HEAD)
298315
rebase_head_oneline=$(git show --no-patch --format='%h %s' REBASE_HEAD)
299316

300-
# First, try trivial skip check (exact range-diff match)
301-
if local trivial_upstream_oid=$(check_trivial_skip); then
302-
echo "$rebase_head_oid $trivial_upstream_oid" >>"$SKIPPED_MAP_FILE"
303-
echo "::notice::Trivial skip (upstream: $trivial_upstream_oid): $rebase_head_oneline"
317+
# Check upstream correspondence (= means identical, skip it)
318+
if corresponding_oid=$(find_correspondence "$rebase_head_oid" "$UPSTREAM_MAP") &&
319+
test "$CORRESPONDENCE_TYPE" = "="; then
320+
echo "$rebase_head_oid $corresponding_oid" >>"$SKIPPED_MAP_FILE"
321+
echo "::notice::Trivial skip (upstream: $corresponding_oid): $rebase_head_oneline"
304322
cat >>"$REPORT_FILE" <<TRIVIAL_SKIP_EOF
305323
306324
### Skipped (trivial): $rebase_head_ref
307325
308-
Upstream equivalent: $(git show --no-patch --format=reference "$trivial_upstream_oid" || echo "$trivial_upstream_oid")
326+
Upstream equivalent: $(git show --no-patch --format=reference "$corresponding_oid" 2>/dev/null || echo "$corresponding_oid")
309327
310328
Detected via exact range-diff match (no AI needed).
311329
@@ -315,8 +333,34 @@ TRIVIAL_SKIP_EOF
315333
continue
316334
fi
317335

318-
# Non-trivial conflict - invoke AI
319-
resolve_conflict_with_ai
336+
# Try previous/sibling correspondences (reuse their resolution via merge-tree)
337+
local tried_correspondences=""
338+
for map_file in "$PREVIOUS_MAP" "$SIBLING_MAP"; do
339+
test -s "$map_file" || continue
340+
corresponding_oid=$(find_correspondence "$rebase_head_oid" "$map_file") || continue
341+
342+
echo "::notice::Found correspondence: $corresponding_oid for $rebase_head_oneline"
343+
tried_correspondences="${tried_correspondences:+$tried_correspondences }$corresponding_oid"
344+
# Try merge-tree: if clean, use the resulting tree
345+
if result_tree=$(git merge-tree --write-tree HEAD^ REBASE_HEAD "$corresponding_oid" 2>/dev/null) &&
346+
git read-tree --reset -u "$result_tree" &&
347+
git commit -C REBASE_HEAD; then
348+
echo "::notice::Used resolution from: $corresponding_oid"
349+
cat >>"$REPORT_FILE" <<RESOLVED_EOF
350+
351+
### Resolved via correspondence: $rebase_head_ref
352+
353+
Used resolution from: $(git show --no-patch --format=reference "$corresponding_oid" 2>/dev/null || echo "$corresponding_oid")
354+
355+
RESOLVED_EOF
356+
CONFLICTS_RESOLVED=$((CONFLICTS_RESOLVED + 1))
357+
git rebase --continue
358+
continue 2
359+
fi
360+
done
361+
362+
# Non-trivial conflict - invoke AI (pass tried correspondences as context)
363+
resolve_conflict_with_ai "$tried_correspondences"
320364
done
321365
}
322366

@@ -369,14 +413,28 @@ echo "::notice::Current tip: $TIP_OID"
369413
# The second parent of the marker points to the GfW branch tip at marker creation time
370414
GFW_MAIN_BRANCH="origin/main"
371415
BEHIND_COUNT=$(git rev-list --count "$TIP_OID..$GFW_MAIN_BRANCH" || echo "0")
416+
PREVIOUS_MAP=""
372417

373418
if test "$BEHIND_COUNT" -gt 0; then
374-
echo "::notice::Syncing $BEHIND_COUNT commits from $GFW_MAIN_BRANCH"
375-
echo "::group::Sync rebase from $GFW_MAIN_BRANCH"
376-
run_rebase_with_ai -r HEAD "$GFW_MAIN_BRANCH"
377-
git checkout -B "$SHEARS_BRANCH"
378-
TIP_OID=$(git rev-parse HEAD)
379-
echo "::endgroup::"
419+
if git rev-list --grep='^Start the merging-rebase' "$TIP_OID..$GFW_MAIN_BRANCH" | grep -q .; then
420+
# origin/main was rebased - generate correspondence before adopting
421+
PREVIOUS_MAP="$WORKTREE_DIR/previous-correspondence.map"
422+
MAIN_MARKER=$(git rev-parse "$GFW_MAIN_BRANCH^{/Start.the.merging-rebase}")
423+
generate_correspondence_map "$MAIN_MARKER..$GFW_MAIN_BRANCH" "$OLD_MARKER..$TIP_OID" "$PREVIOUS_MAP"
424+
425+
echo "::notice::origin/main was rebased, adopting its $BEHIND_COUNT commits"
426+
git checkout -B "$SHEARS_BRANCH" "$GFW_MAIN_BRANCH"
427+
TIP_OID=$(git rev-parse HEAD)
428+
OLD_MARKER=$(git rev-parse "HEAD^{/Start.the.merging-rebase}")
429+
OLD_UPSTREAM=$(git rev-parse "$OLD_MARKER^1")
430+
else
431+
echo "::notice::Syncing $BEHIND_COUNT commits from $GFW_MAIN_BRANCH"
432+
echo "::group::Rebasing $BEHIND_COUNT commits from $GFW_MAIN_BRANCH on top of $SHEARS_BRANCH"
433+
run_rebase_with_ai -r HEAD "$GFW_MAIN_BRANCH"
434+
git checkout -B "$SHEARS_BRANCH"
435+
TIP_OID=$(git rev-parse HEAD)
436+
echo "::endgroup::"
437+
fi
380438
fi
381439

382440
# Initialize report
@@ -396,21 +454,41 @@ CONFLICTS_RESOLVED=0
396454
SKIPPED_MAP_FILE="$WORKTREE_DIR/skipped-commits.map"
397455
: >"$SKIPPED_MAP_FILE"
398456

399-
# Create new marker with two parents: upstream + current tip
457+
# Generate correspondence maps for conflict resolution
458+
UPSTREAM_MAP="$WORKTREE_DIR/upstream-correspondence.map"
459+
SIBLING_MAP="$WORKTREE_DIR/sibling-correspondence.map"
460+
461+
# Map 1: Our commits vs upstream (for trivial skips)
462+
generate_correspondence_map "$OLD_MARKER..$TIP_OID" "$OLD_UPSTREAM..$NEW_UPSTREAM" "$UPSTREAM_MAP"
463+
464+
# Map 2: Our commits vs sibling branch (seen→next→main hierarchy)
465+
case "${SHEARS_BRANCH##*/}" in
466+
main) SIBLING_BRANCH="origin/shears/next" ;;
467+
next) SIBLING_BRANCH="origin/shears/seen" ;;
468+
*) SIBLING_BRANCH="" ;;
469+
esac
470+
if test -n "$SIBLING_BRANCH" && git rev-parse --verify "$SIBLING_BRANCH" >/dev/null 2>&1; then
471+
SIBLING_MARKER=$(git rev-parse "$SIBLING_BRANCH^{/Start.the.merging-rebase}" 2>/dev/null) || SIBLING_MARKER=""
472+
if test -n "$SIBLING_MARKER"; then
473+
generate_correspondence_map "$OLD_MARKER..$TIP_OID" "$SIBLING_MARKER..$SIBLING_BRANCH" "$SIBLING_MAP"
474+
fi
475+
fi
476+
477+
# Create new marker with two parents: upstream + origin/main
400478
echo "::group::Creating marker and running rebase"
401479
MARKER_OID=$(git commit-tree "$UPSTREAM_BRANCH^{tree}" \
402480
-p "$UPSTREAM_BRANCH" \
403-
-p "$TIP_OID" \
481+
-p "$GFW_MAIN_BRANCH" \
404482
-m "Start the merging-rebase to $UPSTREAM_BRANCH
405483
406484
This commit starts the rebase of $OLD_MARKER to $NEW_UPSTREAM")
407485

408486
git replace --graft "$MARKER_OID" "$UPSTREAM_BRANCH"
409487
REBASE_TODO_COUNT=$(git rev-list --count "$OLD_MARKER..$TIP_OID")
410488
echo "Rebasing $REBASE_TODO_COUNT commits onto $MARKER_OID"
411-
echo "::endgroup::"
412489

413490
run_rebase_with_ai -r --onto "$MARKER_OID" "$OLD_MARKER"
491+
echo "::endgroup::"
414492

415493
# Clean up graft and verify
416494
git replace -d "$MARKER_OID"

0 commit comments

Comments
 (0)