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' % ( |