Rev 1: New plugin that sets the Copyright line from bzr information. in http://bzr.arbash-meinel.com/plugins/update_copyright

John Arbash Meinel john at arbash-meinel.com
Thu Jan 7 19:45:02 GMT 2010


At http://bzr.arbash-meinel.com/plugins/update_copyright

------------------------------------------------------------
revno: 1
revision-id: john at arbash-meinel.com-20100107194456-7chqyxethzihlcsx
committer: John Arbash Meinel <john at arbash-meinel.com>
branch nick: update_copyright
timestamp: Thu 2010-01-07 13:44:56 -0600
message:
  New plugin that sets the Copyright line from bzr information.
  
  Currently it only supports exact filenames. We'd like to support recursing
  and updating all files.
-------------- next part --------------
=== added file '__init__.py'
--- a/__init__.py	1970-01-01 00:00:00 +0000
+++ b/__init__.py	2010-01-07 19:44:56 +0000
@@ -0,0 +1,71 @@
+# Copyright (C) 2010 Canonical Ltd
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+# 
+
+"""A plugin that uses last-modified information to determine copyright dates.
+
+This looks in the bzr metadata for a given file and determines what years the
+file was actually modified. It then sets a copyright header as the first line
+of the file.
+"""
+
+
+from bzrlib import (
+    commands,
+    errors,
+    option,
+    )
+
+
+class cmd_update_copyright(commands.Command):
+    """Update the Copyright header for files using the bzr metadata."""
+
+    takes_args = ['path*']
+    takes_options = []
+
+    def run(self, path_list=None):
+        from bzrlib import builtins, osutils
+        import update_copyright
+
+        tree, relpaths = builtins.tree_files(path_list)
+        if relpaths == ['']:
+            # Selecting just the root selects everything
+            relpaths = None
+        if relpaths is not None:
+            relpaths = osutils.minimum_path_selection(relpaths)
+        tree.lock_write()
+        try:
+            basis_tree = tree.basis_tree()
+            basis_tree.lock_read()
+            try:
+                for relpath in relpaths:
+                    update_copyright.update_copyright(tree, basis_tree,
+                                                      relpath)
+            finally:
+                basis_tree.unlock()
+        finally:
+            tree.unlock()
+
+
+commands.register_command(cmd_update_copyright)
+
+
+def load_tests(standard_tests, module, loader):
+    standard_tests.addTests(loader.loadTestsFromModuleNames(
+        [__name__ + '.' + x for x in [
+            'test_update_copyright',
+        ]]))
+    return standard_tests

=== added file 'test_update_copyright.py'
--- a/test_update_copyright.py	1970-01-01 00:00:00 +0000
+++ b/test_update_copyright.py	2010-01-07 19:44:56 +0000
@@ -0,0 +1,137 @@
+# Copyright (C) 2010 Canonical Ltd
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+# 
+
+import datetime
+
+from bzrlib import (
+    errors,
+    tests,
+    )
+
+from bzrlib.plugins.update_copyright import (
+    update_copyright,
+    )
+
+
+class TestUpdateCopyrightDirect(tests.TestCase):
+
+    def assertCopyrightMatch(self, prefix, dates, suffix, line):
+        m = update_copyright.copyright_re.match(line)
+        if m is None:
+            self.fail('Copyright regex did not match: %r.' % (line,))
+        self.assertEqual((prefix, dates, suffix),
+                         m.group('prefix', 'dates', 'suffix'))
+
+    def test_copyright_regex(self):
+        self.assertCopyrightMatch(
+            '# Copyright (C) ', '2010 ', 'Canonical Ltd',
+            '# Copyright (C) 2010 Canonical Ltd')
+        self.assertCopyrightMatch(
+            '# Copyright ', '2010 ', 'Canonical Ltd',
+            '# Copyright 2010 Canonical Ltd')
+        self.assertCopyrightMatch(
+            'Copyright (c) ', '2010 ', 'Canonical Ltd',
+            'Copyright (c) 2010 Canonical Ltd')
+        self.assertCopyrightMatch(
+            '/** Copyright (c) ', '2010 ', 'Canonical Ltd',
+            '/** Copyright (c) 2010 Canonical Ltd')
+        self.assertCopyrightMatch(
+            '# Copyright (c) ', '2008, 2010 ', 'Canonical Ltd',
+            '# Copyright (c) 2008, 2010 Canonical Ltd')
+        self.assertCopyrightMatch(
+            '# Copyright (c) ', '2008-2010 ', 'Foobar Ltd',
+            '# Copyright (c) 2008-2010 Foobar Ltd')
+        self.assertCopyrightMatch(
+            '# Copyright (c) ', '2008-2010', '',
+            '# Copyright (c) 2008-2010')
+        self.assertCopyrightMatch(
+            '# Copyright (c) ', '2008-2010 ', 'Foobar',
+            '# Copyright (c) 2008-2010 Foobar\n')
+        # suffix will match '\r' so we don't have to worry about messing up
+        # line-endings
+        self.assertCopyrightMatch(
+            '# Copyright (c) ', '2008-2010 ', 'Foobar\r',
+            '# Copyright (c) 2008-2010 Foobar\r\n')
+
+
+class TestUpdateCopyrightDisk(tests.TestCaseWithTransport):
+
+    def make_old_modified_tree(self):
+        t = self.make_branch_and_tree('.')
+        t.lock_write()
+        self.addCleanup(t.unlock)
+        self.build_tree_contents([('file',
+            'Copyright (c) 2008 Foo Bar\nsome content\nmore content\n')])
+        t.add(['file'], ['file-id'])
+        rev_ids = []
+        rev_ids.append(
+            t.commit('new file', timestamp=1199817864, timezone=0)) # 2008
+        self.build_tree_contents([('file',
+            'Copyright (c) 2008 Foo Bar\ndifferent content\n')])
+        rev_ids.append(
+            t.commit('mod file', timestamp=1231353864, timezone=0)) # 2009
+        return t, rev_ids
+
+    def make_old_modified_but_correct_tree(self):
+        t, rev_ids = self.make_old_modified_tree()
+        self.build_tree_contents([('file',
+            'Copyright (c) 2008, 2009 Foo Bar\ndifferent content\n')])
+        rev_ids.append(
+            t.commit('fix copyright', timestamp=1231353865, timezone=0)) # 2009
+        return t, rev_ids
+
+    def test_get_years(self):
+        t, rev_ids = self.make_old_modified_tree()
+        self.assertEqual([2008],
+                         sorted(update_copyright.get_years(t.branch.repository,
+                                'file-id', rev_ids[0])))
+        self.assertEqual([2008, 2009],
+                         sorted(update_copyright.get_years(t.branch.repository,
+                                'file-id', rev_ids[1])))
+
+    def test_update_copyright(self):
+        t, rev_ids = self.make_old_modified_tree()
+        bt = t.basis_tree()
+        bt.lock_read()
+        self.addCleanup(bt.unlock)
+        update_copyright.update_copyright(t, bt, 'file')
+        year = datetime.datetime.now().year
+        self.assertFileEqual('Copyright (c) 2008, 2009, %d Foo Bar\n'
+                             'different content\n' % (year,), 'file')
+
+    def test_noop_update(self):
+        t, rev_ids = self.make_old_modified_but_correct_tree()
+        bt = t.basis_tree()
+        bt.lock_read()
+        self.addCleanup(bt.unlock)
+        update_copyright.update_copyright(t, bt, 'file')
+        # Should not at *this* year, because the copyright is already correct
+        self.assertFileEqual('Copyright (c) 2008, 2009 Foo Bar\n'
+                             'different content\n', 'file')
+
+    def test_blackbox(self):
+        t, rev_ids = self.make_old_modified_tree()
+        # We need to unlock so that run_bzr can grab a lock
+        t.unlock()
+        try:
+            self.run_bzr('update-copyright file')
+        finally:
+            # leave the tree locked, because we have pending cleanup
+            t.lock_read()
+        year = datetime.datetime.now().year
+        self.assertFileEqual('Copyright (c) 2008, 2009, %d Foo Bar\n'
+                             'different content\n' % (year,), 'file')

