| 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 |