From 1e0bb15fbf0be2d440e5d1dbbaa82562c9f918b0 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 16 Feb 2012 15:50:19 -0500 Subject: [PATCH] Add ability to run on a remote root based on the config file. 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 | 3 ++- update_copyright/project.py | 14 ++++++++++---- update_copyright/vcs/__init__.py | 12 +++++++++--- update_copyright/vcs/bazaar.py | 15 ++++++++++++--- update_copyright/vcs/git.py | 11 +++++------ update_copyright/vcs/mercurial.py | 10 +++++----- update_copyright/vcs/utils.py | 10 ++++++---- 7 files changed, 49 insertions(+), 26 deletions(-) diff --git a/bin/update-copyright.py b/bin/update-copyright.py index c291939..ca5f5c5 100755 --- a/bin/update-copyright.py +++ b/bin/update-copyright.py @@ -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) diff --git a/update_copyright/project.py b/update_copyright/project.py index 0dab126..98b9bd6 100644 --- a/update_copyright/project.py +++ b/update_copyright/project.py @@ -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): diff --git a/update_copyright/vcs/__init__.py b/update_copyright/vcs/__init__.py index 17281ab..8b58908 100644 --- a/update_copyright/vcs/__init__.py +++ b/update_copyright/vcs/__init__.py @@ -18,13 +18,17 @@ """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] diff --git a/update_copyright/vcs/bazaar.py b/update_copyright/vcs/bazaar.py index ecdbda0..3896fae 100644 --- a/update_copyright/vcs/bazaar.py +++ b/update_copyright/vcs/bazaar.py @@ -17,6 +17,7 @@ # . 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 diff --git a/update_copyright/vcs/git.py b/update_copyright/vcs/git.py index 46f15f1..65411fe 100644 --- a/update_copyright/vcs/git.py +++ b/update_copyright/vcs/git.py @@ -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: diff --git a/update_copyright/vcs/mercurial.py b/update_copyright/vcs/mercurial.py index 70af636..310b6b8 100644 --- a/update_copyright/vcs/mercurial.py +++ b/update_copyright/vcs/mercurial.py @@ -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', diff --git a/update_copyright/vcs/utils.py b/update_copyright/vcs/utils.py index f7f0b40..ada2ec0 100644 --- a/update_copyright/vcs/utils.py +++ b/update_copyright/vcs/utils.py @@ -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) -- 2.26.2