Chromium Code Reviews| Index: checkout.py |
| diff --git a/checkout.py b/checkout.py |
| index 0ed6050d17dce6db4697187b0dbb0ab9ed332488..4754e0e0d21b04d70ffe2c8248b8baba6158c017 100644 |
| --- a/checkout.py |
| +++ b/checkout.py |
| @@ -13,9 +13,12 @@ import re |
| import subprocess |
| import tempfile |
| +# From depot_tools |
| +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): |
|
Dirk Pranke
2010/12/22 23:18:10
This is only used by SvnCheckout, right? If so, I
M-A Ruel
2010/12/23 17:19:49
Ok, I'll move it in a later change otherwise it cr
|
| + """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')) |