Add ability to run on a remote root based on the config file.
authorW. Trevor King <wking@drexel.edu>
Thu, 16 Feb 2012 20:50:19 +0000 (15:50 -0500)
committerW. Trevor King <wking@drexel.edu>
Thu, 16 Feb 2012 20:50:23 +0000 (15:50 -0500)
For example

  update-copyright.py --config /path/to/project/.update-copyright.conf

will now update the code in `/path/to/project/` without you having to
change into that directory.

bin/update-copyright.py
update_copyright/project.py
update_copyright/vcs/__init__.py
update_copyright/vcs/bazaar.py
update_copyright/vcs/git.py
update_copyright/vcs/mercurial.py
update_copyright/vcs/utils.py

index c2919396ef0a7dcb1ff697349bb25b6d35ac9ceb..ca5f5c528bad4827e43b8bc0ab70c97646975bc3 100755 (executable)
@@ -33,6 +33,7 @@ file in your project root.
 """
 
 import logging as _logging
+import os.path as _os_path
 
 from update_copyright import LOG as _LOG
 from update_copyright.project import Project
@@ -61,7 +62,7 @@ if __name__ == '__main__':
 
     _LOG.setLevel(max(_logging.DEBUG, _logging.ERROR - 10*options.verbose))
 
-    project = Project()
+    project = Project(root=_os_path.dirname(_os_path.abspath(options.config)))
     project.load_config(open(options.config, 'r'))
     if options.authors:
         project.update_authors(dry_run=options.dry_run)
index 0dab1261117905b60fd13fa98139fce084d5ad12..98b9bd6b02ba19055f43ef8a87037cde403bb6a7 100644 (file)
@@ -38,8 +38,9 @@ except ImportError, _mercurial_import_error:
 
 
 class Project (object):
-    def __init__(self, name=None, vcs=None, copyright=None,
+    def __init__(self, root='.', name=None, vcs=None, copyright=None,
                  short_copyright=None):
+        self._root = _os_path.normpath(_os_path.abspath(root))
         self._name = name
         self._vcs = vcs
         self._author_hacks = None
@@ -80,6 +81,7 @@ class Project (object):
             pass
         else:
             kwargs = {
+                'root': self._root,
                 'author_hacks': self._author_hacks,
                 'year_hacks': self._year_hacks,
                 'aliases': self._aliases,
@@ -124,9 +126,11 @@ class Project (object):
         else:
             self._ignored_paths = [pth.strip() for pth in ignored.split(',')]
         try:
-            self._pyfile = parser.get('files', 'pyfile')
+            pyfile = parser.get('files', 'pyfile')
         except _configparser.NoOptionError:
             pass
+        else:
+            self._pyfile = _os_path.join(self._root, pyfile)
 
     def _load_author_hacks_conf(self, parser, encoding=None):
         if encoding is None:
@@ -173,7 +177,8 @@ class Project (object):
         new_contents = u'{} was written by:\n{}\n'.format(
             self._name, u'\n'.join(authors))
         _utils.set_contents(
-            'AUTHORS', new_contents, unicode=True, encoding=self._encoding,
+            _os_path.join(self._root, 'AUTHORS'),
+            new_contents, unicode=True, encoding=self._encoding,
             dry_run=dry_run)
 
     def update_file(self, filename, dry_run=False):
@@ -193,7 +198,7 @@ class Project (object):
 
     def update_files(self, files=None, dry_run=False):
         if files is None or len(files) == 0:
-            files = _utils.list_files(root='.')
+            files = _utils.list_files(root=self._root)
         for filename in files:
             if self._ignored_file(filename=filename):
                 continue
@@ -259,6 +264,7 @@ class Project (object):
         >>> ignored_file('./z', ignored_paths, ignored_files, False, False)
         False
         """
+        filename = _os_path.relpath(filename, self._root)
         if self._ignored_paths is not None:
             for path in self._ignored_paths:
                 if _fnmatch.fnmatch(filename, path):
index 17281ab34886b293db1d4cacc54c24cd40568b36..8b589080cb0ff182286cc424811d6c8aa78a9fd8 100644 (file)
 
 """Backends for version control systems."""
 
+import os.path as _os_path
+
 from . import utils as _utils
 
 
 class VCSBackend (object):
     name = None
 
-    def __init__(self, author_hacks=None, year_hacks=None, aliases=None):
+    def __init__(self, root='.', author_hacks=None, year_hacks=None,
+                 aliases=None):
+        self._root = root
         if author_hacks is None:
             author_hacks = {}
         self._author_hacks = author_hacks
@@ -42,8 +46,10 @@ class VCSBackend (object):
         years = self._years(filename=filename)
         if filename is None:
             years.update(self._year_hacks.values())
-        elif _utils.splitpath(filename) in self._year_hacks:
-            years.add(self._year_hacks[_utils.splitpath(filename)])
+        else:
+            filename = _os_path.relpath(filename, self._root)
+            if _utils.splitpath(filename) in self._year_hacks:
+                years.add(self._year_hacks[_utils.splitpath(filename)])
         years = sorted(years)
         return years[0]
 
index ecdbda0000ecd3c753521450c5c725cb460266a5..3896fae2cda25bf519e931be0ef044852cb0bad3 100644 (file)
@@ -17,6 +17,7 @@
 # <http://www.gnu.org/licenses/>.
 
 import StringIO as _StringIO
+import os as _os
 
 import bzrlib as _bzrlib
 import bzrlib.builtins as _bzrlib_builtins
@@ -56,13 +57,21 @@ class BazaarBackend (_VCSBackend):
         super(BazaarBackend, self).__init__(**kwargs)
         self._version = _bzrlib.__version__
 
