3 # Apply a patch from a file, standard input, or a commit
4 # Copyright (c) Petr Baudis, 2005
6 # Apply a patch in a manner similar to the 'patch' tool, but while also
7 # handling the Git extensions to the diff format: file mode changes, file
8 # renames, distinguishing removal of files and empty files, etc. Newly
9 # created files are automatically `cg-add`ed and removed files are
12 # `cg-patch` can also automatically commit the applied patches and extract
13 # patches from existing commits, therefore effectively enabling you to
14 # 'cherrypick' certain changes from a different branch.
16 # In comparison with the 'git-apply' tool, `cg-patch` will also apply
21 # -c:: Automatically commit the patch
22 # Automatically extract the commit message and authorship information
23 # (if provided) from the patch and commit it after applying it
26 # -C COMMIT:: Cherry-pick the given commit
27 # Instead of applying a patch from stdin, apply and commit the patch
28 # introduced by the given commit. This is basically an extension of
29 # `cg-commit -c`, it also applies the commit diff.
31 # In combination with '-R', this does the opposite - it will revert
32 # the given commit and then try to commit a revert commit - it will
33 # prefill the headline and open the commit editor for you to write
36 # Note that even though this is functionally equivalent to the
37 # cherry-picking concept present in other version control systems,
38 # this does not play very well together with regular merges and if
39 # you both cherry-pick and merge between two branches, the picking
40 # may increase the number of conflicts you will get when merging.
42 # -d DIRNAME:: Apply all patches in directory
43 # Instead of applying a patch from stdin, apply and separately commit
44 # all patches in the specified directory. This can be used to import
45 # a range of patches made by `cg-mkpatch -d`. Implies '-c'.
47 # -e:: Edit commit message before committing
48 # Edit the commit message before performing a commit. Makes sense
49 # only with '-c' or other options implying '-c' (e.g. '-m').
51 # -m:: Apply patches in a mailbox
52 # Applies series of patches in a mailbox fed to the command's
53 # standard input. Implies '-c'.
55 # -pN:: Strip path to the level N
56 # Strip path of filenames in the diff to the level N. This works
57 # exactly the same as in the `patch` tool except that the default
58 # strip level is not infinite but 1 (or more if you are in a
59 # subdirectory; in short, `cg-diff | cg-patch -R` and such always
62 # -R:: Apply in reverse
63 # Apply the patch in reverse (therefore effectively unapply it).
64 # Implies '-e' except when the input is not a tty.
66 # --resolved:: Resume -d or -m after conflicts were resolved
67 # In case the patch series application failed in the middle and
68 # you resolved the situation, running cg-patch with with the '-d' or '-m'
69 # argument as well as '--resolved' will cause it to pick up where it
70 # dropped off and go on applying. (This includes committing the failed
71 # patch; do not commit it on your own!) (For '-m', you don't need to
72 # feed the mailbox on stdin anymore.)
74 # -s, --signoff[=STRING]:: Automatically append a sign off line
75 # Add Signed-off-by line at the end of the commit message when
76 # autocommitting (-c, -C, -d or -m). Optionally, specify the exact name
77 # and email to sign off with by passing:
78 # `--signoff="Author Name <user@example.com>"`.
80 # Takes the diff on stdin (unless specified otherwise).
84 USAGE
="cg-patch [-c] [-C COMMIT] [-pN] [-R] [-m | -d DIR] [OTHER_OPTIONS] < PATCH"
86 .
"${COGITO_LIB}"cg-Xlib ||
exit 1
91 local file="$1" where
="$2"
92 local author
="$(sed -n '/^\(---\|-- \)$/,$p' < "$file" | sed -n '/^author /p')"
93 [ "$author" ] || warn
"no author info found$where, assuming your authorship"
94 eval "$(echo "$author" | pick_author)"
101 [ -z "$signoff" ] || ciargs
[${#ciargs[@]}]="$signoff"
102 [ -z "$edit" ] || ciargs
[${#ciargs[@]}]="$edit"
103 # FIXME: -e is broken, it won't pre-fill the message
104 sed '/^\(---\|-- \|diff --git .*\)$/,$d' < "$file" | cg-commit
"${ciargs[@]}"
109 sed "0,/\/$(echo "$lastpatch" | sed 's#/#\\/#g')$/d"
119 export GIT_AUTHOR_NAME
="${line#* }";;
121 export GIT_AUTHOR_EMAIL
="${line#* }";;
123 export GIT_AUTHOR_DATE
="${line#* }";;
125 mi_subj
="${line#* }";;
127 done <"$resume/i/$patch"
130 # Assuming that parse_mail_info() has been already ran on the patch.
135 [ -z "$signoff" ] || ciargs
[${#ciargs[@]}]="$signoff"
136 [ -z "$edit" ] || ciargs
[${#ciargs[@]}]="$edit"
137 cg-commit
-m"$mi_subj" -M"$resume/m/$patch" "${ciargs[@]}"
146 strip
=$
((1+$
(echo "$_git_relpath" |
tr -cd / |
wc -c)))
152 if optparse
-C=; then
153 commitid
="$(cg-object-id -c "$OPTARG")" ||
exit 1
154 commitparent
="$(cg-object-id -p "$commitid")" ||
exit 1
155 [ -z "$commitparent" ] && die
"cannot pick initial commit"
156 [ "$(echo "$commitparent" | wc -l)" -gt 1 ] &&
157 die
"refusing to pick merge commits"
159 elif optparse
-c; then
162 elif optparse
-d=; then
163 commitdir
="$(echo "$OPTARG" | sed 's,/*$,,')"
164 [ -d "$commitdir" ] || die
"$commitdir: not a directory"
166 elif optparse
-m; then
169 elif optparse
-p=; then
171 [ -n "$(echo "$strip" | tr -d 0-9)" ] &&
172 die
"the -p argument must be numeric"
173 applyargs
[${#applyargs[@]}]="-p$strip"
175 elif optparse
--resolved; then
178 elif optparse
-R; then
180 applyargs
[${#applyargs[@]}]="-R"
182 elif optparse
-s || optparse
--signoff; then
183 [ "$signoff" ] || signoff
="--signoff=$(git-var GIT_AUTHOR_IDENT | sed 's/> .*/>/')"
185 elif optparse
--signoff=; then
186 signoff
="--signoff=$OPTARG"
188 elif optparse
-e; then
197 [ "$resolved" ] && [ -z "$commitdir" ] && [ -z "$mbox" ] &&
198 die
"--resolved can be passed only with -d"
201 if [ "$commitid" ] ||
[ "$commit" ] ||
[ "$commitdir" ] ||
[ "$mbox" ]; then
202 [ "$_git_relpath" ] && die
"must be ran from project root"
204 if [ "$commitid" ]; then
205 [ "$unidiff" ] && die
"-u does not make sense here"
206 [ $strip -ne 1 ] && die
"-p does not make sense here"
208 files
="$(mktemp -t gitpatch.XXXXXX)"
209 git-diff-tree
-m -r "$commitparent" "$commitid" | cut
-f 2- >"$files"
210 if local_changes_in
"$files"; then
212 die
"cherry-pick blocked by local changes"
214 eval "afiles=($(cat "$files" | sed -e 's/"\|
\\/\\&/g
' -e 's
/^.
*$
/"&"/'))"
218 if ! [ "$reverse" ]; then
219 ciargs=(-c "$commitid")
221 ciargs=(-m "Revert ${commitid:0:12}")
223 ciargs[${#ciargs[@]}]="-e"
227 [ -z "$signoff" ] || ciargs[${#ciargs[@]}]="$signoff"
228 [ -z "$edit" ] || ciargs[${#ciargs[@]}]="$edit"
230 if ! cg-diff -p -r "$commitid" | cg-patch "${applyargs[@]}"; then
231 echo "Cherry-picking $commitid failed, please fix up the rejects." >&2
232 echo "You can use cg-commit -c $commitid to commit afterwards (that will" >&2
233 echo "reuse the commit message and authorship); throw in -e to add own comments." >&2
236 cg-commit "${ciargs[@]}" "${afiles[@]}"
239 if [ "$commit" ]; then
240 [ "$reverse" ] && die "cannot do -R here"
243 die "cannot auto-commit patches when the tree has local changes"
244 file="$(mktemp -t gitpatch.XXXXXX)"
246 lookover_patch "$file"
247 cg-patch "${applyargs[@]}" <"$file" || exit 1
252 if [ "$commitdir" ]; then
253 [ "$reverse" ] && die "cannot do -R here"
255 resume="$commitdir/.cg-patch-resume"
256 if [ -s "$resume" ]; then
257 if [ ! "$resolved" ]; then
258 echo "cg-patch: previous import in progress" >&2
259 echo "Use --resolved to resume after conflicts." >&2
260 echo "Cancel the resume by deleting $resume" >&2
264 echo "Resuming import of $commitdir:"
267 lastpatch="$(cat "$resume")"
269 commit_patch "$commitdir/$lastpatch"
272 elif local_changes; then
273 die "cannot auto-commit patches when the tree has local changes"
276 echo "Importing $commitdir:"
280 find "$commitdir" -name '[0-9]*-*' | sort | "$filter" | \
281 while read -r file; do
282 echo "* ${file#$commitdir/}"
283 lookover_patch "$file" " in $file"
284 if ! cg-patch "${applyargs[@]}" < "$file"; then
285 echo "${file#$commitdir/}" > "$resume"
286 echo "cg-patch: conflicts during import" >&2
287 echo "Rerun cg-patch with the -d... --resolved arguments to resume after resolving them." >&2
288 echo "Cancel the resume by deleting $resume" >&2
296 [ "$reverse" ] && die "cannot do -R here"
298 resume="$_git/info/cg-patch-mresume"
299 if [ -d "$resume" ]; then
300 if [ ! "$resolved" ]; then
301 echo "cg-patch: previous import in progress" >&2
302 echo "Use --resolved to resume after conflicts." >&2
303 echo "Cancel the resume by deleting $resume" >&2
307 echo "Resuming import of mailbox:"
309 lastpatch="$(cat "$resume/last")"
310 parse_mail_info "$lastpatch"
312 echo "* $lastpatch $mi_subj"
313 commit_mail_patch "$lastpatch"
314 rm "$resume/a/$lastpatch"
316 elif local_changes; then
317 die "cannot auto-commit patches when the tree has local changes"
320 echo "Importing mailbox:"
321 mkdir -p "$resume" || exit 1
326 git-mailsplit -o"$resume/a" >/dev/null
329 for patch in "$resume"/a/*; do
330 patch="${patch#$resume/a/}"
331 [ "$patch" = "*" ] && break # Out of patches
332 echo "$patch" >"$resume/last"
333 git-mailinfo "$resume/m/$patch" "$resume/p/$patch" \
334 <"$resume/a/$patch" >"$resume/i/$patch"
335 parse_mail_info "$patch"
337 echo "* $patch $mi_subj"
338 if ! cg-patch "${applyargs[@]}" < "$resume/p/$patch"; then
339 echo "cg-patch: conflicts during import" >&2
340 echo "Rerun cg-patch with the -m --resolved arguments to resume after resolving them." >&2
341 echo "Cancel the resume by deleting $resume" >&2
344 commit_mail_patch "$patch"
345 rm "$resume/a/$patch"
355 # We want to run patch in the subdirectory and at any rate protect other
356 # parts of the tree from inadverent pollution.
357 [ -n "$_git_relpath" ] && cd "$_git_relpath"
360 exec git-apply --reject "${applyargs[@]}"