| 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 |
| 11 import os | 11 import os |
| 12 import re | 12 import re |
| 13 import subprocess | 13 import subprocess |
| 14 import tempfile | 14 import tempfile |
| 15 | 15 |
| 16 import find_depot_tools # pylint: disable=W0611 |
| 17 import gclient_utils |
| 18 import scm |
| 19 |
| 16 import patch | 20 import patch |
| 17 import subprocess2 | 21 import subprocess2 |
| 18 import svn_utils | |
| 19 | 22 |
| 20 | 23 |
| 21 def get_code_review_setting(path, key, | 24 def get_code_review_setting(path, key, |
| 22 codereview_settings_file='codereview.settings'): | 25 codereview_settings_file='codereview.settings'): |
| 23 """Parses codereview.settings and return the value for the key if present. | 26 """Parses codereview.settings and return the value for the key if present. |
| 24 | 27 |
| 25 Don't cache the values in case the file is changed.""" | 28 Don't cache the values in case the file is changed.""" |
| 26 settings = {} | 29 settings = {} |
| 27 try: | 30 try: |
| 28 settings_file = open(os.path.join(path, codereview_settings_file), 'r') | 31 settings_file = open(os.path.join(path, codereview_settings_file), 'r') |
| (...skipping 16 matching lines...) Expand all Loading... |
| 45 class CheckoutBase(object): | 48 class CheckoutBase(object): |
| 46 def __init__(self, root_dir, project_name): | 49 def __init__(self, root_dir, project_name): |
| 47 self.root_dir = root_dir | 50 self.root_dir = root_dir |
| 48 self.project_name = project_name | 51 self.project_name = project_name |
| 49 self.project_path = os.path.join(self.root_dir, self.project_name) | 52 self.project_path = os.path.join(self.root_dir, self.project_name) |
| 50 | 53 |
| 51 def get_settings(self, key): | 54 def get_settings(self, key): |
| 52 return get_code_review_setting(self.project_path, key) | 55 return get_code_review_setting(self.project_path, key) |
| 53 | 56 |
| 54 | 57 |
| 55 class SvnCheckout(CheckoutBase): | 58 class SvnMixIn(object): |
| 59 """MixIn class to add svn commands common to both svn and git-svn clients.""" |
| 60 # These members need to be set by the subclass. |
| 61 commit_user = None |
| 62 commit_pwd = None |
| 63 svn_url = None |
| 64 project_path = None |
| 65 |
| 66 def _check_call_svn(self, args, **kwargs): |
| 67 """Runs svn and throws an exception if the command failed.""" |
| 68 kwargs.setdefault('cwd', self.project_path) |
| 69 return subprocess2.check_call( |
| 70 ['svn'] + args + ['--no-auth-cache', '--non-interactive'], **kwargs) |
| 71 |
| 72 def _capture_svn(self, args, **kwargs): |
| 73 """Runs svn and throws an exception if the command failed. |
| 74 |
| 75 Returns the output. |
| 76 """ |
| 77 kwargs.setdefault('cwd', self.project_path) |
| 78 if self.commit_user: |
| 79 args = args + [ |
| 80 '--username', self.commit_user, '--password', self.commit_pwd] |
| 81 cmd = ['svn'] + args + ['--no-auth-cache', '--non-interactive'] |
| 82 return subprocess2.check_capture(cmd, **kwargs) |
| 83 |
| 84 @staticmethod |
| 85 def _parse_svn_info(output, key): |
| 86 """Returns value for key from svn info output. |
| 87 |
| 88 Case insensitive. |
| 89 """ |
| 90 values = {} |
| 91 for line in output.splitlines(False): |
| 92 if not line: |
| 93 continue |
| 94 k, v = line.split(':', 1) |
| 95 k = k.strip().lower() |
| 96 v = v.strip() |
| 97 assert not k in values |
| 98 values[k] = v |
| 99 return values.get(key, None) |
| 100 |
| 101 def _update_committer(self, revision, new_author): |
| 102 """Changes the author of a commit a posteriori. |
| 103 |
| 104 This is necessary since the actual commit is done with a "commit-bot" |
| 105 credential but the original patch author needs to be assigned authorship |
| 106 of the revision. |
| 107 """ |
| 108 self._check_call_svn( |
| 109 ['propset', '--revprop', 'svn:author', |
| 110 '-r', revision, |
| 111 new_author, |
| 112 '--username', self.commit_user, |
| 113 '--password', self.commit_pwd, |
| 114 self.svn_url]) |
| 115 |
| 116 |
| 117 class SvnCheckout(CheckoutBase, SvnMixIn): |
| 56 """Manages a subversion checkout. | 118 """Manages a subversion checkout. |
| 57 | 119 |
| 58 Commit is not fully implemented yet. Reimplementing all the commands is | 120 Commit is not fully implemented yet. Reimplementing all the commands is |
| 59 slightly complex, svn add with history needs to be done with svn | 121 slightly complex, svn add with history needs to be done with svn |
| 60 copy/rename/move and the situation quickly becomes harder when directories are | 122 copy/rename/move and the situation quickly becomes harder when directories are |
| 61 moved. | 123 moved. |
| 62 """ | 124 """ |
| 63 def __init__(self, root_dir, project_name, commit_user, commit_pwd, svn_url): | 125 def __init__(self, root_dir, project_name, commit_user, commit_pwd, svn_url): |
| 64 super(SvnCheckout, self).__init__(root_dir, project_name) | 126 super(SvnCheckout, self).__init__(root_dir, project_name) |
| 65 self.commit_user = commit_user | 127 self.commit_user = commit_user |
| 66 self.commit_pwd = commit_pwd | 128 self.commit_pwd = commit_pwd |
| 67 self.svn_url = svn_url | 129 self.svn_url = svn_url |
| 68 assert bool(self.commit_user) == bool(self.commit_pwd) | 130 assert bool(self.commit_user) == bool(self.commit_pwd) |
| 69 assert bool(self.svn_url) | 131 assert bool(self.svn_url) |
| 70 | 132 |
| 71 def prepare(self): | 133 def prepare(self): |
| 72 """Creates the initial checkouts for the repo.""" | 134 """Creates the initial checkouts for the repo.""" |
| 73 # Will checkout if the directory is not present. | 135 # Will checkout if the directory is not present. |
| 74 logging.info('Checking out %s in %s' % | 136 logging.info('Checking out %s in %s' % |
| 75 (self.project_name, self.project_path)) | 137 (self.project_name, self.project_path)) |
| 76 return svn_utils.revert( | 138 return self._revert() |
| 77 self.svn_url, | |
| 78 self.project_path, | |
| 79 self.commit_user, | |
| 80 self.commit_pwd) | |
| 81 | 139 |
| 82 def apply_patch(self, patch_data): | 140 def apply_patch(self, patch_data): |
| 83 """Applies a patch.""" | 141 """Applies a patch.""" |
| 84 try: | 142 try: |
| 85 patch.apply_patch(self.project_path, patch_data) | 143 patch.apply_patch(self.project_path, patch_data) |
| 86 return True | 144 return True |
| 87 except subprocess.CalledProcessError, e: | 145 except subprocess.CalledProcessError, e: |
| 88 if e.returncode == 1: | 146 if e.returncode == 1: |
| 89 return False | 147 return False |
| 90 raise | 148 raise |
| 91 | 149 |
| 92 def commit(self, commit_message, user): | 150 def commit(self, commit_message, user): |
| 93 """Commits a patch.""" | 151 """Commits a patch.""" |
| 94 logging.info('Committing patch for %s' % user) | 152 logging.info('Committing patch for %s' % user) |
| 95 assert self.commit_user | 153 assert self.commit_user |
| 96 assert self.commit_pwd | 154 assert self.commit_pwd |
| 97 handle, commit_filename = tempfile.mkstemp(text=True) | 155 handle, commit_filename = tempfile.mkstemp(text=True) |
| 98 os.write(handle, commit_message) | 156 os.write(handle, commit_message) |
| 99 os.close(handle) | 157 os.close(handle) |
| 100 try: | 158 try: |
| 101 output = svn_utils.capture_svn([ | 159 output = self._capture_svn(['commit', '--file', commit_filename]) |
| 102 'commit', | |
| 103 '--username', self.commit_user, | |
| 104 '--password', self.commit_pwd, | |
| 105 '--file', commit_filename], | |
| 106 cwd=self.project_path) | |
| 107 revision = re.compile( | 160 revision = re.compile( |
| 108 r'.*?\nCommitted revision (\d+)', | 161 r'.*?\nCommitted revision (\d+)', |
| 109 re.DOTALL).match(output).group(1) | 162 re.DOTALL).match(output).group(1) |
| 110 # Fix the committer. | 163 # Fix the committer. |
| 111 svn_utils.update_committer( | 164 self._update_committer(revision, user) |
| 112 self.svn_url, revision, self.commit_user, self.commit_pwd, user, | |
| 113 self.project_path) | |
| 114 finally: | 165 finally: |
| 115 os.remove(commit_filename) | 166 os.remove(commit_filename) |
| 116 return int(revision) | 167 return int(revision) |
| 117 | 168 |
| 169 def _revert(self): |
| 170 """Reverts local modifications or checks out if the directory is not |
| 171 present. |
| 172 """ |
| 173 flags = ['--ignore-externals'] |
| 174 if not os.path.isdir(self.project_path): |
| 175 logging.info('Directory %s is not present, checking it out.' % |
| 176 self.project_path) |
| 177 self._check_call_svn(['checkout', self.svn_url, self.project_path] + |
| 178 flags, cwd=None) |
| 179 else: |
| 180 for file_status in scm.SVN.CaptureStatus(self.project_path): |
| 181 file_path = os.path.join(self.project_path, file_status[1]) |
| 182 if file_status[0][0] == 'X': |
| 183 # Ignore externals. |
| 184 logging.info('Ignoring external %s' % file_path) |
| 185 continue |
| 186 |
| 187 logging.info('%s%s' % (file_status[0], file_status[1])) |
| 188 |
| 189 if file_status[0].isspace(): |
| 190 raise EnvironmentError( |
| 191 'No idea what is the status of %s.\n' |
| 192 'You just found a bug in gclient, please ping ' |
| 193 'maruel@chromium.org ASAP!' % file_path) |
| 194 |
| 195 # svn revert is really stupid. It fails on inconsistent line-endings, |
| 196 # on switched directories, etc. So take no chance and delete everything! |
| 197 try: |
| 198 if not os.path.exists(file_path): |
| 199 pass |
| 200 elif os.path.isfile(file_path) or os.path.islink(file_path): |
| 201 logging.info('os.remove(%s)' % file_path) |
| 202 os.remove(file_path) |
| 203 elif os.path.isdir(file_path): |
| 204 logging.info('gclient_utils.RemoveDirectory(%s)' % file_path) |
| 205 gclient_utils.RemoveDirectory(file_path) |
| 206 else: |
| 207 logging.error('no idea what is %s.\nYou just found a bug in gclient' |
| 208 ', please ping maruel@chromium.org ASAP!' % file_path) |
| 209 except EnvironmentError: |
| 210 logging.error('Failed to remove %s.' % file_path) |
| 211 |
| 212 # Revive files that were deleted above. |
| 213 self._check_call_svn(['update', '--force'] + flags) |
| 214 |
| 215 out = self._capture_svn(['info', '.']) |
| 216 return int(self._parse_svn_info(out, 'revision')) |
| 217 |
| 118 | 218 |
| 119 class GitCheckoutBase(CheckoutBase): | 219 class GitCheckoutBase(CheckoutBase): |
| 120 """Base class for git checkout. Not to be used as-is.""" | 220 """Base class for git checkout. Not to be used as-is.""" |
| 121 def __init__(self, root_dir, project_name, remote_branch): | 221 def __init__(self, root_dir, project_name, remote_branch): |
| 122 super(GitCheckoutBase, self).__init__(root_dir, project_name) | 222 super(GitCheckoutBase, self).__init__(root_dir, project_name) |
| 123 # There is no reason to not hardcode it. | 223 # There is no reason to not hardcode it. |
| 124 self.remote = 'origin' | 224 self.remote = 'origin' |
| 125 self.remote_branch = remote_branch | 225 self.remote_branch = remote_branch |
| 126 self.working_branch = 'working_branch' | 226 self.working_branch = 'working_branch' |
| 127 | 227 |
| (...skipping 22 matching lines...) Expand all Loading... |
| 150 def _call_git(self, args, **kwargs): | 250 def _call_git(self, args, **kwargs): |
| 151 """Like check_call but doesn't throw on failure.""" | 251 """Like check_call but doesn't throw on failure.""" |
| 152 kwargs.setdefault('cwd', self.project_path) | 252 kwargs.setdefault('cwd', self.project_path) |
| 153 return subprocess2.call(['git'] + args, **kwargs) | 253 return subprocess2.call(['git'] + args, **kwargs) |
| 154 | 254 |
| 155 def _check_capture_git(self, args, **kwargs): | 255 def _check_capture_git(self, args, **kwargs): |
| 156 kwargs.setdefault('cwd', self.project_path) | 256 kwargs.setdefault('cwd', self.project_path) |
| 157 return subprocess2.check_capture(['git'] + args, **kwargs) | 257 return subprocess2.check_capture(['git'] + args, **kwargs) |
| 158 | 258 |
| 159 | 259 |
| 160 class GitSvnCheckoutBase(GitCheckoutBase): | 260 class GitSvnCheckoutBase(GitCheckoutBase, SvnMixIn): |
| 161 """Base class for git-svn checkout. Not to be used as-is.""" | 261 """Base class for git-svn checkout. Not to be used as-is.""" |
| 162 def __init__(self, | 262 def __init__(self, |
| 163 root_dir, project_name, remote_branch, | 263 root_dir, project_name, remote_branch, |
| 164 commit_user, commit_pwd, | 264 commit_user, commit_pwd, |
| 165 svn_repo, trunk): | 265 svn_url, trunk): |
| 166 """trunk is optional.""" | 266 """trunk is optional.""" |
| 167 super(GitSvnCheckoutBase, self).__init__( | 267 super(GitSvnCheckoutBase, self).__init__( |
| 168 root_dir, project_name + '.git', remote_branch) | 268 root_dir, project_name + '.git', remote_branch) |
| 169 self.commit_user = commit_user | 269 self.commit_user = commit_user |
| 170 self.commit_pwd = commit_pwd | 270 self.commit_pwd = commit_pwd |
| 171 self.svn_repo = svn_repo | 271 # svn_url in this case is the root of the svn repository. |
| 272 self.svn_url = svn_url |
| 172 self.trunk = trunk | 273 self.trunk = trunk |
| 173 | 274 |
| 174 def prepare(self): | 275 def prepare(self): |
| 175 """Resets the git repository in a clean state.""" | 276 """Resets the git repository in a clean state.""" |
| 176 self._check_call_git(['checkout', 'master', '--force']) | 277 self._check_call_git(['checkout', 'master', '--force']) |
| 177 self._check_call_git(['svn', 'rebase']) | 278 self._check_call_git(['svn', 'rebase']) |
| 178 self._call_git(['branch', '-D', self.working_branch]) | 279 self._call_git(['branch', '-D', self.working_branch]) |
| 179 return int(self._git_svn_info('revision')) | 280 return int(self._git_svn_info('revision')) |
| 180 | 281 |
| 181 def _git_svn_info(self, key): | 282 def _git_svn_info(self, key): |
| 182 return svn_utils.parse_svn_info( | 283 return self._parse_svn_info( |
| 183 self._check_capture_git(['svn', 'info']), key) | 284 self._check_capture_git(['svn', 'info']), key) |
| 184 | 285 |
| 185 def commit(self, commit_message, user): | 286 def commit(self, commit_message, user): |
| 186 """Commits a patch.""" | 287 """Commits a patch.""" |
| 187 logging.info('Committing patch for %s' % user) | 288 logging.info('Committing patch for %s' % user) |
| 188 # Fix the commit message. | 289 # Fix the commit message. |
| 189 super(GitSvnCheckoutBase, self).commit(commit_message, user) | 290 super(GitSvnCheckoutBase, self).commit(commit_message, user) |
| 190 # Commit with git svn dcommit, then use svn directly to update the | 291 # Commit with git svn dcommit, then use svn directly to update the |
| 191 # committer on the revision. | 292 # committer on the revision. |
| 192 self._check_call_git_svn(['dcommit', '--rmdir', '--find-copies-harder']) | 293 self._check_call_git_svn(['dcommit', '--rmdir', '--find-copies-harder']) |
| 193 revision = int(self._git_svn_info('revision')) | 294 revision = int(self._git_svn_info('revision')) |
| 194 # Fix the committer. | 295 # Fix the committer. |
| 195 svn_utils.update_committer( | 296 self._update_committer(revision, user) |
| 196 self.svn_repo, revision, self.commit_user, self.commit_pwd, user, | |
| 197 self.project_path) | |
| 198 return revision | 297 return revision |
| 199 | 298 |
| 200 def _cache_svn_auth(self): | 299 def _cache_svn_auth(self): |
| 201 """Caches the svn credentials. It is necessary since git-svn doesn't prompt | 300 """Caches the svn credentials. It is necessary since git-svn doesn't prompt |
| 202 for it.""" | 301 for it.""" |
| 203 if not self.commit_user: | 302 if not self.commit_user: |
| 204 return | 303 return |
| 205 logging.info('Caching svn credentials for %s' % self.commit_user) | 304 logging.info('Caching svn credentials for %s' % self.commit_user) |
| 206 subprocess2.check_call( | 305 subprocess2.check_call( |
| 207 ['svn', 'ls', self.svn_repo, | 306 ['svn', 'ls', self.svn_url, |
| 208 '--username', self.commit_user, | 307 '--username', self.commit_user, |
| 209 '--password', self.commit_pwd, | 308 '--password', self.commit_pwd, |
| 210 '--non-interactive']) | 309 '--non-interactive']) |
| 211 | 310 |
| 212 def _check_call_git_svn(self, args, **kwargs): | 311 def _check_call_git_svn(self, args, **kwargs): |
| 213 """Handles svn authentication while calling git svn.""" | 312 """Handles svn authentication while calling git svn.""" |
| 214 args = ['svn'] + args | 313 args = ['svn'] + args |
| 215 if self.commit_user: | 314 if self.commit_user: |
| 216 args = args + ['--username', self.commit_user, '--no-auth-cache'] | 315 args = args + ['--username', self.commit_user, '--no-auth-cache'] |
| 217 return self._check_call_git(args, stdin=self.commit_pwd, **kwargs) | 316 return self._check_call_git(args, stdin=self.commit_pwd, **kwargs) |
| 218 | 317 |
| 219 | 318 |
| 220 class GitSvnPremadeCheckout(GitSvnCheckoutBase): | 319 class GitSvnPremadeCheckout(GitSvnCheckoutBase): |
| 221 """Manages a git-svn clone made out from an initial git-svn seed. | 320 """Manages a git-svn clone made out from an initial git-svn seed. |
| 222 | 321 |
| 223 This class is very similar to GitSvnCheckout but is faster to bootstrap | 322 This class is very similar to GitSvnCheckout but is faster to bootstrap |
| 224 because it starts right off with an existing git-svn clone. | 323 because it starts right off with an existing git-svn clone. |
| 225 """ | 324 """ |
| 226 def __init__(self, | 325 def __init__(self, |
| 227 root_dir, project_name, remote_branch, | 326 root_dir, project_name, remote_branch, |
| 228 commit_user, commit_pwd, | 327 commit_user, commit_pwd, |
| 229 svn_repo, trunk, git_url): | 328 svn_url, trunk, git_url): |
| 230 super(GitSvnPremadeCheckout, self).__init__( | 329 super(GitSvnPremadeCheckout, self).__init__( |
| 231 root_dir, project_name, remote_branch, | 330 root_dir, project_name, remote_branch, |
| 232 commit_user, commit_pwd, | 331 commit_user, commit_pwd, |
| 233 svn_repo, trunk) | 332 svn_url, trunk) |
| 234 self.git_url = git_url | 333 self.git_url = git_url |
| 235 | 334 |
| 236 def prepare(self): | 335 def prepare(self): |
| 237 """Creates the initial checkout for the repo.""" | 336 """Creates the initial checkout for the repo.""" |
| 238 if not os.path.isdir(self.project_path): | 337 if not os.path.isdir(self.project_path): |
| 239 logging.info('Checking out %s in %s' % | 338 logging.info('Checking out %s in %s' % |
| 240 (self.project_name, self.project_path)) | 339 (self.project_name, self.project_path)) |
| 241 assert self.remote == 'origin' | 340 assert self.remote == 'origin' |
| 242 self._check_call_git( | 341 self._check_call_git( |
| 243 ['clone', self.git_url, self.project_name], | 342 ['clone', self.git_url, self.project_name], |
| 244 cwd=self.root_dir) | 343 cwd=self.root_dir) |
| 245 self._check_call_git( | 344 self._check_call_git( |
| 246 ['svn', 'init', | 345 ['svn', 'init', |
| 247 '--prefix', self.remote + '/', | 346 '--prefix', self.remote + '/', |
| 248 '-T', self.trunk, | 347 '-T', self.trunk, |
| 249 self.svn_repo]) | 348 self.svn_url]) |
| 250 self._check_call_git_svn(['fetch']) | 349 self._check_call_git_svn(['fetch']) |
| 251 super(GitSvnPremadeCheckout, self).prepare() | 350 super(GitSvnPremadeCheckout, self).prepare() |
| 252 return int(self._git_svn_info('revision')) | 351 return int(self._git_svn_info('revision')) |
| 253 | 352 |
| 254 | 353 |
| 255 class GitSvnCheckout(GitSvnCheckoutBase): | 354 class GitSvnCheckout(GitSvnCheckoutBase): |
| 256 """Manages a git-svn clone. | 355 """Manages a git-svn clone. |
| 257 | 356 |
| 258 Using git-svn hides some of the complexity of using a svn checkout. | 357 Using git-svn hides some of the complexity of using a svn checkout. |
| 259 """ | 358 """ |
| 260 def __init__(self, | 359 def __init__(self, |
| 261 root_dir, project_name, | 360 root_dir, project_name, |
| 262 commit_user, commit_pwd, | 361 commit_user, commit_pwd, |
| 263 svn_repo, trunk): | 362 svn_url, trunk): |
| 264 super(GitSvnCheckout, self).__init__( | 363 super(GitSvnCheckout, self).__init__( |
| 265 root_dir, project_name, 'trunk', | 364 root_dir, project_name, 'trunk', |
| 266 commit_user, commit_pwd, | 365 commit_user, commit_pwd, |
| 267 svn_repo, trunk) | 366 svn_url, trunk) |
| 268 | 367 |
| 269 def prepare(self): | 368 def prepare(self): |
| 270 """Creates the initial checkout for the repo.""" | 369 """Creates the initial checkout for the repo.""" |
| 271 if not os.path.isdir(self.project_path): | 370 if not os.path.isdir(self.project_path): |
| 272 logging.info('Checking out %s in %s' % | 371 logging.info('Checking out %s in %s' % |
| 273 (self.project_name, self.project_path)) | 372 (self.project_name, self.project_path)) |
| 274 # TODO: Create a shallow clone. | 373 # TODO: Create a shallow clone. |
| 275 self._check_call_git_svn( | 374 self._check_call_git_svn( |
| 276 ['clone', | 375 ['clone', |
| 277 '--prefix', self.remote + '/', | 376 '--prefix', self.remote + '/', |
| 278 '-T', self.trunk, | 377 '-T', self.trunk, |
| 279 self.svn_repo, self.project_path], | 378 self.svn_url, self.project_path], |
| 280 cwd=self.root_dir) | 379 cwd=self.root_dir) |
| 281 super(GitSvnCheckout, self).prepare() | 380 super(GitSvnCheckout, self).prepare() |
| 282 return int(self._git_svn_info('revision')) | 381 return int(self._git_svn_info('revision')) |
| 283 | 382 |
| 284 | 383 |
| 285 class ReadOnlyCheckout(object): | 384 class ReadOnlyCheckout(object): |
| 286 """Converts a checkout into a read-only one.""" | 385 """Converts a checkout into a read-only one.""" |
| 287 def __init__(self, checkout): | 386 def __init__(self, checkout): |
| 288 self.checkout = checkout | 387 self.checkout = checkout |
| 289 | 388 |
| (...skipping 11 matching lines...) Expand all Loading... |
| 301 user, message)) | 400 user, message)) |
| 302 return 'FAKE' | 401 return 'FAKE' |
| 303 | 402 |
| 304 @property | 403 @property |
| 305 def project_name(self): | 404 def project_name(self): |
| 306 return self.checkout.project_name | 405 return self.checkout.project_name |
| 307 | 406 |
| 308 @property | 407 @property |
| 309 def project_path(self): | 408 def project_path(self): |
| 310 return self.checkout.project_path | 409 return self.checkout.project_path |
| OLD | NEW |