+    def _bzr_cmd(self, cmd, **kwargs):
+        cwd = _os.getcwd()
+        _os.chdir(self._root)
+        try:
+            cmd.run(**kwargs)
+        finally:
+            _os.chdir(cwd)
+
     def _years(self, filename=None):
         cmd = _bzrlib_builtins.cmd_log()
         cmd.outf = _StringIO.StringIO()
         kwargs = {'log_format':_YearLogFormatter, 'levels':0}
         if filename is not None:
             kwargs['file_list'] = [filename]
-        cmd.run(**kwargs)
+        self._bzr_cmd(cmd=cmd, **kwargs)
         years = set(int(year) for year in cmd.outf.getvalue().splitlines())
         return years
 
@@ -72,12 +81,12 @@ class BazaarBackend (_VCSBackend):
         kwargs = {'log_format':_AuthorLogFormatter, 'levels':0}
         if filename is not None:
             kwargs['file_list'] = [filename]
-        cmd.run(**kwargs)
+        self._bzr_cmd(cmd=cmd, **kwargs)
         authors = set(cmd.outf.getvalue().splitlines())
         return authors
 
     def is_versioned(self, filename):
         cmd = _bzrlib_builtins.cmd_log()
         cmd.outf = StringIO.StringIO()
-        cmd.run(file_list=[filename])
+        self._bzr_cmd(cmd=cmd, file_list=[filename])
         return True
index 46f15f13a323d68bc46b71158dab40725f16f918..65411fe2e3db6de2a6f9a87ada428b4890e8dbbc 100644 (file)
@@ -23,12 +23,6 @@ from . import utils as _utils
 class GitBackend (_VCSBackend):
     name = 'Git'
 
-    @staticmethod
-    def _git_cmd(*args):
-        status,stdout,stderr = _utils.invoke(
-            ['git'] + list(args), unicode_output=True)
-        return stdout.rstrip('\n')
-
     def __init__(self, **kwargs):
         super(GitBackend, self).__init__(**kwargs)
         self._version = self._git_cmd('--version').split(' ')[-1]
@@ -43,6 +37,11 @@ class GitBackend (_VCSBackend):
             self._year_format = ['--pretty=format:%ad',  # Author date
                                  '--date=short']         # YYYY-MM-DD
 
+    def _git_cmd(self, *args):
+        status,stdout,stderr = _utils.invoke(
+            ['git'] + list(args), cwd=self._root, unicode_output=True)
+        return stdout.rstrip('\n')
+
     def _years(self, filename=None):
         args = ['log'] + self._year_format
         if filename is not None:
index 70af636f480337f82930f76edeb4a9d6a81ef1c5..310b6b83b23400ad6dcfe186b9d7176df5750581 100644 (file)
@@ -33,7 +33,10 @@ from . import utils as _utils
 class MercurialBackend (_VCSBackend):
     name = 'Mercurial'
 
-    @staticmethod
+    def __init__(self, **kwargs):
+        super(MercurialBackend, self).__init__(**kwargs)
+        self._version = _version
+
     def _hg_cmd(*args):
         cwd = _os.getcwd()
         stdout = _sys.stdout
@@ -42,6 +45,7 @@ class MercurialBackend (_VCSBackend):
         tmp_stderr = _StringIO.StringIO()
         _sys.stdout = tmp_stdout
         _sys.stderr = tmp_stderr
+        _os.chdir(self._root)
         try:
             _mercurial_dispatch.dispatch(list(args))
         finally:
@@ -51,10 +55,6 @@ class MercurialBackend (_VCSBackend):
         return (tmp_stdout.getvalue().rstrip('\n'),
                 tmp_stderr.getvalue().rstrip('\n'))
 
-    def __init__(self, **kwargs):
-        super(MercurialBackend, self).__init__(**kwargs)
-        self._version = _version
-
     def _years(self, filename=None):
         args = [
             '--template', '{date|shortdate}\n',
index f7f0b407d9130d125bf69373e274f7fee7b2e28c..ada2ec03b79a9a0398b71342f64d3d3ae1bad7a8 100644 (file)
@@ -31,7 +31,7 @@ _POSIX = not _MSWINDOWS
 
 
 def invoke(args, stdin=None, stdout=_subprocess.PIPE, stderr=_subprocess.PIPE,
-           expect=(0,), unicode_output=False, encoding=None):
+           cwd=None, expect=(0,), unicode_output=False, encoding=None):
     """Invoke an external program and return the results
 
     ``expect`` should be a tuple of allowed exit codes.
@@ -42,12 +42,14 @@ def invoke(args, stdin=None, stdout=_subprocess.PIPE, stderr=_subprocess.PIPE,
     try :
         if _POSIX:
             q = _subprocess.Popen(args, stdin=_subprocess.PIPE,
-                                  stdout=stdout, stderr=stderr)
+                                  stdout=stdout, stderr=stderr,
+                                  close_fds=True, cwd=cwd)
         else:
             assert _MSWINDOWS == True, 'invalid platform'
             # win32 don't have os.execvp() so run the command in a shell
             q = _subprocess.Popen(args, stdin=_subprocess.PIPE,
-                                  stdout=stdout, stderr=stderr, shell=True)
+                                  stdout=stdout, stderr=stderr, shell=True,
+                                  cwd=cwd)
     except OSError, e:
         raise ValueError([args, e])
     stdout,stderr = q.communicate(input=stdin)
@@ -80,7 +82,7 @@ def splitpath(path):
     while True:
         dirname,basename = _os_path.split(path)
         elements.insert(0,basename)
-        if dirname in ['', '.']:
+        if dirname in ['/', '', '.']:
             break
         path = dirname
     return tuple(elements)