-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmigrate_rename_commit.sh
More file actions
executable file
·166 lines (152 loc) · 5.98 KB
/
Copy pathmigrate_rename_commit.sh
File metadata and controls
executable file
·166 lines (152 loc) · 5.98 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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
#!/bin/bash
# Migrate the patch set across a commit of the target repository that
# RENAMES (or moves) files targeted by the patch set.
#
# "git apply" cannot follow renames: when commit T renames a patched file,
# applying the patch fails at T even though there is no semantic conflict.
# This script uses git's rename detection instead:
# 1. Apply the patch set at T^ (the parent) on a temporary branch and
# commit the patched state.
# 2. Merge that temporary branch INTO T - git's rename detection carries
# the patched changes over to the new file paths.
# Any real conflicts are left in the tree for manual resolution (same
# workflow as a failed 3way apply, see README).
#
# Afterwards (once the tree looks right / conflicts are resolved):
# cd <target_dp> && git add -A && git reset --mixed <rename_commit>
# create_patches.sh ... # regenerate the patch set at the new paths
# Remember to delete patch files whose target path no longer exists.
#
# Exit codes:
# 0 merged cleanly
# 1 merge left conflicts (manual resolution required)
# 2 invalid arguments / paths
# 3 patch set does not apply at the parent commit
script_dp="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
usage() {
echo "Usage: $0
--patch_dp <patch_dp>
--target_dp <target_dp>
--commit <rename_commit>
[--branch <working_branch>(default: vissat)]"
echo "--patch_dp:"
echo " Path to the directory containing the patch files."
echo "--target_dp:"
echo " Path to the directory containing the target repository."
echo "--commit:"
echo " The commit of the target repository that renames patched files."
echo "--branch:"
echo " Working branch left checked out with the merge result."
exit 2
}
patch_dp=""
target_repository_dp=""
commit=""
working_branch="vissat"
tmp_branch="repositorypatcher_tmp_premigrate"
while [[ $# -gt 0 ]]; do
case "$1" in
-p|--patch_dp)
shift; patch_dp="$1"; shift ;;
-r|--target_repository_dp|--target_dp)
shift; target_repository_dp="$1"; shift ;;
-c|--commit)
shift; commit="$1"; shift ;;
-b|--branch)
shift; working_branch="$1"; shift ;;
*)
echo "Error: Unknown option $1"
usage ;;
esac
done
if [[ -z "$patch_dp" || -z "$target_repository_dp" || -z "$commit" ]]; then
echo "Error: All mandatory parameters must be provided."
usage
fi
# Resolve and validate the paths (no implicit defaults - operating on the
# wrong repository must fail loudly, not silently).
patch_dp="$(realpath "$patch_dp" 2>/dev/null)"
if [[ -z "$patch_dp" || ! -d "$patch_dp" ]]; then
echo "Error: --patch_dp does not exist."
exit 2
fi
target_repository_dp="$(realpath "$target_repository_dp" 2>/dev/null)"
if [[ -z "$target_repository_dp" || ! -d "$target_repository_dp" ]]; then
echo "Error: --target_dp does not exist."
exit 2
fi
if ! git -C "$target_repository_dp" rev-parse --git-dir >/dev/null 2>&1; then
echo "Error: --target_dp is not a git repository: $target_repository_dp"
exit 2
fi
num_patch_files=$(find "$patch_dp" -type f -name "*.patch" | wc -l)
if [ "$num_patch_files" -eq 0 ]; then
echo "Error: No *.patch files found in --patch_dp: $patch_dp"
exit 2
fi
cd "$target_repository_dp"
if ! git rev-parse --verify --quiet "${commit}^{commit}" >/dev/null; then
echo "Error: --commit is not a commit of the target repository: $commit"
exit 2
fi
parent="$(git rev-parse "${commit}^")"
echo "Patch DP: $patch_dp ($num_patch_files patch files)"
echo "Target DP: $target_repository_dp"
echo "Rename commit: $commit"
echo "Parent commit: $parent"
# 1. Patched state at the parent commit on a temporary branch.
if [ -n "$(git branch --list $tmp_branch)" ]; then
git branch --force --delete $tmp_branch >/dev/null
fi
git switch --force --create $tmp_branch "$parent" >/dev/null 2>&1
apply_failed=0
for patch_fp in "$patch_dp"/*.patch; do
if ! git apply "$patch_fp" 2>/dev/null; then
# Context drift since the last checkpoint - try 3way. A remaining
# conflict is a real failure: the patch set must first be made
# compatible with the parent commit (regular apply_patches.sh walk).
if git apply --3way "$patch_fp" 2>/dev/null; then
echo "3way-merged at parent: $(basename "$patch_fp")"
else
echo "APPLY FAIL at parent: $(basename "$patch_fp")"
apply_failed=1
fi
fi
done
if [ $apply_failed -ne 0 ]; then
echo "Error: The patch set does not apply at the parent commit $parent."
echo "Bring the patch set up to the parent first (apply_patches.sh)."
git switch --force "$working_branch" >/dev/null 2>&1
git branch --force --delete $tmp_branch >/dev/null
exit 3
fi
git add -A
git commit --quiet -m "RepositoryPatcher: temporary patched state at $parent"
# 2. Merge the patched parent state into the rename commit - git's rename
# detection carries the patched changes to the new paths.
if [ -n "$(git branch --list $working_branch)" ]; then
git branch --force --delete $working_branch >/dev/null
fi
git switch --force --create $working_branch "$commit" >/dev/null 2>&1
if git merge $tmp_branch >/dev/null 2>&1; then
echo "MERGE CLEAN - the patched changes were carried to the new paths."
merge_result=0
else
echo "MERGE CONFLICTS - resolve manually in the following files:"
git diff --name-only --diff-filter=U
merge_result=1
fi
git branch --force --delete $tmp_branch >/dev/null
echo ""
echo "Next steps:"
if [ $merge_result -ne 0 ]; then
echo " 1. Resolve the conflicts (e.g. VSCode merge editor), then: git add -A"
echo " 2. git reset --mixed $commit"
echo " 3. Regenerate the patch set with create_patches.sh (and delete"
echo " patch files whose target path no longer exists)."
else
echo " 1. git reset --mixed $commit"
echo " 2. Regenerate the patch set with create_patches.sh (and delete"
echo " patch files whose target path no longer exists)."
fi
exit $merge_result