Merge lp:~allenap/bzr-update-copyright/force-ranges into lp:bzr-update-copyright
- force-ranges
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Richard Wilbur |
Approved revision: | 19 |
Merged at revision: | 14 |
Proposed branch: | lp:~allenap/bzr-update-copyright/force-ranges |
Merge into: | lp:bzr-update-copyright |
Diff against target: |
427 lines (+145/-61) 3 files modified
__init__.py (+14/-12) test_update_copyright.py (+94/-30) update_copyright.py (+37/-19) |
To merge this branch: | bzr merge lp:~allenap/bzr-update-copyright/force-ranges |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Richard Wilbur | Approve | ||
Review via email: mp+257236@code.launchpad.net |
Commit message
Description of the change
This branch adds a --force-ranges option (which I'm happy to rename) which always formats years as $min-$max or $year; it ignores gaps. This is how we do it on MAAS because it means we don't have to re-wrap copyright stanzas to appease the linter. It may not be "proper" but it's good enough.
I've also cleaned up all PEP8 warnings. I can revert these changes if they're unwanted.
Richard Wilbur (richard-wilbur) wrote : | # |
Gavin Panella (allenap) wrote : | # |
> Since I don't see a bug number that this is solving, a little
> background would be useful to help me understand. You mention "MAAS"
> as formatting years in the copyright statement either as a range or a
> single year. What is "MAAS"?
MAAS is https:/
at the top of each file. Naturally it begins life with a single year,
but that can later be substituted with a date range, "2012-2015" or
"2012, 2013" for example, without causing the line to extend to more
than 80 characters.
When there are three years and they're not all contiguous with one
another, bzr-update-
2015" for example. For us it means that the line grows long enough that
our linter complains, meaning we would have to reflow the header.
It sounds really lazy to have an issue with that, and I kind of agree,
but then I also don't want to replace one manual job (changing the
header dates) with another (reflowing the paragraph), especially when
forgetting to do the latter would cause our landing bot to reject
submissions.
In any case, we've always used ${year-
MAAS, albeit manually. We would be happy to condense the non-contiguous
example from earlier into "2012-2015", even though no changes were made
in 2014.
So, this isn't in response to a bug, it's an itch that I scratched.
> My second question regarding copyright dates is, "What is 'proper' and
> how hard would that be to implement?"
"Proper" may have been an overstatement. I think of the current
behaviour of this plugin as "proper", and --force-range as somewhat
"improper", but I don't actually know which is more proper than the
other. I was motivated only to automate our current practice without
disrupting existing users of bzr-update-
Richard Wilbur (richard-wilbur) wrote : | # |
Thanks for the background.
I have a question regarding line 32 of __init__.py: Why is this the only line of option description text delimited by double quotes(")? All the others seems to use single quotes(').
Other than that, it looks good to me.
- 19. By Gavin Panella
-
Make quoting consistent.
Gavin Panella (allenap) wrote : | # |
Thanks Richard. I've fixed the quotes to be consistent.
Richard Wilbur (richard-wilbur) wrote : | # |
Thank you for the PEP8 clean up and the new option and documentation.
+1
Preview Diff
1 | === modified file '__init__.py' |
2 | --- __init__.py 2014-04-10 08:16:12 +0000 |
3 | +++ __init__.py 2015-05-07 09:22:34 +0000 |
4 | @@ -28,12 +28,10 @@ |
5 | want it active everywhere. |
6 | """ |
7 | |
8 | - |
9 | from bzrlib import ( |
10 | commands, |
11 | - errors, |
12 | _format_version_tuple, |
13 | - mutabletree, # I wish we could make the hook registration fully lazy |
14 | + mutabletree, # I wish we could make the hook registration fully lazy |
15 | option, |
16 | trace, |
17 | ) |
18 | @@ -56,12 +54,16 @@ |
19 | """ |
20 | |
21 | takes_args = ['path*'] |
22 | - takes_options = [option.Option('only-changed', |
23 | - help='Only update the copyright for files which are' |
24 | - ' marked as modified.'), |
25 | - ] |
26 | + takes_options = [ |
27 | + option.Option( |
28 | + 'only-changed', help='Only update the copyright for files which ' |
29 | + 'are marked as modified.'), |
30 | + option.Option( |
31 | + 'force-range', help='Only update the copyright with a range of ' |
32 | + 'years, i.e. ignore years in which no change was made.'), |
33 | + ] |
34 | |
35 | - def run(self, path_list=None, only_changed=False): |
36 | + def run(self, path_list=None, only_changed=False, force_range=False): |
37 | from bzrlib import osutils, workingtree |
38 | import update_copyright |
39 | |
40 | @@ -72,8 +74,8 @@ |
41 | relpaths = None |
42 | if relpaths is not None: |
43 | relpaths = osutils.minimum_path_selection(relpaths) |
44 | - result = update_copyright.update_tree_copyright(tree, relpaths, |
45 | - only_changed=only_changed) |
46 | + result = update_copyright.update_tree_copyright( |
47 | + tree, relpaths, only_changed=only_changed, force_range=force_range) |
48 | self.outf.write('Checked %(count)d files\n' |
49 | 'Updated %(updated)d\n' |
50 | 'Already correct %(copyright-correct)d\n' |
51 | @@ -105,8 +107,8 @@ |
52 | % res) |
53 | |
54 | |
55 | -mutabletree.MutableTree.hooks.install_named_hook('start_commit', |
56 | - _update_copyright_start_commit_hook, 'update-copyright') |
57 | +mutabletree.MutableTree.hooks.install_named_hook( |
58 | + 'start_commit', _update_copyright_start_commit_hook, 'update-copyright') |
59 | |
60 | |
61 | def load_tests(standard_tests, module, loader): |
62 | |
63 | === modified file 'test_update_copyright.py' |
64 | --- test_update_copyright.py 2014-04-10 08:10:39 +0000 |
65 | +++ test_update_copyright.py 2015-05-07 09:22:34 +0000 |
66 | @@ -13,12 +13,11 @@ |
67 | # You should have received a copy of the GNU General Public License |
68 | # along with this program; if not, write to the Free Software |
69 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
70 | -# |
71 | +# |
72 | |
73 | import datetime |
74 | |
75 | from bzrlib import ( |
76 | - errors, |
77 | mutabletree, |
78 | tests, |
79 | ) |
80 | @@ -80,6 +79,18 @@ |
81 | self.assertFormatYears('2005, 2007-2010', |
82 | [2005, 2007, 2008, 2009, 2010]) |
83 | |
84 | + def assertFormatYearsAsRange(self, expected, years): |
85 | + self.assertEqual( |
86 | + expected, update_copyright.format_years_as_range(set(years))) |
87 | + |
88 | + def test_format_years_as_range(self): |
89 | + self.assertFormatYearsAsRange('2008-2009', [2008, 2009]) |
90 | + self.assertFormatYearsAsRange('2007-2009', [2007, 2009]) |
91 | + self.assertFormatYearsAsRange('2007-2009', [2007, 2008, 2009]) |
92 | + self.assertFormatYearsAsRange('2007-2010', [2007, 2008, 2009, 2010]) |
93 | + self.assertFormatYearsAsRange( |
94 | + '2005-2010', [2005, 2007, 2008, 2009, 2010]) |
95 | + |
96 | |
97 | class TestUpdateCopyrightDisk(tests.TestCaseWithTransport): |
98 | |
99 | @@ -87,24 +98,26 @@ |
100 | t = self.make_branch_and_tree('.') |
101 | t.lock_write() |
102 | self.addCleanup(t.unlock) |
103 | - self.build_tree_contents([('file', |
104 | - 'Copyright (c) 2008 Foo Bar\nsome content\nmore content\n')]) |
105 | + self.build_tree_contents([ |
106 | + ('file', |
107 | + 'Copyright (c) 2008 Foo Bar\nsome content\nmore content\n')]) |
108 | t.add(['file'], ['file-id']) |
109 | rev_ids = [] |
110 | rev_ids.append( |
111 | - t.commit('new file', timestamp=1199817864, timezone=0)) # 2008 |
112 | - self.build_tree_contents([('file', |
113 | - 'Copyright (c) 2008 Foo Bar\ndifferent content\n')]) |
114 | + t.commit('new file', timestamp=1199817864, timezone=0)) # 2008 |
115 | + self.build_tree_contents([ |
116 | + ('file', 'Copyright (c) 2008 Foo Bar\ndifferent content\n')]) |
117 | rev_ids.append( |
118 | - t.commit('mod file', timestamp=1231353864, timezone=0)) # 2009 |
119 | + t.commit('mod file', timestamp=1231353864, timezone=0)) # 2009 |
120 | return t, rev_ids |
121 | |
122 | def make_old_modified_but_correct_tree(self): |
123 | t, rev_ids = self.make_old_modified_tree() |
124 | - self.build_tree_contents([('file', |
125 | - 'Copyright (c) 2008, 2009 Foo Bar\ndifferent content\n')]) |
126 | - rev_ids.append( |
127 | - t.commit('fix copyright', timestamp=1231353865, timezone=0)) # 2009 |
128 | + self.build_tree_contents([ |
129 | + ('file', 'Copyright (c) 2008, 2009 Foo Bar\ndifferent content\n'), |
130 | + ]) |
131 | + rev_ids.append(t.commit( |
132 | + 'fix copyright', timestamp=1231353865, timezone=0)) # 2009 |
133 | return t, rev_ids |
134 | |
135 | def make_tree_with_dirs(self): |
136 | @@ -112,15 +125,15 @@ |
137 | self.build_tree(['dir/']) |
138 | self.build_tree_contents([('dir/file', 'Copyright 2008 Foo\r\n')]) |
139 | t.add(['dir', 'dir/file'], ['dir-id', 'dfile-id']) |
140 | - rev_ids.append( |
141 | - t.commit('dir and file', timestamp=1199817865, timezone=0)) # 2008 |
142 | + rev_ids.append(t.commit( |
143 | + 'dir and file', timestamp=1199817865, timezone=0)) # 2008 |
144 | self.build_tree(['dir/subdir/']) |
145 | self.build_tree_contents([ |
146 | ('dir/file', 'Copyright 2008 Foo\r\nnew content\r\n'), |
147 | ('dir/subdir/foo', 'Copyright 2008 Bar\ncontent\n')]) |
148 | t.add(['dir/subdir', 'dir/subdir/foo'], ['subdir-id', 'foo-id']) |
149 | rev_ids.append( |
150 | - t.commit('mods', timestamp=1231353866, timezone=0)) # 2009 |
151 | + t.commit('mods', timestamp=1231353866, timezone=0)) # 2009 |
152 | return t, rev_ids |
153 | |
154 | def make_tree_with_dirs_modified(self): |
155 | @@ -160,12 +173,24 @@ |
156 | self.assertFileEqual('Copyright (c) 2008, 2009, %d Foo Bar\n' |
157 | 'different content\n' % (year,), 'file') |
158 | |
159 | + def test_update_copyright_with_ranges(self): |
160 | + t, rev_ids = self.make_old_modified_tree() |
161 | + bt = t.basis_tree() |
162 | + bt.lock_read() |
163 | + self.addCleanup(bt.unlock) |
164 | + self.assertEqual( |
165 | + 'updated', update_copyright.update_copyright( |
166 | + t, bt, 'file', False, update_copyright.format_years_as_range)) |
167 | + year = datetime.datetime.now().year |
168 | + self.assertFileEqual('Copyright (c) 2008-%d Foo Bar\n' |
169 | + 'different content\n' % (year,), 'file') |
170 | + |
171 | def test_update_copyright_not_first_line(self): |
172 | t, rev_ids = self.make_old_modified_tree() |
173 | - self.build_tree_contents([('file', |
174 | - '\n\n\nCopyright (c) 2008 Foo Bar\nultimate content\n')]) |
175 | + self.build_tree_contents([ |
176 | + ('file', '\n\n\nCopyright (c) 2008 Foo Bar\nultimate content\n')]) |
177 | rev_ids.append( |
178 | - t.commit('remod file', timestamp=1231353864, timezone=0)) # 2010 |
179 | + t.commit('remod file', timestamp=1231353864, timezone=0)) # 2010 |
180 | bt = t.basis_tree() |
181 | bt.lock_read() |
182 | self.addCleanup(bt.unlock) |
183 | @@ -178,10 +203,11 @@ |
184 | |
185 | def test_update_copyright_after_max_line(self): |
186 | t, rev_ids = self.make_old_modified_tree() |
187 | - self.build_tree_contents([('file', |
188 | - '\n\n\n\n\nCopyright (c) 2008 Foo Bar\nultimate content\n')]) |
189 | + self.build_tree_contents([ |
190 | + ('file', |
191 | + '\n\n\n\n\nCopyright (c) 2008 Foo Bar\nultimate content\n')]) |
192 | rev_ids.append( |
193 | - t.commit('remod file', timestamp=1231353864, timezone=0)) # 2010 |
194 | + t.commit('remod file', timestamp=1231353864, timezone=0)) # 2010 |
195 | bt = t.basis_tree() |
196 | bt.lock_read() |
197 | self.addCleanup(bt.unlock) |
198 | @@ -280,9 +306,19 @@ |
199 | self.assertFileEqual('Copyright 2008 Bar\n' |
200 | 'content\n', 'dir/subdir/foo') |
201 | |
202 | + def check_tree_with_dirs_modified_using_ranges(self): |
203 | + year = datetime.datetime.now().year |
204 | + self.assertFileEqual('Copyright (c) 2008 Foo Bar\n' |
205 | + 'different content\n', 'file') |
206 | + self.assertFileEqual('Copyright 2008-%d Foo\r\n' |
207 | + 'modified content\r\n' % (year,), 'dir/file') |
208 | + self.assertFileEqual('Copyright 2008 Bar\n' |
209 | + 'content\n', 'dir/subdir/foo') |
210 | + |
211 | def test_update_tree_only_changed(self): |
212 | t, rev_ids = self.make_tree_with_dirs_modified() |
213 | - res = update_copyright.update_tree_copyright(t, None, only_changed=True) |
214 | + res = update_copyright.update_tree_copyright( |
215 | + t, None, only_changed=True) |
216 | self.check_tree_with_dirs_modified() |
217 | self.assertEqual({'count': 1, 'updated': 1, 'no-copyright': 0, |
218 | 'copyright-correct': 0, 'not-versioned': 0}, |
219 | @@ -292,7 +328,8 @@ |
220 | t, rev_ids = self.make_tree_with_dirs_modified() |
221 | self.build_tree_contents([('file2', 'Copyright 1700 Fooz\ncontent\n')]) |
222 | t.add(['file2']) |
223 | - res = update_copyright.update_tree_copyright(t, None, only_changed=True) |
224 | + res = update_copyright.update_tree_copyright( |
225 | + t, None, only_changed=True) |
226 | year = datetime.datetime.now().year |
227 | self.check_tree_with_dirs_modified() |
228 | self.assertEqual({'count': 2, 'updated': 2, 'no-copyright': 0, |
229 | @@ -307,7 +344,8 @@ |
230 | # modified versus the committed content |
231 | self.build_tree_contents([ |
232 | ('dir/file', 'Copyright 2008, 2009 Foo\r\nmodified content\r\n')]) |
233 | - res = update_copyright.update_tree_copyright(t, None, only_changed=True) |
234 | + res = update_copyright.update_tree_copyright( |
235 | + t, None, only_changed=True) |
236 | self.check_tree_with_dirs_modified() |
237 | self.assertEqual({'count': 1, 'updated': 1, 'no-copyright': 0, |
238 | 'copyright-correct': 0, 'not-versioned': 0}, |
239 | @@ -317,8 +355,9 @@ |
240 | t, rev_ids = self.make_tree_with_dirs_modified() |
241 | # We have to install it here, because the test suite clears out hooks |
242 | # as part of its isolation mechanism. |
243 | - mutabletree.MutableTree.hooks.install_named_hook('start_commit', |
244 | - _update_copyright_start_commit_hook, 'update-copyright') |
245 | + mutabletree.MutableTree.hooks.install_named_hook( |
246 | + 'start_commit', _update_copyright_start_commit_hook, |
247 | + 'update-copyright') |
248 | # Without any config, nothing should be modified. |
249 | t.commit('Modified content') |
250 | self.assertFileEqual('Copyright (c) 2008 Foo Bar\n' |
251 | @@ -332,8 +371,9 @@ |
252 | t, rev_ids = self.make_tree_with_dirs_modified() |
253 | # We have to install it here, because the test suite clears out hooks |
254 | # as part of its isolation mechanism. |
255 | - mutabletree.MutableTree.hooks.install_named_hook('start_commit', |
256 | - _update_copyright_start_commit_hook, 'update-copyright') |
257 | + mutabletree.MutableTree.hooks.install_named_hook( |
258 | + 'start_commit', _update_copyright_start_commit_hook, |
259 | + 'update-copyright') |
260 | t.branch.get_config().set_user_option('auto_update_copyright', 'True') |
261 | t.commit('Modified content') |
262 | # With the hook enabled, commit should update the copyright in the |
263 | @@ -353,6 +393,19 @@ |
264 | self.assertFileEqual('Copyright (c) 2008, 2009, %d Foo Bar\n' |
265 | 'different content\n' % (year,), 'file') |
266 | |
267 | + def test_blackbox_force_range(self): |
268 | + t, rev_ids = self.make_old_modified_tree() |
269 | + # We need to unlock so that run_bzr can grab a lock |
270 | + t.unlock() |
271 | + try: |
272 | + self.run_bzr('update-copyright --force-range file') |
273 | + finally: |
274 | + # leave the tree locked, because we have pending cleanup |
275 | + t.lock_read() |
276 | + year = datetime.datetime.now().year |
277 | + self.assertFileEqual('Copyright (c) 2008-%d Foo Bar\n' |
278 | + 'different content\n' % (year,), 'file') |
279 | + |
280 | def test_blackbox_only_changed(self): |
281 | t, rev_ids = self.make_tree_with_dirs_modified() |
282 | t.unlock() |
283 | @@ -363,10 +416,21 @@ |
284 | t.lock_read() |
285 | self.check_tree_with_dirs_modified() |
286 | |
287 | + def test_blackbox_only_changed_force_range(self): |
288 | + t, rev_ids = self.make_tree_with_dirs_modified() |
289 | + t.unlock() |
290 | + try: |
291 | + self.run_bzr('update-copyright --only-changed --force-range') |
292 | + finally: |
293 | + # leave the tree locked, because we have pending cleanup |
294 | + t.lock_read() |
295 | + self.check_tree_with_dirs_modified_using_ranges() |
296 | + |
297 | def test_blackbox_with_commit_hook(self): |
298 | t, rev_ids = self.make_tree_with_dirs_modified() |
299 | - mutabletree.MutableTree.hooks.install_named_hook('start_commit', |
300 | - _update_copyright_start_commit_hook, 'update-copyright') |
301 | + mutabletree.MutableTree.hooks.install_named_hook( |
302 | + 'start_commit', _update_copyright_start_commit_hook, |
303 | + 'update-copyright') |
304 | t.branch.get_config().set_user_option('auto_update_copyright', 'True') |
305 | t.unlock() |
306 | try: |
307 | |
308 | === modified file 'update_copyright.py' |
309 | --- update_copyright.py 2014-04-10 08:10:39 +0000 |
310 | +++ update_copyright.py 2015-05-07 09:22:34 +0000 |
311 | @@ -13,12 +13,11 @@ |
312 | # You should have received a copy of the GNU General Public License |
313 | # along with this program; if not, write to the Free Software |
314 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
315 | -# |
316 | +# |
317 | |
318 | """Tools for updating the copyright of given files.""" |
319 | |
320 | -import cStringIO |
321 | -import datetime |
322 | +from datetime import datetime |
323 | import re |
324 | |
325 | from bzrlib import ( |
326 | @@ -39,13 +38,12 @@ |
327 | """Determine the dates a given file was modified.""" |
328 | tip = (file_id, last_rev_id) |
329 | ancestry = graph.Graph(repository.texts).iter_ancestry([tip]) |
330 | - revision_ids = set([k[1] for k,p in ancestry if p is not None]) |
331 | + revision_ids = set(k[1] for k, p in ancestry if p is not None) |
332 | # Oddly enough, caching the Revision object *slows* it down. My guess is |
333 | # that it interacts poorly with gc |
334 | revisions = repository.get_revisions(revision_ids) |
335 | # TODO: Should we use utcfromtimestamp? |
336 | - years = set([datetime.datetime.fromtimestamp(r.timestamp).year |
337 | - for r in revisions]) |
338 | + years = set(datetime.fromtimestamp(r.timestamp).year for r in revisions) |
339 | return years |
340 | |
341 | |
342 | @@ -76,6 +74,14 @@ |
343 | return ', '.join(out_years) |
344 | |
345 | |
346 | +def format_years_as_range(years): |
347 | + """Format a bunch of years back into a date range string.""" |
348 | + if len(years) == 1: |
349 | + return '%d' % years.pop() |
350 | + else: |
351 | + return '%d-%d' % (min(years), max(years)) |
352 | + |
353 | + |
354 | def _iter_all_files(tree, relpaths, basis_tree): |
355 | file_ids = tree.paths2ids(relpaths, trees=[basis_tree]) |
356 | for path, ie in tree.iter_entries_by_dir(file_ids): |
357 | @@ -90,18 +96,25 @@ |
358 | yield path[1] |
359 | |
360 | |
361 | -def update_tree_copyright(tree, relpaths, only_changed=False): |
362 | +def update_tree_copyright( |
363 | + tree, relpaths, only_changed=False, force_range=False): |
364 | """Update all of the paths specified in the tree.""" |
365 | - result = {'not-versioned': 0, |
366 | - 'no-copyright': 0, |
367 | - 'copyright-correct': 0, |
368 | - 'updated': 0, |
369 | - 'count': 0, |
370 | - } |
371 | + if force_range: |
372 | + fmt = format_years_as_range |
373 | + else: |
374 | + fmt = format_years |
375 | + |
376 | + result = { |
377 | + 'not-versioned': 0, |
378 | + 'no-copyright': 0, |
379 | + 'copyright-correct': 0, |
380 | + 'updated': 0, |
381 | + 'count': 0, |
382 | + } |
383 | # We only *need* lock_tree_write, however the |
384 | # WorkingTree.put_file_bytes_non_atomic api uses a @needs_lock_write |
385 | # decorator, which means it tries to write lock the branch... |
386 | - #tree.lock_tree_write() |
387 | + # tree.lock_tree_write() |
388 | tree.lock_write() |
389 | try: |
390 | basis_tree = tree.basis_tree() |
391 | @@ -117,8 +130,9 @@ |
392 | result['count'] += 1 |
393 | # If we are iterating changed files only, then we know this |
394 | # file is changed |
395 | - action = update_copyright(tree, basis_tree, path, |
396 | - is_changed=only_changed) |
397 | + action = update_copyright( |
398 | + tree, basis_tree, path, is_changed=only_changed, |
399 | + format_years=fmt) |
400 | result[action] += 1 |
401 | pb.update('%s %s' % (action, path), result[action], |
402 | result['count']) |
403 | @@ -131,17 +145,21 @@ |
404 | return result |
405 | |
406 | |
407 | -def update_copyright(tree, basis_tree, filename, is_changed=False): |
408 | +def update_copyright( |
409 | + tree, basis_tree, filename, is_changed=False, |
410 | + format_years=format_years): |
411 | """Update the copyright line for a given file. |
412 | - |
413 | + |
414 | :param is_changed: If we know the file is changed, then we know it must |
415 | have this years date in the Copyright header. If it is unknown if the |
416 | file has changed, just pass False. |
417 | + :param format_years: The function to use to format a set of years into a |
418 | + string for inclusion in the copyright header. |
419 | """ |
420 | file_id = tree.path2id(filename) |
421 | if file_id is None: |
422 | return 'not-versioned' |
423 | - this_year = datetime.datetime.now().year |
424 | + this_year = datetime.now().year |
425 | f = tree.get_file(file_id, path=filename) |
426 | max_lines = 5 |
427 | cur_line = 0 |
Since I don't see a bug number that this is solving, a little background would be useful to help me understand. You mention "MAAS" as formatting years in the copyright statement either as a range or a single year. What is "MAAS"?
My second question regarding copyright dates is, "What is 'proper' and how hard would that be to implement?"