| Index: checkout.py
|
| diff --git a/checkout.py b/checkout.py
|
| index 0ed6050d17dce6db4697187b0dbb0ab9ed332488..8c878db5efea5f696764c6435a08e7ee49807841 100644
|
| --- a/checkout.py
|
| +++ b/checkout.py
|
| @@ -13,9 +13,12 @@ import re
|
| import subprocess
|
| import tempfile
|
|
|
| +import find_depot_tools # pylint: disable=W0611
|
| +import gclient_utils
|
| +import scm
|
| +
|
| import patch
|
| import subprocess2
|
| -import svn_utils
|
|
|
|
|
| def get_code_review_setting(path, key,
|
| @@ -52,7 +55,66 @@ class CheckoutBase(object):
|
| return get_code_review_setting(self.project_path, key)
|
|
|
|
|
| -class SvnCheckout(CheckoutBase):
|
| +class SvnMixIn(object):
|
| + """MixIn class to add svn commands common to both svn and git-svn clients."""
|
| + # These members need to be set by the subclass.
|
| + commit_user = None
|
| + commit_pwd = None
|
| + svn_url = None
|
| + project_path = None
|
| +
|
| + def _check_call_svn(self, args, **kwargs):
|
| + """Runs svn and throws an exception if the command failed."""
|
| + kwargs.setdefault('cwd', self.project_path)
|
| + return subprocess2.check_call(
|
| + ['svn'] + args + ['--no-auth-cache', '--non-interactive'], **kwargs)
|
| +
|
| + def _capture_svn(self, args, **kwargs):
|
| + """Runs svn and throws an exception if the command failed.
|
| +
|
| + Returns the output.
|
| + """
|
| + kwargs.setdefault('cwd', self.project_path)
|
| + if self.commit_user:
|
| + args = args + [
|
| + '--username', self.commit_user, '--password', self.commit_pwd]
|
| + cmd = ['svn'] + args + ['--no-auth-cache', '--non-interactive']
|
| + return subprocess2.check_capture(cmd, **kwargs)
|
| +
|
| + @staticmethod
|
| + def _parse_svn_info(output, key):
|
| + """Returns value for key from svn info output.
|
| +
|
| + Case insensitive.
|
| + """
|
| + values = {}
|
| + for line in output.splitlines(False):
|
| + if not line:
|
| + continue
|
| + k, v = line.split(':', 1)
|
| + k = k.strip().lower()
|
| + v = v.strip()
|
| + assert not k in values
|
| + values[k] = v
|
| + return values.get(key, None)
|
| +
|
| + def _update_committer(self, revision, new_author):
|
| + """Changes the author of a commit a posteriori.
|
| +
|
| + This is necessary since the actual commit is done with a "commit-bot"
|
| + credential but the original patch author needs to be assigned authorship
|
| + of the revision.
|
| + """
|
| + self._check_call_svn(
|
| + ['propset', '--revprop', 'svn:author',
|
| + '-r', revision,
|
| + new_author,
|
| + '--username', self.commit_user,
|
| + '--password', self.commit_pwd,
|
| + self.svn_url])
|
| +
|
| +
|
| +class SvnCheckout(CheckoutBase, SvnMixIn):
|
| """Manages a subversion checkout.
|
|
|
| Commit is not fully implemented yet. Reimplementing all the commands is
|
| @@ -73,11 +135,7 @@ class SvnCheckout(CheckoutBase):
|
| # Will checkout if the directory is not present.
|
| logging.info('Checking out %s in %s' %
|
| (self.project_name, self.project_path))
|
| - return svn_utils.revert(
|
| - self.svn_url,
|
| - self.project_path,
|
| - self.commit_user,
|
| - self.commit_pwd)
|
| + return self._revert()
|
|
|
| def apply_patch(self, patch_data):
|
| """Applies a patch."""
|
| @@ -98,23 +156,65 @@ class SvnCheckout(CheckoutBase):
|
| os.write(handle, commit_message)
|
| os.close(handle)
|
| try:
|
| - output = svn_utils.capture_svn([
|
| - 'commit',
|
| - '--username', self.commit_user,
|
| - '--password', self.commit_pwd,
|
| - '--file', commit_filename],
|
| - cwd=self.project_path)
|
| + output = self._capture_svn(['commit', '--file', commit_filename])
|
| revision = re.compile(
|
| r'.*?\nCommitted revision (\d+)',
|
| re.DOTALL).match(output).group(1)
|
| # Fix the committer.
|
| - svn_utils.update_committer(
|
| - self.svn_url, revision, self.commit_user, self.commit_pwd, user,
|
| - self.project_path)
|
| + self._update_committer(revision, user)
|
| finally:
|
| os.remove(commit_filename)
|
| return int(revision)
|
|
|
| + def _revert(self):
|
| + """Reverts local modifications or checks out if the directory is not
|
| + present.
|
| + """
|
| + flags = ['--ignore-externals']
|
| + if not os.path.isdir(self.project_path):
|
| + logging.info('Directory %s is not present, checking it out.' %
|
| + self.project_path)
|
| + self._check_call_svn(['checkout', self.svn_url, self.project_path] +
|
| + flags, cwd=None)
|
| + else:
|
| + for file_status in scm.SVN.CaptureStatus(self.project_path):
|
| + file_path = os.path.join(self.project_path, file_status[1])
|
| + if file_status[0][0] == 'X':
|
| + # Ignore externals.
|
| + logging.info('Ignoring external %s' % file_path)
|
| + continue
|
| +
|
| + logging.info('%s%s' % (file_status[0], file_status[1]))
|
| +
|
| + if file_status[0].isspace():
|
| + raise EnvironmentError(
|
| + 'No idea what is the status of %s.\n'
|
| + 'You just found a bug in gclient, please ping '
|
| + 'maruel@chromium.org ASAP!' % file_path)
|
| +
|
| + # svn revert is really stupid. It fails on inconsistent line-endings,
|
| + # on switched directories, etc. So take no chance and delete everything!
|
| + try:
|
| + if not os.path.exists(file_path):
|
| + pass
|
| + elif os.path.isfile(file_path) or os.path.islink(file_path):
|
| + logging.info('os.remove(%s)' % file_path)
|
| + os.remove(file_path)
|
| + elif os.path.isdir(file_path):
|
| + logging.info('gclient_utils.RemoveDirectory(%s)' % file_path)
|
| + gclient_utils.RemoveDirectory(file_path)
|
| + else:
|
| + logging.error('no idea what is %s.\nYou just found a bug in gclient'
|
| + ', please ping maruel@chromium.org ASAP!' % file_path)
|
| + except EnvironmentError:
|
| + logging.error('Failed to remove %s.' % file_path)
|
| +
|
| + # Revive files that were deleted above.
|
| + self._check_call_svn(['update', '--force'] + flags)
|
| +
|
| + out = self._capture_svn(['info', '.'])
|
| + return int(self._parse_svn_info(out, 'revision'))
|
| +
|
|
|
| class GitCheckoutBase(CheckoutBase):
|
| """Base class for git checkout. Not to be used as-is."""
|
| @@ -157,18 +257,19 @@ class GitCheckoutBase(CheckoutBase):
|
| return subprocess2.check_capture(['git'] + args, **kwargs)
|
|
|
|
|
| -class GitSvnCheckoutBase(GitCheckoutBase):
|
| +class GitSvnCheckoutBase(GitCheckoutBase, SvnMixIn):
|
| """Base class for git-svn checkout. Not to be used as-is."""
|
| def __init__(self,
|
| root_dir, project_name, remote_branch,
|
| commit_user, commit_pwd,
|
| - svn_repo, trunk):
|
| + svn_url, trunk):
|
| """trunk is optional."""
|
| super(GitSvnCheckoutBase, self).__init__(
|
| root_dir, project_name + '.git', remote_branch)
|
| self.commit_user = commit_user
|
| self.commit_pwd = commit_pwd
|
| - self.svn_repo = svn_repo
|
| + # svn_url in this case is the root of the svn repository.
|
| + self.svn_url = svn_url
|
| self.trunk = trunk
|
|
|
| def prepare(self):
|
| @@ -179,7 +280,7 @@ class GitSvnCheckoutBase(GitCheckoutBase):
|
| return int(self._git_svn_info('revision'))
|
|
|
| def _git_svn_info(self, key):
|
| - return svn_utils.parse_svn_info(
|
| + return self._parse_svn_info(
|
| self._check_capture_git(['svn', 'info']), key)
|
|
|
| def commit(self, commit_message, user):
|
| @@ -192,9 +293,7 @@ class GitSvnCheckoutBase(GitCheckoutBase):
|
| self._check_call_git_svn(['dcommit', '--rmdir', '--find-copies-harder'])
|
| revision = int(self._git_svn_info('revision'))
|
| # Fix the committer.
|
| - svn_utils.update_committer(
|
| - self.svn_repo, revision, self.commit_user, self.commit_pwd, user,
|
| - self.project_path)
|
| + self._update_committer(revision, user)
|
| return revision
|
|
|
| def _cache_svn_auth(self):
|
| @@ -204,7 +303,7 @@ class GitSvnCheckoutBase(GitCheckoutBase):
|
| return
|
| logging.info('Caching svn credentials for %s' % self.commit_user)
|
| subprocess2.check_call(
|
| - ['svn', 'ls', self.svn_repo,
|
| + ['svn', 'ls', self.svn_url,
|
| '--username', self.commit_user,
|
| '--password', self.commit_pwd,
|
| '--non-interactive'])
|
| @@ -226,11 +325,11 @@ class GitSvnPremadeCheckout(GitSvnCheckoutBase):
|
| def __init__(self,
|
| root_dir, project_name, remote_branch,
|
| commit_user, commit_pwd,
|
| - svn_repo, trunk, git_url):
|
| + svn_url, trunk, git_url):
|
| super(GitSvnPremadeCheckout, self).__init__(
|
| root_dir, project_name, remote_branch,
|
| commit_user, commit_pwd,
|
| - svn_repo, trunk)
|
| + svn_url, trunk)
|
| self.git_url = git_url
|
|
|
| def prepare(self):
|
| @@ -246,7 +345,7 @@ class GitSvnPremadeCheckout(GitSvnCheckoutBase):
|
| ['svn', 'init',
|
| '--prefix', self.remote + '/',
|
| '-T', self.trunk,
|
| - self.svn_repo])
|
| + self.svn_url])
|
| self._check_call_git_svn(['fetch'])
|
| super(GitSvnPremadeCheckout, self).prepare()
|
| return int(self._git_svn_info('revision'))
|
| @@ -260,11 +359,11 @@ class GitSvnCheckout(GitSvnCheckoutBase):
|
| def __init__(self,
|
| root_dir, project_name,
|
| commit_user, commit_pwd,
|
| - svn_repo, trunk):
|
| + svn_url, trunk):
|
| super(GitSvnCheckout, self).__init__(
|
| root_dir, project_name, 'trunk',
|
| commit_user, commit_pwd,
|
| - svn_repo, trunk)
|
| + svn_url, trunk)
|
|
|
| def prepare(self):
|
| """Creates the initial checkout for the repo."""
|
| @@ -276,7 +375,7 @@ class GitSvnCheckout(GitSvnCheckoutBase):
|
| ['clone',
|
| '--prefix', self.remote + '/',
|
| '-T', self.trunk,
|
| - self.svn_repo, self.project_path],
|
| + self.svn_url, self.project_path],
|
| cwd=self.root_dir)
|
| super(GitSvnCheckout, self).prepare()
|
| return int(self._git_svn_info('revision'))
|
|
|