cogito: understand permissions written as "100755"
[cogito.git] / cg-mkpatch
blob505ed47890016b4a8d53c85da53f2039452068ee
1 #!/usr/bin/env bash
3 # Create a patch from a commit or a series of commits
4 # Copyright (c) Petr Baudis, 2005
6 # Generate a patch with diff statistics and meta info about each commit.
8 # Note that if you want to use this as the output interface for your
9 # GIT tree containing changes against upstream GIT tree, please consider
10 # using the StGIT tool ("quilt for GIT"), which will enable you to
11 # update your patches seamlessly, rebase them against the latest upstream
12 # version, directly send them over mail, etc.
14 # OPTIONS
15 # -------
16 # -d DIRNAME:: Create patches in the DIRNAME directory
17 # Split the patches to separate files with their names in the
18 # format "%02d.patch", created in directory DIRNAME (will be
19 # created if non-existent). Note that this makes sense only
20 # when generating patch series, that is when you use the -r
21 # argument.
23 # -f FORMAT:: Specify patch file name format
24 # Format string used for generating the patch filename when
25 # outputting the split-out patches (that is, passed the -d
26 # option). This is by default "%s/%02d-%s.patch". The first %s
27 # represents the directory name and %d represents the patch
28 # sequence number. The last %s is mangled first line of the
29 # commit message - kind of patch title.
31 # -m:: Base the diff at the merge base
32 # Base the patches at the merge base of the -r arguments
33 # (defaulting to HEAD and 'origin' or the current branch's default
34 # remote branch, see `cg-fetch` for details).
36 # --no-renames:: Do not detect renames
37 # By default, `cg-mkpatch` will automatically detect file renames.
38 # Diff produced by the rename-aware `cg-mkpatch` will be unappliable
39 # using patch(1) (you need to use `cg-patch`) and the renames
40 # detection can add slight extra performance penalty. This switch
41 # will turn the rename detection off.
43 # -r FROM_ID[..TO_ID]:: Limit to revision range
44 # Specify a set of commits to make patches from using either
45 # '-r FROM_ID[..TO_ID]' or '-r FROM_ID -r TO_ID'. In both cases the
46 # option expects IDs which resolve to commits and will include the
47 # specified IDs. If 'TO_ID' is omitted patches for all commits
48 # from 'FROM_ID' to the initial commit will be generated. If the
49 # `-r` option is not given the commit ID defaults to 'HEAD'.
51 # -s:: Omit patch header
52 # Specify whether to print a short version of the patch without
53 # a patch header with meta info such as author and committer.
55 # EXAMPLE USAGE
56 # -------------
57 # To make patches for all commits between two releases tagged as
58 # 'releasetag-0.9' and 'releasetag-0.10' do:
60 # $ cg-mkpatch -r releasetag-0.9..releasetag-0.10
62 # The output will be a continuous dump of patches each separated by
63 # the line:
65 # !-------------------------------------------------------------flip-
67 # NOTES
68 # -----
69 # The ':' is equivalent to '..' in revisions range specification (to make
70 # things more comfortable to SVN users). See cogito(7) for more details
71 # about revision specification.
73 # Testsuite: TODO
75 USAGE="cg-mkpatch [-m] [-s] [-r FROM_ID[..TO_ID] [-d DIRNAME]]"
76 _git_requires_root=1
77 _git_wc_unneeded=1
79 . "${COGITO_LIB}"cg-Xlib || exit 1
81 showpatch()
83 header="$(mktemp -t gitpatch.XXXXXX)"
84 patch="$(mktemp -t gitpatch.XXXXXX)"
85 id="$1"
86 cg-diff $no_renames -p -r "$id" >"$patch"
87 git-cat-file commit "$id" | while read -r key rest; do
88 case "$key" in
89 "author"|"committer")
90 date=(${rest#*> })
91 showdate ${date[*]}; pdate="$_showdate"
92 [ "$pdate" ] && rest="${rest%> *}> $pdate"
93 echo "$key" "$rest" >>"$header"
95 "")
96 cat
97 if [ ! "$omit_header" ]; then
98 echo
99 echo ---
101 echo commit "$id"
102 cat "$header"
103 echo
104 cat "$patch" | git-apply --stat
108 echo "$key" "$rest" >>"$header"
110 esac
111 done
112 echo
113 cat "$patch"
114 rm "$header" "$patch"
118 omit_header=
119 log_start=
120 log_end=
121 mergebase=
122 outdir=
123 fileformat="%s/%02d-%s.patch"
124 no_renames=
125 while optparse; do
126 if optparse -s; then
127 omit_header=1
128 elif optparse -r=; then
129 if echo "$OPTARG" | fgrep -q '..'; then
130 log_end="${OPTARG#*..}"
131 [ "$log_end" ] || log_end="HEAD"
132 log_start="${OPTARG%..*}"
133 elif echo "$OPTARG" | grep -q ':'; then
134 log_end="${OPTARG#*:}"
135 [ "$log_end" ] || log_end="HEAD"
136 log_start="${OPTARG%:*}"
137 elif [ -z "$log_start" ]; then
138 log_start="$OPTARG"
139 else
140 log_end="$OPTARG"
142 elif optparse -m; then
143 mergebase=1
144 elif optparse -d=; then
145 outdir="$OPTARG"
146 elif optparse -f=; then
147 fileformat="$OPTARG"
148 elif optparse --no-renames; then
149 no_renames=--no-renames
150 else
151 optfail
153 done
155 if [ "$mergebase" ]; then
156 [ "$log_start" ] || log_start="HEAD"
157 [ "$log_end" ] || { log_end="$(choose_origin refs/heads "what to mkpatch against?")" || exit 1; }
158 id1="$(cg-object-id -c "$log_start")" || exit 1
159 id2="$(cg-object-id -c "$log_end")" || exit 1
160 conservative_merge_base "$id1" "$id2" || exit 1
161 [ "$_cg_base_conservative" ] &&
162 warn -b "multiple merge bases, picking the most conservative one"
163 log_start="$_cg_baselist"
166 if [ "$log_end" ]; then
167 id1="$(cg-object-id -c "$log_start")" || exit 1
168 id2="$(cg-object-id -c "$log_end")" || exit 1
170 if [ "$outdir" ]; then
171 mkdir -p "$outdir" || die "cannot create patch directory"
172 pnum=001
175 git-rev-list --topo-order "$id2" "^$id1" | tac | while read id; do
176 if [ "$outdir" ]; then
177 title="$(git-cat-file commit "$id" |
178 sed -n '/^$/{n;
179 s/^[ \t]*\[[^]]*\][ \t]*//;
180 s/[:., \t][:., \t]*/-/g;
181 s/_/-/g;
182 # *cough*
183 y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/;
184 s/[^a-zA-Z0-9-]//g;
185 p;q;}')"
186 filename="$(printf "$fileformat" "$outdir" "$pnum" "$title")"
187 echo "$filename"
188 showpatch "$id" >"$filename"
189 pnum=$((pnum+1))
190 else
191 showpatch "$id"
192 echo
193 echo
194 echo -e '\014'
195 echo '!-------------------------------------------------------------flip-'
196 echo
197 echo
199 done
201 else
202 [ "$outdir" ] && die "-d makes sense only for patch series"
203 id="$(cg-object-id -c "$log_start")" || exit 1
204 showpatch "$id"