| OLD | NEW |
| 1 # coding=utf8 | 1 # coding=utf8 |
| 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 """Manages a project checkout. | 5 """Manages a project checkout. |
| 6 | 6 |
| 7 Includes support for svn, git-svn and git. | 7 Includes support for svn, git-svn and git. |
| 8 """ | 8 """ |
| 9 | 9 |
| 10 import ConfigParser | 10 import ConfigParser |
| (...skipping 29 matching lines...) Expand all Loading... |
| 40 return None | 40 return None |
| 41 k, v = line.split(':', 1) | 41 k, v = line.split(':', 1) |
| 42 settings[k.strip()] = v.strip() | 42 settings[k.strip()] = v.strip() |
| 43 finally: | 43 finally: |
| 44 settings_file.close() | 44 settings_file.close() |
| 45 except IOError: | 45 except IOError: |
| 46 return None | 46 return None |
| 47 return settings.get(key, None) | 47 return settings.get(key, None) |
| 48 | 48 |
| 49 | 49 |
| 50 def align_stdout(stdout): |
| 51 """Returns the aligned output of multiple stdouts.""" |
| 52 output = '' |
| 53 for item in stdout: |
| 54 item = item.strip() |
| 55 if not item: |
| 56 continue |
| 57 output += ''.join(' %s\n' % line for line in item.splitlines()) |
| 58 return output |
| 59 |
| 60 |
| 50 class PatchApplicationFailed(Exception): | 61 class PatchApplicationFailed(Exception): |
| 51 """Patch failed to be applied.""" | 62 """Patch failed to be applied.""" |
| 52 def __init__(self, p, status): | 63 def __init__(self, p, status): |
| 53 super(PatchApplicationFailed, self).__init__(p, status) | 64 super(PatchApplicationFailed, self).__init__(p, status) |
| 54 self.patch = p | 65 self.patch = p |
| 55 self.status = status | 66 self.status = status |
| 56 | 67 |
| 57 @property | 68 @property |
| 58 def filename(self): | 69 def filename(self): |
| 59 if self.patch: | 70 if self.patch: |
| 60 return self.patch.filename | 71 return self.patch.filename |
| 61 | 72 |
| 62 def __str__(self): | 73 def __str__(self): |
| 63 out = [] | 74 out = [] |
| 64 if self.filename: | 75 if self.filename: |
| 65 out.append('Failed to apply patch for %s:' % self.filename) | 76 out.append('Failed to apply patch for %s:' % self.filename) |
| 66 if self.status: | 77 if self.status: |
| 67 out.append(self.status) | 78 out.append(self.status) |
| 79 out.append('Patch: %s' % self.patch.dump()) |
| 68 return '\n'.join(out) | 80 return '\n'.join(out) |
| 69 | 81 |
| 70 | 82 |
| 71 class CheckoutBase(object): | 83 class CheckoutBase(object): |
| 72 # Set to None to have verbose output. | 84 # Set to None to have verbose output. |
| 73 VOID = subprocess2.VOID | 85 VOID = subprocess2.VOID |
| 74 | 86 |
| 75 def __init__(self, root_dir, project_name, post_processors): | 87 def __init__(self, root_dir, project_name, post_processors): |
| 76 """ | 88 """ |
| 77 Args: | 89 Args: |
| (...skipping 20 matching lines...) Expand all Loading... |
| 98 """Checks out a clean copy of the tree and removes any local modification. | 110 """Checks out a clean copy of the tree and removes any local modification. |
| 99 | 111 |
| 100 This function shouldn't throw unless the remote repository is inaccessible, | 112 This function shouldn't throw unless the remote repository is inaccessible, |
| 101 there is no free disk space or hard issues like that. | 113 there is no free disk space or hard issues like that. |
| 102 | 114 |
| 103 Args: | 115 Args: |
| 104 revision: The revision it should sync to, SCM specific. | 116 revision: The revision it should sync to, SCM specific. |
| 105 """ | 117 """ |
| 106 raise NotImplementedError() | 118 raise NotImplementedError() |
| 107 | 119 |
| 108 def apply_patch(self, patches, post_processors=None): | 120 def apply_patch(self, patches, post_processors=None, verbose=False): |
| 109 """Applies a patch and returns the list of modified files. | 121 """Applies a patch and returns the list of modified files. |
| 110 | 122 |
| 111 This function should throw patch.UnsupportedPatchFormat or | 123 This function should throw patch.UnsupportedPatchFormat or |
| 112 PatchApplicationFailed when relevant. | 124 PatchApplicationFailed when relevant. |
| 113 | 125 |
| 114 Args: | 126 Args: |
| 115 patches: patch.PatchSet object. | 127 patches: patch.PatchSet object. |
| 116 """ | 128 """ |
| 117 raise NotImplementedError() | 129 raise NotImplementedError() |
| 118 | 130 |
| (...skipping 13 matching lines...) Expand all Loading... |
| 132 | 144 |
| 133 class RawCheckout(CheckoutBase): | 145 class RawCheckout(CheckoutBase): |
| 134 """Used to apply a patch locally without any intent to commit it. | 146 """Used to apply a patch locally without any intent to commit it. |
| 135 | 147 |
| 136 To be used by the try server. | 148 To be used by the try server. |
| 137 """ | 149 """ |
| 138 def prepare(self, revision): | 150 def prepare(self, revision): |
| 139 """Stubbed out.""" | 151 """Stubbed out.""" |
| 140 pass | 152 pass |
| 141 | 153 |
| 142 def apply_patch(self, patches, post_processors=None): | 154 def apply_patch(self, patches, post_processors=None, verbose=False): |
| 143 """Ignores svn properties.""" | 155 """Ignores svn properties.""" |
| 144 post_processors = post_processors or self.post_processors or [] | 156 post_processors = post_processors or self.post_processors or [] |
| 145 for p in patches: | 157 for p in patches: |
| 146 logging.debug('Applying %s' % p.filename) | 158 stdout = [] |
| 147 try: | 159 try: |
| 148 stdout = '' | 160 filepath = os.path.join(self.project_path, p.filename) |
| 149 filename = os.path.join(self.project_path, p.filename) | |
| 150 if p.is_delete: | 161 if p.is_delete: |
| 151 os.remove(filename) | 162 os.remove(filepath) |
| 163 stdout.append('Deleted.') |
| 152 else: | 164 else: |
| 153 dirname = os.path.dirname(p.filename) | 165 dirname = os.path.dirname(p.filename) |
| 154 full_dir = os.path.join(self.project_path, dirname) | 166 full_dir = os.path.join(self.project_path, dirname) |
| 155 if dirname and not os.path.isdir(full_dir): | 167 if dirname and not os.path.isdir(full_dir): |
| 156 os.makedirs(full_dir) | 168 os.makedirs(full_dir) |
| 169 stdout.append('Created missing directory %s.' % dirname) |
| 157 | 170 |
| 158 filepath = os.path.join(self.project_path, p.filename) | |
| 159 if p.is_binary: | 171 if p.is_binary: |
| 172 content = p.get() |
| 160 with open(filepath, 'wb') as f: | 173 with open(filepath, 'wb') as f: |
| 161 f.write(p.get()) | 174 f.write(content) |
| 175 stdout.append('Added binary file %d bytes.' % len(content)) |
| 162 else: | 176 else: |
| 163 if p.source_filename: | 177 if p.source_filename: |
| 164 if not p.is_new: | 178 if not p.is_new: |
| 165 raise PatchApplicationFailed( | 179 raise PatchApplicationFailed( |
| 166 p, | 180 p, |
| 167 'File has a source filename specified but is not new') | 181 'File has a source filename specified but is not new') |
| 168 # Copy the file first. | 182 # Copy the file first. |
| 169 if os.path.isfile(filepath): | 183 if os.path.isfile(filepath): |
| 170 raise PatchApplicationFailed( | 184 raise PatchApplicationFailed( |
| 171 p, 'File exist but was about to be overwriten') | 185 p, 'File exist but was about to be overwriten') |
| 172 shutil.copy2( | 186 shutil.copy2( |
| 173 os.path.join(self.project_path, p.source_filename), filepath) | 187 os.path.join(self.project_path, p.source_filename), filepath) |
| 188 stdout.append('Copied %s -> %s' % (p.source_filename, p.filename)) |
| 174 if p.diff_hunks: | 189 if p.diff_hunks: |
| 175 stdout = subprocess2.check_output( | 190 cmd = ['patch', '-u', '--binary', '-p%s' % p.patchlevel] |
| 176 ['patch', '-u', '--binary', '-p%s' % p.patchlevel], | 191 if verbose: |
| 177 stdin=p.get(False), | 192 cmd.append('--verbose') |
| 178 stderr=subprocess2.STDOUT, | 193 stdout.append( |
| 179 cwd=self.project_path) | 194 subprocess2.check_output( |
| 195 cmd, |
| 196 stdin=p.get(False), |
| 197 stderr=subprocess2.STDOUT, |
| 198 cwd=self.project_path)) |
| 180 elif p.is_new and not os.path.exists(filepath): | 199 elif p.is_new and not os.path.exists(filepath): |
| 181 # There is only a header. Just create the file. | 200 # There is only a header. Just create the file. |
| 182 open(filepath, 'w').close() | 201 open(filepath, 'w').close() |
| 202 stdout.append('Created an empty file.') |
| 183 for post in post_processors: | 203 for post in post_processors: |
| 184 post(self, p) | 204 post(self, p) |
| 205 if verbose: |
| 206 print p.filename |
| 207 print align_stdout(stdout) |
| 185 except OSError, e: | 208 except OSError, e: |
| 186 raise PatchApplicationFailed(p, '%s%s' % (stdout, e)) | 209 raise PatchApplicationFailed(p, '%s%s' % (align_stdout(stdout), e)) |
| 187 except subprocess.CalledProcessError, e: | 210 except subprocess.CalledProcessError, e: |
| 188 raise PatchApplicationFailed( | 211 raise PatchApplicationFailed( |
| 189 p, '%s%s' % (stdout, getattr(e, 'stdout', None))) | 212 p, |
| 213 'While running %s;\n%s%s' % ( |
| 214 ' '.join(e.cmd), |
| 215 align_stdout(stdout), |
| 216 align_stdout([getattr(e, 'stdout', '')]))) |
| 190 | 217 |
| 191 def commit(self, commit_message, user): | 218 def commit(self, commit_message, user): |
| 192 """Stubbed out.""" | 219 """Stubbed out.""" |
| 193 raise NotImplementedError('RawCheckout can\'t commit') | 220 raise NotImplementedError('RawCheckout can\'t commit') |
| 194 | 221 |
| 195 def revisions(self, _rev1, _rev2): | 222 def revisions(self, _rev1, _rev2): |
| 196 return None | 223 return None |
| 197 | 224 |
| 198 | 225 |
| 199 class SvnConfig(object): | 226 class SvnConfig(object): |
| (...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 292 assert bool(self.commit_user) >= bool(self.commit_pwd) | 319 assert bool(self.commit_user) >= bool(self.commit_pwd) |
| 293 | 320 |
| 294 def prepare(self, revision): | 321 def prepare(self, revision): |
| 295 # Will checkout if the directory is not present. | 322 # Will checkout if the directory is not present. |
| 296 assert self.svn_url | 323 assert self.svn_url |
| 297 if not os.path.isdir(self.project_path): | 324 if not os.path.isdir(self.project_path): |
| 298 logging.info('Checking out %s in %s' % | 325 logging.info('Checking out %s in %s' % |
| 299 (self.project_name, self.project_path)) | 326 (self.project_name, self.project_path)) |
| 300 return self._revert(revision) | 327 return self._revert(revision) |
| 301 | 328 |
| 302 def apply_patch(self, patches, post_processors=None): | 329 def apply_patch(self, patches, post_processors=None, verbose=False): |
| 303 post_processors = post_processors or self.post_processors or [] | 330 post_processors = post_processors or self.post_processors or [] |
| 304 for p in patches: | 331 for p in patches: |
| 305 logging.debug('Applying %s' % p.filename) | 332 stdout = [] |
| 306 try: | 333 try: |
| 334 filepath = os.path.join(self.project_path, p.filename) |
| 307 # It is important to use credentials=False otherwise credentials could | 335 # It is important to use credentials=False otherwise credentials could |
| 308 # leak in the error message. Credentials are not necessary here for the | 336 # leak in the error message. Credentials are not necessary here for the |
| 309 # following commands anyway. | 337 # following commands anyway. |
| 310 stdout = '' | |
| 311 if p.is_delete: | 338 if p.is_delete: |
| 312 stdout += self._check_output_svn( | 339 stdout.append(self._check_output_svn( |
| 313 ['delete', p.filename, '--force'], credentials=False) | 340 ['delete', p.filename, '--force'], credentials=False)) |
| 341 stdout.append('Deleted.') |
| 314 else: | 342 else: |
| 315 # svn add while creating directories otherwise svn add on the | 343 # svn add while creating directories otherwise svn add on the |
| 316 # contained files will silently fail. | 344 # contained files will silently fail. |
| 317 # First, find the root directory that exists. | 345 # First, find the root directory that exists. |
| 318 dirname = os.path.dirname(p.filename) | 346 dirname = os.path.dirname(p.filename) |
| 319 dirs_to_create = [] | 347 dirs_to_create = [] |
| 320 while (dirname and | 348 while (dirname and |
| 321 not os.path.isdir(os.path.join(self.project_path, dirname))): | 349 not os.path.isdir(os.path.join(self.project_path, dirname))): |
| 322 dirs_to_create.append(dirname) | 350 dirs_to_create.append(dirname) |
| 323 dirname = os.path.dirname(dirname) | 351 dirname = os.path.dirname(dirname) |
| 324 for dir_to_create in reversed(dirs_to_create): | 352 for dir_to_create in reversed(dirs_to_create): |
| 325 os.mkdir(os.path.join(self.project_path, dir_to_create)) | 353 os.mkdir(os.path.join(self.project_path, dir_to_create)) |
| 326 stdout += self._check_output_svn( | 354 stdout.append( |
| 327 ['add', dir_to_create, '--force'], credentials=False) | 355 self._check_output_svn( |
| 356 ['add', dir_to_create, '--force'], credentials=False)) |
| 357 stdout.append('Created missing directory %s.' % dir_to_create) |
| 328 | 358 |
| 329 filepath = os.path.join(self.project_path, p.filename) | |
| 330 if p.is_binary: | 359 if p.is_binary: |
| 360 content = p.get() |
| 331 with open(filepath, 'wb') as f: | 361 with open(filepath, 'wb') as f: |
| 332 f.write(p.get()) | 362 f.write(content) |
| 363 stdout.append('Added binary file %d bytes.' % len(content)) |
| 333 else: | 364 else: |
| 334 if p.source_filename: | 365 if p.source_filename: |
| 335 if not p.is_new: | 366 if not p.is_new: |
| 336 raise PatchApplicationFailed( | 367 raise PatchApplicationFailed( |
| 337 p, | 368 p, |
| 338 'File has a source filename specified but is not new') | 369 'File has a source filename specified but is not new') |
| 339 # Copy the file first. | 370 # Copy the file first. |
| 340 if os.path.isfile(filepath): | 371 if os.path.isfile(filepath): |
| 341 raise PatchApplicationFailed( | 372 raise PatchApplicationFailed( |
| 342 p, 'File exist but was about to be overwriten') | 373 p, 'File exist but was about to be overwriten') |
| 343 self._check_output_svn( | 374 stdout.append( |
| 344 ['copy', p.source_filename, p.filename]) | 375 self._check_output_svn( |
| 376 ['copy', p.source_filename, p.filename])) |
| 377 stdout.append('Copied %s -> %s' % (p.source_filename, p.filename)) |
| 345 if p.diff_hunks: | 378 if p.diff_hunks: |
| 346 cmd = [ | 379 cmd = [ |
| 347 'patch', | 380 'patch', |
| 348 '-p%s' % p.patchlevel, | 381 '-p%s' % p.patchlevel, |
| 349 '--forward', | 382 '--forward', |
| 350 '--force', | 383 '--force', |
| 351 '--no-backup-if-mismatch', | 384 '--no-backup-if-mismatch', |
| 352 ] | 385 ] |
| 353 stdout += subprocess2.check_output( | 386 stdout.append( |
| 354 cmd, stdin=p.get(False), cwd=self.project_path) | 387 subprocess2.check_output( |
| 388 cmd, stdin=p.get(False), cwd=self.project_path)) |
| 355 elif p.is_new and not os.path.exists(filepath): | 389 elif p.is_new and not os.path.exists(filepath): |
| 356 # There is only a header. Just create the file if it doesn't | 390 # There is only a header. Just create the file if it doesn't |
| 357 # exist. | 391 # exist. |
| 358 open(filepath, 'w').close() | 392 open(filepath, 'w').close() |
| 393 stdout.append('Created an empty file.') |
| 359 if p.is_new and not p.source_filename: | 394 if p.is_new and not p.source_filename: |
| 360 # Do not run it if p.source_filename is defined, since svn copy was | 395 # Do not run it if p.source_filename is defined, since svn copy was |
| 361 # using above. | 396 # using above. |
| 362 stdout += self._check_output_svn( | 397 stdout.append( |
| 363 ['add', p.filename, '--force'], credentials=False) | 398 self._check_output_svn( |
| 399 ['add', p.filename, '--force'], credentials=False)) |
| 364 for name, value in p.svn_properties: | 400 for name, value in p.svn_properties: |
| 365 if value is None: | 401 if value is None: |
| 366 stdout += self._check_output_svn( | 402 stdout.append( |
| 367 ['propdel', '--quiet', name, p.filename], credentials=False) | 403 self._check_output_svn( |
| 404 ['propdel', '--quiet', name, p.filename], |
| 405 credentials=False)) |
| 406 stdout.append('Property %s deleted.' % name) |
| 368 else: | 407 else: |
| 369 stdout += self._check_output_svn( | 408 stdout.append( |
| 370 ['propset', name, value, p.filename], credentials=False) | 409 self._check_output_svn( |
| 410 ['propset', name, value, p.filename], credentials=False)) |
| 411 stdout.append('Property %s=%s' % (name, value)) |
| 371 for prop, values in self.svn_config.auto_props.iteritems(): | 412 for prop, values in self.svn_config.auto_props.iteritems(): |
| 372 if fnmatch.fnmatch(p.filename, prop): | 413 if fnmatch.fnmatch(p.filename, prop): |
| 373 for value in values.split(';'): | 414 for value in values.split(';'): |
| 374 if '=' not in value: | 415 if '=' not in value: |
| 375 params = [value, '.'] | 416 params = [value, '.'] |
| 376 else: | 417 else: |
| 377 params = value.split('=', 1) | 418 params = value.split('=', 1) |
| 378 if params[1] == '*': | 419 if params[1] == '*': |
| 379 # Works around crbug.com/150960 on Windows. | 420 # Works around crbug.com/150960 on Windows. |
| 380 params[1] = '.' | 421 params[1] = '.' |
| 381 stdout += self._check_output_svn( | 422 stdout.append( |
| 382 ['propset'] + params + [p.filename], credentials=False) | 423 self._check_output_svn( |
| 424 ['propset'] + params + [p.filename], credentials=False)) |
| 425 stdout.append('Property (auto) %s' % '='.join(params)) |
| 383 for post in post_processors: | 426 for post in post_processors: |
| 384 post(self, p) | 427 post(self, p) |
| 428 if verbose: |
| 429 print p.filename |
| 430 print align_stdout(stdout) |
| 385 except OSError, e: | 431 except OSError, e: |
| 386 raise PatchApplicationFailed(p, '%s%s' % (stdout, e)) | 432 raise PatchApplicationFailed(p, '%s%s' % (align_stdout(stdout), e)) |
| 387 except subprocess.CalledProcessError, e: | 433 except subprocess.CalledProcessError, e: |
| 388 raise PatchApplicationFailed( | 434 raise PatchApplicationFailed( |
| 389 p, | 435 p, |
| 390 'While running %s;\n%s%s' % ( | 436 'While running %s;\n%s%s' % ( |
| 391 ' '.join(e.cmd), stdout, getattr(e, 'stdout', ''))) | 437 ' '.join(e.cmd), |
| 438 align_stdout(stdout), |
| 439 align_stdout([getattr(e, 'stdout', '')]))) |
| 392 | 440 |
| 393 def commit(self, commit_message, user): | 441 def commit(self, commit_message, user): |
| 394 logging.info('Committing patch for %s' % user) | 442 logging.info('Committing patch for %s' % user) |
| 395 assert self.commit_user | 443 assert self.commit_user |
| 396 assert isinstance(commit_message, unicode) | 444 assert isinstance(commit_message, unicode) |
| 397 handle, commit_filename = tempfile.mkstemp(text=True) | 445 handle, commit_filename = tempfile.mkstemp(text=True) |
| 398 try: | 446 try: |
| 399 # Shouldn't assume default encoding is UTF-8. But really, if you are using | 447 # Shouldn't assume default encoding is UTF-8. But really, if you are using |
| 400 # anything else, you are living in another world. | 448 # anything else, you are living in another world. |
| 401 os.write(handle, commit_message.encode('utf-8')) | 449 os.write(handle, commit_message.encode('utf-8')) |
| (...skipping 90 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 492 revision = self._check_output_git(['rev-parse', revision]) | 540 revision = self._check_output_git(['rev-parse', revision]) |
| 493 self._check_call_git(['checkout', '--force', '--quiet', revision]) | 541 self._check_call_git(['checkout', '--force', '--quiet', revision]) |
| 494 else: | 542 else: |
| 495 branches, active = self._branches() | 543 branches, active = self._branches() |
| 496 if active != 'master': | 544 if active != 'master': |
| 497 self._check_call_git(['checkout', '--force', '--quiet', 'master']) | 545 self._check_call_git(['checkout', '--force', '--quiet', 'master']) |
| 498 self._check_call_git(['pull', self.remote, self.remote_branch, '--quiet']) | 546 self._check_call_git(['pull', self.remote, self.remote_branch, '--quiet']) |
| 499 if self.working_branch in branches: | 547 if self.working_branch in branches: |
| 500 self._call_git(['branch', '-D', self.working_branch]) | 548 self._call_git(['branch', '-D', self.working_branch]) |
| 501 | 549 |
| 502 def apply_patch(self, patches, post_processors=None): | 550 def apply_patch(self, patches, post_processors=None, verbose=False): |
| 503 """Applies a patch on 'working_branch' and switch to it. | 551 """Applies a patch on 'working_branch' and switch to it. |
| 504 | 552 |
| 505 Also commits the changes on the local branch. | 553 Also commits the changes on the local branch. |
| 506 | 554 |
| 507 Ignores svn properties and raise an exception on unexpected ones. | 555 Ignores svn properties and raise an exception on unexpected ones. |
| 508 """ | 556 """ |
| 509 post_processors = post_processors or self.post_processors or [] | 557 post_processors = post_processors or self.post_processors or [] |
| 510 # It this throws, the checkout is corrupted. Maybe worth deleting it and | 558 # It this throws, the checkout is corrupted. Maybe worth deleting it and |
| 511 # trying again? | 559 # trying again? |
| 512 if self.remote_branch: | 560 if self.remote_branch: |
| 513 self._check_call_git( | 561 self._check_call_git( |
| 514 ['checkout', '-b', self.working_branch, | 562 ['checkout', '-b', self.working_branch, |
| 515 '%s/%s' % (self.remote, self.remote_branch), '--quiet']) | 563 '%s/%s' % (self.remote, self.remote_branch), '--quiet']) |
| 516 for index, p in enumerate(patches): | 564 for index, p in enumerate(patches): |
| 517 logging.debug('Applying %s' % p.filename) | 565 stdout = [] |
| 518 try: | 566 try: |
| 519 stdout = '' | 567 filepath = os.path.join(self.project_path, p.filename) |
| 520 if p.is_delete: | 568 if p.is_delete: |
| 521 if (not os.path.exists(p.filename) and | 569 if (not os.path.exists(filepath) and |
| 522 any(p1.source_filename == p.filename for p1 in patches[0:index])): | 570 any(p1.source_filename == p.filename for p1 in patches[0:index])): |
| 523 # The file could already be deleted if a prior patch with file | 571 # The file was already deleted if a prior patch with file rename |
| 524 # rename was already processed. To be sure, look at all the previous | 572 # was already processed because 'git apply' did it for us. |
| 525 # patches to see if they were a file rename. | |
| 526 pass | 573 pass |
| 527 else: | 574 else: |
| 528 stdout += self._check_output_git(['rm', p.filename]) | 575 stdout.append(self._check_output_git(['rm', p.filename])) |
| 576 stdout.append('Deleted.') |
| 529 else: | 577 else: |
| 530 dirname = os.path.dirname(p.filename) | 578 dirname = os.path.dirname(p.filename) |
| 531 full_dir = os.path.join(self.project_path, dirname) | 579 full_dir = os.path.join(self.project_path, dirname) |
| 532 if dirname and not os.path.isdir(full_dir): | 580 if dirname and not os.path.isdir(full_dir): |
| 533 os.makedirs(full_dir) | 581 os.makedirs(full_dir) |
| 582 stdout.append('Created missing directory %s.' % dirname) |
| 534 if p.is_binary: | 583 if p.is_binary: |
| 535 with open(os.path.join(self.project_path, p.filename), 'wb') as f: | 584 content = p.get() |
| 536 f.write(p.get()) | 585 with open(filepath, 'wb') as f: |
| 537 stdout += self._check_output_git(['add', p.filename]) | 586 f.write(content) |
| 587 stdout.append('Added binary file %d bytes' % len(content)) |
| 588 cmd = ['add', p.filename] |
| 589 if verbose: |
| 590 cmd.append('--verbose') |
| 591 stdout.append(self._check_output_git(cmd)) |
| 538 else: | 592 else: |
| 539 # No need to do anything special with p.is_new or if not | 593 # No need to do anything special with p.is_new or if not |
| 540 # p.diff_hunks. git apply manages all that already. | 594 # p.diff_hunks. git apply manages all that already. |
| 541 stdout += self._check_output_git( | 595 cmd = ['apply', '--index', '-p%s' % p.patchlevel] |
| 542 ['apply', '--index', '-p%s' % p.patchlevel], stdin=p.get(True)) | 596 if verbose: |
| 543 for name, _ in p.svn_properties: | 597 cmd.append('--verbose') |
| 598 stdout.append(self._check_output_git(cmd, stdin=p.get(True))) |
| 599 for name, value in p.svn_properties: |
| 544 # Ignore some known auto-props flags through .subversion/config, | 600 # Ignore some known auto-props flags through .subversion/config, |
| 545 # bails out on the other ones. | 601 # bails out on the other ones. |
| 546 # TODO(maruel): Read ~/.subversion/config and detect the rules that | 602 # TODO(maruel): Read ~/.subversion/config and detect the rules that |
| 547 # applies here to figure out if the property will be correctly | 603 # applies here to figure out if the property will be correctly |
| 548 # handled. | 604 # handled. |
| 605 stdout.append('Property %s=%s' % (name, value)) |
| 549 if not name in ( | 606 if not name in ( |
| 550 'svn:eol-style', 'svn:executable', 'svn:mime-type'): | 607 'svn:eol-style', 'svn:executable', 'svn:mime-type'): |
| 551 raise patch.UnsupportedPatchFormat( | 608 raise patch.UnsupportedPatchFormat( |
| 552 p.filename, | 609 p.filename, |
| 553 'Cannot apply svn property %s to file %s.' % ( | 610 'Cannot apply svn property %s to file %s.' % ( |
| 554 name, p.filename)) | 611 name, p.filename)) |
| 555 for post in post_processors: | 612 for post in post_processors: |
| 556 post(self, p) | 613 post(self, p) |
| 614 if verbose: |
| 615 print p.filename |
| 616 print align_stdout(stdout) |
| 557 except OSError, e: | 617 except OSError, e: |
| 558 raise PatchApplicationFailed(p, '%s%s' % (stdout, e)) | 618 raise PatchApplicationFailed(p, '%s%s' % (align_stdout(stdout), e)) |
| 559 except subprocess.CalledProcessError, e: | 619 except subprocess.CalledProcessError, e: |
| 560 raise PatchApplicationFailed( | 620 raise PatchApplicationFailed( |
| 561 p, '%s%s' % (stdout, getattr(e, 'stdout', None))) | 621 p, |
| 622 'While running %s;\n%s%s' % ( |
| 623 ' '.join(e.cmd), |
| 624 align_stdout(stdout), |
| 625 align_stdout([getattr(e, 'stdout', '')]))) |
| 562 # Once all the patches are processed and added to the index, commit the | 626 # Once all the patches are processed and added to the index, commit the |
| 563 # index. | 627 # index. |
| 564 self._check_call_git(['commit', '-m', 'Committed patch']) | 628 cmd = ['commit', '-m', 'Committed patch'] |
| 629 if verbose: |
| 630 cmd.append('--verbose') |
| 631 self._check_call_git(cmd) |
| 565 # TODO(maruel): Weirdly enough they don't match, need to investigate. | 632 # TODO(maruel): Weirdly enough they don't match, need to investigate. |
| 566 #found_files = self._check_output_git( | 633 #found_files = self._check_output_git( |
| 567 # ['diff', 'master', '--name-only']).splitlines(False) | 634 # ['diff', 'master', '--name-only']).splitlines(False) |
| 568 #assert sorted(patches.filenames) == sorted(found_files), ( | 635 #assert sorted(patches.filenames) == sorted(found_files), ( |
| 569 # sorted(out), sorted(found_files)) | 636 # sorted(out), sorted(found_files)) |
| 570 | 637 |
| 571 def commit(self, commit_message, user): | 638 def commit(self, commit_message, user): |
| 572 """Updates the commit message. | 639 """Updates the commit message. |
| 573 | 640 |
| 574 Subclass needs to dcommit or push. | 641 Subclass needs to dcommit or push. |
| (...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 636 self.checkout = checkout | 703 self.checkout = checkout |
| 637 self.post_processors = (post_processors or []) + ( | 704 self.post_processors = (post_processors or []) + ( |
| 638 self.checkout.post_processors or []) | 705 self.checkout.post_processors or []) |
| 639 | 706 |
| 640 def prepare(self, revision): | 707 def prepare(self, revision): |
| 641 return self.checkout.prepare(revision) | 708 return self.checkout.prepare(revision) |
| 642 | 709 |
| 643 def get_settings(self, key): | 710 def get_settings(self, key): |
| 644 return self.checkout.get_settings(key) | 711 return self.checkout.get_settings(key) |
| 645 | 712 |
| 646 def apply_patch(self, patches, post_processors=None): | 713 def apply_patch(self, patches, post_processors=None, verbose=False): |
| 647 return self.checkout.apply_patch( | 714 return self.checkout.apply_patch( |
| 648 patches, post_processors or self.post_processors) | 715 patches, post_processors or self.post_processors, verbose) |
| 649 | 716 |
| 650 def commit(self, message, user): # pylint: disable=R0201 | 717 def commit(self, message, user): # pylint: disable=R0201 |
| 651 logging.info('Would have committed for %s with message: %s' % ( | 718 logging.info('Would have committed for %s with message: %s' % ( |
| 652 user, message)) | 719 user, message)) |
| 653 return 'FAKE' | 720 return 'FAKE' |
| 654 | 721 |
| 655 def revisions(self, rev1, rev2): | 722 def revisions(self, rev1, rev2): |
| 656 return self.checkout.revisions(rev1, rev2) | 723 return self.checkout.revisions(rev1, rev2) |
| 657 | 724 |
| 658 @property | 725 @property |
| 659 def project_name(self): | 726 def project_name(self): |
| 660 return self.checkout.project_name | 727 return self.checkout.project_name |
| 661 | 728 |
| 662 @property | 729 @property |
| 663 def project_path(self): | 730 def project_path(self): |
| 664 return self.checkout.project_path | 731 return self.checkout.project_path |
| OLD | NEW |