Commit 102061bd8b0b174e1cf811dfd35641d8a9e4eba3
1 parent
757f64a89b
Exists in
v2017.01-smarct4x
and in
40 other branches
patman: Avoid duplicate sign-offs
Keep track of all Signed-off-by tags in a commit and silently suppress any duplicates. Signed-off-by: Simon Glass <sjg@chromium.org>
Showing 3 changed files with 23 additions and 2 deletions Inline Diff
tools/patman/README
1 | # Copyright (c) 2011 The Chromium OS Authors. | 1 | # Copyright (c) 2011 The Chromium OS Authors. |
2 | # | 2 | # |
3 | # SPDX-License-Identifier: GPL-2.0+ | 3 | # SPDX-License-Identifier: GPL-2.0+ |
4 | # | 4 | # |
5 | 5 | ||
6 | What is this? | 6 | What is this? |
7 | ============= | 7 | ============= |
8 | 8 | ||
9 | This tool is a Python script which: | 9 | This tool is a Python script which: |
10 | - Creates patch directly from your branch | 10 | - Creates patch directly from your branch |
11 | - Cleans them up by removing unwanted tags | 11 | - Cleans them up by removing unwanted tags |
12 | - Inserts a cover letter with change lists | 12 | - Inserts a cover letter with change lists |
13 | - Runs the patches through checkpatch.pl and its own checks | 13 | - Runs the patches through checkpatch.pl and its own checks |
14 | - Optionally emails them out to selected people | 14 | - Optionally emails them out to selected people |
15 | 15 | ||
16 | It is intended to automate patch creation and make it a less | 16 | It is intended to automate patch creation and make it a less |
17 | error-prone process. It is useful for U-Boot and Linux work so far, | 17 | error-prone process. It is useful for U-Boot and Linux work so far, |
18 | since it uses the checkpatch.pl script. | 18 | since it uses the checkpatch.pl script. |
19 | 19 | ||
20 | It is configured almost entirely by tags it finds in your commits. | 20 | It is configured almost entirely by tags it finds in your commits. |
21 | This means that you can work on a number of different branches at | 21 | This means that you can work on a number of different branches at |
22 | once, and keep the settings with each branch rather than having to | 22 | once, and keep the settings with each branch rather than having to |
23 | git format-patch, git send-email, etc. with the correct parameters | 23 | git format-patch, git send-email, etc. with the correct parameters |
24 | each time. So for example if you put: | 24 | each time. So for example if you put: |
25 | 25 | ||
26 | Series-to: fred.blogs@napier.co.nz | 26 | Series-to: fred.blogs@napier.co.nz |
27 | 27 | ||
28 | in one of your commits, the series will be sent there. | 28 | in one of your commits, the series will be sent there. |
29 | 29 | ||
30 | In Linux this will also call get_maintainer.pl on each of your | 30 | In Linux this will also call get_maintainer.pl on each of your |
31 | patches automatically. | 31 | patches automatically. |
32 | 32 | ||
33 | 33 | ||
34 | How to use this tool | 34 | How to use this tool |
35 | ==================== | 35 | ==================== |
36 | 36 | ||
37 | This tool requires a certain way of working: | 37 | This tool requires a certain way of working: |
38 | 38 | ||
39 | - Maintain a number of branches, one for each patch series you are | 39 | - Maintain a number of branches, one for each patch series you are |
40 | working on | 40 | working on |
41 | - Add tags into the commits within each branch to indicate where the | 41 | - Add tags into the commits within each branch to indicate where the |
42 | series should be sent, cover letter, version, etc. Most of these are | 42 | series should be sent, cover letter, version, etc. Most of these are |
43 | normally in the top commit so it is easy to change them with 'git | 43 | normally in the top commit so it is easy to change them with 'git |
44 | commit --amend' | 44 | commit --amend' |
45 | - Each branch tracks the upstream branch, so that this script can | 45 | - Each branch tracks the upstream branch, so that this script can |
46 | automatically determine the number of commits in it (optional) | 46 | automatically determine the number of commits in it (optional) |
47 | - Check out a branch, and run this script to create and send out your | 47 | - Check out a branch, and run this script to create and send out your |
48 | patches. Weeks later, change the patches and repeat, knowing that you | 48 | patches. Weeks later, change the patches and repeat, knowing that you |
49 | will get a consistent result each time. | 49 | will get a consistent result each time. |
50 | 50 | ||
51 | 51 | ||
52 | How to configure it | 52 | How to configure it |
53 | =================== | 53 | =================== |
54 | 54 | ||
55 | For most cases of using patman for U-Boot development, patman will | 55 | For most cases of using patman for U-Boot development, patman will |
56 | locate and use the file 'doc/git-mailrc' in your U-Boot directory. | 56 | locate and use the file 'doc/git-mailrc' in your U-Boot directory. |
57 | This contains most of the aliases you will need. | 57 | This contains most of the aliases you will need. |
58 | 58 | ||
59 | For Linux the 'scripts/get_maintainer.pl' handles figuring out where | 59 | For Linux the 'scripts/get_maintainer.pl' handles figuring out where |
60 | to send patches pretty well. | 60 | to send patches pretty well. |
61 | 61 | ||
62 | During the first run patman creates a config file for you by taking the default | 62 | During the first run patman creates a config file for you by taking the default |
63 | user name and email address from the global .gitconfig file. | 63 | user name and email address from the global .gitconfig file. |
64 | 64 | ||
65 | To add your own, create a file ~/.patman like this: | 65 | To add your own, create a file ~/.patman like this: |
66 | 66 | ||
67 | >>>> | 67 | >>>> |
68 | # patman alias file | 68 | # patman alias file |
69 | 69 | ||
70 | [alias] | 70 | [alias] |
71 | me: Simon Glass <sjg@chromium.org> | 71 | me: Simon Glass <sjg@chromium.org> |
72 | 72 | ||
73 | u-boot: U-Boot Mailing List <u-boot@lists.denx.de> | 73 | u-boot: U-Boot Mailing List <u-boot@lists.denx.de> |
74 | wolfgang: Wolfgang Denk <wd@denx.de> | 74 | wolfgang: Wolfgang Denk <wd@denx.de> |
75 | others: Mike Frysinger <vapier@gentoo.org>, Fred Bloggs <f.bloggs@napier.net> | 75 | others: Mike Frysinger <vapier@gentoo.org>, Fred Bloggs <f.bloggs@napier.net> |
76 | 76 | ||
77 | <<<< | 77 | <<<< |
78 | 78 | ||
79 | Aliases are recursive. | 79 | Aliases are recursive. |
80 | 80 | ||
81 | The checkpatch.pl in the U-Boot tools/ subdirectory will be located and | 81 | The checkpatch.pl in the U-Boot tools/ subdirectory will be located and |
82 | used. Failing that you can put it into your path or ~/bin/checkpatch.pl | 82 | used. Failing that you can put it into your path or ~/bin/checkpatch.pl |
83 | 83 | ||
84 | 84 | ||
85 | If you want to change the defaults for patman's command-line arguments, | 85 | If you want to change the defaults for patman's command-line arguments, |
86 | you can add a [settings] section to your .patman file. This can be used | 86 | you can add a [settings] section to your .patman file. This can be used |
87 | for any command line option by referring to the "dest" for the option in | 87 | for any command line option by referring to the "dest" for the option in |
88 | patman.py. For reference, the useful ones (at the moment) shown below | 88 | patman.py. For reference, the useful ones (at the moment) shown below |
89 | (all with the non-default setting): | 89 | (all with the non-default setting): |
90 | 90 | ||
91 | >>> | 91 | >>> |
92 | 92 | ||
93 | [settings] | 93 | [settings] |
94 | ignore_errors: True | 94 | ignore_errors: True |
95 | process_tags: False | 95 | process_tags: False |
96 | verbose: True | 96 | verbose: True |
97 | 97 | ||
98 | <<< | 98 | <<< |
99 | 99 | ||
100 | 100 | ||
101 | If you want to adjust settings (or aliases) that affect just a single | 101 | If you want to adjust settings (or aliases) that affect just a single |
102 | project you can add a section that looks like [project_settings] or | 102 | project you can add a section that looks like [project_settings] or |
103 | [project_alias]. If you want to use tags for your linux work, you could | 103 | [project_alias]. If you want to use tags for your linux work, you could |
104 | do: | 104 | do: |
105 | 105 | ||
106 | >>> | 106 | >>> |
107 | 107 | ||
108 | [linux_settings] | 108 | [linux_settings] |
109 | process_tags: True | 109 | process_tags: True |
110 | 110 | ||
111 | <<< | 111 | <<< |
112 | 112 | ||
113 | 113 | ||
114 | How to run it | 114 | How to run it |
115 | ============= | 115 | ============= |
116 | 116 | ||
117 | First do a dry run: | 117 | First do a dry run: |
118 | 118 | ||
119 | $ ./tools/patman/patman -n | 119 | $ ./tools/patman/patman -n |
120 | 120 | ||
121 | If it can't detect the upstream branch, try telling it how many patches | 121 | If it can't detect the upstream branch, try telling it how many patches |
122 | there are in your series: | 122 | there are in your series: |
123 | 123 | ||
124 | $ ./tools/patman/patman -n -c5 | 124 | $ ./tools/patman/patman -n -c5 |
125 | 125 | ||
126 | This will create patch files in your current directory and tell you who | 126 | This will create patch files in your current directory and tell you who |
127 | it is thinking of sending them to. Take a look at the patch files. | 127 | it is thinking of sending them to. Take a look at the patch files. |
128 | 128 | ||
129 | $ ./tools/patman/patman -n -c5 -s1 | 129 | $ ./tools/patman/patman -n -c5 -s1 |
130 | 130 | ||
131 | Similar to the above, but skip the first commit and take the next 5. This | 131 | Similar to the above, but skip the first commit and take the next 5. This |
132 | is useful if your top commit is for setting up testing. | 132 | is useful if your top commit is for setting up testing. |
133 | 133 | ||
134 | 134 | ||
135 | How to add tags | 135 | How to add tags |
136 | =============== | 136 | =============== |
137 | 137 | ||
138 | To make this script useful you must add tags like the following into any | 138 | To make this script useful you must add tags like the following into any |
139 | commit. Most can only appear once in the whole series. | 139 | commit. Most can only appear once in the whole series. |
140 | 140 | ||
141 | Series-to: email / alias | 141 | Series-to: email / alias |
142 | Email address / alias to send patch series to (you can add this | 142 | Email address / alias to send patch series to (you can add this |
143 | multiple times) | 143 | multiple times) |
144 | 144 | ||
145 | Series-cc: email / alias, ... | 145 | Series-cc: email / alias, ... |
146 | Email address / alias to Cc patch series to (you can add this | 146 | Email address / alias to Cc patch series to (you can add this |
147 | multiple times) | 147 | multiple times) |
148 | 148 | ||
149 | Series-version: n | 149 | Series-version: n |
150 | Sets the version number of this patch series | 150 | Sets the version number of this patch series |
151 | 151 | ||
152 | Series-prefix: prefix | 152 | Series-prefix: prefix |
153 | Sets the subject prefix. Normally empty but it can be RFC for | 153 | Sets the subject prefix. Normally empty but it can be RFC for |
154 | RFC patches, or RESEND if you are being ignored. | 154 | RFC patches, or RESEND if you are being ignored. |
155 | 155 | ||
156 | Series-name: name | 156 | Series-name: name |
157 | Sets the name of the series. You don't need to have a name, and | 157 | Sets the name of the series. You don't need to have a name, and |
158 | patman does not yet use it, but it is convenient to put the branch | 158 | patman does not yet use it, but it is convenient to put the branch |
159 | name here to help you keep track of multiple upstreaming efforts. | 159 | name here to help you keep track of multiple upstreaming efforts. |
160 | 160 | ||
161 | Cover-letter: | 161 | Cover-letter: |
162 | This is the patch set title | 162 | This is the patch set title |
163 | blah blah | 163 | blah blah |
164 | more blah blah | 164 | more blah blah |
165 | END | 165 | END |
166 | Sets the cover letter contents for the series. The first line | 166 | Sets the cover letter contents for the series. The first line |
167 | will become the subject of the cover letter | 167 | will become the subject of the cover letter |
168 | 168 | ||
169 | Cover-letter-cc: email / alias | 169 | Cover-letter-cc: email / alias |
170 | Additional email addresses / aliases to send cover letter to (you | 170 | Additional email addresses / aliases to send cover letter to (you |
171 | can add this multiple times) | 171 | can add this multiple times) |
172 | 172 | ||
173 | Series-notes: | 173 | Series-notes: |
174 | blah blah | 174 | blah blah |
175 | blah blah | 175 | blah blah |
176 | more blah blah | 176 | more blah blah |
177 | END | 177 | END |
178 | Sets some notes for the patch series, which you don't want in | 178 | Sets some notes for the patch series, which you don't want in |
179 | the commit messages, but do want to send, The notes are joined | 179 | the commit messages, but do want to send, The notes are joined |
180 | together and put after the cover letter. Can appear multiple | 180 | together and put after the cover letter. Can appear multiple |
181 | times. | 181 | times. |
182 | 182 | ||
183 | Commit-notes: | 183 | Commit-notes: |
184 | blah blah | 184 | blah blah |
185 | blah blah | 185 | blah blah |
186 | more blah blah | 186 | more blah blah |
187 | END | 187 | END |
188 | Similar, but for a single commit (patch). These notes will appear | 188 | Similar, but for a single commit (patch). These notes will appear |
189 | immediately below the --- cut in the patch file. | 189 | immediately below the --- cut in the patch file. |
190 | 190 | ||
191 | Signed-off-by: Their Name <email> | 191 | Signed-off-by: Their Name <email> |
192 | A sign-off is added automatically to your patches (this is | 192 | A sign-off is added automatically to your patches (this is |
193 | probably a bug). If you put this tag in your patches, it will | 193 | probably a bug). If you put this tag in your patches, it will |
194 | override the default signoff that patman automatically adds. | 194 | override the default signoff that patman automatically adds. |
195 | Multiple duplicate signoffs will be removed. | ||
195 | 196 | ||
196 | Tested-by: Their Name <email> | 197 | Tested-by: Their Name <email> |
197 | Reviewed-by: Their Name <email> | 198 | Reviewed-by: Their Name <email> |
198 | Acked-by: Their Name <email> | 199 | Acked-by: Their Name <email> |
199 | These indicate that someone has tested/reviewed/acked your patch. | 200 | These indicate that someone has tested/reviewed/acked your patch. |
200 | When you get this reply on the mailing list, you can add this | 201 | When you get this reply on the mailing list, you can add this |
201 | tag to the relevant commit and the script will include it when | 202 | tag to the relevant commit and the script will include it when |
202 | you send out the next version. If 'Tested-by:' is set to | 203 | you send out the next version. If 'Tested-by:' is set to |
203 | yourself, it will be removed. No one will believe you. | 204 | yourself, it will be removed. No one will believe you. |
204 | 205 | ||
205 | Series-changes: n | 206 | Series-changes: n |
206 | - Guinea pig moved into its cage | 207 | - Guinea pig moved into its cage |
207 | - Other changes ending with a blank line | 208 | - Other changes ending with a blank line |
208 | <blank line> | 209 | <blank line> |
209 | This can appear in any commit. It lists the changes for a | 210 | This can appear in any commit. It lists the changes for a |
210 | particular version n of that commit. The change list is | 211 | particular version n of that commit. The change list is |
211 | created based on this information. Each commit gets its own | 212 | created based on this information. Each commit gets its own |
212 | change list and also the whole thing is repeated in the cover | 213 | change list and also the whole thing is repeated in the cover |
213 | letter (where duplicate change lines are merged). | 214 | letter (where duplicate change lines are merged). |
214 | 215 | ||
215 | By adding your change lists into your commits it is easier to | 216 | By adding your change lists into your commits it is easier to |
216 | keep track of what happened. When you amend a commit, remember | 217 | keep track of what happened. When you amend a commit, remember |
217 | to update the log there and then, knowing that the script will | 218 | to update the log there and then, knowing that the script will |
218 | do the rest. | 219 | do the rest. |
219 | 220 | ||
220 | Patch-cc: Their Name <email> | 221 | Patch-cc: Their Name <email> |
221 | This copies a single patch to another email address. Note that the | 222 | This copies a single patch to another email address. Note that the |
222 | Cc: used by git send-email is ignored by patman, but will be | 223 | Cc: used by git send-email is ignored by patman, but will be |
223 | interpreted by git send-email if you use it. | 224 | interpreted by git send-email if you use it. |
224 | 225 | ||
225 | Series-process-log: sort, uniq | 226 | Series-process-log: sort, uniq |
226 | This tells patman to sort and/or uniq the change logs. It is | 227 | This tells patman to sort and/or uniq the change logs. It is |
227 | assumed that each change log entry is only a single line long. | 228 | assumed that each change log entry is only a single line long. |
228 | Use 'sort' to sort the entries, and 'uniq' to include only | 229 | Use 'sort' to sort the entries, and 'uniq' to include only |
229 | unique entries. If omitted, no change log processing is done. | 230 | unique entries. If omitted, no change log processing is done. |
230 | Separate each tag with a comma. | 231 | Separate each tag with a comma. |
231 | 232 | ||
232 | Various other tags are silently removed, like these Chrome OS and | 233 | Various other tags are silently removed, like these Chrome OS and |
233 | Gerrit tags: | 234 | Gerrit tags: |
234 | 235 | ||
235 | BUG=... | 236 | BUG=... |
236 | TEST=... | 237 | TEST=... |
237 | Change-Id: | 238 | Change-Id: |
238 | Review URL: | 239 | Review URL: |
239 | Reviewed-on: | 240 | Reviewed-on: |
240 | Commit-xxxx: (except Commit-notes) | 241 | Commit-xxxx: (except Commit-notes) |
241 | 242 | ||
242 | Exercise for the reader: Try adding some tags to one of your current | 243 | Exercise for the reader: Try adding some tags to one of your current |
243 | patch series and see how the patches turn out. | 244 | patch series and see how the patches turn out. |
244 | 245 | ||
245 | 246 | ||
246 | Where Patches Are Sent | 247 | Where Patches Are Sent |
247 | ====================== | 248 | ====================== |
248 | 249 | ||
249 | Once the patches are created, patman sends them using git send-email. The | 250 | Once the patches are created, patman sends them using git send-email. The |
250 | whole series is sent to the recipients in Series-to: and Series-cc. | 251 | whole series is sent to the recipients in Series-to: and Series-cc. |
251 | You can Cc individual patches to other people with the Patch-cc: tag. Tags | 252 | You can Cc individual patches to other people with the Patch-cc: tag. Tags |
252 | in the subject are also picked up to Cc patches. For example, a commit like | 253 | in the subject are also picked up to Cc patches. For example, a commit like |
253 | this: | 254 | this: |
254 | 255 | ||
255 | >>>> | 256 | >>>> |
256 | commit 10212537b85ff9b6e09c82045127522c0f0db981 | 257 | commit 10212537b85ff9b6e09c82045127522c0f0db981 |
257 | Author: Mike Frysinger <vapier@gentoo.org> | 258 | Author: Mike Frysinger <vapier@gentoo.org> |
258 | Date: Mon Nov 7 23:18:44 2011 -0500 | 259 | Date: Mon Nov 7 23:18:44 2011 -0500 |
259 | 260 | ||
260 | x86: arm: add a git mailrc file for maintainers | 261 | x86: arm: add a git mailrc file for maintainers |
261 | 262 | ||
262 | This should make sending out e-mails to the right people easier. | 263 | This should make sending out e-mails to the right people easier. |
263 | 264 | ||
264 | Patch-cc: sandbox, mikef, ag | 265 | Patch-cc: sandbox, mikef, ag |
265 | Patch-cc: afleming | 266 | Patch-cc: afleming |
266 | <<<< | 267 | <<<< |
267 | 268 | ||
268 | will create a patch which is copied to x86, arm, sandbox, mikef, ag and | 269 | will create a patch which is copied to x86, arm, sandbox, mikef, ag and |
269 | afleming. | 270 | afleming. |
270 | 271 | ||
271 | If you have a cover letter it will get sent to the union of the Patch-cc | 272 | If you have a cover letter it will get sent to the union of the Patch-cc |
272 | lists of all of the other patches. If you want to sent it to additional | 273 | lists of all of the other patches. If you want to sent it to additional |
273 | people you can add a tag: | 274 | people you can add a tag: |
274 | 275 | ||
275 | Cover-letter-cc: <list of addresses> | 276 | Cover-letter-cc: <list of addresses> |
276 | 277 | ||
277 | These people will get the cover letter even if they are not on the To/Cc | 278 | These people will get the cover letter even if they are not on the To/Cc |
278 | list for any of the patches. | 279 | list for any of the patches. |
279 | 280 | ||
280 | 281 | ||
281 | Example Work Flow | 282 | Example Work Flow |
282 | ================= | 283 | ================= |
283 | 284 | ||
284 | The basic workflow is to create your commits, add some tags to the top | 285 | The basic workflow is to create your commits, add some tags to the top |
285 | commit, and type 'patman' to check and send them. | 286 | commit, and type 'patman' to check and send them. |
286 | 287 | ||
287 | Here is an example workflow for a series of 4 patches. Let's say you have | 288 | Here is an example workflow for a series of 4 patches. Let's say you have |
288 | these rather contrived patches in the following order in branch us-cmd in | 289 | these rather contrived patches in the following order in branch us-cmd in |
289 | your tree where 'us' means your upstreaming activity (newest to oldest as | 290 | your tree where 'us' means your upstreaming activity (newest to oldest as |
290 | output by git log --oneline): | 291 | output by git log --oneline): |
291 | 292 | ||
292 | 7c7909c wip | 293 | 7c7909c wip |
293 | 89234f5 Don't include standard parser if hush is used | 294 | 89234f5 Don't include standard parser if hush is used |
294 | 8d640a7 mmc: sparc: Stop using builtin_run_command() | 295 | 8d640a7 mmc: sparc: Stop using builtin_run_command() |
295 | 0c859a9 Rename run_command2() to run_command() | 296 | 0c859a9 Rename run_command2() to run_command() |
296 | a74443f sandbox: Rename run_command() to builtin_run_command() | 297 | a74443f sandbox: Rename run_command() to builtin_run_command() |
297 | 298 | ||
298 | The first patch is some test things that enable your code to be compiled, | 299 | The first patch is some test things that enable your code to be compiled, |
299 | but that you don't want to submit because there is an existing patch for it | 300 | but that you don't want to submit because there is an existing patch for it |
300 | on the list. So you can tell patman to create and check some patches | 301 | on the list. So you can tell patman to create and check some patches |
301 | (skipping the first patch) with: | 302 | (skipping the first patch) with: |
302 | 303 | ||
303 | patman -s1 -n | 304 | patman -s1 -n |
304 | 305 | ||
305 | If you want to do all of them including the work-in-progress one, then | 306 | If you want to do all of them including the work-in-progress one, then |
306 | (if you are tracking an upstream branch): | 307 | (if you are tracking an upstream branch): |
307 | 308 | ||
308 | patman -n | 309 | patman -n |
309 | 310 | ||
310 | Let's say that patman reports an error in the second patch. Then: | 311 | Let's say that patman reports an error in the second patch. Then: |
311 | 312 | ||
312 | git rebase -i HEAD~6 | 313 | git rebase -i HEAD~6 |
313 | <change 'pick' to 'edit' in 89234f5> | 314 | <change 'pick' to 'edit' in 89234f5> |
314 | <use editor to make code changes> | 315 | <use editor to make code changes> |
315 | git add -u | 316 | git add -u |
316 | git rebase --continue | 317 | git rebase --continue |
317 | 318 | ||
318 | Now you have an updated patch series. To check it: | 319 | Now you have an updated patch series. To check it: |
319 | 320 | ||
320 | patman -s1 -n | 321 | patman -s1 -n |
321 | 322 | ||
322 | Let's say it is now clean and you want to send it. Now you need to set up | 323 | Let's say it is now clean and you want to send it. Now you need to set up |
323 | the destination. So amend the top commit with: | 324 | the destination. So amend the top commit with: |
324 | 325 | ||
325 | git commit --amend | 326 | git commit --amend |
326 | 327 | ||
327 | Use your editor to add some tags, so that the whole commit message is: | 328 | Use your editor to add some tags, so that the whole commit message is: |
328 | 329 | ||
329 | The current run_command() is really only one of the options, with | 330 | The current run_command() is really only one of the options, with |
330 | hush providing the other. It really shouldn't be called directly | 331 | hush providing the other. It really shouldn't be called directly |
331 | in case the hush parser is bring used, so rename this function to | 332 | in case the hush parser is bring used, so rename this function to |
332 | better explain its purpose. | 333 | better explain its purpose. |
333 | 334 | ||
334 | Series-to: u-boot | 335 | Series-to: u-boot |
335 | Series-cc: bfin, marex | 336 | Series-cc: bfin, marex |
336 | Series-prefix: RFC | 337 | Series-prefix: RFC |
337 | Cover-letter: | 338 | Cover-letter: |
338 | Unified command execution in one place | 339 | Unified command execution in one place |
339 | 340 | ||
340 | At present two parsers have similar code to execute commands. Also | 341 | At present two parsers have similar code to execute commands. Also |
341 | cmd_usage() is called all over the place. This series adds a single | 342 | cmd_usage() is called all over the place. This series adds a single |
342 | function which processes commands called cmd_process(). | 343 | function which processes commands called cmd_process(). |
343 | END | 344 | END |
344 | 345 | ||
345 | Change-Id: Ica71a14c1f0ecb5650f771a32fecb8d2eb9d8a17 | 346 | Change-Id: Ica71a14c1f0ecb5650f771a32fecb8d2eb9d8a17 |
346 | 347 | ||
347 | 348 | ||
348 | You want this to be an RFC and Cc the whole series to the bfin alias and | 349 | You want this to be an RFC and Cc the whole series to the bfin alias and |
349 | to Marek. Two of the patches have tags (those are the bits at the front of | 350 | to Marek. Two of the patches have tags (those are the bits at the front of |
350 | the subject that say mmc: sparc: and sandbox:), so 8d640a7 will be Cc'd to | 351 | the subject that say mmc: sparc: and sandbox:), so 8d640a7 will be Cc'd to |
351 | mmc and sparc, and the last one to sandbox. | 352 | mmc and sparc, and the last one to sandbox. |
352 | 353 | ||
353 | Now to send the patches, take off the -n flag: | 354 | Now to send the patches, take off the -n flag: |
354 | 355 | ||
355 | patman -s1 | 356 | patman -s1 |
356 | 357 | ||
357 | The patches will be created, shown in your editor, and then sent along with | 358 | The patches will be created, shown in your editor, and then sent along with |
358 | the cover letter. Note that patman's tags are automatically removed so that | 359 | the cover letter. Note that patman's tags are automatically removed so that |
359 | people on the list don't see your secret info. | 360 | people on the list don't see your secret info. |
360 | 361 | ||
361 | Of course patches often attract comments and you need to make some updates. | 362 | Of course patches often attract comments and you need to make some updates. |
362 | Let's say one person sent comments and you get an Acked-by: on one patch. | 363 | Let's say one person sent comments and you get an Acked-by: on one patch. |
363 | Also, the patch on the list that you were waiting for has been merged, | 364 | Also, the patch on the list that you were waiting for has been merged, |
364 | so you can drop your wip commit. So you resync with upstream: | 365 | so you can drop your wip commit. So you resync with upstream: |
365 | 366 | ||
366 | git fetch origin (or whatever upstream is called) | 367 | git fetch origin (or whatever upstream is called) |
367 | git rebase origin/master | 368 | git rebase origin/master |
368 | 369 | ||
369 | and use git rebase -i to edit the commits, dropping the wip one. You add | 370 | and use git rebase -i to edit the commits, dropping the wip one. You add |
370 | the ack tag to one commit: | 371 | the ack tag to one commit: |
371 | 372 | ||
372 | Acked-by: Heiko Schocher <hs@denx.de> | 373 | Acked-by: Heiko Schocher <hs@denx.de> |
373 | 374 | ||
374 | update the Series-cc: in the top commit: | 375 | update the Series-cc: in the top commit: |
375 | 376 | ||
376 | Series-cc: bfin, marex, Heiko Schocher <hs@denx.de> | 377 | Series-cc: bfin, marex, Heiko Schocher <hs@denx.de> |
377 | 378 | ||
378 | and remove the Series-prefix: tag since it it isn't an RFC any more. The | 379 | and remove the Series-prefix: tag since it it isn't an RFC any more. The |
379 | series is now version two, so the series info in the top commit looks like | 380 | series is now version two, so the series info in the top commit looks like |
380 | this: | 381 | this: |
381 | 382 | ||
382 | Series-to: u-boot | 383 | Series-to: u-boot |
383 | Series-cc: bfin, marex, Heiko Schocher <hs@denx.de> | 384 | Series-cc: bfin, marex, Heiko Schocher <hs@denx.de> |
384 | Series-version: 2 | 385 | Series-version: 2 |
385 | Cover-letter: | 386 | Cover-letter: |
386 | ... | 387 | ... |
387 | 388 | ||
388 | Finally, you need to add a change log to the two commits you changed. You | 389 | Finally, you need to add a change log to the two commits you changed. You |
389 | add change logs to each individual commit where the changes happened, like | 390 | add change logs to each individual commit where the changes happened, like |
390 | this: | 391 | this: |
391 | 392 | ||
392 | Series-changes: 2 | 393 | Series-changes: 2 |
393 | - Updated the command decoder to reduce code size | 394 | - Updated the command decoder to reduce code size |
394 | - Wound the torque propounder up a little more | 395 | - Wound the torque propounder up a little more |
395 | 396 | ||
396 | (note the blank line at the end of the list) | 397 | (note the blank line at the end of the list) |
397 | 398 | ||
398 | When you run patman it will collect all the change logs from the different | 399 | When you run patman it will collect all the change logs from the different |
399 | commits and combine them into the cover letter, if you have one. So finally | 400 | commits and combine them into the cover letter, if you have one. So finally |
400 | you have a new series of commits: | 401 | you have a new series of commits: |
401 | 402 | ||
402 | faeb973 Don't include standard parser if hush is used | 403 | faeb973 Don't include standard parser if hush is used |
403 | 1b2f2fe mmc: sparc: Stop using builtin_run_command() | 404 | 1b2f2fe mmc: sparc: Stop using builtin_run_command() |
404 | cfbe330 Rename run_command2() to run_command() | 405 | cfbe330 Rename run_command2() to run_command() |
405 | 0682677 sandbox: Rename run_command() to builtin_run_command() | 406 | 0682677 sandbox: Rename run_command() to builtin_run_command() |
406 | 407 | ||
407 | so to send them: | 408 | so to send them: |
408 | 409 | ||
409 | patman | 410 | patman |
410 | 411 | ||
411 | and it will create and send the version 2 series. | 412 | and it will create and send the version 2 series. |
412 | 413 | ||
413 | General points: | 414 | General points: |
414 | 415 | ||
415 | 1. When you change back to the us-cmd branch days or weeks later all your | 416 | 1. When you change back to the us-cmd branch days or weeks later all your |
416 | information is still there, safely stored in the commits. You don't need | 417 | information is still there, safely stored in the commits. You don't need |
417 | to remember what version you are up to, who you sent the last lot of patches | 418 | to remember what version you are up to, who you sent the last lot of patches |
418 | to, or anything about the change logs. | 419 | to, or anything about the change logs. |
419 | 420 | ||
420 | 2. If you put tags in the subject, patman will Cc the maintainers | 421 | 2. If you put tags in the subject, patman will Cc the maintainers |
421 | automatically in many cases. | 422 | automatically in many cases. |
422 | 423 | ||
423 | 3. If you want to keep the commits from each series you sent so that you can | 424 | 3. If you want to keep the commits from each series you sent so that you can |
424 | compare change and see what you did, you can either create a new branch for | 425 | compare change and see what you did, you can either create a new branch for |
425 | each version, or just tag the branch before you start changing it: | 426 | each version, or just tag the branch before you start changing it: |
426 | 427 | ||
427 | git tag sent/us-cmd-rfc | 428 | git tag sent/us-cmd-rfc |
428 | ...later... | 429 | ...later... |
429 | git tag sent/us-cmd-v2 | 430 | git tag sent/us-cmd-v2 |
430 | 431 | ||
431 | 4. If you want to modify the patches a little before sending, you can do | 432 | 4. If you want to modify the patches a little before sending, you can do |
432 | this in your editor, but be careful! | 433 | this in your editor, but be careful! |
433 | 434 | ||
434 | 5. If you want to run git send-email yourself, use the -n flag which will | 435 | 5. If you want to run git send-email yourself, use the -n flag which will |
435 | print out the command line patman would have used. | 436 | print out the command line patman would have used. |
436 | 437 | ||
437 | 6. It is a good idea to add the change log info as you change the commit, | 438 | 6. It is a good idea to add the change log info as you change the commit, |
438 | not later when you can't remember which patch you changed. You can always | 439 | not later when you can't remember which patch you changed. You can always |
439 | go back and change or remove logs from commits. | 440 | go back and change or remove logs from commits. |
440 | 441 | ||
441 | 442 | ||
442 | Other thoughts | 443 | Other thoughts |
443 | ============== | 444 | ============== |
444 | 445 | ||
445 | This script has been split into sensible files but still needs work. | 446 | This script has been split into sensible files but still needs work. |
446 | Most of these are indicated by a TODO in the code. | 447 | Most of these are indicated by a TODO in the code. |
447 | 448 | ||
448 | It would be nice if this could handle the In-reply-to side of things. | 449 | It would be nice if this could handle the In-reply-to side of things. |
449 | 450 | ||
450 | The tests are incomplete, as is customary. Use the --test flag to run them, | 451 | The tests are incomplete, as is customary. Use the --test flag to run them, |
451 | and make sure you are in the tools/patman directory first: | 452 | and make sure you are in the tools/patman directory first: |
452 | 453 | ||
453 | $ cd /path/to/u-boot | 454 | $ cd /path/to/u-boot |
454 | $ cd tools/patman | 455 | $ cd tools/patman |
455 | $ ./patman --test | 456 | $ ./patman --test |
456 | 457 | ||
457 | Error handling doesn't always produce friendly error messages - e.g. | 458 | Error handling doesn't always produce friendly error messages - e.g. |
458 | putting an incorrect tag in a commit may provide a confusing message. | 459 | putting an incorrect tag in a commit may provide a confusing message. |
459 | 460 | ||
460 | There might be a few other features not mentioned in this README. They | 461 | There might be a few other features not mentioned in this README. They |
461 | might be bugs. In particular, tags are case sensitive which is probably | 462 | might be bugs. In particular, tags are case sensitive which is probably |
462 | a bad thing. | 463 | a bad thing. |
463 | 464 | ||
464 | 465 | ||
465 | Simon Glass <sjg@chromium.org> | 466 | Simon Glass <sjg@chromium.org> |
466 | v1, v2, 19-Oct-11 | 467 | v1, v2, 19-Oct-11 |
467 | revised v3 24-Nov-11 | 468 | revised v3 24-Nov-11 |
468 | 469 |
tools/patman/commit.py
1 | # Copyright (c) 2011 The Chromium OS Authors. | 1 | # Copyright (c) 2011 The Chromium OS Authors. |
2 | # | 2 | # |
3 | # SPDX-License-Identifier: GPL-2.0+ | 3 | # SPDX-License-Identifier: GPL-2.0+ |
4 | # | 4 | # |
5 | 5 | ||
6 | import re | 6 | import re |
7 | 7 | ||
8 | # Separates a tag: at the beginning of the subject from the rest of it | 8 | # Separates a tag: at the beginning of the subject from the rest of it |
9 | re_subject_tag = re.compile('([^:\s]*):\s*(.*)') | 9 | re_subject_tag = re.compile('([^:\s]*):\s*(.*)') |
10 | 10 | ||
11 | class Commit: | 11 | class Commit: |
12 | """Holds information about a single commit/patch in the series. | 12 | """Holds information about a single commit/patch in the series. |
13 | 13 | ||
14 | Args: | 14 | Args: |
15 | hash: Commit hash (as a string) | 15 | hash: Commit hash (as a string) |
16 | 16 | ||
17 | Variables: | 17 | Variables: |
18 | hash: Commit hash | 18 | hash: Commit hash |
19 | subject: Subject line | 19 | subject: Subject line |
20 | tags: List of maintainer tag strings | 20 | tags: List of maintainer tag strings |
21 | changes: Dict containing a list of changes (single line strings). | 21 | changes: Dict containing a list of changes (single line strings). |
22 | The dict is indexed by change version (an integer) | 22 | The dict is indexed by change version (an integer) |
23 | cc_list: List of people to aliases/emails to cc on this commit | 23 | cc_list: List of people to aliases/emails to cc on this commit |
24 | notes: List of lines in the commit (not series) notes | 24 | notes: List of lines in the commit (not series) notes |
25 | """ | 25 | """ |
26 | def __init__(self, hash): | 26 | def __init__(self, hash): |
27 | self.hash = hash | 27 | self.hash = hash |
28 | self.subject = None | 28 | self.subject = None |
29 | self.tags = [] | 29 | self.tags = [] |
30 | self.changes = {} | 30 | self.changes = {} |
31 | self.cc_list = [] | 31 | self.cc_list = [] |
32 | self.signoff_set = set() | ||
32 | self.notes = [] | 33 | self.notes = [] |
33 | 34 | ||
34 | def AddChange(self, version, info): | 35 | def AddChange(self, version, info): |
35 | """Add a new change line to the change list for a version. | 36 | """Add a new change line to the change list for a version. |
36 | 37 | ||
37 | Args: | 38 | Args: |
38 | version: Patch set version (integer: 1, 2, 3) | 39 | version: Patch set version (integer: 1, 2, 3) |
39 | info: Description of change in this version | 40 | info: Description of change in this version |
40 | """ | 41 | """ |
41 | if not self.changes.get(version): | 42 | if not self.changes.get(version): |
42 | self.changes[version] = [] | 43 | self.changes[version] = [] |
43 | self.changes[version].append(info) | 44 | self.changes[version].append(info) |
44 | 45 | ||
45 | def CheckTags(self): | 46 | def CheckTags(self): |
46 | """Create a list of subject tags in the commit | 47 | """Create a list of subject tags in the commit |
47 | 48 | ||
48 | Subject tags look like this: | 49 | Subject tags look like this: |
49 | 50 | ||
50 | propounder: fort: Change the widget to propound correctly | 51 | propounder: fort: Change the widget to propound correctly |
51 | 52 | ||
52 | Here the tags are propounder and fort. Multiple tags are supported. | 53 | Here the tags are propounder and fort. Multiple tags are supported. |
53 | The list is updated in self.tag. | 54 | The list is updated in self.tag. |
54 | 55 | ||
55 | Returns: | 56 | Returns: |
56 | None if ok, else the name of a tag with no email alias | 57 | None if ok, else the name of a tag with no email alias |
57 | """ | 58 | """ |
58 | str = self.subject | 59 | str = self.subject |
59 | m = True | 60 | m = True |
60 | while m: | 61 | while m: |
61 | m = re_subject_tag.match(str) | 62 | m = re_subject_tag.match(str) |
62 | if m: | 63 | if m: |
63 | tag = m.group(1) | 64 | tag = m.group(1) |
64 | self.tags.append(tag) | 65 | self.tags.append(tag) |
65 | str = m.group(2) | 66 | str = m.group(2) |
66 | return None | 67 | return None |
67 | 68 | ||
68 | def AddCc(self, cc_list): | 69 | def AddCc(self, cc_list): |
69 | """Add a list of people to Cc when we send this patch. | 70 | """Add a list of people to Cc when we send this patch. |
70 | 71 | ||
71 | Args: | 72 | Args: |
72 | cc_list: List of aliases or email addresses | 73 | cc_list: List of aliases or email addresses |
73 | """ | 74 | """ |
74 | self.cc_list += cc_list | 75 | self.cc_list += cc_list |
76 | |||
77 | def CheckDuplicateSignoff(self, signoff): | ||
78 | """Check a list of signoffs we have send for this patch | ||
79 | |||
80 | Args: | ||
81 | signoff: Signoff line | ||
82 | Returns: | ||
83 | True if this signoff is new, False if we have already seen it. | ||
84 | """ | ||
85 | if signoff in self.signoff_set: | ||
86 | return False | ||
87 | self.signoff_set.add(signoff) | ||
88 | return True | ||
75 | 89 |
tools/patman/patchstream.py
1 | # Copyright (c) 2011 The Chromium OS Authors. | 1 | # Copyright (c) 2011 The Chromium OS Authors. |
2 | # | 2 | # |
3 | # SPDX-License-Identifier: GPL-2.0+ | 3 | # SPDX-License-Identifier: GPL-2.0+ |
4 | # | 4 | # |
5 | 5 | ||
6 | import os | 6 | import os |
7 | import re | 7 | import re |
8 | import shutil | 8 | import shutil |
9 | import tempfile | 9 | import tempfile |
10 | 10 | ||
11 | import command | 11 | import command |
12 | import commit | 12 | import commit |
13 | import gitutil | 13 | import gitutil |
14 | from series import Series | 14 | from series import Series |
15 | 15 | ||
16 | # Tags that we detect and remove | 16 | # Tags that we detect and remove |
17 | re_remove = re.compile('^BUG=|^TEST=|^BRANCH=|^Change-Id:|^Review URL:' | 17 | re_remove = re.compile('^BUG=|^TEST=|^BRANCH=|^Change-Id:|^Review URL:' |
18 | '|Reviewed-on:|Commit-\w*:') | 18 | '|Reviewed-on:|Commit-\w*:') |
19 | 19 | ||
20 | # Lines which are allowed after a TEST= line | 20 | # Lines which are allowed after a TEST= line |
21 | re_allowed_after_test = re.compile('^Signed-off-by:') | 21 | re_allowed_after_test = re.compile('^Signed-off-by:') |
22 | 22 | ||
23 | # Signoffs | 23 | # Signoffs |
24 | re_signoff = re.compile('^Signed-off-by:') | 24 | re_signoff = re.compile('^Signed-off-by: *(.*)') |
25 | 25 | ||
26 | # The start of the cover letter | 26 | # The start of the cover letter |
27 | re_cover = re.compile('^Cover-letter:') | 27 | re_cover = re.compile('^Cover-letter:') |
28 | 28 | ||
29 | # A cover letter Cc | 29 | # A cover letter Cc |
30 | re_cover_cc = re.compile('^Cover-letter-cc: *(.*)') | 30 | re_cover_cc = re.compile('^Cover-letter-cc: *(.*)') |
31 | 31 | ||
32 | # Patch series tag | 32 | # Patch series tag |
33 | re_series_tag = re.compile('^Series-([a-z-]*): *(.*)') | 33 | re_series_tag = re.compile('^Series-([a-z-]*): *(.*)') |
34 | 34 | ||
35 | # Commit series tag | 35 | # Commit series tag |
36 | re_commit_tag = re.compile('^Commit-([a-z-]*): *(.*)') | 36 | re_commit_tag = re.compile('^Commit-([a-z-]*): *(.*)') |
37 | 37 | ||
38 | # Commit tags that we want to collect and keep | 38 | # Commit tags that we want to collect and keep |
39 | re_tag = re.compile('^(Tested-by|Acked-by|Reviewed-by|Patch-cc): (.*)') | 39 | re_tag = re.compile('^(Tested-by|Acked-by|Reviewed-by|Patch-cc): (.*)') |
40 | 40 | ||
41 | # The start of a new commit in the git log | 41 | # The start of a new commit in the git log |
42 | re_commit = re.compile('^commit ([0-9a-f]*)$') | 42 | re_commit = re.compile('^commit ([0-9a-f]*)$') |
43 | 43 | ||
44 | # We detect these since checkpatch doesn't always do it | 44 | # We detect these since checkpatch doesn't always do it |
45 | re_space_before_tab = re.compile('^[+].* \t') | 45 | re_space_before_tab = re.compile('^[+].* \t') |
46 | 46 | ||
47 | # States we can be in - can we use range() and still have comments? | 47 | # States we can be in - can we use range() and still have comments? |
48 | STATE_MSG_HEADER = 0 # Still in the message header | 48 | STATE_MSG_HEADER = 0 # Still in the message header |
49 | STATE_PATCH_SUBJECT = 1 # In patch subject (first line of log for a commit) | 49 | STATE_PATCH_SUBJECT = 1 # In patch subject (first line of log for a commit) |
50 | STATE_PATCH_HEADER = 2 # In patch header (after the subject) | 50 | STATE_PATCH_HEADER = 2 # In patch header (after the subject) |
51 | STATE_DIFFS = 3 # In the diff part (past --- line) | 51 | STATE_DIFFS = 3 # In the diff part (past --- line) |
52 | 52 | ||
53 | class PatchStream: | 53 | class PatchStream: |
54 | """Class for detecting/injecting tags in a patch or series of patches | 54 | """Class for detecting/injecting tags in a patch or series of patches |
55 | 55 | ||
56 | We support processing the output of 'git log' to read out the tags we | 56 | We support processing the output of 'git log' to read out the tags we |
57 | are interested in. We can also process a patch file in order to remove | 57 | are interested in. We can also process a patch file in order to remove |
58 | unwanted tags or inject additional ones. These correspond to the two | 58 | unwanted tags or inject additional ones. These correspond to the two |
59 | phases of processing. | 59 | phases of processing. |
60 | """ | 60 | """ |
61 | def __init__(self, series, name=None, is_log=False): | 61 | def __init__(self, series, name=None, is_log=False): |
62 | self.skip_blank = False # True to skip a single blank line | 62 | self.skip_blank = False # True to skip a single blank line |
63 | self.found_test = False # Found a TEST= line | 63 | self.found_test = False # Found a TEST= line |
64 | self.lines_after_test = 0 # MNumber of lines found after TEST= | 64 | self.lines_after_test = 0 # MNumber of lines found after TEST= |
65 | self.warn = [] # List of warnings we have collected | 65 | self.warn = [] # List of warnings we have collected |
66 | self.linenum = 1 # Output line number we are up to | 66 | self.linenum = 1 # Output line number we are up to |
67 | self.in_section = None # Name of start...END section we are in | 67 | self.in_section = None # Name of start...END section we are in |
68 | self.notes = [] # Series notes | 68 | self.notes = [] # Series notes |
69 | self.section = [] # The current section...END section | 69 | self.section = [] # The current section...END section |
70 | self.series = series # Info about the patch series | 70 | self.series = series # Info about the patch series |
71 | self.is_log = is_log # True if indent like git log | 71 | self.is_log = is_log # True if indent like git log |
72 | self.in_change = 0 # Non-zero if we are in a change list | 72 | self.in_change = 0 # Non-zero if we are in a change list |
73 | self.blank_count = 0 # Number of blank lines stored up | 73 | self.blank_count = 0 # Number of blank lines stored up |
74 | self.state = STATE_MSG_HEADER # What state are we in? | 74 | self.state = STATE_MSG_HEADER # What state are we in? |
75 | self.tags = [] # Tags collected, like Tested-by... | 75 | self.tags = [] # Tags collected, like Tested-by... |
76 | self.signoff = [] # Contents of signoff line | 76 | self.signoff = [] # Contents of signoff line |
77 | self.commit = None # Current commit | 77 | self.commit = None # Current commit |
78 | 78 | ||
79 | def AddToSeries(self, line, name, value): | 79 | def AddToSeries(self, line, name, value): |
80 | """Add a new Series-xxx tag. | 80 | """Add a new Series-xxx tag. |
81 | 81 | ||
82 | When a Series-xxx tag is detected, we come here to record it, if we | 82 | When a Series-xxx tag is detected, we come here to record it, if we |
83 | are scanning a 'git log'. | 83 | are scanning a 'git log'. |
84 | 84 | ||
85 | Args: | 85 | Args: |
86 | line: Source line containing tag (useful for debug/error messages) | 86 | line: Source line containing tag (useful for debug/error messages) |
87 | name: Tag name (part after 'Series-') | 87 | name: Tag name (part after 'Series-') |
88 | value: Tag value (part after 'Series-xxx: ') | 88 | value: Tag value (part after 'Series-xxx: ') |
89 | """ | 89 | """ |
90 | if name == 'notes': | 90 | if name == 'notes': |
91 | self.in_section = name | 91 | self.in_section = name |
92 | self.skip_blank = False | 92 | self.skip_blank = False |
93 | if self.is_log: | 93 | if self.is_log: |
94 | self.series.AddTag(self.commit, line, name, value) | 94 | self.series.AddTag(self.commit, line, name, value) |
95 | 95 | ||
96 | def AddToCommit(self, line, name, value): | 96 | def AddToCommit(self, line, name, value): |
97 | """Add a new Commit-xxx tag. | 97 | """Add a new Commit-xxx tag. |
98 | 98 | ||
99 | When a Commit-xxx tag is detected, we come here to record it. | 99 | When a Commit-xxx tag is detected, we come here to record it. |
100 | 100 | ||
101 | Args: | 101 | Args: |
102 | line: Source line containing tag (useful for debug/error messages) | 102 | line: Source line containing tag (useful for debug/error messages) |
103 | name: Tag name (part after 'Commit-') | 103 | name: Tag name (part after 'Commit-') |
104 | value: Tag value (part after 'Commit-xxx: ') | 104 | value: Tag value (part after 'Commit-xxx: ') |
105 | """ | 105 | """ |
106 | if name == 'notes': | 106 | if name == 'notes': |
107 | self.in_section = 'commit-' + name | 107 | self.in_section = 'commit-' + name |
108 | self.skip_blank = False | 108 | self.skip_blank = False |
109 | 109 | ||
110 | def CloseCommit(self): | 110 | def CloseCommit(self): |
111 | """Save the current commit into our commit list, and reset our state""" | 111 | """Save the current commit into our commit list, and reset our state""" |
112 | if self.commit and self.is_log: | 112 | if self.commit and self.is_log: |
113 | self.series.AddCommit(self.commit) | 113 | self.series.AddCommit(self.commit) |
114 | self.commit = None | 114 | self.commit = None |
115 | 115 | ||
116 | def FormatTags(self, tags): | 116 | def FormatTags(self, tags): |
117 | out_list = [] | 117 | out_list = [] |
118 | for tag in sorted(tags): | 118 | for tag in sorted(tags): |
119 | if tag.startswith('Cc:'): | 119 | if tag.startswith('Cc:'): |
120 | tag_list = tag[4:].split(',') | 120 | tag_list = tag[4:].split(',') |
121 | out_list += gitutil.BuildEmailList(tag_list, 'Cc:') | 121 | out_list += gitutil.BuildEmailList(tag_list, 'Cc:') |
122 | else: | 122 | else: |
123 | out_list.append(tag) | 123 | out_list.append(tag) |
124 | return out_list | 124 | return out_list |
125 | 125 | ||
126 | def ProcessLine(self, line): | 126 | def ProcessLine(self, line): |
127 | """Process a single line of a patch file or commit log | 127 | """Process a single line of a patch file or commit log |
128 | 128 | ||
129 | This process a line and returns a list of lines to output. The list | 129 | This process a line and returns a list of lines to output. The list |
130 | may be empty or may contain multiple output lines. | 130 | may be empty or may contain multiple output lines. |
131 | 131 | ||
132 | This is where all the complicated logic is located. The class's | 132 | This is where all the complicated logic is located. The class's |
133 | state is used to move between different states and detect things | 133 | state is used to move between different states and detect things |
134 | properly. | 134 | properly. |
135 | 135 | ||
136 | We can be in one of two modes: | 136 | We can be in one of two modes: |
137 | self.is_log == True: This is 'git log' mode, where most output is | 137 | self.is_log == True: This is 'git log' mode, where most output is |
138 | indented by 4 characters and we are scanning for tags | 138 | indented by 4 characters and we are scanning for tags |
139 | 139 | ||
140 | self.is_log == False: This is 'patch' mode, where we already have | 140 | self.is_log == False: This is 'patch' mode, where we already have |
141 | all the tags, and are processing patches to remove junk we | 141 | all the tags, and are processing patches to remove junk we |
142 | don't want, and add things we think are required. | 142 | don't want, and add things we think are required. |
143 | 143 | ||
144 | Args: | 144 | Args: |
145 | line: text line to process | 145 | line: text line to process |
146 | 146 | ||
147 | Returns: | 147 | Returns: |
148 | list of output lines, or [] if nothing should be output | 148 | list of output lines, or [] if nothing should be output |
149 | """ | 149 | """ |
150 | # Initially we have no output. Prepare the input line string | 150 | # Initially we have no output. Prepare the input line string |
151 | out = [] | 151 | out = [] |
152 | line = line.rstrip('\n') | 152 | line = line.rstrip('\n') |
153 | if self.is_log: | 153 | if self.is_log: |
154 | if line[:4] == ' ': | 154 | if line[:4] == ' ': |
155 | line = line[4:] | 155 | line = line[4:] |
156 | 156 | ||
157 | # Handle state transition and skipping blank lines | 157 | # Handle state transition and skipping blank lines |
158 | series_tag_match = re_series_tag.match(line) | 158 | series_tag_match = re_series_tag.match(line) |
159 | commit_tag_match = re_commit_tag.match(line) | 159 | commit_tag_match = re_commit_tag.match(line) |
160 | commit_match = re_commit.match(line) if self.is_log else None | 160 | commit_match = re_commit.match(line) if self.is_log else None |
161 | cover_cc_match = re_cover_cc.match(line) | 161 | cover_cc_match = re_cover_cc.match(line) |
162 | signoff_match = re_signoff.match(line) | ||
162 | tag_match = None | 163 | tag_match = None |
163 | if self.state == STATE_PATCH_HEADER: | 164 | if self.state == STATE_PATCH_HEADER: |
164 | tag_match = re_tag.match(line) | 165 | tag_match = re_tag.match(line) |
165 | is_blank = not line.strip() | 166 | is_blank = not line.strip() |
166 | if is_blank: | 167 | if is_blank: |
167 | if (self.state == STATE_MSG_HEADER | 168 | if (self.state == STATE_MSG_HEADER |
168 | or self.state == STATE_PATCH_SUBJECT): | 169 | or self.state == STATE_PATCH_SUBJECT): |
169 | self.state += 1 | 170 | self.state += 1 |
170 | 171 | ||
171 | # We don't have a subject in the text stream of patch files | 172 | # We don't have a subject in the text stream of patch files |
172 | # It has its own line with a Subject: tag | 173 | # It has its own line with a Subject: tag |
173 | if not self.is_log and self.state == STATE_PATCH_SUBJECT: | 174 | if not self.is_log and self.state == STATE_PATCH_SUBJECT: |
174 | self.state += 1 | 175 | self.state += 1 |
175 | elif commit_match: | 176 | elif commit_match: |
176 | self.state = STATE_MSG_HEADER | 177 | self.state = STATE_MSG_HEADER |
177 | 178 | ||
178 | # If we are in a section, keep collecting lines until we see END | 179 | # If we are in a section, keep collecting lines until we see END |
179 | if self.in_section: | 180 | if self.in_section: |
180 | if line == 'END': | 181 | if line == 'END': |
181 | if self.in_section == 'cover': | 182 | if self.in_section == 'cover': |
182 | self.series.cover = self.section | 183 | self.series.cover = self.section |
183 | elif self.in_section == 'notes': | 184 | elif self.in_section == 'notes': |
184 | if self.is_log: | 185 | if self.is_log: |
185 | self.series.notes += self.section | 186 | self.series.notes += self.section |
186 | elif self.in_section == 'commit-notes': | 187 | elif self.in_section == 'commit-notes': |
187 | if self.is_log: | 188 | if self.is_log: |
188 | self.commit.notes += self.section | 189 | self.commit.notes += self.section |
189 | else: | 190 | else: |
190 | self.warn.append("Unknown section '%s'" % self.in_section) | 191 | self.warn.append("Unknown section '%s'" % self.in_section) |
191 | self.in_section = None | 192 | self.in_section = None |
192 | self.skip_blank = True | 193 | self.skip_blank = True |
193 | self.section = [] | 194 | self.section = [] |
194 | else: | 195 | else: |
195 | self.section.append(line) | 196 | self.section.append(line) |
196 | 197 | ||
197 | # Detect the commit subject | 198 | # Detect the commit subject |
198 | elif not is_blank and self.state == STATE_PATCH_SUBJECT: | 199 | elif not is_blank and self.state == STATE_PATCH_SUBJECT: |
199 | self.commit.subject = line | 200 | self.commit.subject = line |
200 | 201 | ||
201 | # Detect the tags we want to remove, and skip blank lines | 202 | # Detect the tags we want to remove, and skip blank lines |
202 | elif re_remove.match(line) and not commit_tag_match: | 203 | elif re_remove.match(line) and not commit_tag_match: |
203 | self.skip_blank = True | 204 | self.skip_blank = True |
204 | 205 | ||
205 | # TEST= should be the last thing in the commit, so remove | 206 | # TEST= should be the last thing in the commit, so remove |
206 | # everything after it | 207 | # everything after it |
207 | if line.startswith('TEST='): | 208 | if line.startswith('TEST='): |
208 | self.found_test = True | 209 | self.found_test = True |
209 | elif self.skip_blank and is_blank: | 210 | elif self.skip_blank and is_blank: |
210 | self.skip_blank = False | 211 | self.skip_blank = False |
211 | 212 | ||
212 | # Detect the start of a cover letter section | 213 | # Detect the start of a cover letter section |
213 | elif re_cover.match(line): | 214 | elif re_cover.match(line): |
214 | self.in_section = 'cover' | 215 | self.in_section = 'cover' |
215 | self.skip_blank = False | 216 | self.skip_blank = False |
216 | 217 | ||
217 | elif cover_cc_match: | 218 | elif cover_cc_match: |
218 | value = cover_cc_match.group(1) | 219 | value = cover_cc_match.group(1) |
219 | self.AddToSeries(line, 'cover-cc', value) | 220 | self.AddToSeries(line, 'cover-cc', value) |
220 | 221 | ||
221 | # If we are in a change list, key collected lines until a blank one | 222 | # If we are in a change list, key collected lines until a blank one |
222 | elif self.in_change: | 223 | elif self.in_change: |
223 | if is_blank: | 224 | if is_blank: |
224 | # Blank line ends this change list | 225 | # Blank line ends this change list |
225 | self.in_change = 0 | 226 | self.in_change = 0 |
226 | elif line == '---' or re_signoff.match(line): | 227 | elif line == '---': |
227 | self.in_change = 0 | 228 | self.in_change = 0 |
228 | out = self.ProcessLine(line) | 229 | out = self.ProcessLine(line) |
229 | else: | 230 | else: |
230 | if self.is_log: | 231 | if self.is_log: |
231 | self.series.AddChange(self.in_change, self.commit, line) | 232 | self.series.AddChange(self.in_change, self.commit, line) |
232 | self.skip_blank = False | 233 | self.skip_blank = False |
233 | 234 | ||
234 | # Detect Series-xxx tags | 235 | # Detect Series-xxx tags |
235 | elif series_tag_match: | 236 | elif series_tag_match: |
236 | name = series_tag_match.group(1) | 237 | name = series_tag_match.group(1) |
237 | value = series_tag_match.group(2) | 238 | value = series_tag_match.group(2) |
238 | if name == 'changes': | 239 | if name == 'changes': |
239 | # value is the version number: e.g. 1, or 2 | 240 | # value is the version number: e.g. 1, or 2 |
240 | try: | 241 | try: |
241 | value = int(value) | 242 | value = int(value) |
242 | except ValueError as str: | 243 | except ValueError as str: |
243 | raise ValueError("%s: Cannot decode version info '%s'" % | 244 | raise ValueError("%s: Cannot decode version info '%s'" % |
244 | (self.commit.hash, line)) | 245 | (self.commit.hash, line)) |
245 | self.in_change = int(value) | 246 | self.in_change = int(value) |
246 | else: | 247 | else: |
247 | self.AddToSeries(line, name, value) | 248 | self.AddToSeries(line, name, value) |
248 | self.skip_blank = True | 249 | self.skip_blank = True |
249 | 250 | ||
250 | # Detect Commit-xxx tags | 251 | # Detect Commit-xxx tags |
251 | elif commit_tag_match: | 252 | elif commit_tag_match: |
252 | name = commit_tag_match.group(1) | 253 | name = commit_tag_match.group(1) |
253 | value = commit_tag_match.group(2) | 254 | value = commit_tag_match.group(2) |
254 | if name == 'notes': | 255 | if name == 'notes': |
255 | self.AddToCommit(line, name, value) | 256 | self.AddToCommit(line, name, value) |
256 | self.skip_blank = True | 257 | self.skip_blank = True |
257 | 258 | ||
258 | # Detect the start of a new commit | 259 | # Detect the start of a new commit |
259 | elif commit_match: | 260 | elif commit_match: |
260 | self.CloseCommit() | 261 | self.CloseCommit() |
261 | # TODO: We should store the whole hash, and just display a subset | 262 | # TODO: We should store the whole hash, and just display a subset |
262 | self.commit = commit.Commit(commit_match.group(1)[:8]) | 263 | self.commit = commit.Commit(commit_match.group(1)[:8]) |
263 | 264 | ||
264 | # Detect tags in the commit message | 265 | # Detect tags in the commit message |
265 | elif tag_match: | 266 | elif tag_match: |
266 | # Remove Tested-by self, since few will take much notice | 267 | # Remove Tested-by self, since few will take much notice |
267 | if (tag_match.group(1) == 'Tested-by' and | 268 | if (tag_match.group(1) == 'Tested-by' and |
268 | tag_match.group(2).find(os.getenv('USER') + '@') != -1): | 269 | tag_match.group(2).find(os.getenv('USER') + '@') != -1): |
269 | self.warn.append("Ignoring %s" % line) | 270 | self.warn.append("Ignoring %s" % line) |
270 | elif tag_match.group(1) == 'Patch-cc': | 271 | elif tag_match.group(1) == 'Patch-cc': |
271 | self.commit.AddCc(tag_match.group(2).split(',')) | 272 | self.commit.AddCc(tag_match.group(2).split(',')) |
272 | else: | 273 | else: |
273 | self.tags.append(line); | 274 | self.tags.append(line); |
275 | |||
276 | # Suppress duplicate signoffs | ||
277 | elif signoff_match: | ||
278 | if self.commit.CheckDuplicateSignoff(signoff_match.group(1)): | ||
279 | out = [line] | ||
274 | 280 | ||
275 | # Well that means this is an ordinary line | 281 | # Well that means this is an ordinary line |
276 | else: | 282 | else: |
277 | pos = 1 | 283 | pos = 1 |
278 | # Look for ugly ASCII characters | 284 | # Look for ugly ASCII characters |
279 | for ch in line: | 285 | for ch in line: |
280 | # TODO: Would be nicer to report source filename and line | 286 | # TODO: Would be nicer to report source filename and line |
281 | if ord(ch) > 0x80: | 287 | if ord(ch) > 0x80: |
282 | self.warn.append("Line %d/%d ('%s') has funny ascii char" % | 288 | self.warn.append("Line %d/%d ('%s') has funny ascii char" % |
283 | (self.linenum, pos, line)) | 289 | (self.linenum, pos, line)) |
284 | pos += 1 | 290 | pos += 1 |
285 | 291 | ||
286 | # Look for space before tab | 292 | # Look for space before tab |
287 | m = re_space_before_tab.match(line) | 293 | m = re_space_before_tab.match(line) |
288 | if m: | 294 | if m: |
289 | self.warn.append('Line %d/%d has space before tab' % | 295 | self.warn.append('Line %d/%d has space before tab' % |
290 | (self.linenum, m.start())) | 296 | (self.linenum, m.start())) |
291 | 297 | ||
292 | # OK, we have a valid non-blank line | 298 | # OK, we have a valid non-blank line |
293 | out = [line] | 299 | out = [line] |
294 | self.linenum += 1 | 300 | self.linenum += 1 |
295 | self.skip_blank = False | 301 | self.skip_blank = False |
296 | if self.state == STATE_DIFFS: | 302 | if self.state == STATE_DIFFS: |
297 | pass | 303 | pass |
298 | 304 | ||
299 | # If this is the start of the diffs section, emit our tags and | 305 | # If this is the start of the diffs section, emit our tags and |
300 | # change log | 306 | # change log |
301 | elif line == '---': | 307 | elif line == '---': |
302 | self.state = STATE_DIFFS | 308 | self.state = STATE_DIFFS |
303 | 309 | ||
304 | # Output the tags (signeoff first), then change list | 310 | # Output the tags (signeoff first), then change list |
305 | out = [] | 311 | out = [] |
306 | log = self.series.MakeChangeLog(self.commit) | 312 | log = self.series.MakeChangeLog(self.commit) |
307 | out += self.FormatTags(self.tags) | 313 | out += self.FormatTags(self.tags) |
308 | out += [line] + self.commit.notes + [''] + log | 314 | out += [line] + self.commit.notes + [''] + log |
309 | elif self.found_test: | 315 | elif self.found_test: |
310 | if not re_allowed_after_test.match(line): | 316 | if not re_allowed_after_test.match(line): |
311 | self.lines_after_test += 1 | 317 | self.lines_after_test += 1 |
312 | 318 | ||
313 | return out | 319 | return out |
314 | 320 | ||
315 | def Finalize(self): | 321 | def Finalize(self): |
316 | """Close out processing of this patch stream""" | 322 | """Close out processing of this patch stream""" |
317 | self.CloseCommit() | 323 | self.CloseCommit() |
318 | if self.lines_after_test: | 324 | if self.lines_after_test: |
319 | self.warn.append('Found %d lines after TEST=' % | 325 | self.warn.append('Found %d lines after TEST=' % |
320 | self.lines_after_test) | 326 | self.lines_after_test) |
321 | 327 | ||
322 | def ProcessStream(self, infd, outfd): | 328 | def ProcessStream(self, infd, outfd): |
323 | """Copy a stream from infd to outfd, filtering out unwanting things. | 329 | """Copy a stream from infd to outfd, filtering out unwanting things. |
324 | 330 | ||
325 | This is used to process patch files one at a time. | 331 | This is used to process patch files one at a time. |
326 | 332 | ||
327 | Args: | 333 | Args: |
328 | infd: Input stream file object | 334 | infd: Input stream file object |
329 | outfd: Output stream file object | 335 | outfd: Output stream file object |
330 | """ | 336 | """ |
331 | # Extract the filename from each diff, for nice warnings | 337 | # Extract the filename from each diff, for nice warnings |
332 | fname = None | 338 | fname = None |
333 | last_fname = None | 339 | last_fname = None |
334 | re_fname = re.compile('diff --git a/(.*) b/.*') | 340 | re_fname = re.compile('diff --git a/(.*) b/.*') |
335 | while True: | 341 | while True: |
336 | line = infd.readline() | 342 | line = infd.readline() |
337 | if not line: | 343 | if not line: |
338 | break | 344 | break |
339 | out = self.ProcessLine(line) | 345 | out = self.ProcessLine(line) |
340 | 346 | ||
341 | # Try to detect blank lines at EOF | 347 | # Try to detect blank lines at EOF |
342 | for line in out: | 348 | for line in out: |
343 | match = re_fname.match(line) | 349 | match = re_fname.match(line) |
344 | if match: | 350 | if match: |
345 | last_fname = fname | 351 | last_fname = fname |
346 | fname = match.group(1) | 352 | fname = match.group(1) |
347 | if line == '+': | 353 | if line == '+': |
348 | self.blank_count += 1 | 354 | self.blank_count += 1 |
349 | else: | 355 | else: |
350 | if self.blank_count and (line == '-- ' or match): | 356 | if self.blank_count and (line == '-- ' or match): |
351 | self.warn.append("Found possible blank line(s) at " | 357 | self.warn.append("Found possible blank line(s) at " |
352 | "end of file '%s'" % last_fname) | 358 | "end of file '%s'" % last_fname) |
353 | outfd.write('+\n' * self.blank_count) | 359 | outfd.write('+\n' * self.blank_count) |
354 | outfd.write(line + '\n') | 360 | outfd.write(line + '\n') |
355 | self.blank_count = 0 | 361 | self.blank_count = 0 |
356 | self.Finalize() | 362 | self.Finalize() |
357 | 363 | ||
358 | 364 | ||
359 | def GetMetaDataForList(commit_range, git_dir=None, count=None, | 365 | def GetMetaDataForList(commit_range, git_dir=None, count=None, |
360 | series = Series()): | 366 | series = Series()): |
361 | """Reads out patch series metadata from the commits | 367 | """Reads out patch series metadata from the commits |
362 | 368 | ||
363 | This does a 'git log' on the relevant commits and pulls out the tags we | 369 | This does a 'git log' on the relevant commits and pulls out the tags we |
364 | are interested in. | 370 | are interested in. |
365 | 371 | ||
366 | Args: | 372 | Args: |
367 | commit_range: Range of commits to count (e.g. 'HEAD..base') | 373 | commit_range: Range of commits to count (e.g. 'HEAD..base') |
368 | git_dir: Path to git repositiory (None to use default) | 374 | git_dir: Path to git repositiory (None to use default) |
369 | count: Number of commits to list, or None for no limit | 375 | count: Number of commits to list, or None for no limit |
370 | series: Series object to add information into. By default a new series | 376 | series: Series object to add information into. By default a new series |
371 | is started. | 377 | is started. |
372 | Returns: | 378 | Returns: |
373 | A Series object containing information about the commits. | 379 | A Series object containing information about the commits. |
374 | """ | 380 | """ |
375 | params = ['git', 'log', '--no-color', '--reverse', '--no-decorate', | 381 | params = ['git', 'log', '--no-color', '--reverse', '--no-decorate', |
376 | commit_range] | 382 | commit_range] |
377 | if count is not None: | 383 | if count is not None: |
378 | params[2:2] = ['-n%d' % count] | 384 | params[2:2] = ['-n%d' % count] |
379 | if git_dir: | 385 | if git_dir: |
380 | params[1:1] = ['--git-dir', git_dir] | 386 | params[1:1] = ['--git-dir', git_dir] |
381 | pipe = [params] | 387 | pipe = [params] |
382 | stdout = command.RunPipe(pipe, capture=True).stdout | 388 | stdout = command.RunPipe(pipe, capture=True).stdout |
383 | ps = PatchStream(series, is_log=True) | 389 | ps = PatchStream(series, is_log=True) |
384 | for line in stdout.splitlines(): | 390 | for line in stdout.splitlines(): |
385 | ps.ProcessLine(line) | 391 | ps.ProcessLine(line) |
386 | ps.Finalize() | 392 | ps.Finalize() |
387 | return series | 393 | return series |
388 | 394 | ||
389 | def GetMetaData(start, count): | 395 | def GetMetaData(start, count): |
390 | """Reads out patch series metadata from the commits | 396 | """Reads out patch series metadata from the commits |
391 | 397 | ||
392 | This does a 'git log' on the relevant commits and pulls out the tags we | 398 | This does a 'git log' on the relevant commits and pulls out the tags we |
393 | are interested in. | 399 | are interested in. |
394 | 400 | ||
395 | Args: | 401 | Args: |
396 | start: Commit to start from: 0=HEAD, 1=next one, etc. | 402 | start: Commit to start from: 0=HEAD, 1=next one, etc. |
397 | count: Number of commits to list | 403 | count: Number of commits to list |
398 | """ | 404 | """ |
399 | return GetMetaDataForList('HEAD~%d' % start, None, count) | 405 | return GetMetaDataForList('HEAD~%d' % start, None, count) |
400 | 406 | ||
401 | def FixPatch(backup_dir, fname, series, commit): | 407 | def FixPatch(backup_dir, fname, series, commit): |
402 | """Fix up a patch file, by adding/removing as required. | 408 | """Fix up a patch file, by adding/removing as required. |
403 | 409 | ||
404 | We remove our tags from the patch file, insert changes lists, etc. | 410 | We remove our tags from the patch file, insert changes lists, etc. |
405 | The patch file is processed in place, and overwritten. | 411 | The patch file is processed in place, and overwritten. |
406 | 412 | ||
407 | A backup file is put into backup_dir (if not None). | 413 | A backup file is put into backup_dir (if not None). |
408 | 414 | ||
409 | Args: | 415 | Args: |
410 | fname: Filename to patch file to process | 416 | fname: Filename to patch file to process |
411 | series: Series information about this patch set | 417 | series: Series information about this patch set |
412 | commit: Commit object for this patch file | 418 | commit: Commit object for this patch file |
413 | Return: | 419 | Return: |
414 | A list of errors, or [] if all ok. | 420 | A list of errors, or [] if all ok. |
415 | """ | 421 | """ |
416 | handle, tmpname = tempfile.mkstemp() | 422 | handle, tmpname = tempfile.mkstemp() |
417 | outfd = os.fdopen(handle, 'w') | 423 | outfd = os.fdopen(handle, 'w') |
418 | infd = open(fname, 'r') | 424 | infd = open(fname, 'r') |
419 | ps = PatchStream(series) | 425 | ps = PatchStream(series) |
420 | ps.commit = commit | 426 | ps.commit = commit |
421 | ps.ProcessStream(infd, outfd) | 427 | ps.ProcessStream(infd, outfd) |
422 | infd.close() | 428 | infd.close() |
423 | outfd.close() | 429 | outfd.close() |
424 | 430 | ||
425 | # Create a backup file if required | 431 | # Create a backup file if required |
426 | if backup_dir: | 432 | if backup_dir: |
427 | shutil.copy(fname, os.path.join(backup_dir, os.path.basename(fname))) | 433 | shutil.copy(fname, os.path.join(backup_dir, os.path.basename(fname))) |
428 | shutil.move(tmpname, fname) | 434 | shutil.move(tmpname, fname) |
429 | return ps.warn | 435 | return ps.warn |
430 | 436 | ||
431 | def FixPatches(series, fnames): | 437 | def FixPatches(series, fnames): |
432 | """Fix up a list of patches identified by filenames | 438 | """Fix up a list of patches identified by filenames |
433 | 439 | ||
434 | The patch files are processed in place, and overwritten. | 440 | The patch files are processed in place, and overwritten. |
435 | 441 | ||
436 | Args: | 442 | Args: |
437 | series: The series object | 443 | series: The series object |
438 | fnames: List of patch files to process | 444 | fnames: List of patch files to process |
439 | """ | 445 | """ |
440 | # Current workflow creates patches, so we shouldn't need a backup | 446 | # Current workflow creates patches, so we shouldn't need a backup |
441 | backup_dir = None #tempfile.mkdtemp('clean-patch') | 447 | backup_dir = None #tempfile.mkdtemp('clean-patch') |
442 | count = 0 | 448 | count = 0 |
443 | for fname in fnames: | 449 | for fname in fnames: |
444 | commit = series.commits[count] | 450 | commit = series.commits[count] |
445 | commit.patch = fname | 451 | commit.patch = fname |
446 | result = FixPatch(backup_dir, fname, series, commit) | 452 | result = FixPatch(backup_dir, fname, series, commit) |
447 | if result: | 453 | if result: |
448 | print '%d warnings for %s:' % (len(result), fname) | 454 | print '%d warnings for %s:' % (len(result), fname) |
449 | for warn in result: | 455 | for warn in result: |
450 | print '\t', warn | 456 | print '\t', warn |
451 | 457 | ||
452 | count += 1 | 458 | count += 1 |
453 | print 'Cleaned %d patches' % count | 459 | print 'Cleaned %d patches' % count |
454 | return series | 460 | return series |
455 | 461 | ||
456 | def InsertCoverLetter(fname, series, count): | 462 | def InsertCoverLetter(fname, series, count): |
457 | """Inserts a cover letter with the required info into patch 0 | 463 | """Inserts a cover letter with the required info into patch 0 |
458 | 464 | ||
459 | Args: | 465 | Args: |
460 | fname: Input / output filename of the cover letter file | 466 | fname: Input / output filename of the cover letter file |
461 | series: Series object | 467 | series: Series object |
462 | count: Number of patches in the series | 468 | count: Number of patches in the series |
463 | """ | 469 | """ |
464 | fd = open(fname, 'r') | 470 | fd = open(fname, 'r') |
465 | lines = fd.readlines() | 471 | lines = fd.readlines() |
466 | fd.close() | 472 | fd.close() |
467 | 473 | ||
468 | fd = open(fname, 'w') | 474 | fd = open(fname, 'w') |
469 | text = series.cover | 475 | text = series.cover |
470 | prefix = series.GetPatchPrefix() | 476 | prefix = series.GetPatchPrefix() |
471 | for line in lines: | 477 | for line in lines: |
472 | if line.startswith('Subject:'): | 478 | if line.startswith('Subject:'): |
473 | # TODO: if more than 10 patches this should save 00/xx, not 0/xx | 479 | # TODO: if more than 10 patches this should save 00/xx, not 0/xx |
474 | line = 'Subject: [%s 0/%d] %s\n' % (prefix, count, text[0]) | 480 | line = 'Subject: [%s 0/%d] %s\n' % (prefix, count, text[0]) |
475 | 481 | ||
476 | # Insert our cover letter | 482 | # Insert our cover letter |
477 | elif line.startswith('*** BLURB HERE ***'): | 483 | elif line.startswith('*** BLURB HERE ***'): |
478 | # First the blurb test | 484 | # First the blurb test |
479 | line = '\n'.join(text[1:]) + '\n' | 485 | line = '\n'.join(text[1:]) + '\n' |
480 | if series.get('notes'): | 486 | if series.get('notes'): |
481 | line += '\n'.join(series.notes) + '\n' | 487 | line += '\n'.join(series.notes) + '\n' |
482 | 488 | ||
483 | # Now the change list | 489 | # Now the change list |
484 | out = series.MakeChangeLog(None) | 490 | out = series.MakeChangeLog(None) |
485 | line += '\n' + '\n'.join(out) | 491 | line += '\n' + '\n'.join(out) |
486 | fd.write(line) | 492 | fd.write(line) |
487 | fd.close() | 493 | fd.close() |
488 | 494 |