Test commit
[cogito/jonas.git] / cg-patch
blob1a3188e42a006fd595a6b1232c832e3421b29c90
1 #!/usr/bin/env bash
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
10 # `cg-rm`oved.
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
17 # fuzzy patches.
19 # OPTIONS
20 # -------
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
24 # successfully.
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
34 # further details.
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
60 # works).
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).
82 # Testsuite: TODO
84 USAGE="cg-patch [-c] [-C COMMIT] [-pN] [-R] [-m | -d DIR] [OTHER_OPTIONS] < PATCH"
86 . "${COGITO_LIB}"cg-Xlib || exit 1
89 lookover_patch()
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)"
97 commit_patch()
99 local file="$1"
100 local -a ciargs=()
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[@]}"
107 resume_filter()
109 sed "0,/\/$(echo "$lastpatch" | sed 's#/#\\/#g')$/d"
113 parse_mail_info()
115 local patch="$1"
116 while read line; do
117 case $line in
118 Author:*)
119 export GIT_AUTHOR_NAME="${line#* }";;
120 Email:*)
121 export GIT_AUTHOR_EMAIL="${line#* }";;
122 Date:*)
123 export GIT_AUTHOR_DATE="${line#* }";;
124 Subject:*)
125 mi_subj="${line#* }";;
126 esac
127 done <"$resume/i/$patch"
130 # Assuming that parse_mail_info() has been already ran on the patch.
131 commit_mail_patch()
133 local patch="$1"
134 local -a ciargs=()
135 [ -z "$signoff" ] || ciargs[${#ciargs[@]}]="$signoff"
136 [ -z "$edit" ] || ciargs[${#ciargs[@]}]="$edit"
137 cg-commit -m"$mi_subj" -M"$resume/m/$patch" "${ciargs[@]}"
141 applyargs=()
142 commitid=
143 commit=
144 mbox=
145 commitdir=
146 strip=$((1+$(echo "$_git_relpath" | tr -cd / | wc -c)))
147 reverse=
148 resolved=
149 signoff=
150 edit=
151 while optparse; do
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
160 commit=1
162 elif optparse -d=; then
163 commitdir="$(echo "$OPTARG" | sed 's,/*$,,')"
164 [ -d "$commitdir" ] || die "$commitdir: not a directory"
166 elif optparse -m; then
167 mbox=1
169 elif optparse -p=; then
170 strip="$OPTARG"
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
176 resolved=1
178 elif optparse -R; then
179 reverse=1
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
189 edit="-e"
191 else
192 optfail
194 done
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
211 rm "$files"
212 die "cherry-pick blocked by local changes"
214 eval "afiles=($(cat "$files" | sed -e 's/"\|\\/\\&/g' -e 's/^.*$/"&"/'))"
215 rm "$files"
217 ciargs=()
218 if ! [ "$reverse" ]; then
219 ciargs=(-c "$commitid")
220 else
221 ciargs=(-m "Revert ${commitid:0:12}")
222 if tty -s; then
223 ciargs[${#ciargs[@]}]="-e"
225 reverse=-R
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
234 exit 1
236 cg-commit "${ciargs[@]}" "${afiles[@]}"
239 if [ "$commit" ]; then
240 [ "$reverse" ] && die "cannot do -R here"
242 local_changes &&
243 die "cannot auto-commit patches when the tree has local changes"
244 file="$(mktemp -t gitpatch.XXXXXX)"
245 cat >"$file"
246 lookover_patch "$file"
247 cg-patch "${applyargs[@]}" <"$file" || exit 1
248 commit_patch "$file"
249 rm "$file"
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
261 exit 1
264 echo "Resuming import of $commitdir:"
265 filter=resume_filter
267 lastpatch="$(cat "$resume")"
268 echo "* $lastpatch"
269 commit_patch "$commitdir/$lastpatch"
270 rm -f "$resume"
272 elif local_changes; then
273 die "cannot auto-commit patches when the tree has local changes"
275 else
276 echo "Importing $commitdir:"
277 filter=cat
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
289 exit 1
291 commit_patch "$file"
292 done
295 if [ "$mbox" ]; then
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
304 exit 1
307 echo "Resuming import of mailbox:"
309 lastpatch="$(cat "$resume/last")"
310 parse_mail_info "$lastpatch"
311 echo
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"
319 else
320 echo "Importing mailbox:"
321 mkdir -p "$resume" || exit 1
322 mkdir -p "$resume/a"
323 mkdir -p "$resume/i"
324 mkdir -p "$resume/m"
325 mkdir -p "$resume/p"
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"
336 echo
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
342 exit 1
344 commit_mail_patch "$patch"
345 rm "$resume/a/$patch"
346 done
348 rm -r "$resume"
351 exit
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[@]}"