| Index: infra/services/gnumbd/support/git.py
|
| diff --git a/infra/services/gnumbd/support/git.py b/infra/services/gnumbd/support/git.py
|
| deleted file mode 100644
|
| index b337ac20c4a65e682839ac350a1f56d5783eb255..0000000000000000000000000000000000000000
|
| --- a/infra/services/gnumbd/support/git.py
|
| +++ /dev/null
|
| @@ -1,270 +0,0 @@
|
| -# Copyright 2014 The Chromium Authors. All rights reserved.
|
| -# Use of this source code is governed by a BSD-style license that can be
|
| -# found in the LICENSE file.
|
| -import collections
|
| -import fnmatch
|
| -import logging
|
| -import os
|
| -import subprocess
|
| -import sys
|
| -import tempfile
|
| -import urlparse
|
| -
|
| -from infra.services.gnumbd.support.util import (
|
| - cached_property, CalledProcessError)
|
| -
|
| -from infra.services.gnumbd.support.data import CommitData
|
| -
|
| -LOGGER = logging.getLogger(__name__)
|
| -
|
| -
|
| -class _Invalid(object):
|
| - def __call__(self, *_args, **_kwargs):
|
| - return self
|
| -
|
| - def __getattr__(self, _key):
|
| - return self
|
| -
|
| - def __eq__(self, _other):
|
| - return False
|
| -
|
| - def __ne__(self, _other): # pylint: disable=R0201
|
| - return True
|
| -
|
| -INVALID = _Invalid()
|
| -
|
| -
|
| -class Repo(object):
|
| - """Represents a remote git repo.
|
| -
|
| - Manages the (bare) on-disk mirror of the remote repo.
|
| - """
|
| - MAX_CACHE_SIZE = 1024
|
| -
|
| - def __init__(self, url):
|
| - self.dry_run = False
|
| - self.repos_dir = None
|
| -
|
| - self._url = url
|
| - self._repo_path = None
|
| - self._commit_cache = collections.OrderedDict()
|
| - self._log = LOGGER.getChild('Repo')
|
| -
|
| - def reify(self):
|
| - """Ensures the local mirror of this Repo exists."""
|
| - assert self.repos_dir is not None
|
| - parsed = urlparse.urlparse(self._url)
|
| - norm_url = parsed.netloc + parsed.path
|
| - if norm_url.endswith('.git'):
|
| - norm_url = norm_url[:-len('.git')]
|
| - folder = norm_url.replace('-', '--').replace('/', '-').lower()
|
| -
|
| - rpath = os.path.abspath(os.path.join(self.repos_dir, folder))
|
| - if not os.path.isdir(rpath):
|
| - self._log.debug('initializing %r -> %r', self, rpath)
|
| - name = tempfile.mkdtemp(dir=self.repos_dir)
|
| - self.run('clone', '--mirror', self._url, os.path.basename(name),
|
| - stdout=sys.stdout, stderr=sys.stderr, cwd=self.repos_dir)
|
| - os.rename(os.path.join(self.repos_dir, name),
|
| - os.path.join(self.repos_dir, folder))
|
| - else:
|
| - self._log.debug('%r already initialized', self)
|
| - self._repo_path = rpath
|
| -
|
| - # This causes pushes to fail, so unset it.
|
| - self.run('config', '--unset', 'remote.origin.mirror', ok_ret={0, 5})
|
| -
|
| - # Representation
|
| - def __repr__(self):
|
| - return 'Repo({_url!r})'.format(**self.__dict__)
|
| -
|
| - # Methods
|
| - def get_commit(self, hsh):
|
| - """Creates a new |Commit| object for this |Repo|.
|
| -
|
| - Uses a very basic LRU cache for commit objects, keeping up to
|
| - |MAX_CACHE_SIZE| before eviction. This cuts down on the number of redundant
|
| - git commands by > 50%, and allows expensive cached_property's to remain
|
| - for the life of the process.
|
| - """
|
| - if hsh in self._commit_cache:
|
| - self._log.debug('Hit %s', hsh)
|
| - r = self._commit_cache.pop(hsh)
|
| - else:
|
| - self._log.debug('Miss %s', hsh)
|
| - if len(self._commit_cache) >= self.MAX_CACHE_SIZE:
|
| - self._commit_cache.popitem(last=False)
|
| - r = Commit(self, hsh)
|
| -
|
| - self._commit_cache[hsh] = r
|
| - return r
|
| -
|
| - def refglob(self, globstring):
|
| - """Yield every Ref in this repo which matches |globstring|."""
|
| - for _, ref in (l.split() for l in self.run('show-ref').splitlines()):
|
| - if fnmatch.fnmatch(ref, globstring):
|
| - yield Ref(self, ref)
|
| -
|
| - def run(self, *args, **kwargs):
|
| - """Yet-another-git-subprocess-wrapper.
|
| -
|
| - Args: argv tokens. 'git' is always argv[0]
|
| -
|
| - Kwargs:
|
| - indata - String data to feed to communicate()
|
| - ok_ret - A set() of valid return codes. Defaults to {0}.
|
| - ... - passes through to subprocess.Popen()
|
| - """
|
| - if args[0] == 'push' and self.dry_run:
|
| - self._log.warn('DRY-RUN: Would have pushed %r', args[1:])
|
| - return
|
| -
|
| - if not 'cwd' in kwargs:
|
| - assert self._repo_path is not None
|
| - kwargs.setdefault('cwd', self._repo_path)
|
| -
|
| - kwargs.setdefault('stderr', subprocess.PIPE)
|
| - kwargs.setdefault('stdout', subprocess.PIPE)
|
| - indata = kwargs.pop('indata', None)
|
| - if indata:
|
| - assert 'stdin' not in kwargs
|
| - kwargs['stdin'] = subprocess.PIPE
|
| - ok_ret = kwargs.pop('ok_ret', {0})
|
| - cmd = ('git',) + args
|
| -
|
| - self._log.debug('Running %r', cmd)
|
| - process = subprocess.Popen(cmd, **kwargs)
|
| - output, errout = process.communicate(indata)
|
| - retcode = process.poll()
|
| - if retcode not in ok_ret:
|
| - raise CalledProcessError(retcode, cmd, output, errout)
|
| -
|
| - if errout:
|
| - sys.stderr.write(errout)
|
| - return output
|
| -
|
| - def intern(self, data, typ='blob'):
|
| - return self.run(
|
| - 'hash-object', '-w', '-t', typ, '--stdin', indata=str(data)).strip()
|
| -
|
| -
|
| -class Commit(object):
|
| - """Represents the identity of a commit in a git repo."""
|
| -
|
| - def __init__(self, repo, hsh):
|
| - """
|
| - @type repo: Repo
|
| - """
|
| - assert CommitData.HASH_RE.match(hsh)
|
| - self._repo = repo
|
| - self._hsh = hsh
|
| -
|
| - # Comparison & Representation
|
| - def __eq__(self, other):
|
| - return (self is other) or (
|
| - isinstance(other, Commit) and (
|
| - self.hsh == other.hsh
|
| - )
|
| - )
|
| -
|
| - def __ne__(self, other):
|
| - return not (self == other)
|
| -
|
| - def __repr__(self):
|
| - return 'Commit({_repo!r}, {_hsh!r})'.format(**self.__dict__)
|
| -
|
| - # Accessors
|
| - # pylint: disable=W0212
|
| - repo = property(lambda self: self._repo)
|
| - hsh = property(lambda self: self._hsh)
|
| -
|
| - # Properties
|
| - @cached_property
|
| - def data(self):
|
| - """Get a structured data representation of this commit."""
|
| - try:
|
| - raw_data = self.repo.run('cat-file', 'commit', self.hsh)
|
| - except CalledProcessError:
|
| - return INVALID
|
| - return CommitData.from_raw(raw_data)
|
| -
|
| - @cached_property
|
| - def parent(self):
|
| - """Get the corresponding parent Commit() for this Commit(), or None.
|
| -
|
| - If self has more than one parent, this raises an Exception.
|
| - """
|
| - parents = self.data.parents
|
| - if len(parents) > 1:
|
| - LOGGER.error('Commit %r has more than one parent!', self.hsh)
|
| - return INVALID
|
| - return self.repo.get_commit(parents[0]) if parents else None
|
| -
|
| - # Methods
|
| - def alter(self, **kwargs):
|
| - """Get a new Commit which is the same as this one, except for alterations
|
| - specified by kwargs.
|
| -
|
| - This will intern the new Commit object into the Repo.
|
| - """
|
| - return self.repo.get_commit(
|
| - self.repo.intern(self.data.alter(**kwargs), 'commit'))
|
| -
|
| -
|
| -class Ref(object):
|
| - """Represents a single simple ref in a git Repo."""
|
| - def __init__(self, repo, ref_str):
|
| - """
|
| - @type repo: Repo
|
| - @type ref_str: str
|
| - """
|
| - self._repo = repo
|
| - self._ref = ref_str
|
| -
|
| - # Comparison & Representation
|
| - def __eq__(self, other):
|
| - return (self is other) or (
|
| - isinstance(other, Ref) and (
|
| - self.ref == other.ref and
|
| - self.repo is other.repo
|
| - )
|
| - )
|
| -
|
| - def __ne__(self, other):
|
| - return not (self == other)
|
| -
|
| - def __repr__(self):
|
| - return 'Ref({_repo!r}, {_ref!r})'.format(**self.__dict__)
|
| -
|
| - # Accessors
|
| - # pylint: disable=W0212
|
| - repo = property(lambda self: self._repo)
|
| - ref = property(lambda self: self._ref)
|
| -
|
| - # Properties
|
| - @property
|
| - def commit(self):
|
| - """Get the Commit at the tip of this Ref."""
|
| - try:
|
| - val = self._repo.run('show-ref', '--verify', self._ref)
|
| - except CalledProcessError:
|
| - return INVALID
|
| - return self._repo.get_commit(val.split()[0])
|
| -
|
| - # Methods
|
| - def to(self, other):
|
| - """Generate Commit()'s which occur from `self..other`."""
|
| - assert self.commit is not INVALID
|
| - arg = '%s..%s' % (self.ref, other.ref)
|
| - for hsh in self.repo.run('rev-list', '--reverse', arg).splitlines():
|
| - yield self.repo.get_commit(hsh)
|
| -
|
| - def fast_forward_push(self, commit):
|
| - """Push |commit| to this ref on the remote, and update the local copy of the
|
| - ref to |commit|."""
|
| - self.repo.run('push', 'origin', '%s:%s' % (commit.hsh, self.ref))
|
| - self.update_to(commit)
|
| -
|
| - def update_to(self, commit):
|
| - """Update the local copy of the ref to |commit|."""
|
| - self.repo.run('update-ref', self.ref, commit.hsh)
|
|
|