summaryrefslogtreecommitdiffstats
path: root/project.py
diff options
context:
space:
mode:
Diffstat (limited to 'project.py')
-rw-r--r--project.py259
1 files changed, 149 insertions, 110 deletions
diff --git a/project.py b/project.py
index d54e336c..6e8dcbe7 100644
--- a/project.py
+++ b/project.py
@@ -30,7 +30,8 @@ import traceback
30 30
31from color import Coloring 31from color import Coloring
32from git_command import GitCommand, git_require 32from git_command import GitCommand, git_require
33from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, ID_RE 33from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, \
34 ID_RE
34from error import GitError, HookError, UploadError, DownloadError 35from error import GitError, HookError, UploadError, DownloadError
35from error import ManifestInvalidRevisionError 36from error import ManifestInvalidRevisionError
36from error import NoManifestException 37from error import NoManifestException
@@ -44,6 +45,7 @@ if not is_python3():
44 input = raw_input 45 input = raw_input
45 # pylint:enable=W0622 46 # pylint:enable=W0622
46 47
48
47def _lwrite(path, content): 49def _lwrite(path, content):
48 lock = '%s.lock' % path 50 lock = '%s.lock' % path
49 51
@@ -59,21 +61,27 @@ def _lwrite(path, content):
59 os.remove(lock) 61 os.remove(lock)
60 raise 62 raise
61 63
64
62def _error(fmt, *args): 65def _error(fmt, *args):
63 msg = fmt % args 66 msg = fmt % args
64 print('error: %s' % msg, file=sys.stderr) 67 print('error: %s' % msg, file=sys.stderr)
65 68
69
66def _warn(fmt, *args): 70def _warn(fmt, *args):
67 msg = fmt % args 71 msg = fmt % args
68 print('warn: %s' % msg, file=sys.stderr) 72 print('warn: %s' % msg, file=sys.stderr)
69 73
74
70def not_rev(r): 75def not_rev(r):
71 return '^' + r 76 return '^' + r
72 77
78
73def sq(r): 79def sq(r):
74 return "'" + r.replace("'", "'\''") + "'" 80 return "'" + r.replace("'", "'\''") + "'"
75 81
76_project_hook_list = None 82_project_hook_list = None
83
84
77def _ProjectHooks(): 85def _ProjectHooks():
78 """List the hooks present in the 'hooks' directory. 86 """List the hooks present in the 'hooks' directory.
79 87
@@ -107,15 +115,14 @@ class DownloadedChange(object):
107 @property 115 @property
108 def commits(self): 116 def commits(self):
109 if self._commit_cache is None: 117 if self._commit_cache is None:
110 self._commit_cache = self.project.bare_git.rev_list( 118 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
111 '--abbrev=8', 119 '--abbrev-commit',
112 '--abbrev-commit', 120 '--pretty=oneline',
113 '--pretty=oneline', 121 '--reverse',
114 '--reverse', 122 '--date-order',
115 '--date-order', 123 not_rev(self.base),
116 not_rev(self.base), 124 self.commit,
117 self.commit, 125 '--')
118 '--')
119 return self._commit_cache 126 return self._commit_cache
120 127
121 128
@@ -134,36 +141,36 @@ class ReviewableBranch(object):
134 @property 141 @property
135 def commits(self): 142 def commits(self):
136 if self._commit_cache is None: 143 if self._commit_cache is None:
137 self._commit_cache = self.project.bare_git.rev_list( 144 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
138 '--abbrev=8', 145 '--abbrev-commit',
139 '--abbrev-commit', 146 '--pretty=oneline',
140 '--pretty=oneline', 147 '--reverse',
141 '--reverse', 148 '--date-order',
142 '--date-order', 149 not_rev(self.base),
143 not_rev(self.base), 150 R_HEADS + self.name,
144 R_HEADS + self.name, 151 '--')
145 '--')
146 return self._commit_cache 152 return self._commit_cache
147 153
148 @property 154 @property
149 def unabbrev_commits(self): 155 def unabbrev_commits(self):
150 r = dict() 156 r = dict()
151 for commit in self.project.bare_git.rev_list( 157 for commit in self.project.bare_git.rev_list(not_rev(self.base),
152 not_rev(self.base), 158 R_HEADS + self.name,
153 R_HEADS + self.name, 159 '--'):
154 '--'):
155 r[commit[0:8]] = commit 160 r[commit[0:8]] = commit
156 return r 161 return r
157 162
158 @property 163 @property
159 def date(self): 164 def date(self):
160 return self.project.bare_git.log( 165 return self.project.bare_git.log('--pretty=format:%cd',
161 '--pretty=format:%cd', 166 '-n', '1',
162 '-n', '1', 167 R_HEADS + self.name,
163 R_HEADS + self.name, 168 '--')
164 '--')
165 169
166 def UploadForReview(self, people, auto_topic=False, draft=False, dest_branch=None): 170 def UploadForReview(self, people,
171 auto_topic=False,
172 draft=False,
173 dest_branch=None):
167 self.project.UploadForReview(self.name, 174 self.project.UploadForReview(self.name,
168 people, 175 people,
169 auto_topic=auto_topic, 176 auto_topic=auto_topic,
@@ -173,8 +180,8 @@ class ReviewableBranch(object):
173 def GetPublishedRefs(self): 180 def GetPublishedRefs(self):
174 refs = {} 181 refs = {}
175 output = self.project.bare_git.ls_remote( 182 output = self.project.bare_git.ls_remote(
176 self.branch.remote.SshReviewUrl(self.project.UserEmail), 183 self.branch.remote.SshReviewUrl(self.project.UserEmail),
177 'refs/changes/*') 184 'refs/changes/*')
178 for line in output.split('\n'): 185 for line in output.split('\n'):
179 try: 186 try:
180 (sha, ref) = line.split() 187 (sha, ref) = line.split()
@@ -184,7 +191,9 @@ class ReviewableBranch(object):
184 191
185 return refs 192 return refs
186 193
194
187class StatusColoring(Coloring): 195class StatusColoring(Coloring):
196
188 def __init__(self, config): 197 def __init__(self, config):
189 Coloring.__init__(self, config, 'status') 198 Coloring.__init__(self, config, 'status')
190 self.project = self.printer('header', attr='bold') 199 self.project = self.printer('header', attr='bold')
@@ -198,17 +207,22 @@ class StatusColoring(Coloring):
198 207
199 208
200class DiffColoring(Coloring): 209class DiffColoring(Coloring):
210
201 def __init__(self, config): 211 def __init__(self, config):
202 Coloring.__init__(self, config, 'diff') 212 Coloring.__init__(self, config, 'diff')
203 self.project = self.printer('header', attr='bold') 213 self.project = self.printer('header', attr='bold')
204 214
215
205class _Annotation(object): 216class _Annotation(object):
217
206 def __init__(self, name, value, keep): 218 def __init__(self, name, value, keep):
207 self.name = name 219 self.name = name
208 self.value = value 220 self.value = value
209 self.keep = keep 221 self.keep = keep
210 222
223
211class _CopyFile(object): 224class _CopyFile(object):
225
212 def __init__(self, src, dest, abssrc, absdest): 226 def __init__(self, src, dest, abssrc, absdest):
213 self.src = src 227 self.src = src
214 self.dest = dest 228 self.dest = dest
@@ -236,7 +250,9 @@ class _CopyFile(object):
236 except IOError: 250 except IOError:
237 _error('Cannot copy file %s to %s', src, dest) 251 _error('Cannot copy file %s to %s', src, dest)
238 252
253
239class _LinkFile(object): 254class _LinkFile(object):
255
240 def __init__(self, git_worktree, src, dest, relsrc, absdest): 256 def __init__(self, git_worktree, src, dest, relsrc, absdest):
241 self.git_worktree = git_worktree 257 self.git_worktree = git_worktree
242 self.src = src 258 self.src = src
@@ -275,7 +291,7 @@ class _LinkFile(object):
275 absDestDir = self.abs_dest 291 absDestDir = self.abs_dest
276 if os.path.exists(absDestDir) and not os.path.isdir(absDestDir): 292 if os.path.exists(absDestDir) and not os.path.isdir(absDestDir):
277 _error('Link error: src with wildcard, %s must be a directory', 293 _error('Link error: src with wildcard, %s must be a directory',
278 absDestDir) 294 absDestDir)
279 else: 295 else:
280 absSrcFiles = glob.glob(absSrc) 296 absSrcFiles = glob.glob(absSrc)
281 for absSrcFile in absSrcFiles: 297 for absSrcFile in absSrcFiles:
@@ -292,7 +308,9 @@ class _LinkFile(object):
292 relSrc = os.path.join(relSrcDir, srcFile) 308 relSrc = os.path.join(relSrcDir, srcFile)
293 self.__linkIt(relSrc, absDest) 309 self.__linkIt(relSrc, absDest)
294 310
311
295class RemoteSpec(object): 312class RemoteSpec(object):
313
296 def __init__(self, 314 def __init__(self,
297 name, 315 name,
298 url=None, 316 url=None,
@@ -303,7 +321,9 @@ class RemoteSpec(object):
303 self.review = review 321 self.review = review
304 self.revision = revision 322 self.revision = revision
305 323
324
306class RepoHook(object): 325class RepoHook(object):
326
307 """A RepoHook contains information about a script to run as a hook. 327 """A RepoHook contains information about a script to run as a hook.
308 328
309 Hooks are used to run a python script before running an upload (for instance, 329 Hooks are used to run a python script before running an upload (for instance,
@@ -316,6 +336,7 @@ class RepoHook(object):
316 Hooks are always python. When a hook is run, we will load the hook into the 336 Hooks are always python. When a hook is run, we will load the hook into the
317 interpreter and execute its main() function. 337 interpreter and execute its main() function.
318 """ 338 """
339
319 def __init__(self, 340 def __init__(self,
320 hook_type, 341 hook_type,
321 hooks_project, 342 hooks_project,
@@ -430,8 +451,8 @@ class RepoHook(object):
430 ' %s\n' 451 ' %s\n'
431 '\n' 452 '\n'
432 'Do you want to allow this script to run ' 453 'Do you want to allow this script to run '
433 '(yes/yes-never-ask-again/NO)? ') % ( 454 '(yes/yes-never-ask-again/NO)? ') % (self._GetMustVerb(),
434 self._GetMustVerb(), self._script_fullpath) 455 self._script_fullpath)
435 response = input(prompt).lower() 456 response = input(prompt).lower()
436 print() 457 print()
437 458
@@ -480,14 +501,13 @@ class RepoHook(object):
480 exec(compile(open(self._script_fullpath).read(), 501 exec(compile(open(self._script_fullpath).read(),
481 self._script_fullpath, 'exec'), context) 502 self._script_fullpath, 'exec'), context)
482 except Exception: 503 except Exception:
483 raise HookError('%s\nFailed to import %s hook; see traceback above.' % ( 504 raise HookError('%s\nFailed to import %s hook; see traceback above.' %
484 traceback.format_exc(), self._hook_type)) 505 (traceback.format_exc(), self._hook_type))
485 506
486 # Running the script should have defined a main() function. 507 # Running the script should have defined a main() function.
487 if 'main' not in context: 508 if 'main' not in context:
488 raise HookError('Missing main() in: "%s"' % self._script_fullpath) 509 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
489 510
490
491 # Add 'hook_should_take_kwargs' to the arguments to be passed to main. 511 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
492 # We don't actually want hooks to define their main with this argument-- 512 # We don't actually want hooks to define their main with this argument--
493 # it's there to remind them that their hook should always take **kwargs. 513 # it's there to remind them that their hook should always take **kwargs.
@@ -505,8 +525,8 @@ class RepoHook(object):
505 context['main'](**kwargs) 525 context['main'](**kwargs)
506 except Exception: 526 except Exception:
507 raise HookError('%s\nFailed to run main() for %s hook; see traceback ' 527 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
508 'above.' % ( 528 'above.' % (traceback.format_exc(),
509 traceback.format_exc(), self._hook_type)) 529 self._hook_type))
510 finally: 530 finally:
511 # Restore sys.path and CWD. 531 # Restore sys.path and CWD.
512 sys.path = orig_syspath 532 sys.path = orig_syspath
@@ -530,8 +550,8 @@ class RepoHook(object):
530 to run a required hook (from _CheckForHookApproval). 550 to run a required hook (from _CheckForHookApproval).
531 """ 551 """
532 # No-op if there is no hooks project or if hook is disabled. 552 # No-op if there is no hooks project or if hook is disabled.
533 if ((not self._hooks_project) or 553 if ((not self._hooks_project) or (self._hook_type not in
534 (self._hook_type not in self._hooks_project.enabled_repo_hooks)): 554 self._hooks_project.enabled_repo_hooks)):
535 return 555 return
536 556
537 # Bail with a nice error if we can't find the hook. 557 # Bail with a nice error if we can't find the hook.
@@ -553,6 +573,7 @@ class Project(object):
553 # These objects can only be used by a single working tree. 573 # These objects can only be used by a single working tree.
554 working_tree_files = ['config', 'packed-refs', 'shallow'] 574 working_tree_files = ['config', 'packed-refs', 'shallow']
555 working_tree_dirs = ['logs', 'refs'] 575 working_tree_dirs = ['logs', 'refs']
576
556 def __init__(self, 577 def __init__(self,
557 manifest, 578 manifest,
558 name, 579 name,
@@ -611,9 +632,9 @@ class Project(object):
611 self.relpath = relpath 632 self.relpath = relpath
612 self.revisionExpr = revisionExpr 633 self.revisionExpr = revisionExpr
613 634
614 if revisionId is None \ 635 if revisionId is None \
615 and revisionExpr \ 636 and revisionExpr \
616 and IsId(revisionExpr): 637 and IsId(revisionExpr):
617 self.revisionId = revisionExpr 638 self.revisionId = revisionExpr
618 else: 639 else:
619 self.revisionId = revisionId 640 self.revisionId = revisionId
@@ -633,9 +654,8 @@ class Project(object):
633 self.copyfiles = [] 654 self.copyfiles = []
634 self.linkfiles = [] 655 self.linkfiles = []
635 self.annotations = [] 656 self.annotations = []
636 self.config = GitConfig.ForRepository( 657 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
637 gitdir=self.gitdir, 658 defaults=self.manifest.globalConfig)
638 defaults=self.manifest.globalConfig)
639 659
640 if self.worktree: 660 if self.worktree:
641 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir) 661 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
@@ -773,7 +793,7 @@ class Project(object):
773 """ 793 """
774 expanded_manifest_groups = manifest_groups or ['default'] 794 expanded_manifest_groups = manifest_groups or ['default']
775 expanded_project_groups = ['all'] + (self.groups or []) 795 expanded_project_groups = ['all'] + (self.groups or [])
776 if not 'notdefault' in expanded_project_groups: 796 if 'notdefault' not in expanded_project_groups:
777 expanded_project_groups += ['default'] 797 expanded_project_groups += ['default']
778 798
779 matched = False 799 matched = False
@@ -785,7 +805,7 @@ class Project(object):
785 805
786 return matched 806 return matched
787 807
788## Status Display ## 808# Status Display ##
789 def UncommitedFiles(self, get_all=True): 809 def UncommitedFiles(self, get_all=True):
790 """Returns a list of strings, uncommitted files in the git tree. 810 """Returns a list of strings, uncommitted files in the git tree.
791 811
@@ -837,7 +857,7 @@ class Project(object):
837 output: If specified, redirect the output to this object. 857 output: If specified, redirect the output to this object.
838 """ 858 """
839 if not os.path.isdir(self.worktree): 859 if not os.path.isdir(self.worktree):
840 if output_redir == None: 860 if output_redir is None:
841 output_redir = sys.stdout 861 output_redir = sys.stdout
842 print(file=output_redir) 862 print(file=output_redir)
843 print('project %s/' % self.relpath, file=output_redir) 863 print('project %s/' % self.relpath, file=output_redir)
@@ -856,7 +876,7 @@ class Project(object):
856 return 'CLEAN' 876 return 'CLEAN'
857 877
858 out = StatusColoring(self.config) 878 out = StatusColoring(self.config)
859 if not output_redir == None: 879 if output_redir is not None:
860 out.redirect(output_redir) 880 out.redirect(output_redir)
861 out.project('project %-40s', self.relpath + '/ ') 881 out.project('project %-40s', self.relpath + '/ ')
862 882
@@ -899,7 +919,7 @@ class Project(object):
899 919
900 if i and i.src_path: 920 if i and i.src_path:
901 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status, 921 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
902 i.src_path, p, i.level) 922 i.src_path, p, i.level)
903 else: 923 else:
904 line = ' %s%s\t%s' % (i_status, f_status, p) 924 line = ' %s%s\t%s' % (i_status, f_status, p)
905 925
@@ -942,7 +962,7 @@ class Project(object):
942 p.Wait() 962 p.Wait()
943 963
944 964
945## Publish / Upload ## 965# Publish / Upload ##
946 966
947 def WasPublished(self, branch, all_refs=None): 967 def WasPublished(self, branch, all_refs=None):
948 """Was the branch published (uploaded) for code review? 968 """Was the branch published (uploaded) for code review?
@@ -1085,7 +1105,7 @@ class Project(object):
1085 message=msg) 1105 message=msg)
1086 1106
1087 1107
1088## Sync ## 1108# Sync ##
1089 1109
1090 def _ExtractArchive(self, tarpath, path=None): 1110 def _ExtractArchive(self, tarpath, path=None):
1091 """Extract the given tar on its current location 1111 """Extract the given tar on its current location
@@ -1103,15 +1123,15 @@ class Project(object):
1103 return False 1123 return False
1104 1124
1105 def Sync_NetworkHalf(self, 1125 def Sync_NetworkHalf(self,
1106 quiet=False, 1126 quiet=False,
1107 is_new=None, 1127 is_new=None,
1108 current_branch_only=False, 1128 current_branch_only=False,
1109 force_sync=False, 1129 force_sync=False,
1110 clone_bundle=True, 1130 clone_bundle=True,
1111 no_tags=False, 1131 no_tags=False,
1112 archive=False, 1132 archive=False,
1113 optimized_fetch=False, 1133 optimized_fetch=False,
1114 prune=False): 1134 prune=False):
1115 """Perform only the network IO portion of the sync process. 1135 """Perform only the network IO portion of the sync process.
1116 Local working directory/branch state is not affected. 1136 Local working directory/branch state is not affected.
1117 """ 1137 """
@@ -1164,8 +1184,8 @@ class Project(object):
1164 alt_dir = None 1184 alt_dir = None
1165 1185
1166 if clone_bundle \ 1186 if clone_bundle \
1167 and alt_dir is None \ 1187 and alt_dir is None \
1168 and self._ApplyCloneBundle(initial=is_new, quiet=quiet): 1188 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
1169 is_new = False 1189 is_new = False
1170 1190
1171 if not current_branch_only: 1191 if not current_branch_only:
@@ -1177,12 +1197,13 @@ class Project(object):
1177 elif self.manifest.default.sync_c: 1197 elif self.manifest.default.sync_c:
1178 current_branch_only = True 1198 current_branch_only = True
1179 1199
1180 need_to_fetch = not (optimized_fetch and \ 1200 need_to_fetch = not (optimized_fetch and
1181 (ID_RE.match(self.revisionExpr) and self._CheckForSha1())) 1201 (ID_RE.match(self.revisionExpr) and
1182 if (need_to_fetch 1202 self._CheckForSha1()))
1183 and not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir, 1203 if (need_to_fetch and
1184 current_branch_only=current_branch_only, 1204 not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1185 no_tags=no_tags, prune=prune)): 1205 current_branch_only=current_branch_only,
1206 no_tags=no_tags, prune=prune)):
1186 return False 1207 return False
1187 1208
1188 if self.worktree: 1209 if self.worktree:
@@ -1219,9 +1240,8 @@ class Project(object):
1219 try: 1240 try:
1220 return self.bare_git.rev_list(self.revisionExpr, '-1')[0] 1241 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1221 except GitError: 1242 except GitError:
1222 raise ManifestInvalidRevisionError( 1243 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1223 'revision %s in %s not found' % (self.revisionExpr, 1244 (self.revisionExpr, self.name))
1224 self.name))
1225 1245
1226 def GetRevisionId(self, all_refs=None): 1246 def GetRevisionId(self, all_refs=None):
1227 if self.revisionId: 1247 if self.revisionId:
@@ -1236,9 +1256,8 @@ class Project(object):
1236 try: 1256 try:
1237 return self.bare_git.rev_parse('--verify', '%s^0' % rev) 1257 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1238 except GitError: 1258 except GitError:
1239 raise ManifestInvalidRevisionError( 1259 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1240 'revision %s in %s not found' % (self.revisionExpr, 1260 (self.revisionExpr, self.name))
1241 self.name))
1242 1261
1243 def Sync_LocalHalf(self, syncbuf, force_sync=False): 1262 def Sync_LocalHalf(self, syncbuf, force_sync=False):
1244 """Perform only the local IO portion of the sync process. 1263 """Perform only the local IO portion of the sync process.
@@ -1327,8 +1346,8 @@ class Project(object):
1327 # to rewrite the published commits so we punt. 1346 # to rewrite the published commits so we punt.
1328 # 1347 #
1329 syncbuf.fail(self, 1348 syncbuf.fail(self,
1330 "branch %s is published (but not merged) and is now %d commits behind" 1349 "branch %s is published (but not merged) and is now "
1331 % (branch.name, len(upstream_gain))) 1350 "%d commits behind" % (branch.name, len(upstream_gain)))
1332 return 1351 return
1333 elif pub == head: 1352 elif pub == head:
1334 # All published commits are merged, and thus we are a 1353 # All published commits are merged, and thus we are a
@@ -1422,7 +1441,7 @@ class Project(object):
1422 remote = self.GetRemote(self.remote.name) 1441 remote = self.GetRemote(self.remote.name)
1423 1442
1424 cmd = ['fetch', remote.name] 1443 cmd = ['fetch', remote.name]
1425 cmd.append('refs/changes/%2.2d/%d/%d' \ 1444 cmd.append('refs/changes/%2.2d/%d/%d'
1426 % (change_id % 100, change_id, patch_id)) 1445 % (change_id % 100, change_id, patch_id))
1427 if GitCommand(self, cmd, bare=True).Wait() != 0: 1446 if GitCommand(self, cmd, bare=True).Wait() != 0:
1428 return None 1447 return None
@@ -1433,7 +1452,7 @@ class Project(object):
1433 self.bare_git.rev_parse('FETCH_HEAD')) 1452 self.bare_git.rev_parse('FETCH_HEAD'))
1434 1453
1435 1454
1436## Branch Management ## 1455# Branch Management ##
1437 1456
1438 def StartBranch(self, name, branch_merge=''): 1457 def StartBranch(self, name, branch_merge=''):
1439 """Create a new branch off the manifest's revision. 1458 """Create a new branch off the manifest's revision.
@@ -1620,10 +1639,11 @@ class Project(object):
1620 return kept 1639 return kept
1621 1640
1622 1641
1623## Submodule Management ## 1642# Submodule Management ##
1624 1643
1625 def GetRegisteredSubprojects(self): 1644 def GetRegisteredSubprojects(self):
1626 result = [] 1645 result = []
1646
1627 def rec(subprojects): 1647 def rec(subprojects):
1628 if not subprojects: 1648 if not subprojects:
1629 return 1649 return
@@ -1658,6 +1678,7 @@ class Project(object):
1658 1678
1659 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$') 1679 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1660 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$') 1680 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1681
1661 def parse_gitmodules(gitdir, rev): 1682 def parse_gitmodules(gitdir, rev):
1662 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev] 1683 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1663 try: 1684 try:
@@ -1767,7 +1788,7 @@ class Project(object):
1767 return result 1788 return result
1768 1789
1769 1790
1770## Direct Git Commands ## 1791# Direct Git Commands ##
1771 def _CheckForSha1(self): 1792 def _CheckForSha1(self):
1772 try: 1793 try:
1773 # if revision (sha or tag) is not present then following function 1794 # if revision (sha or tag) is not present then following function
@@ -1791,7 +1812,6 @@ class Project(object):
1791 if command.Wait() != 0: 1812 if command.Wait() != 0:
1792 raise GitError('git archive %s: %s' % (self.name, command.stderr)) 1813 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1793 1814
1794
1795 def _RemoteFetch(self, name=None, 1815 def _RemoteFetch(self, name=None,
1796 current_branch_only=False, 1816 current_branch_only=False,
1797 initial=False, 1817 initial=False,
@@ -1955,9 +1975,9 @@ class Project(object):
1955 break 1975 break
1956 continue 1976 continue
1957 elif current_branch_only and is_sha1 and ret == 128: 1977 elif current_branch_only and is_sha1 and ret == 128:
1958 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1 1978 # Exit code 128 means "couldn't find the ref you asked for"; if we're
1959 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus 1979 # in sha1 mode, we just tried sync'ing from the upstream field; it
1960 # abort the optimization attempt and do a full sync. 1980 # doesn't exist, thus abort the optimization attempt and do a full sync.
1961 break 1981 break
1962 elif ret < 0: 1982 elif ret < 0:
1963 # Git died with a signal, exit immediately 1983 # Git died with a signal, exit immediately
@@ -1984,20 +2004,24 @@ class Project(object):
1984 initial=False, quiet=quiet, alt_dir=alt_dir) 2004 initial=False, quiet=quiet, alt_dir=alt_dir)
1985 if self.clone_depth: 2005 if self.clone_depth:
1986 self.clone_depth = None 2006 self.clone_depth = None
1987 return self._RemoteFetch(name=name, current_branch_only=current_branch_only, 2007 return self._RemoteFetch(name=name,
2008 current_branch_only=current_branch_only,
1988 initial=False, quiet=quiet, alt_dir=alt_dir) 2009 initial=False, quiet=quiet, alt_dir=alt_dir)
1989 2010
1990 return ok 2011 return ok
1991 2012
1992 def _ApplyCloneBundle(self, initial=False, quiet=False): 2013 def _ApplyCloneBundle(self, initial=False, quiet=False):
1993 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth): 2014 if initial and \
2015 (self.manifest.manifestProject.config.GetString('repo.depth') or
2016 self.clone_depth):
1994 return False 2017 return False
1995 2018
1996 remote = self.GetRemote(self.remote.name) 2019 remote = self.GetRemote(self.remote.name)
1997 bundle_url = remote.url + '/clone.bundle' 2020 bundle_url = remote.url + '/clone.bundle'
1998 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url) 2021 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
1999 if GetSchemeFromUrl(bundle_url) not in ( 2022 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2000 'http', 'https', 'persistent-http', 'persistent-https'): 2023 'persistent-http',
2024 'persistent-https'):
2001 return False 2025 return False
2002 2026
2003 bundle_dst = os.path.join(self.gitdir, 'clone.bundle') 2027 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
@@ -2046,7 +2070,7 @@ class Project(object):
2046 os.remove(tmpPath) 2070 os.remove(tmpPath)
2047 if 'http_proxy' in os.environ and 'darwin' == sys.platform: 2071 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
2048 cmd += ['--proxy', os.environ['http_proxy']] 2072 cmd += ['--proxy', os.environ['http_proxy']]
2049 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy): 2073 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, _proxy):
2050 if cookiefile: 2074 if cookiefile:
2051 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile] 2075 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
2052 if srcUrl.startswith('persistent-'): 2076 if srcUrl.startswith('persistent-'):
@@ -2165,11 +2189,12 @@ class Project(object):
2165 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False) 2189 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2166 except GitError as e: 2190 except GitError as e:
2167 if force_sync: 2191 if force_sync:
2168 print("Retrying clone after deleting %s" % self.gitdir, file=sys.stderr) 2192 print("Retrying clone after deleting %s" %
2193 self.gitdir, file=sys.stderr)
2169 try: 2194 try:
2170 shutil.rmtree(os.path.realpath(self.gitdir)) 2195 shutil.rmtree(os.path.realpath(self.gitdir))
2171 if self.worktree and os.path.exists( 2196 if self.worktree and os.path.exists(os.path.realpath
2172 os.path.realpath(self.worktree)): 2197 (self.worktree)):
2173 shutil.rmtree(os.path.realpath(self.worktree)) 2198 shutil.rmtree(os.path.realpath(self.worktree))
2174 return self._InitGitDir(mirror_git=mirror_git, force_sync=False) 2199 return self._InitGitDir(mirror_git=mirror_git, force_sync=False)
2175 except: 2200 except:
@@ -2228,7 +2253,7 @@ class Project(object):
2228 name = os.path.basename(stock_hook) 2253 name = os.path.basename(stock_hook)
2229 2254
2230 if name in ('commit-msg',) and not self.remote.review \ 2255 if name in ('commit-msg',) and not self.remote.review \
2231 and not self is self.manifest.manifestProject: 2256 and self is not self.manifest.manifestProject:
2232 # Don't install a Gerrit Code Review hook if this 2257 # Don't install a Gerrit Code Review hook if this
2233 # project does not appear to use it for reviews. 2258 # project does not appear to use it for reviews.
2234 # 2259 #
@@ -2243,7 +2268,8 @@ class Project(object):
2243 if filecmp.cmp(stock_hook, dst, shallow=False): 2268 if filecmp.cmp(stock_hook, dst, shallow=False):
2244 os.remove(dst) 2269 os.remove(dst)
2245 else: 2270 else:
2246 _warn("%s: Not replacing locally modified %s hook", self.relpath, name) 2271 _warn("%s: Not replacing locally modified %s hook",
2272 self.relpath, name)
2247 continue 2273 continue
2248 try: 2274 try:
2249 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst) 2275 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
@@ -2449,6 +2475,7 @@ class Project(object):
2449 return logs 2475 return logs
2450 2476
2451 class _GitGetByExec(object): 2477 class _GitGetByExec(object):
2478
2452 def __init__(self, project, bare, gitdir): 2479 def __init__(self, project, bare, gitdir):
2453 self._project = project 2480 self._project = project
2454 self._bare = bare 2481 self._bare = bare
@@ -2467,8 +2494,8 @@ class Project(object):
2467 if p.Wait() == 0: 2494 if p.Wait() == 0:
2468 out = p.stdout 2495 out = p.stdout
2469 if out: 2496 if out:
2497 # Backslash is not anomalous
2470 return out[:-1].split('\0') # pylint: disable=W1401 2498 return out[:-1].split('\0') # pylint: disable=W1401
2471 # Backslash is not anomalous
2472 return [] 2499 return []
2473 2500
2474 def DiffZ(self, name, *args): 2501 def DiffZ(self, name, *args):
@@ -2494,6 +2521,7 @@ class Project(object):
2494 break 2521 break
2495 2522
2496 class _Info(object): 2523 class _Info(object):
2524
2497 def __init__(self, path, omode, nmode, oid, nid, state): 2525 def __init__(self, path, omode, nmode, oid, nid, state):
2498 self.path = path 2526 self.path = path
2499 self.src_path = None 2527 self.src_path = None
@@ -2596,10 +2624,8 @@ class Project(object):
2596 line = line[:-1] 2624 line = line[:-1]
2597 r.append(line) 2625 r.append(line)
2598 if p.Wait() != 0: 2626 if p.Wait() != 0:
2599 raise GitError('%s rev-list %s: %s' % ( 2627 raise GitError('%s rev-list %s: %s' %
2600 self._project.name, 2628 (self._project.name, str(args), p.stderr))
2601 str(args),
2602 p.stderr))
2603 return r 2629 return r
2604 2630
2605 def __getattr__(self, name): 2631 def __getattr__(self, name):
@@ -2622,6 +2648,7 @@ class Project(object):
2622 A callable object that will try to call git with the named command. 2648 A callable object that will try to call git with the named command.
2623 """ 2649 """
2624 name = name.replace('_', '-') 2650 name = name.replace('_', '-')
2651
2625 def runner(*args, **kwargs): 2652 def runner(*args, **kwargs):
2626 cmdv = [] 2653 cmdv = []
2627 config = kwargs.pop('config', None) 2654 config = kwargs.pop('config', None)
@@ -2644,10 +2671,8 @@ class Project(object):
2644 capture_stdout=True, 2671 capture_stdout=True,
2645 capture_stderr=True) 2672 capture_stderr=True)
2646 if p.Wait() != 0: 2673 if p.Wait() != 0:
2647 raise GitError('%s %s: %s' % ( 2674 raise GitError('%s %s: %s' %
2648 self._project.name, 2675 (self._project.name, name, p.stderr))
2649 name,
2650 p.stderr))
2651 r = p.stdout 2676 r = p.stdout
2652 try: 2677 try:
2653 r = r.decode('utf-8') 2678 r = r.decode('utf-8')
@@ -2660,14 +2685,19 @@ class Project(object):
2660 2685
2661 2686
2662class _PriorSyncFailedError(Exception): 2687class _PriorSyncFailedError(Exception):
2688
2663 def __str__(self): 2689 def __str__(self):
2664 return 'prior sync failed; rebase still in progress' 2690 return 'prior sync failed; rebase still in progress'
2665 2691
2692
2666class _DirtyError(Exception): 2693class _DirtyError(Exception):
2694
2667 def __str__(self): 2695 def __str__(self):
2668 return 'contains uncommitted changes' 2696 return 'contains uncommitted changes'
2669 2697
2698
2670class _InfoMessage(object): 2699class _InfoMessage(object):
2700
2671 def __init__(self, project, text): 2701 def __init__(self, project, text):
2672 self.project = project 2702 self.project = project
2673 self.text = text 2703 self.text = text
@@ -2676,7 +2706,9 @@ class _InfoMessage(object):
2676 syncbuf.out.info('%s/: %s', self.project.relpath, self.text) 2706 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2677 syncbuf.out.nl() 2707 syncbuf.out.nl()
2678 2708
2709
2679class _Failure(object): 2710class _Failure(object):
2711
2680 def __init__(self, project, why): 2712 def __init__(self, project, why):
2681 self.project = project 2713 self.project = project
2682 self.why = why 2714 self.why = why
@@ -2687,7 +2719,9 @@ class _Failure(object):
2687 str(self.why)) 2719 str(self.why))
2688 syncbuf.out.nl() 2720 syncbuf.out.nl()
2689 2721
2722
2690class _Later(object): 2723class _Later(object):
2724
2691 def __init__(self, project, action): 2725 def __init__(self, project, action):
2692 self.project = project 2726 self.project = project
2693 self.action = action 2727 self.action = action
@@ -2704,14 +2738,18 @@ class _Later(object):
2704 out.nl() 2738 out.nl()
2705 return False 2739 return False
2706 2740
2741
2707class _SyncColoring(Coloring): 2742class _SyncColoring(Coloring):
2743
2708 def __init__(self, config): 2744 def __init__(self, config):
2709 Coloring.__init__(self, config, 'reposync') 2745 Coloring.__init__(self, config, 'reposync')
2710 self.project = self.printer('header', attr='bold') 2746 self.project = self.printer('header', attr='bold')
2711 self.info = self.printer('info') 2747 self.info = self.printer('info')
2712 self.fail = self.printer('fail', fg='red') 2748 self.fail = self.printer('fail', fg='red')
2713 2749
2750
2714class SyncBuffer(object): 2751class SyncBuffer(object):
2752
2715 def __init__(self, config, detach_head=False): 2753 def __init__(self, config, detach_head=False):
2716 self._messages = [] 2754 self._messages = []
2717 self._failures = [] 2755 self._failures = []
@@ -2767,8 +2805,10 @@ class SyncBuffer(object):
2767 2805
2768 2806
2769class MetaProject(Project): 2807class MetaProject(Project):
2808
2770 """A special project housed under .repo. 2809 """A special project housed under .repo.
2771 """ 2810 """
2811
2772 def __init__(self, manifest, name, gitdir, worktree): 2812 def __init__(self, manifest, name, gitdir, worktree):
2773 Project.__init__(self, 2813 Project.__init__(self,
2774 manifest=manifest, 2814 manifest=manifest,
@@ -2802,10 +2842,9 @@ class MetaProject(Project):
2802 syncbuf.Finish() 2842 syncbuf.Finish()
2803 2843
2804 return GitCommand(self, 2844 return GitCommand(self,
2805 ['update-ref', '-d', 'refs/heads/default'], 2845 ['update-ref', '-d', 'refs/heads/default'],
2806 capture_stdout=True, 2846 capture_stdout=True,
2807 capture_stderr=True).Wait() == 0 2847 capture_stderr=True).Wait() == 0
2808
2809 2848
2810 @property 2849 @property
2811 def LastFetch(self): 2850 def LastFetch(self):