=== added file 'update_copyright.py'
--- a/update_copyright.py	1970-01-01 00:00:00 +0000
+++ b/update_copyright.py	2010-01-07 19:44:56 +0000
@@ -0,0 +1,92 @@
+# Copyright (C) 2010 Canonical Ltd
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+# 
+
+"""Tools for updating the copyright of given files."""
+
+import cStringIO
+import datetime
+import re
+
+from bzrlib import (
+    graph,
+    lazy_regex,
+    )
+
+
+copyright_re = lazy_regex.lazy_compile(
+    r'(?P<prefix>.*copyright\s+(\(c\)\s+)?)'
+    r'(?P<dates>[-0-9, ]+)'
+    r'(?P<suffix>.*)',
+    re.IGNORECASE)
+
+
+def get_years(repository, file_id, last_rev_id):
+    """Determine the dates a given file was modified."""
+    tip = (file_id, last_rev_id)
+    ancestry = graph.Graph(repository.texts).iter_ancestry([tip])
+    revision_ids = set([k[1] for k,p in ancestry if p is not None])
+    # TODO: Cache Revision objects between calls
+    revisions = repository.get_revisions(revision_ids)
+    # TODO: Should we use utcfromtimestamp?
+    years = set([datetime.datetime.fromtimestamp(r.timestamp).year
+                 for r in revisions])
+    return years
+
+
+def format_years(years):
+    """Format a bunch of years back into a date string."""
+    return ', '.join(map(str, sorted(years)))
+
+
+def update_copyright(tree, basis_tree, filename):
+    """Update the copyright line for a given file."""
+    file_id = tree.path2id(filename)
+    if file_id is None:
+        return # Not versioned
+    this_year = datetime.datetime.now().year
+    f = tree.get_file(file_id, path=filename)
+    try:
+        copyright_line = f.readline()
+        m = copyright_re.match(copyright_line)
+        if m is None:
+            return # No copyright line
+        # Determine the actual dates that this file was modified
+        # There really should be a better way to determine the revision of a
+        # file in a tree
+        # TODO: Handle newly added files, and consider locally modified files
+        #       as having a modification including today's year
+        #       Also note that running this will modify the file, causing it to
+        #       be marked as modified in the current year...
+        last_mod_revision = basis_tree.inventory[file_id].revision
+        years = get_years(tree.branch.repository, file_id, last_mod_revision)
+        new_copyright_line = '%s %s %s\n' % (
+            m.group('prefix').rstrip(), format_years(years), m.group('suffix'))
+        if copyright_line == new_copyright_line:
+            return # Copyright already correct
+        if this_year not in years:
+            # We are modifying it today, so mark it as such
+            years.add(this_year)
+            new_copyright_line = '%s %s %s\n' % (
+                m.group('prefix').rstrip(), format_years(years),
+                m.group('suffix'))
+        new_content = new_copyright_line + f.read()
+    finally:
+        f.close()
+    # TODO: We could probably do this 'safer' with a big TreeTransform over
+    #       all of the various content. This might also mess up
+    #       'executable' bits, which TT would get correct
+    tree.put_file_bytes_non_atomic(file_id, new_content)



More information about the bazaar-commits mailing list