| Index: checkout.py
|
| diff --git a/checkout.py b/checkout.py
|
| index 03e28f68caf5557edb7a74ce75297bf5eedb3010..5fd236b4a7245ee024775d626dd300decf47c933 100644
|
| --- a/checkout.py
|
| +++ b/checkout.py
|
| @@ -47,6 +47,17 @@ def get_code_review_setting(path, key,
|
| return settings.get(key, None)
|
|
|
|
|
| +def align_stdout(stdout):
|
| + """Returns the aligned output of multiple stdouts."""
|
| + output = ''
|
| + for item in stdout:
|
| + item = item.strip()
|
| + if not item:
|
| + continue
|
| + output += ''.join(' %s\n' % line for line in item.splitlines())
|
| + return output
|
| +
|
| +
|
| class PatchApplicationFailed(Exception):
|
| """Patch failed to be applied."""
|
| def __init__(self, p, status):
|
| @@ -65,6 +76,7 @@ class PatchApplicationFailed(Exception):
|
| out.append('Failed to apply patch for %s:' % self.filename)
|
| if self.status:
|
| out.append(self.status)
|
| + out.append('Patch: %s' % self.patch.dump())
|
| return '\n'.join(out)
|
|
|
|
|
| @@ -105,7 +117,7 @@ class CheckoutBase(object):
|
| """
|
| raise NotImplementedError()
|
|
|
| - def apply_patch(self, patches, post_processors=None):
|
| + def apply_patch(self, patches, post_processors=None, verbose=False):
|
| """Applies a patch and returns the list of modified files.
|
|
|
| This function should throw patch.UnsupportedPatchFormat or
|
| @@ -139,26 +151,28 @@ class RawCheckout(CheckoutBase):
|
| """Stubbed out."""
|
| pass
|
|
|
| - def apply_patch(self, patches, post_processors=None):
|
| + 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:
|
| - logging.debug('Applying %s' % p.filename)
|
| + stdout = []
|
| try:
|
| - stdout = ''
|
| - filename = os.path.join(self.project_path, p.filename)
|
| + filepath = os.path.join(self.project_path, p.filename)
|
| if p.is_delete:
|
| - os.remove(filename)
|
| + os.remove(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)
|
|
|
| - filepath = os.path.join(self.project_path, p.filename)
|
| if p.is_binary:
|
| + content = p.get()
|
| with open(filepath, 'wb') as f:
|
| - f.write(p.get())
|
| + f.write(content)
|
| + stdout.append('Added binary file %d bytes.' % len(content))
|
| else:
|
| if p.source_filename:
|
| if not p.is_new:
|
| @@ -171,22 +185,35 @@ class RawCheckout(CheckoutBase):
|
| 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:
|
| - stdout = subprocess2.check_output(
|
| - ['patch', '-u', '--binary', '-p%s' % p.patchlevel],
|
| - stdin=p.get(False),
|
| - stderr=subprocess2.STDOUT,
|
| - cwd=self.project_path)
|
| + cmd = ['patch', '-u', '--binary', '-p%s' % p.patchlevel]
|
| + if verbose:
|
| + cmd.append('--verbose')
|
| + stdout.append(
|
| + subprocess2.check_output(
|
| + cmd,
|
| + stdin=p.get(False),
|
| + stderr=subprocess2.STDOUT,
|
| + cwd=self.project_path))
|
| 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' % (stdout, e))
|
| + raise PatchApplicationFailed(p, '%s%s' % (align_stdout(stdout), e))
|
| except subprocess.CalledProcessError, e:
|
| raise PatchApplicationFailed(
|
| - p, '%s%s' % (stdout, getattr(e, 'stdout', None)))
|
| + 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."""
|
| @@ -299,18 +326,19 @@ class SvnCheckout(CheckoutBase, SvnMixIn):
|
| (self.project_name, self.project_path))
|
| return self._revert(revision)
|
|
|
| - def apply_patch(self, patches, post_processors=None):
|
| + def apply_patch(self, patches, post_processors=None, verbose=False):
|
| post_processors = post_processors or self.post_processors or []
|
| for p in patches:
|
| - logging.debug('Applying %s' % p.filename)
|
| + 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.
|
| - stdout = ''
|
| if p.is_delete:
|
| - stdout += self._check_output_svn(
|
| - ['delete', p.filename, '--force'], credentials=False)
|
| + stdout.append(self._check_output_svn(
|
| + ['delete', p.filename, '--force'], credentials=False))
|
| + stdout.append('Deleted.')
|
| else:
|
| # svn add while creating directories otherwise svn add on the
|
| # contained files will silently fail.
|
| @@ -323,13 +351,16 @@ class SvnCheckout(CheckoutBase, SvnMixIn):
|
| 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 += self._check_output_svn(
|
| - ['add', dir_to_create, '--force'], credentials=False)
|
| + stdout.append(
|
| + self._check_output_svn(
|
| + ['add', dir_to_create, '--force'], credentials=False))
|
| + stdout.append('Created missing directory %s.' % dir_to_create)
|
|
|
| - filepath = os.path.join(self.project_path, p.filename)
|
| if p.is_binary:
|
| + content = p.get()
|
| with open(filepath, 'wb') as f:
|
| - f.write(p.get())
|
| + f.write(content)
|
| + stdout.append('Added binary file %d bytes.' % len(content))
|
| else:
|
| if p.source_filename:
|
| if not p.is_new:
|
| @@ -340,8 +371,10 @@ class SvnCheckout(CheckoutBase, SvnMixIn):
|
| if os.path.isfile(filepath):
|
| raise PatchApplicationFailed(
|
| p, 'File exist but was about to be overwriten')
|
| - self._check_output_svn(
|
| - ['copy', p.source_filename, p.filename])
|
| + 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',
|
| @@ -350,24 +383,32 @@ class SvnCheckout(CheckoutBase, SvnMixIn):
|
| '--force',
|
| '--no-backup-if-mismatch',
|
| ]
|
| - stdout += subprocess2.check_output(
|
| - cmd, stdin=p.get(False), cwd=self.project_path)
|
| + stdout.append(
|
| + subprocess2.check_output(
|
| + cmd, stdin=p.get(False), cwd=self.project_path))
|
| 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 += self._check_output_svn(
|
| - ['add', p.filename, '--force'], credentials=False)
|
| + stdout.append(
|
| + self._check_output_svn(
|
| + ['add', p.filename, '--force'], credentials=False))
|
| for name, value in p.svn_properties:
|
| if value is None:
|
| - stdout += self._check_output_svn(
|
| - ['propdel', '--quiet', name, p.filename], credentials=False)
|
| + stdout.append(
|
| + self._check_output_svn(
|
| + ['propdel', '--quiet', name, p.filename],
|
| + credentials=False))
|
| + stdout.append('Property %s deleted.' % name)
|
| else:
|
| - stdout += self._check_output_svn(
|
| - ['propset', name, value, p.filename], credentials=False)
|
| + 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(';'):
|
| @@ -378,17 +419,24 @@ class SvnCheckout(CheckoutBase, SvnMixIn):
|
| if params[1] == '*':
|
| # Works around crbug.com/150960 on Windows.
|
| params[1] = '.'
|
| - stdout += self._check_output_svn(
|
| - ['propset'] + params + [p.filename], credentials=False)
|
| + 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' % (stdout, 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), stdout, getattr(e, 'stdout', '')))
|
| + ' '.join(e.cmd),
|
| + align_stdout(stdout),
|
| + align_stdout([getattr(e, 'stdout', '')])))
|
|
|
| def commit(self, commit_message, user):
|
| logging.info('Committing patch for %s' % user)
|
| @@ -499,7 +547,7 @@ class GitCheckoutBase(CheckoutBase):
|
| if self.working_branch in branches:
|
| self._call_git(['branch', '-D', self.working_branch])
|
|
|
| - def apply_patch(self, patches, post_processors=None):
|
| + def apply_patch(self, patches, post_processors=None, verbose=False):
|
| """Applies a patch on 'working_branch' and switch to it.
|
|
|
| Also commits the changes on the local branch.
|
| @@ -514,38 +562,47 @@ class GitCheckoutBase(CheckoutBase):
|
| ['checkout', '-b', self.working_branch,
|
| '%s/%s' % (self.remote, self.remote_branch), '--quiet'])
|
| for index, p in enumerate(patches):
|
| - logging.debug('Applying %s' % p.filename)
|
| + stdout = []
|
| try:
|
| - stdout = ''
|
| + filepath = os.path.join(self.project_path, p.filename)
|
| if p.is_delete:
|
| - if (not os.path.exists(p.filename) and
|
| + if (not os.path.exists(filepath) and
|
| any(p1.source_filename == p.filename for p1 in patches[0:index])):
|
| - # The file could already be deleted if a prior patch with file
|
| - # rename was already processed. To be sure, look at all the previous
|
| - # patches to see if they were a file rename.
|
| + # The file was already deleted if a prior patch with file rename
|
| + # was already processed because 'git apply' did it for us.
|
| pass
|
| else:
|
| - stdout += self._check_output_git(['rm', p.filename])
|
| + stdout.append(self._check_output_git(['rm', p.filename]))
|
| + 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:
|
| - with open(os.path.join(self.project_path, p.filename), 'wb') as f:
|
| - f.write(p.get())
|
| - stdout += self._check_output_git(['add', p.filename])
|
| + content = p.get()
|
| + with open(filepath, 'wb') as f:
|
| + f.write(content)
|
| + stdout.append('Added binary file %d bytes' % len(content))
|
| + cmd = ['add', p.filename]
|
| + if verbose:
|
| + cmd.append('--verbose')
|
| + stdout.append(self._check_output_git(cmd))
|
| else:
|
| # No need to do anything special with p.is_new or if not
|
| # p.diff_hunks. git apply manages all that already.
|
| - stdout += self._check_output_git(
|
| - ['apply', '--index', '-p%s' % p.patchlevel], stdin=p.get(True))
|
| - for name, _ in p.svn_properties:
|
| + cmd = ['apply', '--index', '-p%s' % p.patchlevel]
|
| + if verbose:
|
| + cmd.append('--verbose')
|
| + stdout.append(self._check_output_git(cmd, stdin=p.get(True)))
|
| + for name, 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' % (name, value))
|
| if not name in (
|
| 'svn:eol-style', 'svn:executable', 'svn:mime-type'):
|
| raise patch.UnsupportedPatchFormat(
|
| @@ -554,14 +611,24 @@ class GitCheckoutBase(CheckoutBase):
|
| name, p.filename))
|
| 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' % (stdout, e))
|
| + raise PatchApplicationFailed(p, '%s%s' % (align_stdout(stdout), e))
|
| except subprocess.CalledProcessError, e:
|
| raise PatchApplicationFailed(
|
| - p, '%s%s' % (stdout, getattr(e, 'stdout', None)))
|
| + p,
|
| + 'While running %s;\n%s%s' % (
|
| + ' '.join(e.cmd),
|
| + align_stdout(stdout),
|
| + align_stdout([getattr(e, 'stdout', '')])))
|
| # Once all the patches are processed and added to the index, commit the
|
| # index.
|
| - self._check_call_git(['commit', '-m', 'Committed patch'])
|
| + cmd = ['commit', '-m', 'Committed patch']
|
| + if verbose:
|
| + cmd.append('--verbose')
|
| + self._check_call_git(cmd)
|
| # TODO(maruel): Weirdly enough they don't match, need to investigate.
|
| #found_files = self._check_output_git(
|
| # ['diff', 'master', '--name-only']).splitlines(False)
|
| @@ -643,9 +710,9 @@ class ReadOnlyCheckout(object):
|
| def get_settings(self, key):
|
| return self.checkout.get_settings(key)
|
|
|
| - def apply_patch(self, patches, post_processors=None):
|
| + def apply_patch(self, patches, post_processors=None, verbose=False):
|
| return self.checkout.apply_patch(
|
| - patches, post_processors or self.post_processors)
|
| + patches, post_processors or self.post_processors, verbose)
|
|
|
| def commit(self, message, user): # pylint: disable=R0201
|
| logging.info('Would have committed for %s with message: %s' % (
|
|
|