OLD | NEW |
1 # coding=utf8 | 1 # coding=utf8 |
2 # Copyright (c) 2010 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2010 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 logging | 10 import logging |
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
75 if self.commit_user: | 75 if self.commit_user: |
76 args = args + [ | 76 args = args + [ |
77 '--username', self.commit_user, '--password', self.commit_pwd] | 77 '--username', self.commit_user, '--password', self.commit_pwd] |
78 return args | 78 return args |
79 | 79 |
80 def _check_call_svn(self, args, **kwargs): | 80 def _check_call_svn(self, args, **kwargs): |
81 """Runs svn and throws an exception if the command failed.""" | 81 """Runs svn and throws an exception if the command failed.""" |
82 kwargs.setdefault('cwd', self.project_path) | 82 kwargs.setdefault('cwd', self.project_path) |
83 return subprocess2.check_call(self._add_svn_flags(args), **kwargs) | 83 return subprocess2.check_call(self._add_svn_flags(args), **kwargs) |
84 | 84 |
85 def _capture_svn(self, args, **kwargs): | 85 def _check_capture_svn(self, args, **kwargs): |
86 """Runs svn and throws an exception if the command failed. | 86 """Runs svn and throws an exception if the command failed. |
87 | 87 |
88 Returns the output. | 88 Returns the output. |
89 """ | 89 """ |
90 kwargs.setdefault('cwd', self.project_path) | 90 kwargs.setdefault('cwd', self.project_path) |
91 return subprocess2.check_capture(self._add_svn_flags(args), **kwargs) | 91 return subprocess2.check_capture(self._add_svn_flags(args), **kwargs) |
92 | 92 |
93 @staticmethod | 93 @staticmethod |
94 def _parse_svn_info(output, key): | 94 def _parse_svn_info(output, key): |
95 """Returns value for key from svn info output. | 95 """Returns value for key from svn info output. |
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
156 | 156 |
157 def commit(self, commit_message, user): | 157 def commit(self, commit_message, user): |
158 """Commits a patch.""" | 158 """Commits a patch.""" |
159 logging.info('Committing patch for %s' % user) | 159 logging.info('Committing patch for %s' % user) |
160 assert self.commit_user | 160 assert self.commit_user |
161 assert self.commit_pwd | 161 assert self.commit_pwd |
162 handle, commit_filename = tempfile.mkstemp(text=True) | 162 handle, commit_filename = tempfile.mkstemp(text=True) |
163 os.write(handle, commit_message) | 163 os.write(handle, commit_message) |
164 os.close(handle) | 164 os.close(handle) |
165 try: | 165 try: |
166 output = self._capture_svn(['commit', '--file', commit_filename]) | 166 output = self._check_capture_svn(['commit', '--file', commit_filename]) |
167 revision = re.compile( | 167 revision = re.compile( |
168 r'.*?\nCommitted revision (\d+)', | 168 r'.*?\nCommitted revision (\d+)', |
169 re.DOTALL).match(output).group(1) | 169 re.DOTALL).match(output).group(1) |
170 # Fix the committer. | 170 # Fix the committer. |
171 self._update_committer(revision, user) | 171 self._update_committer(revision, user) |
172 finally: | 172 finally: |
173 os.remove(commit_filename) | 173 os.remove(commit_filename) |
174 return int(revision) | 174 return int(revision) |
175 | 175 |
176 def _revert(self): | 176 def _revert(self): |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
212 gclient_utils.RemoveDirectory(file_path) | 212 gclient_utils.RemoveDirectory(file_path) |
213 else: | 213 else: |
214 logging.error('no idea what is %s.\nYou just found a bug in gclient' | 214 logging.error('no idea what is %s.\nYou just found a bug in gclient' |
215 ', please ping maruel@chromium.org ASAP!' % file_path) | 215 ', please ping maruel@chromium.org ASAP!' % file_path) |
216 except EnvironmentError: | 216 except EnvironmentError: |
217 logging.error('Failed to remove %s.' % file_path) | 217 logging.error('Failed to remove %s.' % file_path) |
218 | 218 |
219 # Revive files that were deleted above. | 219 # Revive files that were deleted above. |
220 self._check_call_svn(['update', '--force'] + flags) | 220 self._check_call_svn(['update', '--force'] + flags) |
221 | 221 |
222 out = self._capture_svn(['info', '.']) | 222 out = self._check_capture_svn(['info', '.']) |
223 return int(self._parse_svn_info(out, 'revision')) | 223 return int(self._parse_svn_info(out, 'revision')) |
224 | 224 |
225 | 225 |
226 class GitCheckoutBase(CheckoutBase): | 226 class GitCheckoutBase(CheckoutBase): |
227 """Base class for git checkout. Not to be used as-is.""" | 227 """Base class for git checkout. Not to be used as-is.""" |
228 def __init__(self, root_dir, project_name, remote_branch): | 228 def __init__(self, root_dir, project_name, remote_branch): |
229 super(GitCheckoutBase, self).__init__(root_dir, project_name) | 229 super(GitCheckoutBase, self).__init__(root_dir, project_name) |
230 # There is no reason to not hardcode it. | 230 # There is no reason to not hardcode it. |
231 self.remote = 'origin' | 231 self.remote = 'origin' |
232 self.remote_branch = remote_branch | 232 self.remote_branch = remote_branch |
233 self.working_branch = 'working_branch' | 233 self.working_branch = 'working_branch' |
234 assert self.remote_branch | 234 assert self.remote_branch |
235 | 235 |
236 def prepare(self): | 236 def prepare(self): |
237 """Resets the git repository in a clean state.""" | 237 """Resets the git repository in a clean state.""" |
238 assert os.path.isdir(self.project_path) | 238 assert os.path.isdir(self.project_path) |
239 self._check_call_git(['checkout', 'master', '--force']) | 239 branches, active = self._branches() |
240 self._check_call_git(['pull', self.remote, self.remote_branch]) | 240 if active != 'master': |
241 self._call_git(['branch', '-D', self.working_branch]) | 241 self._check_call_git(['checkout', 'master', '--force', '--quiet']) |
| 242 self._check_call_git(['pull', self.remote, self.remote_branch, '--quiet']) |
| 243 if self.working_branch in branches: |
| 244 self._call_git(['branch', '-D', self.working_branch]) |
242 | 245 |
243 def apply_patch(self, patch_data): | 246 def apply_patch(self, patch_data): |
244 """Applies a patch on 'working_branch'.""" | 247 """Applies a patch on 'working_branch'.""" |
245 self._check_call_git( | 248 self._check_call_git( |
246 ['checkout', '-b', self.working_branch, | 249 ['checkout', '-b', self.working_branch, |
247 '%s/%s' % (self.remote, self.remote_branch)]) | 250 '%s/%s' % (self.remote, self.remote_branch)]) |
248 self._check_call_git(['apply', '--index', '-p0'], stdin=patch_data) | 251 self._check_call_git(['apply', '--index', '-p0'], stdin=patch_data) |
249 self._check_call_git(['commit', '-m', 'Committed patch']) | 252 self._check_call_git(['commit', '-m', 'Committed patch']) |
250 | 253 |
251 def commit(self, commit_message, user): | 254 def commit(self, commit_message, user): |
252 """Updates the commit message. | 255 """Updates the commit message. |
253 | 256 |
254 Subclass needs to dcommit or push.""" | 257 Subclass needs to dcommit or push.""" |
255 self._check_call_git(['commit', '--amend', '-m', commit_message]) | 258 self._check_call_git(['commit', '--amend', '-m', commit_message]) |
256 | 259 |
257 def _check_call_git(self, args, **kwargs): | 260 def _check_call_git(self, args, **kwargs): |
258 kwargs.setdefault('cwd', self.project_path) | 261 kwargs.setdefault('cwd', self.project_path) |
259 return subprocess2.check_call(['git'] + args, **kwargs) | 262 return subprocess2.check_call(['git'] + args, **kwargs) |
260 | 263 |
261 def _call_git(self, args, **kwargs): | 264 def _call_git(self, args, **kwargs): |
262 """Like check_call but doesn't throw on failure.""" | 265 """Like check_call but doesn't throw on failure.""" |
263 kwargs.setdefault('cwd', self.project_path) | 266 kwargs.setdefault('cwd', self.project_path) |
264 return subprocess2.call(['git'] + args, **kwargs) | 267 return subprocess2.call(['git'] + args, **kwargs) |
265 | 268 |
266 def _check_capture_git(self, args, **kwargs): | 269 def _check_capture_git(self, args, **kwargs): |
267 kwargs.setdefault('cwd', self.project_path) | 270 kwargs.setdefault('cwd', self.project_path) |
268 return subprocess2.check_capture(['git'] + args, **kwargs) | 271 return subprocess2.check_capture(['git'] + args, **kwargs) |
269 | 272 |
| 273 def _branches(self): |
| 274 """Returns the list of branches and the active one.""" |
| 275 out = self._check_capture_git(['branch']).splitlines(False) |
| 276 branches = [l[2:] for l in out] |
| 277 active = None |
| 278 for l in out: |
| 279 if l.startswith('*'): |
| 280 active = l[2:] |
| 281 break |
| 282 return branches, active |
| 283 |
270 | 284 |
271 class GitSvnCheckoutBase(GitCheckoutBase, SvnMixIn): | 285 class GitSvnCheckoutBase(GitCheckoutBase, SvnMixIn): |
272 """Base class for git-svn checkout. Not to be used as-is.""" | 286 """Base class for git-svn checkout. Not to be used as-is.""" |
273 def __init__(self, | 287 def __init__(self, |
274 root_dir, project_name, remote_branch, | 288 root_dir, project_name, remote_branch, |
275 commit_user, commit_pwd, | 289 commit_user, commit_pwd, |
276 svn_url, trunk): | 290 svn_url, trunk): |
277 """trunk is optional.""" | 291 """trunk is optional.""" |
278 super(GitSvnCheckoutBase, self).__init__( | 292 super(GitSvnCheckoutBase, self).__init__( |
279 root_dir, project_name + '.git', remote_branch) | 293 root_dir, project_name + '.git', remote_branch) |
280 self.commit_user = commit_user | 294 self.commit_user = commit_user |
281 self.commit_pwd = commit_pwd | 295 self.commit_pwd = commit_pwd |
282 # svn_url in this case is the root of the svn repository. | 296 # svn_url in this case is the root of the svn repository. |
283 self.svn_url = svn_url | 297 self.svn_url = svn_url |
284 self.trunk = trunk | 298 self.trunk = trunk |
285 assert bool(self.commit_user) == bool(self.commit_pwd) | 299 assert bool(self.commit_user) == bool(self.commit_pwd) |
286 assert self.svn_url | 300 assert self.svn_url |
287 assert self.trunk | 301 assert self.trunk |
288 | 302 |
289 def prepare(self): | 303 def prepare(self): |
290 """Resets the git repository in a clean state.""" | 304 """Resets the git repository in a clean state.""" |
291 self._check_call_git(['checkout', 'master', '--force']) | 305 branches, active = self._branches() |
292 self._check_call_git_svn(['rebase']) | 306 if active != 'master': |
293 self._call_git(['branch', '-D', self.working_branch]) | 307 self._check_call_git(['checkout', 'master', '--force', '--quiet']) |
| 308 # git svn rebase --quiet --quiet doesn't work, use two steps to silence it. |
| 309 self._check_call_git_svn(['fetch']) |
| 310 self._check_call_git( |
| 311 ['rebase', '--quiet', '%s/%s' % (self.remote, self.remote_branch)]) |
| 312 if self.working_branch in branches: |
| 313 self._call_git(['branch', '-D', self.working_branch]) |
294 return int(self._git_svn_info('revision')) | 314 return int(self._git_svn_info('revision')) |
295 | 315 |
296 def _git_svn_info(self, key): | 316 def _git_svn_info(self, key): |
297 """Calls git svn info. This doesn't support nor need --config-dir.""" | 317 """Calls git svn info. This doesn't support nor need --config-dir.""" |
298 return self._parse_svn_info( | 318 return self._parse_svn_info( |
299 self._check_capture_git(['svn', 'info']), key) | 319 self._check_capture_git(['svn', 'info']), key) |
300 | 320 |
301 def commit(self, commit_message, user): | 321 def commit(self, commit_message, user): |
302 """Commits a patch.""" | 322 """Commits a patch.""" |
303 logging.info('Committing patch for %s' % user) | 323 logging.info('Committing patch for %s' % user) |
304 # Fix the commit message. | 324 # Fix the commit message. |
305 super(GitSvnCheckoutBase, self).commit(commit_message, user) | 325 super(GitSvnCheckoutBase, self).commit(commit_message, user) |
306 # Commit with git svn dcommit, then use svn directly to update the | 326 # Commit with git svn dcommit, then use svn directly to update the |
307 # committer on the revision. | 327 # committer on the revision. |
308 self._check_call_git_svn(['dcommit', '--rmdir', '--find-copies-harder']) | 328 self._check_call_git_svn(['dcommit', '--rmdir', '--find-copies-harder']) |
309 revision = int(self._git_svn_info('revision')) | 329 revision = int(self._git_svn_info('revision')) |
310 # Fix the committer. | 330 # Fix the committer. |
311 self._update_committer(revision, user) | 331 self._update_committer(revision, user) |
312 return revision | 332 return revision |
313 | 333 |
314 def _cache_svn_auth(self): | 334 def _cache_svn_auth(self): |
315 """Caches the svn credentials. It is necessary since git-svn doesn't prompt | 335 """Caches the svn credentials. It is necessary since git-svn doesn't prompt |
316 for it.""" | 336 for it.""" |
317 if not self.commit_user: | 337 if not self.commit_user: |
318 return | 338 return |
319 logging.info('Caching svn credentials for %s' % self.commit_user) | 339 # Use capture to lower noise in logs. |
320 self._check_call_svn(['ls', self.svn_url], cwd=None) | 340 self._check_capture_svn(['ls', self.svn_url], cwd=None) |
321 | 341 |
322 def _check_call_git_svn(self, args, **kwargs): | 342 def _check_call_git_svn(self, args, **kwargs): |
323 """Handles svn authentication while calling git svn.""" | 343 """Handles svn authentication while calling git svn.""" |
324 args = ['svn'] + args + ['--config-dir', self.svn_config_dir] | 344 args = ['svn'] + args + ['--config-dir', self.svn_config_dir] |
325 self._cache_svn_auth() | 345 self._cache_svn_auth() |
326 return self._check_call_git(args, **kwargs) | 346 return self._check_call_git(args, **kwargs) |
327 | 347 |
328 | 348 |
329 class GitSvnPremadeCheckout(GitSvnCheckoutBase): | 349 class GitSvnPremadeCheckout(GitSvnCheckoutBase): |
330 """Manages a git-svn clone made out from an initial git-svn seed. | 350 """Manages a git-svn clone made out from an initial git-svn seed. |
(...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
411 user, message)) | 431 user, message)) |
412 return 'FAKE' | 432 return 'FAKE' |
413 | 433 |
414 @property | 434 @property |
415 def project_name(self): | 435 def project_name(self): |
416 return self.checkout.project_name | 436 return self.checkout.project_name |
417 | 437 |
418 @property | 438 @property |
419 def project_path(self): | 439 def project_path(self): |
420 return self.checkout.project_path | 440 return self.checkout.project_path |
OLD | NEW |