| 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 110 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 121 def __init__(self, root_dir, project_name, remote_branch): | 121 def __init__(self, root_dir, project_name, remote_branch): |
| 122 super(GitCheckoutBase, self).__init__(root_dir, project_name) | 122 super(GitCheckoutBase, self).__init__(root_dir, project_name) |
| 123 # There is no reason to not hardcode it. | 123 # There is no reason to not hardcode it. |
| 124 self.remote = 'origin' | 124 self.remote = 'origin' |
| 125 self.remote_branch = remote_branch | 125 self.remote_branch = remote_branch |
| 126 self.working_branch = 'working_branch' | 126 self.working_branch = 'working_branch' |
| 127 | 127 |
| 128 def prepare(self): | 128 def prepare(self): |
| 129 """Resets the git repository in a clean state.""" | 129 """Resets the git repository in a clean state.""" |
| 130 assert os.path.isdir(self.project_path) | 130 assert os.path.isdir(self.project_path) |
| 131 self.check_call(['checkout', 'master', '--force']) | 131 self._check_call_git(['checkout', 'master', '--force']) |
| 132 self.check_call(['pull', self.remote, self.remote_branch]) | 132 self._check_call_git(['pull', self.remote, self.remote_branch]) |
| 133 self.call(['branch', '-D', self.working_branch]) | 133 self._call_git(['branch', '-D', self.working_branch]) |
| 134 | 134 |
| 135 def apply_patch(self, patch_data): | 135 def apply_patch(self, patch_data): |
| 136 """Applies a patch on 'working_branch'.""" | 136 """Applies a patch on 'working_branch'.""" |
| 137 self.check_call( | 137 self._check_call_git( |
| 138 ['checkout', '-b', self.working_branch, | 138 ['checkout', '-b', self.working_branch, |
| 139 '%s/%s' % (self.remote, self.remote_branch)]) | 139 '%s/%s' % (self.remote, self.remote_branch)]) |
| 140 self.check_call(['apply', '--index', '-p0'], stdin=patch_data) | 140 self._check_call_git(['apply', '--index', '-p0'], stdin=patch_data) |
| 141 self.check_call(['commit', '-m', 'Committed patch']) | 141 self._check_call_git(['commit', '-m', 'Committed patch']) |
| 142 | 142 |
| 143 def commit(self, commit_message, user): | 143 def commit(self, commit_message, user): |
| 144 self.check_call(['commit', '--amend', '-m', commit_message]) | 144 self._check_call_git(['commit', '--amend', '-m', commit_message]) |
| 145 | 145 |
| 146 def check_call(self, args, **kwargs): | 146 def _check_call_git(self, args, **kwargs): |
| 147 kwargs.setdefault('cwd', self.project_path) | 147 kwargs.setdefault('cwd', self.project_path) |
| 148 return subprocess2.check_call(['git'] + args, **kwargs) | 148 return subprocess2.check_call(['git'] + args, **kwargs) |
| 149 | 149 |
| 150 def call(self, args, **kwargs): | 150 def _call_git(self, args, **kwargs): |
| 151 """Like check_call but doesn't throw on failure.""" |
| 151 kwargs.setdefault('cwd', self.project_path) | 152 kwargs.setdefault('cwd', self.project_path) |
| 152 return subprocess2.call(['git'] + args, **kwargs) | 153 return subprocess2.call(['git'] + args, **kwargs) |
| 153 | 154 |
| 154 def capture(self, args, **kwargs): | 155 def _check_capture_git(self, args, **kwargs): |
| 155 kwargs.setdefault('cwd', self.project_path) | 156 kwargs.setdefault('cwd', self.project_path) |
| 156 return subprocess2.capture(['git'] + args, **kwargs) | 157 return subprocess2.check_capture(['git'] + args, **kwargs) |
| 157 | 158 |
| 158 | 159 |
| 159 class GitSvnCheckoutBase(GitCheckoutBase): | 160 class GitSvnCheckoutBase(GitCheckoutBase): |
| 160 """Base class for git-svn checkout. Not to be used as-is.""" | 161 """Base class for git-svn checkout. Not to be used as-is.""" |
| 161 def __init__(self, | 162 def __init__(self, |
| 162 root_dir, project_name, remote_branch, | 163 root_dir, project_name, remote_branch, |
| 163 commit_user, commit_pwd, | 164 commit_user, commit_pwd, |
| 164 svn_repo, trunk): | 165 svn_repo, trunk): |
| 165 """trunk is optional.""" | 166 """trunk is optional.""" |
| 166 super(GitSvnCheckoutBase, self).__init__( | 167 super(GitSvnCheckoutBase, self).__init__( |
| 167 root_dir, project_name + '.git', remote_branch) | 168 root_dir, project_name + '.git', remote_branch) |
| 168 self.commit_user = commit_user | 169 self.commit_user = commit_user |
| 169 self.commit_pwd = commit_pwd | 170 self.commit_pwd = commit_pwd |
| 170 self.svn_repo = svn_repo | 171 self.svn_repo = svn_repo |
| 171 self.trunk = trunk | 172 self.trunk = trunk |
| 172 | 173 |
| 173 def prepare(self): | 174 def prepare(self): |
| 174 """Resets the git repository in a clean state.""" | 175 """Resets the git repository in a clean state.""" |
| 175 self.check_call(['checkout', 'master', '--force']) | 176 self._check_call_git(['checkout', 'master', '--force']) |
| 176 self.check_call(['svn', 'rebase']) | 177 self._check_call_git(['svn', 'rebase']) |
| 177 self.call(['branch', '-D', self.working_branch]) | 178 self._call_git(['branch', '-D', self.working_branch]) |
| 178 return int(self.svn_info('revision')) | 179 return int(self._git_svn_info('revision')) |
| 179 | 180 |
| 180 def svn_info(self, key): | 181 def _git_svn_info(self, key): |
| 181 return svn_utils.parse_svn_info(self.capture(['svn', 'info']), key) | 182 return svn_utils.parse_svn_info( |
| 183 self._check_capture_git(['svn', 'info']), key) |
| 182 | 184 |
| 183 def commit(self, commit_message, user): | 185 def commit(self, commit_message, user): |
| 184 """Commits a patch.""" | 186 """Commits a patch.""" |
| 185 logging.info('Committing patch for %s' % user) | 187 logging.info('Committing patch for %s' % user) |
| 186 # Fix the commit message. | 188 # Fix the commit message. |
| 187 super(GitSvnCheckoutBase, self).commit(commit_message, user) | 189 super(GitSvnCheckoutBase, self).commit(commit_message, user) |
| 188 # Commit with git svn dcommit, then use svn directly to update the | 190 # Commit with git svn dcommit, then use svn directly to update the |
| 189 # committer on the revision. | 191 # committer on the revision. |
| 190 self._call_git_svn(['dcommit', '--rmdir', '--find-copies-harder']) | 192 self._check_call_git_svn(['dcommit', '--rmdir', '--find-copies-harder']) |
| 191 revision = int(self.svn_info('revision')) | 193 revision = int(self._git_svn_info('revision')) |
| 192 # Fix the committer. | 194 # Fix the committer. |
| 193 svn_utils.update_committer( | 195 svn_utils.update_committer( |
| 194 self.svn_repo, revision, self.commit_user, self.commit_pwd, user, | 196 self.svn_repo, revision, self.commit_user, self.commit_pwd, user, |
| 195 self.project_path) | 197 self.project_path) |
| 196 return revision | 198 return revision |
| 197 | 199 |
| 198 def _cache_svn_auth(self): | 200 def _cache_svn_auth(self): |
| 199 """Caches the svn credentials. It is necessary since git-svn doesn't prompt | 201 """Caches the svn credentials. It is necessary since git-svn doesn't prompt |
| 200 for it.""" | 202 for it.""" |
| 201 if not self.commit_user: | 203 if not self.commit_user: |
| 202 return | 204 return |
| 203 logging.info('Caching svn credentials for %s' % self.commit_user) | 205 logging.info('Caching svn credentials for %s' % self.commit_user) |
| 204 subprocess2.check_call( | 206 subprocess2.check_call( |
| 205 ['svn', 'ls', self.svn_repo, | 207 ['svn', 'ls', self.svn_repo, |
| 206 '--username', self.commit_user, | 208 '--username', self.commit_user, |
| 207 '--password', self.commit_pwd, | 209 '--password', self.commit_pwd, |
| 208 '--non-interactive']) | 210 '--non-interactive']) |
| 209 | 211 |
| 210 def _call_git_svn(self, args, **kwargs): | 212 def _check_call_git_svn(self, args, **kwargs): |
| 211 """Handles svn authentication while calling git svn.""" | 213 """Handles svn authentication while calling git svn.""" |
| 212 args = ['svn'] + args | 214 args = ['svn'] + args |
| 213 if self.commit_user: | 215 if self.commit_user: |
| 214 args = args + ['--username', self.commit_user, '--no-auth-cache'] | 216 args = args + ['--username', self.commit_user, '--no-auth-cache'] |
| 215 return self.check_call(args, stdin=self.commit_pwd, **kwargs) | 217 return self._check_call_git(args, stdin=self.commit_pwd, **kwargs) |
| 216 | 218 |
| 217 | 219 |
| 218 class GitSvnPremadeCheckout(GitSvnCheckoutBase): | 220 class GitSvnPremadeCheckout(GitSvnCheckoutBase): |
| 219 """Manages a git-svn clone made out from an initial git-svn seed. | 221 """Manages a git-svn clone made out from an initial git-svn seed. |
| 220 | 222 |
| 221 This class is very similar to GitSvnCheckout but is faster to bootstrap | 223 This class is very similar to GitSvnCheckout but is faster to bootstrap |
| 222 because it starts right off with an existing git-svn clone. | 224 because it starts right off with an existing git-svn clone. |
| 223 """ | 225 """ |
| 224 def __init__(self, | 226 def __init__(self, |
| 225 root_dir, project_name, remote_branch, | 227 root_dir, project_name, remote_branch, |
| 226 commit_user, commit_pwd, | 228 commit_user, commit_pwd, |
| 227 svn_repo, trunk, git_url): | 229 svn_repo, trunk, git_url): |
| 228 super(GitSvnPremadeCheckout, self).__init__( | 230 super(GitSvnPremadeCheckout, self).__init__( |
| 229 root_dir, project_name, remote_branch, | 231 root_dir, project_name, remote_branch, |
| 230 commit_user, commit_pwd, | 232 commit_user, commit_pwd, |
| 231 svn_repo, trunk) | 233 svn_repo, trunk) |
| 232 self.git_url = git_url | 234 self.git_url = git_url |
| 233 | 235 |
| 234 def prepare(self): | 236 def prepare(self): |
| 235 """Creates the initial checkout for the repo.""" | 237 """Creates the initial checkout for the repo.""" |
| 236 if not os.path.isdir(self.project_path): | 238 if not os.path.isdir(self.project_path): |
| 237 logging.info('Checking out %s in %s' % | 239 logging.info('Checking out %s in %s' % |
| 238 (self.project_name, self.project_path)) | 240 (self.project_name, self.project_path)) |
| 239 assert self.remote == 'origin' | 241 assert self.remote == 'origin' |
| 240 self.check_call( | 242 self._check_call_git( |
| 241 ['clone', self.git_url, self.project_name], | 243 ['clone', self.git_url, self.project_name], |
| 242 cwd=self.root_dir) | 244 cwd=self.root_dir) |
| 243 self.check_call( | 245 self._check_call_git( |
| 244 ['svn', 'init', | 246 ['svn', 'init', |
| 245 '--prefix', self.remote + '/', | 247 '--prefix', self.remote + '/', |
| 246 '-T', self.trunk, | 248 '-T', self.trunk, |
| 247 self.svn_repo]) | 249 self.svn_repo]) |
| 248 self._call_git_svn(['fetch']) | 250 self._check_call_git_svn(['fetch']) |
| 249 super(GitSvnPremadeCheckout, self).prepare() | 251 super(GitSvnPremadeCheckout, self).prepare() |
| 250 return int(self.svn_info('revision')) | 252 return int(self._git_svn_info('revision')) |
| 251 | 253 |
| 252 | 254 |
| 253 class GitSvnCheckout(GitSvnCheckoutBase): | 255 class GitSvnCheckout(GitSvnCheckoutBase): |
| 254 """Manages a git-svn clone. | 256 """Manages a git-svn clone. |
| 255 | 257 |
| 256 Using git-svn hides some of the complexity of using a svn checkout. | 258 Using git-svn hides some of the complexity of using a svn checkout. |
| 257 """ | 259 """ |
| 258 def __init__(self, | 260 def __init__(self, |
| 259 root_dir, project_name, | 261 root_dir, project_name, |
| 260 commit_user, commit_pwd, | 262 commit_user, commit_pwd, |
| 261 svn_repo, trunk): | 263 svn_repo, trunk): |
| 262 super(GitSvnCheckout, self).__init__( | 264 super(GitSvnCheckout, self).__init__( |
| 263 root_dir, project_name, 'trunk', | 265 root_dir, project_name, 'trunk', |
| 264 commit_user, commit_pwd, | 266 commit_user, commit_pwd, |
| 265 svn_repo, trunk) | 267 svn_repo, trunk) |
| 266 | 268 |
| 267 def prepare(self): | 269 def prepare(self): |
| 268 """Creates the initial checkout for the repo.""" | 270 """Creates the initial checkout for the repo.""" |
| 269 if not os.path.isdir(self.project_path): | 271 if not os.path.isdir(self.project_path): |
| 270 logging.info('Checking out %s in %s' % | 272 logging.info('Checking out %s in %s' % |
| 271 (self.project_name, self.project_path)) | 273 (self.project_name, self.project_path)) |
| 272 # TODO: Create a shallow clone. | 274 # TODO: Create a shallow clone. |
| 273 self._call_git_svn( | 275 self._check_call_git_svn( |
| 274 ['clone', | 276 ['clone', |
| 275 '--prefix', self.remote + '/', | 277 '--prefix', self.remote + '/', |
| 276 '-T', self.trunk, | 278 '-T', self.trunk, |
| 277 self.svn_repo, self.project_path], | 279 self.svn_repo, self.project_path], |
| 278 cwd=self.root_dir) | 280 cwd=self.root_dir) |
| 279 super(GitSvnCheckout, self).prepare() | 281 super(GitSvnCheckout, self).prepare() |
| 280 return int(self.svn_info('revision')) | 282 return int(self._git_svn_info('revision')) |
| 281 | 283 |
| 282 | 284 |
| 283 class ReadOnlyCheckout(object): | 285 class ReadOnlyCheckout(object): |
| 284 """Converts a checkout into a read-only one.""" | 286 """Converts a checkout into a read-only one.""" |
| 285 def __init__(self, checkout): | 287 def __init__(self, checkout): |
| 286 self.checkout = checkout | 288 self.checkout = checkout |
| 287 | 289 |
| 288 def prepare(self): | 290 def prepare(self): |
| 289 return self.checkout.prepare() | 291 return self.checkout.prepare() |
| 290 | 292 |
| 291 def get_settings(self, key): | 293 def get_settings(self, key): |
| 292 return self.checkout.get_settings(key) | 294 return self.checkout.get_settings(key) |
| 293 | 295 |
| 294 def apply_patch(self, patch_data): | 296 def apply_patch(self, patch_data): |
| 295 return self.checkout.apply_patch(patch_data) | 297 return self.checkout.apply_patch(patch_data) |
| 296 | 298 |
| 297 def commit(self, message, user): | 299 def commit(self, message, user): |
| 298 logging.info('Would have committed for %s with message: %s' % ( | 300 logging.info('Would have committed for %s with message: %s' % ( |
| 299 user, message)) | 301 user, message)) |
| 300 return 'FAKE' | 302 return 'FAKE' |
| 301 | 303 |
| 302 @property | 304 @property |
| 303 def project_name(self): | 305 def project_name(self): |
| 304 return self.checkout.project_name | 306 return self.checkout.project_name |
| 305 | 307 |
| 306 @property | 308 @property |
| 307 def project_path(self): | 309 def project_path(self): |
| 308 return self.checkout.project_path | 310 return self.checkout.project_path |
| OLD | NEW |