summaryrefslogtreecommitdiffstats
path: root/project.py
diff options
context:
space:
mode:
Diffstat (limited to 'project.py')
-rw-r--r--project.py286
1 files changed, 166 insertions, 120 deletions
diff --git a/project.py b/project.py
index 6dfb31c3..c91085c3 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,18 +308,24 @@ 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,
299 review=None, 317 review=None,
300 revision=None): 318 revision=None,
319 orig_name=None):
301 self.name = name 320 self.name = name
302 self.url = url 321 self.url = url
303 self.review = review 322 self.review = review
304 self.revision = revision 323 self.revision = revision
324 self.orig_name = orig_name
325
305 326
306class RepoHook(object): 327class RepoHook(object):
328
307 """A RepoHook contains information about a script to run as a hook. 329 """A RepoHook contains information about a script to run as a hook.
308 330
309 Hooks are used to run a python script before running an upload (for instance, 331 Hooks are used to run a python script before running an upload (for instance,
@@ -316,6 +338,7 @@ class RepoHook(object):
316 Hooks are always python. When a hook is run, we will load the hook into the 338 Hooks are always python. When a hook is run, we will load the hook into the
317 interpreter and execute its main() function. 339 interpreter and execute its main() function.
318 """ 340 """
341
319 def __init__(self, 342 def __init__(self,
320 hook_type, 343 hook_type,
321 hooks_project, 344 hooks_project,
@@ -430,8 +453,8 @@ class RepoHook(object):
430 ' %s\n' 453 ' %s\n'
431 '\n' 454 '\n'
432 'Do you want to allow this script to run ' 455 'Do you want to allow this script to run '
433 '(yes/yes-never-ask-again/NO)? ') % ( 456 '(yes/yes-never-ask-again/NO)? ') % (self._GetMustVerb(),
434 self._GetMustVerb(), self._script_fullpath) 457 self._script_fullpath)
435 response = input(prompt).lower() 458 response = input(prompt).lower()
436 print() 459 print()
437 460
@@ -475,19 +498,18 @@ class RepoHook(object):
475 498
476 # Exec, storing global context in the context dict. We catch exceptions 499 # Exec, storing global context in the context dict. We catch exceptions
477 # and convert to a HookError w/ just the failing traceback. 500 # and convert to a HookError w/ just the failing traceback.
478 context = {} 501 context = {'__file__': self._script_fullpath}
479 try: 502 try:
480 exec(compile(open(self._script_fullpath).read(), 503 exec(compile(open(self._script_fullpath).read(),
481 self._script_fullpath, 'exec'), context) 504 self._script_fullpath, 'exec'), context)
482 except Exception: 505 except Exception:
483 raise HookError('%s\nFailed to import %s hook; see traceback above.' % ( 506 raise HookError('%s\nFailed to import %s hook; see traceback above.' %
484 traceback.format_exc(), self._hook_type)) 507 (traceback.format_exc(), self._hook_type))
485 508
486 # Running the script should have defined a main() function. 509 # Running the script should have defined a main() function.
487 if 'main' not in context: 510 if 'main' not in context:
488 raise HookError('Missing main() in: "%s"' % self._script_fullpath) 511 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
489 512
490
491 # Add 'hook_should_take_kwargs' to the arguments to be passed to main. 513 # 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-- 514 # 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. 515 # it's there to remind them that their hook should always take **kwargs.
@@ -505,8 +527,8 @@ class RepoHook(object):
505 context['main'](**kwargs) 527 context['main'](**kwargs)
506 except Exception: 528 except Exception:
507 raise HookError('%s\nFailed to run main() for %s hook; see traceback ' 529 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
508 'above.' % ( 530 'above.' % (traceback.format_exc(),
509 traceback.format_exc(), self._hook_type)) 531 self._hook_type))
510 finally: 532 finally:
511 # Restore sys.path and CWD. 533 # Restore sys.path and CWD.
512 sys.path = orig_syspath 534 sys.path = orig_syspath
@@ -530,8 +552,8 @@ class RepoHook(object):
530 to run a required hook (from _CheckForHookApproval). 552 to run a required hook (from _CheckForHookApproval).
531 """ 553 """
532 # No-op if there is no hooks project or if hook is disabled. 554 # No-op if there is no hooks project or if hook is disabled.
533 if ((not self._hooks_project) or 555 if ((not self._hooks_project) or (self._hook_type not in
534 (self._hook_type not in self._hooks_project.enabled_repo_hooks)): 556 self._hooks_project.enabled_repo_hooks)):
535 return 557 return
536 558
537 # Bail with a nice error if we can't find the hook. 559 # Bail with a nice error if we can't find the hook.
@@ -553,6 +575,7 @@ class Project(object):
553 # These objects can only be used by a single working tree. 575 # These objects can only be used by a single working tree.
554 working_tree_files = ['config', 'packed-refs', 'shallow'] 576 working_tree_files = ['config', 'packed-refs', 'shallow']
555 working_tree_dirs = ['logs', 'refs'] 577 working_tree_dirs = ['logs', 'refs']
578
556 def __init__(self, 579 def __init__(self,
557 manifest, 580 manifest,
558 name, 581 name,
@@ -611,9 +634,9 @@ class Project(object):
611 self.relpath = relpath 634 self.relpath = relpath
612 self.revisionExpr = revisionExpr 635 self.revisionExpr = revisionExpr
613 636
614 if revisionId is None \ 637 if revisionId is None \
615 and revisionExpr \ 638 and revisionExpr \
616 and IsId(revisionExpr): 639 and IsId(revisionExpr):
617 self.revisionId = revisionExpr 640 self.revisionId = revisionExpr
618 else: 641 else:
619 self.revisionId = revisionId 642 self.revisionId = revisionId
@@ -633,9 +656,8 @@ class Project(object):
633 self.copyfiles = [] 656 self.copyfiles = []
634 self.linkfiles = [] 657 self.linkfiles = []
635 self.annotations = [] 658 self.annotations = []
636 self.config = GitConfig.ForRepository( 659 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
637 gitdir=self.gitdir, 660 defaults=self.manifest.globalConfig)
638 defaults=self.manifest.globalConfig)
639 661
640 if self.worktree: 662 if self.worktree:
641 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir) 663 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
@@ -773,7 +795,7 @@ class Project(object):
773 """ 795 """
774 expanded_manifest_groups = manifest_groups or ['default'] 796 expanded_manifest_groups = manifest_groups or ['default']
775 expanded_project_groups = ['all'] + (self.groups or []) 797 expanded_project_groups = ['all'] + (self.groups or [])
776 if not 'notdefault' in expanded_project_groups: 798 if 'notdefault' not in expanded_project_groups:
777 expanded_project_groups += ['default'] 799 expanded_project_groups += ['default']
778 800
779 matched = False 801 matched = False
@@ -785,7 +807,7 @@ class Project(object):
785 807
786 return matched 808 return matched
787 809
788## Status Display ## 810# Status Display ##
789 def UncommitedFiles(self, get_all=True): 811 def UncommitedFiles(self, get_all=True):
790 """Returns a list of strings, uncommitted files in the git tree. 812 """Returns a list of strings, uncommitted files in the git tree.
791 813
@@ -837,7 +859,7 @@ class Project(object):
837 output: If specified, redirect the output to this object. 859 output: If specified, redirect the output to this object.
838 """ 860 """
839 if not os.path.isdir(self.worktree): 861 if not os.path.isdir(self.worktree):
840 if output_redir == None: 862 if output_redir is None:
841 output_redir = sys.stdout 863 output_redir = sys.stdout
842 print(file=output_redir) 864 print(file=output_redir)
843 print('project %s/' % self.relpath, file=output_redir) 865 print('project %s/' % self.relpath, file=output_redir)
@@ -856,7 +878,7 @@ class Project(object):
856 return 'CLEAN' 878 return 'CLEAN'
857 879
858 out = StatusColoring(self.config) 880 out = StatusColoring(self.config)
859 if not output_redir == None: 881 if output_redir is not None:
860 out.redirect(output_redir) 882 out.redirect(output_redir)
861 out.project('project %-40s', self.relpath + '/ ') 883 out.project('project %-40s', self.relpath + '/ ')
862 884
@@ -899,7 +921,7 @@ class Project(object):
899 921
900 if i and i.src_path: 922 if i and i.src_path:
901 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status, 923 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
902 i.src_path, p, i.level) 924 i.src_path, p, i.level)
903 else: 925 else:
904 line = ' %s%s\t%s' % (i_status, f_status, p) 926 line = ' %s%s\t%s' % (i_status, f_status, p)
905 927
@@ -942,7 +964,7 @@ class Project(object):
942 p.Wait() 964 p.Wait()
943 965
944 966
945## Publish / Upload ## 967# Publish / Upload ##
946 968
947 def WasPublished(self, branch, all_refs=None): 969 def WasPublished(self, branch, all_refs=None):
948 """Was the branch published (uploaded) for code review? 970 """Was the branch published (uploaded) for code review?
@@ -1085,7 +1107,7 @@ class Project(object):
1085 message=msg) 1107 message=msg)
1086 1108
1087 1109
1088## Sync ## 1110# Sync ##
1089 1111
1090 def _ExtractArchive(self, tarpath, path=None): 1112 def _ExtractArchive(self, tarpath, path=None):
1091 """Extract the given tar on its current location 1113 """Extract the given tar on its current location
@@ -1103,15 +1125,15 @@ class Project(object):
1103 return False 1125 return False
1104 1126
1105 def Sync_NetworkHalf(self, 1127 def Sync_NetworkHalf(self,
1106 quiet=False, 1128 quiet=False,
1107 is_new=None, 1129 is_new=None,
1108 current_branch_only=False, 1130 current_branch_only=False,
1109 force_sync=False, 1131 force_sync=False,
1110 clone_bundle=True, 1132 clone_bundle=True,
1111 no_tags=False, 1133 no_tags=False,
1112 archive=False, 1134 archive=False,
1113 optimized_fetch=False, 1135 optimized_fetch=False,
1114 prune=False): 1136 prune=False):
1115 """Perform only the network IO portion of the sync process. 1137 """Perform only the network IO portion of the sync process.
1116 Local working directory/branch state is not affected. 1138 Local working directory/branch state is not affected.
1117 """ 1139 """
@@ -1164,8 +1186,8 @@ class Project(object):
1164 alt_dir = None 1186 alt_dir = None
1165 1187
1166 if clone_bundle \ 1188 if clone_bundle \
1167 and alt_dir is None \ 1189 and alt_dir is None \
1168 and self._ApplyCloneBundle(initial=is_new, quiet=quiet): 1190 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
1169 is_new = False 1191 is_new = False
1170 1192
1171 if not current_branch_only: 1193 if not current_branch_only:
@@ -1177,12 +1199,13 @@ class Project(object):
1177 elif self.manifest.default.sync_c: 1199 elif self.manifest.default.sync_c:
1178 current_branch_only = True 1200 current_branch_only = True
1179 1201
1180 need_to_fetch = not (optimized_fetch and \ 1202 need_to_fetch = not (optimized_fetch and
1181 (ID_RE.match(self.revisionExpr) and self._CheckForSha1())) 1203 (ID_RE.match(self.revisionExpr) and
1182 if (need_to_fetch 1204 self._CheckForSha1()))
1183 and not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir, 1205 if (need_to_fetch and
1184 current_branch_only=current_branch_only, 1206 not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1185 no_tags=no_tags, prune=prune)): 1207 current_branch_only=current_branch_only,
1208 no_tags=no_tags, prune=prune)):
1186 return False 1209 return False
1187 1210
1188 if self.worktree: 1211 if self.worktree:
@@ -1219,9 +1242,8 @@ class Project(object):
1219 try: 1242 try:
1220 return self.bare_git.rev_list(self.revisionExpr, '-1')[0] 1243 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1221 except GitError: 1244 except GitError:
1222 raise ManifestInvalidRevisionError( 1245 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1223 'revision %s in %s not found' % (self.revisionExpr, 1246 (self.revisionExpr, self.name))
1224 self.name))
1225 1247
1226 def GetRevisionId(self, all_refs=None): 1248 def GetRevisionId(self, all_refs=None):
1227 if self.revisionId: 1249 if self.revisionId:
@@ -1236,9 +1258,8 @@ class Project(object):
1236 try: 1258 try:
1237 return self.bare_git.rev_parse('--verify', '%s^0' % rev) 1259 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1238 except GitError: 1260 except GitError:
1239 raise ManifestInvalidRevisionError( 1261 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1240 'revision %s in %s not found' % (self.revisionExpr, 1262 (self.revisionExpr, self.name))
1241 self.name))
1242 1263
1243 def Sync_LocalHalf(self, syncbuf, force_sync=False): 1264 def Sync_LocalHalf(self, syncbuf, force_sync=False):
1244 """Perform only the local IO portion of the sync process. 1265 """Perform only the local IO portion of the sync process.
@@ -1327,8 +1348,8 @@ class Project(object):
1327 # to rewrite the published commits so we punt. 1348 # to rewrite the published commits so we punt.
1328 # 1349 #
1329 syncbuf.fail(self, 1350 syncbuf.fail(self,
1330 "branch %s is published (but not merged) and is now %d commits behind" 1351 "branch %s is published (but not merged) and is now "
1331 % (branch.name, len(upstream_gain))) 1352 "%d commits behind" % (branch.name, len(upstream_gain)))
1332 return 1353 return
1333 elif pub == head: 1354 elif pub == head:
1334 # All published commits are merged, and thus we are a 1355 # All published commits are merged, and thus we are a
@@ -1422,7 +1443,7 @@ class Project(object):
1422 remote = self.GetRemote(self.remote.name) 1443 remote = self.GetRemote(self.remote.name)
1423 1444
1424 cmd = ['fetch', remote.name] 1445 cmd = ['fetch', remote.name]
1425 cmd.append('refs/changes/%2.2d/%d/%d' \ 1446 cmd.append('refs/changes/%2.2d/%d/%d'
1426 % (change_id % 100, change_id, patch_id)) 1447 % (change_id % 100, change_id, patch_id))
1427 if GitCommand(self, cmd, bare=True).Wait() != 0: 1448 if GitCommand(self, cmd, bare=True).Wait() != 0:
1428 return None 1449 return None
@@ -1433,7 +1454,7 @@ class Project(object):
1433 self.bare_git.rev_parse('FETCH_HEAD')) 1454 self.bare_git.rev_parse('FETCH_HEAD'))
1434 1455
1435 1456
1436## Branch Management ## 1457# Branch Management ##
1437 1458
1438 def StartBranch(self, name, branch_merge=''): 1459 def StartBranch(self, name, branch_merge=''):
1439 """Create a new branch off the manifest's revision. 1460 """Create a new branch off the manifest's revision.
@@ -1620,10 +1641,11 @@ class Project(object):
1620 return kept 1641 return kept
1621 1642
1622 1643
1623## Submodule Management ## 1644# Submodule Management ##
1624 1645
1625 def GetRegisteredSubprojects(self): 1646 def GetRegisteredSubprojects(self):
1626 result = [] 1647 result = []
1648
1627 def rec(subprojects): 1649 def rec(subprojects):
1628 if not subprojects: 1650 if not subprojects:
1629 return 1651 return
@@ -1658,6 +1680,7 @@ class Project(object):
1658 1680
1659 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$') 1681 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1660 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$') 1682 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1683
1661 def parse_gitmodules(gitdir, rev): 1684 def parse_gitmodules(gitdir, rev):
1662 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev] 1685 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1663 try: 1686 try:
@@ -1767,7 +1790,7 @@ class Project(object):
1767 return result 1790 return result
1768 1791
1769 1792
1770## Direct Git Commands ## 1793# Direct Git Commands ##
1771 def _CheckForSha1(self): 1794 def _CheckForSha1(self):
1772 try: 1795 try:
1773 # if revision (sha or tag) is not present then following function 1796 # if revision (sha or tag) is not present then following function
@@ -1791,7 +1814,6 @@ class Project(object):
1791 if command.Wait() != 0: 1814 if command.Wait() != 0:
1792 raise GitError('git archive %s: %s' % (self.name, command.stderr)) 1815 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1793 1816
1794
1795 def _RemoteFetch(self, name=None, 1817 def _RemoteFetch(self, name=None,
1796 current_branch_only=False, 1818 current_branch_only=False,
1797 initial=False, 1819 initial=False,
@@ -1958,9 +1980,9 @@ class Project(object):
1958 break 1980 break
1959 continue 1981 continue
1960 elif current_branch_only and is_sha1 and ret == 128: 1982 elif current_branch_only and is_sha1 and ret == 128:
1961 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1 1983 # Exit code 128 means "couldn't find the ref you asked for"; if we're
1962 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus 1984 # in sha1 mode, we just tried sync'ing from the upstream field; it
1963 # abort the optimization attempt and do a full sync. 1985 # doesn't exist, thus abort the optimization attempt and do a full sync.
1964 break 1986 break
1965 elif ret < 0: 1987 elif ret < 0:
1966 # Git died with a signal, exit immediately 1988 # Git died with a signal, exit immediately
@@ -1987,20 +2009,24 @@ class Project(object):
1987 initial=False, quiet=quiet, alt_dir=alt_dir) 2009 initial=False, quiet=quiet, alt_dir=alt_dir)
1988 if self.clone_depth: 2010 if self.clone_depth:
1989 self.clone_depth = None 2011 self.clone_depth = None
1990 return self._RemoteFetch(name=name, current_branch_only=current_branch_only, 2012 return self._RemoteFetch(name=name,
2013 current_branch_only=current_branch_only,
1991 initial=False, quiet=quiet, alt_dir=alt_dir) 2014 initial=False, quiet=quiet, alt_dir=alt_dir)
1992 2015
1993 return ok 2016 return ok
1994 2017
1995 def _ApplyCloneBundle(self, initial=False, quiet=False): 2018 def _ApplyCloneBundle(self, initial=False, quiet=False):
1996 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth): 2019 if initial and \
2020 (self.manifest.manifestProject.config.GetString('repo.depth') or
2021 self.clone_depth):
1997 return False 2022 return False
1998 2023
1999 remote = self.GetRemote(self.remote.name) 2024 remote = self.GetRemote(self.remote.name)
2000 bundle_url = remote.url + '/clone.bundle' 2025 bundle_url = remote.url + '/clone.bundle'
2001 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url) 2026 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
2002 if GetSchemeFromUrl(bundle_url) not in ( 2027 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2003 'http', 'https', 'persistent-http', 'persistent-https'): 2028 'persistent-http',
2029 'persistent-https'):
2004 return False 2030 return False
2005 2031
2006 bundle_dst = os.path.join(self.gitdir, 'clone.bundle') 2032 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
@@ -2049,7 +2075,7 @@ class Project(object):
2049 os.remove(tmpPath) 2075 os.remove(tmpPath)
2050 if 'http_proxy' in os.environ and 'darwin' == sys.platform: 2076 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
2051 cmd += ['--proxy', os.environ['http_proxy']] 2077 cmd += ['--proxy', os.environ['http_proxy']]
2052 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy): 2078 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, _proxy):
2053 if cookiefile: 2079 if cookiefile:
2054 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile] 2080 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
2055 if srcUrl.startswith('persistent-'): 2081 if srcUrl.startswith('persistent-'):
@@ -2168,11 +2194,12 @@ class Project(object):
2168 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False) 2194 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2169 except GitError as e: 2195 except GitError as e:
2170 if force_sync: 2196 if force_sync:
2171 print("Retrying clone after deleting %s" % self.gitdir, file=sys.stderr) 2197 print("Retrying clone after deleting %s" %
2198 self.gitdir, file=sys.stderr)
2172 try: 2199 try:
2173 shutil.rmtree(os.path.realpath(self.gitdir)) 2200 shutil.rmtree(os.path.realpath(self.gitdir))
2174 if self.worktree and os.path.exists( 2201 if self.worktree and os.path.exists(os.path.realpath
2175 os.path.realpath(self.worktree)): 2202 (self.worktree)):
2176 shutil.rmtree(os.path.realpath(self.worktree)) 2203 shutil.rmtree(os.path.realpath(self.worktree))
2177 return self._InitGitDir(mirror_git=mirror_git, force_sync=False) 2204 return self._InitGitDir(mirror_git=mirror_git, force_sync=False)
2178 except: 2205 except:
@@ -2231,7 +2258,7 @@ class Project(object):
2231 name = os.path.basename(stock_hook) 2258 name = os.path.basename(stock_hook)
2232 2259
2233 if name in ('commit-msg',) and not self.remote.review \ 2260 if name in ('commit-msg',) and not self.remote.review \
2234 and not self is self.manifest.manifestProject: 2261 and self is not self.manifest.manifestProject:
2235 # Don't install a Gerrit Code Review hook if this 2262 # Don't install a Gerrit Code Review hook if this
2236 # project does not appear to use it for reviews. 2263 # project does not appear to use it for reviews.
2237 # 2264 #
@@ -2246,7 +2273,8 @@ class Project(object):
2246 if filecmp.cmp(stock_hook, dst, shallow=False): 2273 if filecmp.cmp(stock_hook, dst, shallow=False):
2247 os.remove(dst) 2274 os.remove(dst)
2248 else: 2275 else:
2249 _warn("%s: Not replacing locally modified %s hook", self.relpath, name) 2276 _warn("%s: Not replacing locally modified %s hook",
2277 self.relpath, name)
2250 continue 2278 continue
2251 try: 2279 try:
2252 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst) 2280 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
@@ -2292,8 +2320,8 @@ class Project(object):
2292 self.bare_git.symbolic_ref('-m', msg, ref, dst) 2320 self.bare_git.symbolic_ref('-m', msg, ref, dst)
2293 2321
2294 def _CheckDirReference(self, srcdir, destdir, share_refs): 2322 def _CheckDirReference(self, srcdir, destdir, share_refs):
2295 symlink_files = self.shareable_files 2323 symlink_files = self.shareable_files[:]
2296 symlink_dirs = self.shareable_dirs 2324 symlink_dirs = self.shareable_dirs[:]
2297 if share_refs: 2325 if share_refs:
2298 symlink_files += self.working_tree_files 2326 symlink_files += self.working_tree_files
2299 symlink_dirs += self.working_tree_dirs 2327 symlink_dirs += self.working_tree_dirs
@@ -2321,8 +2349,8 @@ class Project(object):
2321 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|. 2349 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2322 This saves you the effort of initializing |dotgit| yourself. 2350 This saves you the effort of initializing |dotgit| yourself.
2323 """ 2351 """
2324 symlink_files = self.shareable_files 2352 symlink_files = self.shareable_files[:]
2325 symlink_dirs = self.shareable_dirs 2353 symlink_dirs = self.shareable_dirs[:]
2326 if share_refs: 2354 if share_refs:
2327 symlink_files += self.working_tree_files 2355 symlink_files += self.working_tree_files
2328 symlink_dirs += self.working_tree_dirs 2356 symlink_dirs += self.working_tree_dirs
@@ -2414,7 +2442,7 @@ class Project(object):
2414 def _allrefs(self): 2442 def _allrefs(self):
2415 return self.bare_ref.all 2443 return self.bare_ref.all
2416 2444
2417 def _getLogs(self, rev1, rev2, oneline=False, color=True): 2445 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
2418 """Get logs between two revisions of this project.""" 2446 """Get logs between two revisions of this project."""
2419 comp = '..' 2447 comp = '..'
2420 if rev1: 2448 if rev1:
@@ -2425,6 +2453,8 @@ class Project(object):
2425 out = DiffColoring(self.config) 2453 out = DiffColoring(self.config)
2426 if out.is_on and color: 2454 if out.is_on and color:
2427 cmd.append('--color') 2455 cmd.append('--color')
2456 if pretty_format is not None:
2457 cmd.append('--pretty=format:%s' % pretty_format)
2428 if oneline: 2458 if oneline:
2429 cmd.append('--oneline') 2459 cmd.append('--oneline')
2430 2460
@@ -2441,17 +2471,21 @@ class Project(object):
2441 raise 2471 raise
2442 return None 2472 return None
2443 2473
2444 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True): 2474 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2475 pretty_format=None):
2445 """Get the list of logs from this revision to given revisionId""" 2476 """Get the list of logs from this revision to given revisionId"""
2446 logs = {} 2477 logs = {}
2447 selfId = self.GetRevisionId(self._allrefs) 2478 selfId = self.GetRevisionId(self._allrefs)
2448 toId = toProject.GetRevisionId(toProject._allrefs) 2479 toId = toProject.GetRevisionId(toProject._allrefs)
2449 2480
2450 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color) 2481 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2451 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color) 2482 pretty_format=pretty_format)
2483 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2484 pretty_format=pretty_format)
2452 return logs 2485 return logs
2453 2486
2454 class _GitGetByExec(object): 2487 class _GitGetByExec(object):
2488
2455 def __init__(self, project, bare, gitdir): 2489 def __init__(self, project, bare, gitdir):
2456 self._project = project 2490 self._project = project
2457 self._bare = bare 2491 self._bare = bare
@@ -2470,8 +2504,8 @@ class Project(object):
2470 if p.Wait() == 0: 2504 if p.Wait() == 0:
2471 out = p.stdout 2505 out = p.stdout
2472 if out: 2506 if out:
2507 # Backslash is not anomalous
2473 return out[:-1].split('\0') # pylint: disable=W1401 2508 return out[:-1].split('\0') # pylint: disable=W1401
2474 # Backslash is not anomalous
2475 return [] 2509 return []
2476 2510
2477 def DiffZ(self, name, *args): 2511 def DiffZ(self, name, *args):
@@ -2497,6 +2531,7 @@ class Project(object):
2497 break 2531 break
2498 2532
2499 class _Info(object): 2533 class _Info(object):
2534
2500 def __init__(self, path, omode, nmode, oid, nid, state): 2535 def __init__(self, path, omode, nmode, oid, nid, state):
2501 self.path = path 2536 self.path = path
2502 self.src_path = None 2537 self.src_path = None
@@ -2599,10 +2634,8 @@ class Project(object):
2599 line = line[:-1] 2634 line = line[:-1]
2600 r.append(line) 2635 r.append(line)
2601 if p.Wait() != 0: 2636 if p.Wait() != 0:
2602 raise GitError('%s rev-list %s: %s' % ( 2637 raise GitError('%s rev-list %s: %s' %
2603 self._project.name, 2638 (self._project.name, str(args), p.stderr))
2604 str(args),
2605 p.stderr))
2606 return r 2639 return r
2607 2640
2608 def __getattr__(self, name): 2641 def __getattr__(self, name):
@@ -2625,6 +2658,7 @@ class Project(object):
2625 A callable object that will try to call git with the named command. 2658 A callable object that will try to call git with the named command.
2626 """ 2659 """
2627 name = name.replace('_', '-') 2660 name = name.replace('_', '-')
2661
2628 def runner(*args, **kwargs): 2662 def runner(*args, **kwargs):
2629 cmdv = [] 2663 cmdv = []
2630 config = kwargs.pop('config', None) 2664 config = kwargs.pop('config', None)
@@ -2647,10 +2681,8 @@ class Project(object):
2647 capture_stdout=True, 2681 capture_stdout=True,
2648 capture_stderr=True) 2682 capture_stderr=True)
2649 if p.Wait() != 0: 2683 if p.Wait() != 0:
2650 raise GitError('%s %s: %s' % ( 2684 raise GitError('%s %s: %s' %
2651 self._project.name, 2685 (self._project.name, name, p.stderr))
2652 name,
2653 p.stderr))
2654 r = p.stdout 2686 r = p.stdout
2655 try: 2687 try:
2656 r = r.decode('utf-8') 2688 r = r.decode('utf-8')
@@ -2663,14 +2695,19 @@ class Project(object):
2663 2695
2664 2696
2665class _PriorSyncFailedError(Exception): 2697class _PriorSyncFailedError(Exception):
2698
2666 def __str__(self): 2699 def __str__(self):
2667 return 'prior sync failed; rebase still in progress' 2700 return 'prior sync failed; rebase still in progress'
2668 2701
2702
2669class _DirtyError(Exception): 2703class _DirtyError(Exception):
2704
2670 def __str__(self): 2705 def __str__(self):
2671 return 'contains uncommitted changes' 2706 return 'contains uncommitted changes'
2672 2707
2708
2673class _InfoMessage(object): 2709class _InfoMessage(object):
2710
2674 def __init__(self, project, text): 2711 def __init__(self, project, text):
2675 self.project = project 2712 self.project = project
2676 self.text = text 2713 self.text = text
@@ -2679,7 +2716,9 @@ class _InfoMessage(object):
2679 syncbuf.out.info('%s/: %s', self.project.relpath, self.text) 2716 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2680 syncbuf.out.nl() 2717 syncbuf.out.nl()
2681 2718
2719
2682class _Failure(object): 2720class _Failure(object):
2721
2683 def __init__(self, project, why): 2722 def __init__(self, project, why):
2684 self.project = project 2723 self.project = project
2685 self.why = why 2724 self.why = why
@@ -2690,7 +2729,9 @@ class _Failure(object):
2690 str(self.why)) 2729 str(self.why))
2691 syncbuf.out.nl() 2730 syncbuf.out.nl()
2692 2731
2732
2693class _Later(object): 2733class _Later(object):
2734
2694 def __init__(self, project, action): 2735 def __init__(self, project, action):
2695 self.project = project 2736 self.project = project
2696 self.action = action 2737 self.action = action
@@ -2707,14 +2748,18 @@ class _Later(object):
2707 out.nl() 2748 out.nl()
2708 return False 2749 return False
2709 2750
2751
2710class _SyncColoring(Coloring): 2752class _SyncColoring(Coloring):
2753
2711 def __init__(self, config): 2754 def __init__(self, config):
2712 Coloring.__init__(self, config, 'reposync') 2755 Coloring.__init__(self, config, 'reposync')
2713 self.project = self.printer('header', attr='bold') 2756 self.project = self.printer('header', attr='bold')
2714 self.info = self.printer('info') 2757 self.info = self.printer('info')
2715 self.fail = self.printer('fail', fg='red') 2758 self.fail = self.printer('fail', fg='red')
2716 2759
2760
2717class SyncBuffer(object): 2761class SyncBuffer(object):
2762
2718 def __init__(self, config, detach_head=False): 2763 def __init__(self, config, detach_head=False):
2719 self._messages = [] 2764 self._messages = []
2720 self._failures = [] 2765 self._failures = []
@@ -2770,8 +2815,10 @@ class SyncBuffer(object):
2770 2815
2771 2816
2772class MetaProject(Project): 2817class MetaProject(Project):
2818
2773 """A special project housed under .repo. 2819 """A special project housed under .repo.
2774 """ 2820 """
2821
2775 def __init__(self, manifest, name, gitdir, worktree): 2822 def __init__(self, manifest, name, gitdir, worktree):
2776 Project.__init__(self, 2823 Project.__init__(self,
2777 manifest=manifest, 2824 manifest=manifest,
@@ -2805,10 +2852,9 @@ class MetaProject(Project):
2805 syncbuf.Finish() 2852 syncbuf.Finish()
2806 2853
2807 return GitCommand(self, 2854 return GitCommand(self,
2808 ['update-ref', '-d', 'refs/heads/default'], 2855 ['update-ref', '-d', 'refs/heads/default'],
2809 capture_stdout=True, 2856 capture_stdout=True,
2810 capture_stderr=True).Wait() == 0 2857 capture_stderr=True).Wait() == 0
2811
2812 2858
2813 @property 2859 @property
2814 def LastFetch(self): 2860 def LastFetch(self):