-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy pathapply-stash-with-conflicts.sh
More file actions
executable file
·105 lines (93 loc) · 3.76 KB
/
apply-stash-with-conflicts.sh
File metadata and controls
executable file
·105 lines (93 loc) · 3.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#!/bin/sh
#
# Like `git stash apply`, but for stashes that contain merge conflicts
# (created by stash-with-conflicts.sh). `git stash` itself cannot
# handle unmerged index entries, so this script reconstructs the full
# conflict state from the octopus merge commit.
#
# Expects a commit with exactly 4 parents:
# Parent 1: HEAD at time of stash
# Parent 2: stage 1 tree (common ancestor)
# Parent 3: stage 2 tree (ours)
# Parent 4: stage 3 tree (theirs)
# Tree: worktree state (with conflict markers)
#
# Usage: apply-stash-with-conflicts.sh <commit> [<dir>]
# Reconstructs the unmerged index and restores the worktree with
# conflict markers, without moving HEAD (just like `git stash apply`).
die () { echo "fatal: $*" >&2; exit 1; }
commit=${1:?usage: apply-stash-with-conflicts.sh <commit> [<dir>]}
dir=${2:-.}
# Like `git stash apply`: refuse to apply on a dirty state
git -C "$dir" update-index -q --refresh ||
die "unable to refresh index"
git -C "$dir" write-tree >/dev/null ||
die "cannot apply a stash in the middle of a merge"
parents=$(git -C "$dir" cat-file -p "$commit" | grep '^parent ' | awk '{print $2}') ||
die "cannot read commit '$commit'"
count=$(echo "$parents" | wc -l)
test "$count" -eq 4 ||
die "expected 4 parents, got $count"
b_commit=$(echo "$parents" | sed -n '1p')
stage1_commit=$(echo "$parents" | sed -n '2p')
stage2_commit=$(echo "$parents" | sed -n '3p')
stage3_commit=$(echo "$parents" | sed -n '4p')
b_tree=$(git -C "$dir" rev-parse "${b_commit}^{tree}") ||
die "cannot resolve base tree"
stage1_tree=$(git -C "$dir" rev-parse "${stage1_commit}^{tree}") ||
die "cannot resolve stage 1 tree"
stage2_tree=$(git -C "$dir" rev-parse "${stage2_commit}^{tree}") ||
die "cannot resolve stage 2 tree"
stage3_tree=$(git -C "$dir" rev-parse "${stage3_commit}^{tree}") ||
die "cannot resolve stage 3 tree"
worktree_tree=$(git -C "$dir" rev-parse "${commit}^{tree}") ||
die "cannot resolve worktree tree"
c_tree=$(git -C "$dir" write-tree) ||
die "cannot determine current index state"
# If HEAD has moved since the stash was created, refuse: the conflict
# state is tied to a specific base and cannot be rebased automatically
test "$c_tree" = "$b_tree" ||
die "HEAD tree differs from stash base; cannot apply"
# Find conflicted paths by comparing the three stage trees
conflicted=$(
{
git -C "$dir" diff-tree -r --name-only \
"$stage1_tree" "$stage2_tree"
git -C "$dir" diff-tree -r --name-only \
"$stage1_tree" "$stage3_tree"
git -C "$dir" diff-tree -r --name-only \
"$stage2_tree" "$stage3_tree"
} | sort -u
)
# Load the stage-2 tree (ours) as the starting index, like
# merge-recursive would leave it for the non-conflicted files
git -C "$dir" read-tree "$stage2_tree" ||
die "failed to read stage 2 tree into index"
if test -n "$conflicted"; then
# Replace stage-0 entries for conflicted paths with proper
# stage 1/2/3 entries
{
echo "$conflicted" | while read -r path; do
echo "0 0000000000000000000000000000000000000000 0 $path"
for stage in 1 2 3; do
eval "tree=\$stage${stage}_tree"
entry=$(git -C "$dir" ls-tree "$tree" -- "$path")
test -n "$entry" || continue
mode=$(echo "$entry" | awk '{print $1}')
oid=$(echo "$entry" | awk '{print $3}')
echo "$mode $oid $stage $path"
done
done
} | git -C "$dir" update-index --index-info ||
die "failed to reconstruct unmerged index entries"
fi
# Restore the worktree: resolved files from the index, then overlay
# conflicted files with their worktree state (conflict markers)
git -C "$dir" checkout-index -a -f ||
die "failed to restore worktree"
echo "$conflicted" | while read -r path; do
test -n "$path" || continue
oid=$(git -C "$dir" ls-tree "$worktree_tree" -- "$path" | awk '{print $3}')
test -n "$oid" || continue
git -C "$dir" cat-file -p "$oid" >"$dir/$path"
done