diff --git a/apply_issue.py b/apply_issue.py index e9c49b90b..8a2f573ac 100755 --- a/apply_issue.py +++ b/apply_issue.py @@ -265,8 +265,6 @@ def main(): scm_type = scm.determine_scm(full_dir) if scm_type == 'git': scm_obj = checkout.GitCheckout(full_dir, None, None, None, None) - elif scm_type == None: - scm_obj = checkout.RawCheckout(full_dir, None, None) else: parser.error('Couldn\'t determine the scm') diff --git a/checkout.py b/checkout.py index 8f7c4be2b..7c497612e 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: