Index: checkout.py |
diff --git a/checkout.py b/checkout.py |
index 8f7c4be2baa44bdaf5c739c891b308ad7ad871ec..7c497612e16d5e9e6cb4f9807f4972a8c6a78c4b 100644 |
--- a/checkout.py |
+++ b/checkout.py |
@@ -4,7 +4,7 @@ |
# found in the LICENSE file. |
"""Manages a project checkout. |
-Includes support for svn, git-svn and git. |
+Includes support only for git. |
""" |
import fnmatch |
@@ -161,403 +161,6 @@ class CheckoutBase(object): |
raise NotImplementedError() |
-class RawCheckout(CheckoutBase): |
- """Used to apply a patch locally without any intent to commit it. |
- |
- To be used by the try server. |
- """ |
- def prepare(self, revision): |
- """Stubbed out.""" |
- pass |
- |
- def apply_patch(self, patches, post_processors=None, verbose=False): |
- """Ignores svn properties.""" |
- post_processors = post_processors or self.post_processors or [] |
- for p in patches: |
- stdout = [] |
- try: |
- filepath = os.path.join(self.project_path, p.filename) |
- if p.is_delete: |
- os.remove(filepath) |
- assert(not os.path.exists(filepath)) |
- stdout.append('Deleted.') |
- else: |
- dirname = os.path.dirname(p.filename) |
- full_dir = os.path.join(self.project_path, dirname) |
- if dirname and not os.path.isdir(full_dir): |
- os.makedirs(full_dir) |
- stdout.append('Created missing directory %s.' % dirname) |
- |
- if p.is_binary: |
- content = p.get() |
- with open(filepath, 'wb') as f: |
- f.write(content) |
- stdout.append('Added binary file %d bytes.' % len(content)) |
- else: |
- if p.source_filename: |
- if not p.is_new: |
- raise PatchApplicationFailed( |
- p, |
- 'File has a source filename specified but is not new') |
- # Copy the file first. |
- if os.path.isfile(filepath): |
- raise PatchApplicationFailed( |
- p, 'File exist but was about to be overwriten') |
- shutil.copy2( |
- os.path.join(self.project_path, p.source_filename), filepath) |
- stdout.append('Copied %s -> %s' % (p.source_filename, p.filename)) |
- if p.diff_hunks: |
- cmd = ['patch', '-u', '--binary', '-p%s' % p.patchlevel] |
- if verbose: |
- cmd.append('--verbose') |
- env = os.environ.copy() |
- env['TMPDIR'] = tempfile.mkdtemp(prefix='crpatch') |
- try: |
- stdout.append( |
- subprocess2.check_output( |
- cmd, |
- stdin=p.get(False), |
- stderr=subprocess2.STDOUT, |
- cwd=self.project_path, |
- timeout=GLOBAL_TIMEOUT, |
- env=env)) |
- finally: |
- shutil.rmtree(env['TMPDIR']) |
- elif p.is_new and not os.path.exists(filepath): |
- # There is only a header. Just create the file. |
- open(filepath, 'w').close() |
- stdout.append('Created an empty file.') |
- for post in post_processors: |
- post(self, p) |
- if verbose: |
- print p.filename |
- print align_stdout(stdout) |
- except OSError, e: |
- raise PatchApplicationFailed(p, '%s%s' % (align_stdout(stdout), e)) |
- except subprocess.CalledProcessError, e: |
- raise PatchApplicationFailed( |
- p, |
- 'While running %s;\n%s%s' % ( |
- ' '.join(e.cmd), |
- align_stdout(stdout), |
- align_stdout([getattr(e, 'stdout', '')]))) |
- |
- def commit(self, commit_message, user): |
- """Stubbed out.""" |
- raise NotImplementedError('RawCheckout can\'t commit') |
- |
- def revisions(self, _rev1, _rev2): |
- return None |
- |
- |
-class SvnConfig(object): |
- """Parses a svn configuration file.""" |
- def __init__(self, svn_config_dir=None): |
- super(SvnConfig, self).__init__() |
- self.svn_config_dir = svn_config_dir |
- self.default = not bool(self.svn_config_dir) |
- if not self.svn_config_dir: |
- if sys.platform == 'win32': |
- self.svn_config_dir = os.path.join(os.environ['APPDATA'], 'Subversion') |
- else: |
- self.svn_config_dir = os.path.expanduser( |
- os.path.join('~', '.subversion')) |
- svn_config_file = os.path.join(self.svn_config_dir, 'config') |
- parser = configparser.SafeConfigParser() |
- if os.path.isfile(svn_config_file): |
- parser.read(svn_config_file) |
- else: |
- parser.add_section('auto-props') |
- self.auto_props = dict(parser.items('auto-props')) |
- |
- |
-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 |
- # Override at class level when necessary. If used, --non-interactive is |
- # implied. |
- svn_config = SvnConfig() |
- # Set to True when non-interactivity is necessary but a custom subversion |
- # configuration directory is not necessary. |
- non_interactive = False |
- |
- def _add_svn_flags(self, args, non_interactive, credentials=True): |
- args = ['svn'] + args |
- if not self.svn_config.default: |
- args.extend(['--config-dir', self.svn_config.svn_config_dir]) |
- if not self.svn_config.default or self.non_interactive or non_interactive: |
- args.append('--non-interactive') |
- if credentials: |
- if self.commit_user: |
- args.extend(['--username', self.commit_user]) |
- if self.commit_pwd: |
- args.extend(['--password', self.commit_pwd]) |
- return args |
- |
- def _check_call_svn(self, args, **kwargs): |
- """Runs svn and throws an exception if the command failed.""" |
- kwargs.setdefault('cwd', self.project_path) |
- kwargs.setdefault('stdout', self.VOID) |
- kwargs.setdefault('timeout', GLOBAL_TIMEOUT) |
- return subprocess2.check_call_out( |
- self._add_svn_flags(args, False), **kwargs) |
- |
- def _check_output_svn(self, args, credentials=True, **kwargs): |
- """Runs svn and throws an exception if the command failed. |
- |
- Returns the output. |
- """ |
- kwargs.setdefault('cwd', self.project_path) |
- return subprocess2.check_output( |
- self._add_svn_flags(args, True, credentials), |
- stderr=subprocess2.STDOUT, |
- timeout=GLOBAL_TIMEOUT, |
- **kwargs) |
- |
- @staticmethod |
- def _parse_svn_info(output, key): |
- """Returns value for key from svn info output. |
- |
- Case insensitive. |
- """ |
- values = {} |
- key = key.lower() |
- 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) |
- |
- |
-class SvnCheckout(CheckoutBase, SvnMixIn): |
- """Manages a subversion checkout.""" |
- def __init__(self, root_dir, project_name, commit_user, commit_pwd, svn_url, |
- post_processors=None): |
- CheckoutBase.__init__(self, root_dir, project_name, post_processors) |
- SvnMixIn.__init__(self) |
- self.commit_user = commit_user |
- self.commit_pwd = commit_pwd |
- self.svn_url = svn_url |
- assert bool(self.commit_user) >= bool(self.commit_pwd) |
- |
- def prepare(self, revision): |
- # Will checkout if the directory is not present. |
- assert self.svn_url |
- if not os.path.isdir(self.project_path): |
- logging.info('Checking out %s in %s' % |
- (self.project_name, self.project_path)) |
- return self._revert(revision) |
- |
- def apply_patch(self, patches, post_processors=None, verbose=False): |
- post_processors = post_processors or self.post_processors or [] |
- for p in patches: |
- stdout = [] |
- try: |
- filepath = os.path.join(self.project_path, p.filename) |
- # It is important to use credentials=False otherwise credentials could |
- # leak in the error message. Credentials are not necessary here for the |
- # following commands anyway. |
- if p.is_delete: |
- stdout.append(self._check_output_svn( |
- ['delete', p.filename, '--force'], credentials=False)) |
- assert(not os.path.exists(filepath)) |
- stdout.append('Deleted.') |
- else: |
- # svn add while creating directories otherwise svn add on the |
- # contained files will silently fail. |
- # First, find the root directory that exists. |
- dirname = os.path.dirname(p.filename) |
- dirs_to_create = [] |
- while (dirname and |
- not os.path.isdir(os.path.join(self.project_path, dirname))): |
- dirs_to_create.append(dirname) |
- dirname = os.path.dirname(dirname) |
- for dir_to_create in reversed(dirs_to_create): |
- os.mkdir(os.path.join(self.project_path, dir_to_create)) |
- stdout.append( |
- self._check_output_svn( |
- ['add', dir_to_create, '--force'], credentials=False)) |
- stdout.append('Created missing directory %s.' % dir_to_create) |
- |
- if p.is_binary: |
- content = p.get() |
- with open(filepath, 'wb') as f: |
- f.write(content) |
- stdout.append('Added binary file %d bytes.' % len(content)) |
- else: |
- if p.source_filename: |
- if not p.is_new: |
- raise PatchApplicationFailed( |
- p, |
- 'File has a source filename specified but is not new') |
- # Copy the file first. |
- if os.path.isfile(filepath): |
- raise PatchApplicationFailed( |
- p, 'File exist but was about to be overwriten') |
- stdout.append( |
- self._check_output_svn( |
- ['copy', p.source_filename, p.filename])) |
- stdout.append('Copied %s -> %s' % (p.source_filename, p.filename)) |
- if p.diff_hunks: |
- cmd = [ |
- 'patch', |
- '-p%s' % p.patchlevel, |
- '--forward', |
- '--force', |
- '--no-backup-if-mismatch', |
- ] |
- env = os.environ.copy() |
- env['TMPDIR'] = tempfile.mkdtemp(prefix='crpatch') |
- try: |
- stdout.append( |
- subprocess2.check_output( |
- cmd, |
- stdin=p.get(False), |
- cwd=self.project_path, |
- timeout=GLOBAL_TIMEOUT, |
- env=env)) |
- finally: |
- shutil.rmtree(env['TMPDIR']) |
- |
- elif p.is_new and not os.path.exists(filepath): |
- # There is only a header. Just create the file if it doesn't |
- # exist. |
- open(filepath, 'w').close() |
- stdout.append('Created an empty file.') |
- if p.is_new and not p.source_filename: |
- # Do not run it if p.source_filename is defined, since svn copy was |
- # using above. |
- stdout.append( |
- self._check_output_svn( |
- ['add', p.filename, '--force'], credentials=False)) |
- for name, value in p.svn_properties: |
- if value is None: |
- stdout.append( |
- self._check_output_svn( |
- ['propdel', '--quiet', name, p.filename], |
- credentials=False)) |
- stdout.append('Property %s deleted.' % name) |
- else: |
- stdout.append( |
- self._check_output_svn( |
- ['propset', name, value, p.filename], credentials=False)) |
- stdout.append('Property %s=%s' % (name, value)) |
- for prop, values in self.svn_config.auto_props.iteritems(): |
- if fnmatch.fnmatch(p.filename, prop): |
- for value in values.split(';'): |
- if '=' not in value: |
- params = [value, '.'] |
- else: |
- params = value.split('=', 1) |
- if params[1] == '*': |
- # Works around crbug.com/150960 on Windows. |
- params[1] = '.' |
- stdout.append( |
- self._check_output_svn( |
- ['propset'] + params + [p.filename], credentials=False)) |
- stdout.append('Property (auto) %s' % '='.join(params)) |
- for post in post_processors: |
- post(self, p) |
- if verbose: |
- print p.filename |
- print align_stdout(stdout) |
- except OSError, e: |
- raise PatchApplicationFailed(p, '%s%s' % (align_stdout(stdout), e)) |
- except subprocess.CalledProcessError, e: |
- raise PatchApplicationFailed( |
- p, |
- 'While running %s;\n%s%s' % ( |
- ' '.join(e.cmd), |
- align_stdout(stdout), |
- align_stdout([getattr(e, 'stdout', '')]))) |
- |
- def commit(self, commit_message, user): |
- logging.info('Committing patch for %s' % user) |
- assert self.commit_user |
- assert isinstance(commit_message, unicode) |
- handle, commit_filename = tempfile.mkstemp(text=True) |
- try: |
- # Shouldn't assume default encoding is UTF-8. But really, if you are using |
- # anything else, you are living in another world. |
- os.write(handle, commit_message.encode('utf-8')) |
- os.close(handle) |
- # When committing, svn won't update the Revision metadata of the checkout, |
- # so if svn commit returns "Committed revision 3.", svn info will still |
- # return "Revision: 2". Since running svn update right after svn commit |
- # creates a race condition with other committers, this code _must_ parse |
- # the output of svn commit and use a regexp to grab the revision number. |
- # Note that "Committed revision N." is localized but subprocess2 forces |
- # LANGUAGE=en. |
- args = ['commit', '--file', commit_filename] |
- # realauthor is parsed by a server-side hook. |
- if user and user != self.commit_user: |
- args.extend(['--with-revprop', 'realauthor=%s' % user]) |
- out = self._check_output_svn(args) |
- finally: |
- os.remove(commit_filename) |
- lines = filter(None, out.splitlines()) |
- match = re.match(r'^Committed revision (\d+).$', lines[-1]) |
- if not match: |
- raise PatchApplicationFailed( |
- None, |
- 'Couldn\'t make sense out of svn commit message:\n' + out) |
- return int(match.group(1)) |
- |
- def _revert(self, revision): |
- """Reverts local modifications or checks out if the directory is not |
- present. Use depot_tools's functionality to do this. |
- """ |
- flags = ['--ignore-externals'] |
- if revision: |
- flags.extend(['--revision', str(revision)]) |
- if os.path.isdir(self.project_path): |
- # This may remove any part (or all) of the checkout. |
- scm.SVN.Revert(self.project_path, no_ignore=True) |
- |
- if os.path.isdir(self.project_path): |
- # Revive files that were deleted in scm.SVN.Revert(). |
- self._check_call_svn(['update', '--force'] + flags, |
- timeout=FETCH_TIMEOUT) |
- else: |
- 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, |
- timeout=FETCH_TIMEOUT) |
- return self._get_revision() |
- |
- def _get_revision(self): |
- out = self._check_output_svn(['info', '.']) |
- revision = int(self._parse_svn_info(out, 'revision')) |
- if revision != self._last_seen_revision: |
- logging.info('Updated to revision %d' % revision) |
- self._last_seen_revision = revision |
- return revision |
- |
- def revisions(self, rev1, rev2): |
- """Returns the number of actual commits, not just the difference between |
- numbers. |
- """ |
- rev2 = rev2 or 'HEAD' |
- # Revision range is inclusive and ordering doesn't matter, they'll appear in |
- # the order specified. |
- try: |
- out = self._check_output_svn( |
- ['log', '-q', self.svn_url, '-r', '%s:%s' % (rev1, rev2)]) |
- except subprocess.CalledProcessError: |
- return None |
- # Ignore the '----' lines. |
- return len([l for l in out.splitlines() if l.startswith('r')]) - 1 |
- |
- |
class GitCheckout(CheckoutBase): |
"""Manages a git checkout.""" |
def __init__(self, root_dir, project_name, remote_branch, git_url, |
@@ -639,8 +242,6 @@ class GitCheckout(CheckoutBase): |
"""Applies a patch on 'working_branch' and switches to it. |
The changes remain staged on the current branch. |
- |
- Ignores svn properties and raise an exception on unexpected ones. |
""" |
post_processors = post_processors or self.post_processors or [] |
# It this throws, the checkout is corrupted. Maybe worth deleting it and |
@@ -686,19 +287,6 @@ class GitCheckout(CheckoutBase): |
if verbose: |
cmd.append('--verbose') |
stdout.append(self._check_output_git(cmd, stdin=p.get(True))) |
- for key, value in p.svn_properties: |
- # Ignore some known auto-props flags through .subversion/config, |
- # bails out on the other ones. |
- # TODO(maruel): Read ~/.subversion/config and detect the rules that |
- # applies here to figure out if the property will be correctly |
- # handled. |
- stdout.append('Property %s=%s' % (key, value)) |
- if not key in ( |
- 'svn:eol-style', 'svn:executable', 'svn:mime-type'): |
- raise patch.UnsupportedPatchFormat( |
- p.filename, |
- 'Cannot apply svn property %s to file %s.' % ( |
- key, p.filename)) |
for post in post_processors: |
post(self, p) |
if verbose: |