OLD | NEW |
---|---|
1 # coding=utf8 | 1 # coding=utf8 |
2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2011 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 from __future__ import with_statement | 10 from __future__ import with_statement |
(...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
85 """Checks out a clean copy of the tree and removes any local modification. | 85 """Checks out a clean copy of the tree and removes any local modification. |
86 | 86 |
87 This function shouldn't throw unless the remote repository is inaccessible, | 87 This function shouldn't throw unless the remote repository is inaccessible, |
88 there is no free disk space or hard issues like that. | 88 there is no free disk space or hard issues like that. |
89 | 89 |
90 Args: | 90 Args: |
91 revision: The revision it should sync to, SCM specific. | 91 revision: The revision it should sync to, SCM specific. |
92 """ | 92 """ |
93 raise NotImplementedError() | 93 raise NotImplementedError() |
94 | 94 |
95 def apply_patch(self, patches): | 95 def apply_patch(self, patches, post_processors=None): |
96 """Applies a patch and returns the list of modified files. | 96 """Applies a patch and returns the list of modified files. |
97 | 97 |
98 This function should throw patch.UnsupportedPatchFormat or | 98 This function should throw patch.UnsupportedPatchFormat or |
99 PatchApplicationFailed when relevant. | 99 PatchApplicationFailed when relevant. |
100 | 100 |
101 Args: | 101 Args: |
102 patches: patch.PatchSet object. | 102 patches: patch.PatchSet object. |
103 """ | 103 """ |
104 raise NotImplementedError() | 104 raise NotImplementedError() |
105 | 105 |
106 def commit(self, commit_message, user): | 106 def commit(self, commit_message, user): |
107 """Commits the patch upstream, while impersonating 'user'.""" | 107 """Commits the patch upstream, while impersonating 'user'.""" |
108 raise NotImplementedError() | 108 raise NotImplementedError() |
109 | 109 |
110 | 110 |
111 class RawCheckout(CheckoutBase): | 111 class RawCheckout(CheckoutBase): |
112 """Used to apply a patch locally without any intent to commit it. | 112 """Used to apply a patch locally without any intent to commit it. |
113 | 113 |
114 To be used by the try server. | 114 To be used by the try server. |
115 """ | 115 """ |
116 def prepare(self, revision): | 116 def prepare(self, revision): |
117 """Stubbed out.""" | 117 """Stubbed out.""" |
118 pass | 118 pass |
119 | 119 |
120 def apply_patch(self, patches): | 120 def apply_patch(self, patches, post_processors=None): |
121 """Ignores svn properties.""" | 121 """Ignores svn properties.""" |
122 post_processors = post_processors or self.post_processors or [] | |
122 for p in patches: | 123 for p in patches: |
123 try: | 124 try: |
124 stdout = '' | 125 stdout = '' |
125 filename = os.path.join(self.project_path, p.filename) | 126 filename = os.path.join(self.project_path, p.filename) |
126 if p.is_delete: | 127 if p.is_delete: |
127 os.remove(filename) | 128 os.remove(filename) |
128 else: | 129 else: |
129 dirname = os.path.dirname(p.filename) | 130 dirname = os.path.dirname(p.filename) |
130 full_dir = os.path.join(self.project_path, dirname) | 131 full_dir = os.path.join(self.project_path, dirname) |
131 if dirname and not os.path.isdir(full_dir): | 132 if dirname and not os.path.isdir(full_dir): |
132 os.makedirs(full_dir) | 133 os.makedirs(full_dir) |
133 | 134 |
134 filepath = os.path.join(self.project_path, p.filename) | 135 filepath = os.path.join(self.project_path, p.filename) |
135 if p.is_binary: | 136 if p.is_binary: |
136 with open(filepath, 'wb') as f: | 137 with open(filepath, 'wb') as f: |
137 f.write(p.get()) | 138 f.write(p.get()) |
138 else: | 139 else: |
139 if p.diff_hunks: | 140 if p.diff_hunks: |
140 stdout = subprocess2.check_output( | 141 stdout = subprocess2.check_output( |
141 ['patch', '-p%s' % p.patchlevel], | 142 ['patch', '-p%s' % p.patchlevel], |
142 stdin=p.get(), | 143 stdin=p.get(), |
143 stderr=subprocess2.STDOUT, | 144 stderr=subprocess2.STDOUT, |
144 cwd=self.project_path) | 145 cwd=self.project_path) |
145 elif p.is_new and not os.path.exists(filepath): | 146 elif p.is_new and not os.path.exists(filepath): |
146 # There is only a header. Just create the file. | 147 # There is only a header. Just create the file. |
147 open(filepath, 'w').close() | 148 open(filepath, 'w').close() |
148 for post in (self.post_processors or []): | 149 for post in post_processors: |
149 post(self, p) | 150 post(self, p) |
150 except OSError, e: | 151 except OSError, e: |
151 raise PatchApplicationFailed(p.filename, '%s%s' % (stdout, e)) | 152 raise PatchApplicationFailed(p.filename, '%s%s' % (stdout, e)) |
152 except subprocess.CalledProcessError, e: | 153 except subprocess.CalledProcessError, e: |
153 raise PatchApplicationFailed( | 154 raise PatchApplicationFailed( |
154 p.filename, '%s%s' % (stdout, getattr(e, 'stdout', None))) | 155 p.filename, '%s%s' % (stdout, getattr(e, 'stdout', None))) |
155 | 156 |
156 def commit(self, commit_message, user): | 157 def commit(self, commit_message, user): |
157 """Stubbed out.""" | 158 """Stubbed out.""" |
158 raise NotImplementedError('RawCheckout can\'t commit') | 159 raise NotImplementedError('RawCheckout can\'t commit') |
(...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
254 assert bool(self.commit_user) >= bool(self.commit_pwd) | 255 assert bool(self.commit_user) >= bool(self.commit_pwd) |
255 | 256 |
256 def prepare(self, revision): | 257 def prepare(self, revision): |
257 # Will checkout if the directory is not present. | 258 # Will checkout if the directory is not present. |
258 assert self.svn_url | 259 assert self.svn_url |
259 if not os.path.isdir(self.project_path): | 260 if not os.path.isdir(self.project_path): |
260 logging.info('Checking out %s in %s' % | 261 logging.info('Checking out %s in %s' % |
261 (self.project_name, self.project_path)) | 262 (self.project_name, self.project_path)) |
262 return self._revert(revision) | 263 return self._revert(revision) |
263 | 264 |
264 def apply_patch(self, patches): | 265 def apply_patch(self, patches, post_processors=None): |
266 post_processors = post_processors or self.post_processors or [] | |
265 for p in patches: | 267 for p in patches: |
266 try: | 268 try: |
267 # It is important to use credentials=False otherwise credentials could | 269 # It is important to use credentials=False otherwise credentials could |
268 # leak in the error message. Credentials are not necessary here for the | 270 # leak in the error message. Credentials are not necessary here for the |
269 # following commands anyway. | 271 # following commands anyway. |
270 stdout = '' | 272 stdout = '' |
271 if p.is_delete: | 273 if p.is_delete: |
272 stdout += self._check_output_svn( | 274 stdout += self._check_output_svn( |
273 ['delete', p.filename, '--force'], credentials=False) | 275 ['delete', p.filename, '--force'], credentials=False) |
274 else: | 276 else: |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
307 ['propset', prop[0], prop[1], p.filename], credentials=False) | 309 ['propset', prop[0], prop[1], p.filename], credentials=False) |
308 for prop, values in self.svn_config.auto_props.iteritems(): | 310 for prop, values in self.svn_config.auto_props.iteritems(): |
309 if fnmatch.fnmatch(p.filename, prop): | 311 if fnmatch.fnmatch(p.filename, prop): |
310 for value in values.split(';'): | 312 for value in values.split(';'): |
311 if '=' not in value: | 313 if '=' not in value: |
312 params = [value, '*'] | 314 params = [value, '*'] |
313 else: | 315 else: |
314 params = value.split('=', 1) | 316 params = value.split('=', 1) |
315 stdout += self._check_output_svn( | 317 stdout += self._check_output_svn( |
316 ['propset'] + params + [p.filename], credentials=False) | 318 ['propset'] + params + [p.filename], credentials=False) |
317 for post in (self.post_processors or []): | 319 for post in post_processors: |
318 post(self, p) | 320 post(self, p) |
319 except OSError, e: | 321 except OSError, e: |
320 raise PatchApplicationFailed(p.filename, '%s%s' % (stdout, e)) | 322 raise PatchApplicationFailed(p.filename, '%s%s' % (stdout, e)) |
321 except subprocess.CalledProcessError, e: | 323 except subprocess.CalledProcessError, e: |
322 raise PatchApplicationFailed( | 324 raise PatchApplicationFailed( |
323 p.filename, | 325 p.filename, |
324 'While running %s;\n%s%s' % ( | 326 'While running %s;\n%s%s' % ( |
325 ' '.join(e.cmd), stdout, getattr(e, 'stdout', ''))) | 327 ' '.join(e.cmd), stdout, getattr(e, 'stdout', ''))) |
326 | 328 |
327 def commit(self, commit_message, user): | 329 def commit(self, commit_message, user): |
(...skipping 83 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
411 revision = self._check_output_git(['rev-parse', revision]) | 413 revision = self._check_output_git(['rev-parse', revision]) |
412 self._check_call_git(['checkout', '--force', '--quiet', revision]) | 414 self._check_call_git(['checkout', '--force', '--quiet', revision]) |
413 else: | 415 else: |
414 branches, active = self._branches() | 416 branches, active = self._branches() |
415 if active != 'master': | 417 if active != 'master': |
416 self._check_call_git(['checkout', '--force', '--quiet', 'master']) | 418 self._check_call_git(['checkout', '--force', '--quiet', 'master']) |
417 self._check_call_git(['pull', self.remote, self.remote_branch, '--quiet']) | 419 self._check_call_git(['pull', self.remote, self.remote_branch, '--quiet']) |
418 if self.working_branch in branches: | 420 if self.working_branch in branches: |
419 self._call_git(['branch', '-D', self.working_branch]) | 421 self._call_git(['branch', '-D', self.working_branch]) |
420 | 422 |
421 def apply_patch(self, patches): | 423 def apply_patch(self, patches, post_processors=None): |
422 """Applies a patch on 'working_branch' and switch to it. | 424 """Applies a patch on 'working_branch' and switch to it. |
423 | 425 |
424 Also commits the changes on the local branch. | 426 Also commits the changes on the local branch. |
425 | 427 |
426 Ignores svn properties and raise an exception on unexpected ones. | 428 Ignores svn properties and raise an exception on unexpected ones. |
427 """ | 429 """ |
430 post_processors = post_processors or self.post_processors or [] | |
428 # It this throws, the checkout is corrupted. Maybe worth deleting it and | 431 # It this throws, the checkout is corrupted. Maybe worth deleting it and |
429 # trying again? | 432 # trying again? |
430 if self.remote_branch: | 433 if self.remote_branch: |
431 self._check_call_git( | 434 self._check_call_git( |
432 ['checkout', '-b', self.working_branch, | 435 ['checkout', '-b', self.working_branch, |
433 '%s/%s' % (self.remote, self.remote_branch), '--quiet']) | 436 '%s/%s' % (self.remote, self.remote_branch), '--quiet']) |
434 for p in patches: | 437 for p in patches: |
435 try: | 438 try: |
436 stdout = '' | 439 stdout = '' |
437 if p.is_delete: | 440 if p.is_delete: |
(...skipping 16 matching lines...) Expand all Loading... | |
454 # Ignore some known auto-props flags through .subversion/config, | 457 # Ignore some known auto-props flags through .subversion/config, |
455 # bails out on the other ones. | 458 # bails out on the other ones. |
456 # TODO(maruel): Read ~/.subversion/config and detect the rules that | 459 # TODO(maruel): Read ~/.subversion/config and detect the rules that |
457 # applies here to figure out if the property will be correctly | 460 # applies here to figure out if the property will be correctly |
458 # handled. | 461 # handled. |
459 if not prop[0] in ('svn:eol-style', 'svn:executable'): | 462 if not prop[0] in ('svn:eol-style', 'svn:executable'): |
460 raise patch.UnsupportedPatchFormat( | 463 raise patch.UnsupportedPatchFormat( |
461 p.filename, | 464 p.filename, |
462 'Cannot apply svn property %s to file %s.' % ( | 465 'Cannot apply svn property %s to file %s.' % ( |
463 prop[0], p.filename)) | 466 prop[0], p.filename)) |
464 for post in (self.post_processors or []): | 467 for post in post_processors: |
465 post(self, p) | 468 post(self, p) |
466 except OSError, e: | 469 except OSError, e: |
467 raise PatchApplicationFailed(p.filename, '%s%s' % (stdout, e)) | 470 raise PatchApplicationFailed(p.filename, '%s%s' % (stdout, e)) |
468 except subprocess.CalledProcessError, e: | 471 except subprocess.CalledProcessError, e: |
469 raise PatchApplicationFailed( | 472 raise PatchApplicationFailed( |
470 p.filename, '%s%s' % (stdout, getattr(e, 'stdout', None))) | 473 p.filename, '%s%s' % (stdout, getattr(e, 'stdout', None))) |
471 # Once all the patches are processed and added to the index, commit the | 474 # Once all the patches are processed and added to the index, commit the |
472 # index. | 475 # index. |
473 self._check_call_git(['commit', '-m', 'Committed patch']) | 476 self._check_call_git(['commit', '-m', 'Committed patch']) |
474 # TODO(maruel): Weirdly enough they don't match, need to investigate. | 477 # TODO(maruel): Weirdly enough they don't match, need to investigate. |
(...skipping 211 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
686 '-T', self.trunk, | 689 '-T', self.trunk, |
687 self.svn_url, self.project_path, | 690 self.svn_url, self.project_path, |
688 '--quiet'], | 691 '--quiet'], |
689 cwd=self.root_dir, | 692 cwd=self.root_dir, |
690 stderr=subprocess2.STDOUT) | 693 stderr=subprocess2.STDOUT) |
691 return super(GitSvnCheckout, self).prepare(revision) | 694 return super(GitSvnCheckout, self).prepare(revision) |
692 | 695 |
693 | 696 |
694 class ReadOnlyCheckout(object): | 697 class ReadOnlyCheckout(object): |
695 """Converts a checkout into a read-only one.""" | 698 """Converts a checkout into a read-only one.""" |
696 def __init__(self, checkout): | 699 def __init__(self, checkout, post_processors=None): |
697 super(ReadOnlyCheckout, self).__init__() | 700 super(ReadOnlyCheckout, self).__init__() |
698 self.checkout = checkout | 701 self.checkout = checkout |
702 self.post_processors = (post_processors or []) + ( | |
703 self.checkout.post_processors or []) | |
699 | 704 |
700 def prepare(self, revision): | 705 def prepare(self, revision): |
701 return self.checkout.prepare(revision) | 706 return self.checkout.prepare(revision) |
702 | 707 |
703 def get_settings(self, key): | 708 def get_settings(self, key): |
704 return self.checkout.get_settings(key) | 709 return self.checkout.get_settings(key) |
705 | 710 |
706 def apply_patch(self, patches): | 711 def apply_patch(self, patches, post_processors=None): |
M-A Ruel
2011/09/28 19:28:07
It's mainly for this edge case.
| |
707 return self.checkout.apply_patch(patches) | 712 return self.checkout.apply_patch( |
713 patches, post_processors or self.post_processors) | |
708 | 714 |
709 def commit(self, message, user): # pylint: disable=R0201 | 715 def commit(self, message, user): # pylint: disable=R0201 |
710 logging.info('Would have committed for %s with message: %s' % ( | 716 logging.info('Would have committed for %s with message: %s' % ( |
711 user, message)) | 717 user, message)) |
712 return 'FAKE' | 718 return 'FAKE' |
713 | 719 |
714 @property | 720 @property |
715 def project_name(self): | 721 def project_name(self): |
716 return self.checkout.project_name | 722 return self.checkout.project_name |
717 | 723 |
718 @property | 724 @property |
719 def project_path(self): | 725 def project_path(self): |
720 return self.checkout.project_path | 726 return self.checkout.project_path |
OLD | NEW |