summaryrefslogtreecommitdiffstats
path: root/project.py
diff options
context:
space:
mode:
Diffstat (limited to 'project.py')
-rw-r--r--project.py786
1 files changed, 502 insertions, 284 deletions
diff --git a/project.py b/project.py
index 023cf732..868425ce 100644
--- a/project.py
+++ b/project.py
@@ -13,9 +13,10 @@
13# limitations under the License. 13# limitations under the License.
14 14
15from __future__ import print_function 15from __future__ import print_function
16import traceback 16import contextlib
17import errno 17import errno
18import filecmp 18import filecmp
19import glob
19import os 20import os
20import random 21import random
21import re 22import re
@@ -26,11 +27,12 @@ import sys
26import tarfile 27import tarfile
27import tempfile 28import tempfile
28import time 29import time
30import traceback
29 31
30from color import Coloring 32from color import Coloring
31from git_command import GitCommand, git_require 33from git_command import GitCommand, git_require
32from git_config import GitConfig, IsId, GetSchemeFromUrl, ID_RE 34from git_config import GitConfig, IsId, GetSchemeFromUrl, ID_RE
33from error import GitError, HookError, UploadError 35from error import GitError, HookError, UploadError, DownloadError
34from error import ManifestInvalidRevisionError 36from error import ManifestInvalidRevisionError
35from error import NoManifestException 37from error import NoManifestException
36from trace import IsTrace, Trace 38from trace import IsTrace, Trace
@@ -46,7 +48,7 @@ if not is_python3():
46def _lwrite(path, content): 48def _lwrite(path, content):
47 lock = '%s.lock' % path 49 lock = '%s.lock' % path
48 50
49 fd = open(lock, 'wb') 51 fd = open(lock, 'w')
50 try: 52 try:
51 fd.write(content) 53 fd.write(content)
52 finally: 54 finally:
@@ -84,7 +86,7 @@ def _ProjectHooks():
84 global _project_hook_list 86 global _project_hook_list
85 if _project_hook_list is None: 87 if _project_hook_list is None:
86 d = os.path.realpath(os.path.abspath(os.path.dirname(__file__))) 88 d = os.path.realpath(os.path.abspath(os.path.dirname(__file__)))
87 d = os.path.join(d , 'hooks') 89 d = os.path.join(d, 'hooks')
88 _project_hook_list = [os.path.join(d, x) for x in os.listdir(d)] 90 _project_hook_list = [os.path.join(d, x) for x in os.listdir(d)]
89 return _project_hook_list 91 return _project_hook_list
90 92
@@ -182,28 +184,28 @@ class ReviewableBranch(object):
182class StatusColoring(Coloring): 184class StatusColoring(Coloring):
183 def __init__(self, config): 185 def __init__(self, config):
184 Coloring.__init__(self, config, 'status') 186 Coloring.__init__(self, config, 'status')
185 self.project = self.printer('header', attr = 'bold') 187 self.project = self.printer('header', attr='bold')
186 self.branch = self.printer('header', attr = 'bold') 188 self.branch = self.printer('header', attr='bold')
187 self.nobranch = self.printer('nobranch', fg = 'red') 189 self.nobranch = self.printer('nobranch', fg='red')
188 self.important = self.printer('important', fg = 'red') 190 self.important = self.printer('important', fg='red')
189 191
190 self.added = self.printer('added', fg = 'green') 192 self.added = self.printer('added', fg='green')
191 self.changed = self.printer('changed', fg = 'red') 193 self.changed = self.printer('changed', fg='red')
192 self.untracked = self.printer('untracked', fg = 'red') 194 self.untracked = self.printer('untracked', fg='red')
193 195
194 196
195class DiffColoring(Coloring): 197class DiffColoring(Coloring):
196 def __init__(self, config): 198 def __init__(self, config):
197 Coloring.__init__(self, config, 'diff') 199 Coloring.__init__(self, config, 'diff')
198 self.project = self.printer('header', attr = 'bold') 200 self.project = self.printer('header', attr='bold')
199 201
200class _Annotation: 202class _Annotation(object):
201 def __init__(self, name, value, keep): 203 def __init__(self, name, value, keep):
202 self.name = name 204 self.name = name
203 self.value = value 205 self.value = value
204 self.keep = keep 206 self.keep = keep
205 207
206class _CopyFile: 208class _CopyFile(object):
207 def __init__(self, src, dest, abssrc, absdest): 209 def __init__(self, src, dest, abssrc, absdest):
208 self.src = src 210 self.src = src
209 self.dest = dest 211 self.dest = dest
@@ -231,14 +233,72 @@ class _CopyFile:
231 except IOError: 233 except IOError:
232 _error('Cannot copy file %s to %s', src, dest) 234 _error('Cannot copy file %s to %s', src, dest)
233 235
236class _LinkFile(object):
237 def __init__(self, git_worktree, src, dest, relsrc, absdest):
238 self.git_worktree = git_worktree
239 self.src = src
240 self.dest = dest
241 self.src_rel_to_dest = relsrc
242 self.abs_dest = absdest
243
244 def __linkIt(self, relSrc, absDest):
245 # link file if it does not exist or is out of date
246 if not os.path.islink(absDest) or (os.readlink(absDest) != relSrc):
247 try:
248 # remove existing file first, since it might be read-only
249 if os.path.exists(absDest):
250 os.remove(absDest)
251 else:
252 dest_dir = os.path.dirname(absDest)
253 if not os.path.isdir(dest_dir):
254 os.makedirs(dest_dir)
255 os.symlink(relSrc, absDest)
256 except IOError:
257 _error('Cannot link file %s to %s', relSrc, absDest)
258
259 def _Link(self):
260 """Link the self.rel_src_to_dest and self.abs_dest. Handles wild cards
261 on the src linking all of the files in the source in to the destination
262 directory.
263 """
264 # We use the absSrc to handle the situation where the current directory
265 # is not the root of the repo
266 absSrc = os.path.join(self.git_worktree, self.src)
267 if os.path.exists(absSrc):
268 # Entity exists so just a simple one to one link operation
269 self.__linkIt(self.src_rel_to_dest, self.abs_dest)
270 else:
271 # Entity doesn't exist assume there is a wild card
272 absDestDir = self.abs_dest
273 if os.path.exists(absDestDir) and not os.path.isdir(absDestDir):
274 _error('Link error: src with wildcard, %s must be a directory',
275 absDestDir)
276 else:
277 absSrcFiles = glob.glob(absSrc)
278 for absSrcFile in absSrcFiles:
279 # Create a releative path from source dir to destination dir
280 absSrcDir = os.path.dirname(absSrcFile)
281 relSrcDir = os.path.relpath(absSrcDir, absDestDir)
282
283 # Get the source file name
284 srcFile = os.path.basename(absSrcFile)
285
286 # Now form the final full paths to srcFile. They will be
287 # absolute for the desintaiton and relative for the srouce.
288 absDest = os.path.join(absDestDir, srcFile)
289 relSrc = os.path.join(relSrcDir, srcFile)
290 self.__linkIt(relSrc, absDest)
291
234class RemoteSpec(object): 292class RemoteSpec(object):
235 def __init__(self, 293 def __init__(self,
236 name, 294 name,
237 url = None, 295 url=None,
238 review = None): 296 review=None,
297 revision=None):
239 self.name = name 298 self.name = name
240 self.url = url 299 self.url = url
241 self.review = review 300 self.review = review
301 self.revision = revision
242 302
243class RepoHook(object): 303class RepoHook(object):
244 """A RepoHook contains information about a script to run as a hook. 304 """A RepoHook contains information about a script to run as a hook.
@@ -414,7 +474,8 @@ class RepoHook(object):
414 # and convert to a HookError w/ just the failing traceback. 474 # and convert to a HookError w/ just the failing traceback.
415 context = {} 475 context = {}
416 try: 476 try:
417 execfile(self._script_fullpath, context) 477 exec(compile(open(self._script_fullpath).read(),
478 self._script_fullpath, 'exec'), context)
418 except Exception: 479 except Exception:
419 raise HookError('%s\nFailed to import %s hook; see traceback above.' % ( 480 raise HookError('%s\nFailed to import %s hook; see traceback above.' % (
420 traceback.format_exc(), self._hook_type)) 481 traceback.format_exc(), self._hook_type))
@@ -483,6 +544,12 @@ class RepoHook(object):
483 544
484 545
485class Project(object): 546class Project(object):
547 # These objects can be shared between several working trees.
548 shareable_files = ['description', 'info']
549 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
550 # These objects can only be used by a single working tree.
551 working_tree_files = ['config', 'packed-refs', 'shallow']
552 working_tree_dirs = ['logs', 'refs']
486 def __init__(self, 553 def __init__(self,
487 manifest, 554 manifest,
488 name, 555 name,
@@ -493,15 +560,16 @@ class Project(object):
493 relpath, 560 relpath,
494 revisionExpr, 561 revisionExpr,
495 revisionId, 562 revisionId,
496 rebase = True, 563 rebase=True,
497 groups = None, 564 groups=None,
498 sync_c = False, 565 sync_c=False,
499 sync_s = False, 566 sync_s=False,
500 clone_depth = None, 567 clone_depth=None,
501 upstream = None, 568 upstream=None,
502 parent = None, 569 parent=None,
503 is_derived = False, 570 is_derived=False,
504 dest_branch = None): 571 dest_branch=None,
572 optimized_fetch=False):
505 """Init a Project object. 573 """Init a Project object.
506 574
507 Args: 575 Args:
@@ -523,6 +591,8 @@ class Project(object):
523 is_derived: False if the project was explicitly defined in the manifest; 591 is_derived: False if the project was explicitly defined in the manifest;
524 True if the project is a discovered submodule. 592 True if the project is a discovered submodule.
525 dest_branch: The branch to which to push changes for review by default. 593 dest_branch: The branch to which to push changes for review by default.
594 optimized_fetch: If True, when a project is set to a sha1 revision, only
595 fetch from the remote if the sha1 is not present locally.
526 """ 596 """
527 self.manifest = manifest 597 self.manifest = manifest
528 self.name = name 598 self.name = name
@@ -551,14 +621,16 @@ class Project(object):
551 self.upstream = upstream 621 self.upstream = upstream
552 self.parent = parent 622 self.parent = parent
553 self.is_derived = is_derived 623 self.is_derived = is_derived
624 self.optimized_fetch = optimized_fetch
554 self.subprojects = [] 625 self.subprojects = []
555 626
556 self.snapshots = {} 627 self.snapshots = {}
557 self.copyfiles = [] 628 self.copyfiles = []
629 self.linkfiles = []
558 self.annotations = [] 630 self.annotations = []
559 self.config = GitConfig.ForRepository( 631 self.config = GitConfig.ForRepository(
560 gitdir = self.gitdir, 632 gitdir=self.gitdir,
561 defaults = self.manifest.globalConfig) 633 defaults=self.manifest.globalConfig)
562 634
563 if self.worktree: 635 if self.worktree:
564 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir) 636 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
@@ -579,7 +651,7 @@ class Project(object):
579 651
580 @property 652 @property
581 def Exists(self): 653 def Exists(self):
582 return os.path.isdir(self.gitdir) 654 return os.path.isdir(self.gitdir) and os.path.isdir(self.objdir)
583 655
584 @property 656 @property
585 def CurrentBranch(self): 657 def CurrentBranch(self):
@@ -708,27 +780,49 @@ class Project(object):
708 return matched 780 return matched
709 781
710## Status Display ## 782## Status Display ##
783 def UncommitedFiles(self, get_all=True):
784 """Returns a list of strings, uncommitted files in the git tree.
711 785
712 def HasChanges(self): 786 Args:
713 """Returns true if there are uncommitted changes. 787 get_all: a boolean, if True - get information about all different
788 uncommitted files. If False - return as soon as any kind of
789 uncommitted files is detected.
714 """ 790 """
791 details = []
715 self.work_git.update_index('-q', 792 self.work_git.update_index('-q',
716 '--unmerged', 793 '--unmerged',
717 '--ignore-missing', 794 '--ignore-missing',
718 '--refresh') 795 '--refresh')
719 if self.IsRebaseInProgress(): 796 if self.IsRebaseInProgress():
720 return True 797 details.append("rebase in progress")
798 if not get_all:
799 return details
721 800
722 if self.work_git.DiffZ('diff-index', '--cached', HEAD): 801 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
723 return True 802 if changes:
803 details.extend(changes)
804 if not get_all:
805 return details
724 806
725 if self.work_git.DiffZ('diff-files'): 807 changes = self.work_git.DiffZ('diff-files').keys()
726 return True 808 if changes:
809 details.extend(changes)
810 if not get_all:
811 return details
727 812
728 if self.work_git.LsOthers(): 813 changes = self.work_git.LsOthers()
729 return True 814 if changes:
815 details.extend(changes)
730 816
731 return False 817 return details
818
819 def HasChanges(self):
820 """Returns true if there are uncommitted changes.
821 """
822 if self.UncommitedFiles(get_all=False):
823 return True
824 else:
825 return False
732 826
733 def PrintWorkTreeStatus(self, output_redir=None): 827 def PrintWorkTreeStatus(self, output_redir=None):
734 """Prints the status of the repository to stdout. 828 """Prints the status of the repository to stdout.
@@ -758,7 +852,7 @@ class Project(object):
758 out = StatusColoring(self.config) 852 out = StatusColoring(self.config)
759 if not output_redir == None: 853 if not output_redir == None:
760 out.redirect(output_redir) 854 out.redirect(output_redir)
761 out.project('project %-40s', self.relpath + '/') 855 out.project('project %-40s', self.relpath + '/ ')
762 856
763 branch = self.CurrentBranch 857 branch = self.CurrentBranch
764 if branch is None: 858 if branch is None:
@@ -829,8 +923,8 @@ class Project(object):
829 cmd.append('--') 923 cmd.append('--')
830 p = GitCommand(self, 924 p = GitCommand(self,
831 cmd, 925 cmd,
832 capture_stdout = True, 926 capture_stdout=True,
833 capture_stderr = True) 927 capture_stderr=True)
834 has_diff = False 928 has_diff = False
835 for line in p.process.stdout: 929 for line in p.process.stdout:
836 if not has_diff: 930 if not has_diff:
@@ -915,7 +1009,7 @@ class Project(object):
915 return None 1009 return None
916 1010
917 def UploadForReview(self, branch=None, 1011 def UploadForReview(self, branch=None,
918 people=([],[]), 1012 people=([], []),
919 auto_topic=False, 1013 auto_topic=False,
920 draft=False, 1014 draft=False,
921 dest_branch=None): 1015 dest_branch=None):
@@ -976,13 +1070,13 @@ class Project(object):
976 ref_spec = ref_spec + '%' + ','.join(rp) 1070 ref_spec = ref_spec + '%' + ','.join(rp)
977 cmd.append(ref_spec) 1071 cmd.append(ref_spec)
978 1072
979 if GitCommand(self, cmd, bare = True).Wait() != 0: 1073 if GitCommand(self, cmd, bare=True).Wait() != 0:
980 raise UploadError('Upload failed') 1074 raise UploadError('Upload failed')
981 1075
982 msg = "posted to %s for %s" % (branch.remote.review, dest_branch) 1076 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
983 self.bare_git.UpdateRef(R_PUB + branch.name, 1077 self.bare_git.UpdateRef(R_PUB + branch.name,
984 R_HEADS + branch.name, 1078 R_HEADS + branch.name,
985 message = msg) 1079 message=msg)
986 1080
987 1081
988## Sync ## 1082## Sync ##
@@ -1007,9 +1101,11 @@ class Project(object):
1007 quiet=False, 1101 quiet=False,
1008 is_new=None, 1102 is_new=None,
1009 current_branch_only=False, 1103 current_branch_only=False,
1104 force_sync=False,
1010 clone_bundle=True, 1105 clone_bundle=True,
1011 no_tags=False, 1106 no_tags=False,
1012 archive=False): 1107 archive=False,
1108 optimized_fetch=False):
1013 """Perform only the network IO portion of the sync process. 1109 """Perform only the network IO portion of the sync process.
1014 Local working directory/branch state is not affected. 1110 Local working directory/branch state is not affected.
1015 """ 1111 """
@@ -1040,13 +1136,12 @@ class Project(object):
1040 except OSError as e: 1136 except OSError as e:
1041 print("warn: Cannot remove archive %s: " 1137 print("warn: Cannot remove archive %s: "
1042 "%s" % (tarpath, str(e)), file=sys.stderr) 1138 "%s" % (tarpath, str(e)), file=sys.stderr)
1043 self._CopyFiles() 1139 self._CopyAndLinkFiles()
1044 return True 1140 return True
1045
1046 if is_new is None: 1141 if is_new is None:
1047 is_new = not self.Exists 1142 is_new = not self.Exists
1048 if is_new: 1143 if is_new:
1049 self._InitGitDir() 1144 self._InitGitDir(force_sync=force_sync)
1050 else: 1145 else:
1051 self._UpdateHooks() 1146 self._UpdateHooks()
1052 self._InitRemote() 1147 self._InitRemote()
@@ -1078,16 +1173,12 @@ class Project(object):
1078 elif self.manifest.default.sync_c: 1173 elif self.manifest.default.sync_c:
1079 current_branch_only = True 1174 current_branch_only = True
1080 1175
1081 is_sha1 = False 1176 need_to_fetch = not (optimized_fetch and \
1082 if ID_RE.match(self.revisionExpr) is not None: 1177 (ID_RE.match(self.revisionExpr) and self._CheckForSha1()))
1083 is_sha1 = True 1178 if (need_to_fetch
1084 if is_sha1 and self._CheckForSha1(): 1179 and not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1085 # Don't need to fetch since we already have this revision 1180 current_branch_only=current_branch_only,
1086 return True 1181 no_tags=no_tags)):
1087
1088 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1089 current_branch_only=current_branch_only,
1090 no_tags=no_tags):
1091 return False 1182 return False
1092 1183
1093 if self.worktree: 1184 if self.worktree:
@@ -1103,9 +1194,11 @@ class Project(object):
1103 def PostRepoUpgrade(self): 1194 def PostRepoUpgrade(self):
1104 self._InitHooks() 1195 self._InitHooks()
1105 1196
1106 def _CopyFiles(self): 1197 def _CopyAndLinkFiles(self):
1107 for copyfile in self.copyfiles: 1198 for copyfile in self.copyfiles:
1108 copyfile._Copy() 1199 copyfile._Copy()
1200 for linkfile in self.linkfiles:
1201 linkfile._Link()
1109 1202
1110 def GetCommitRevisionId(self): 1203 def GetCommitRevisionId(self):
1111 """Get revisionId of a commit. 1204 """Get revisionId of a commit.
@@ -1141,18 +1234,18 @@ class Project(object):
1141 'revision %s in %s not found' % (self.revisionExpr, 1234 'revision %s in %s not found' % (self.revisionExpr,
1142 self.name)) 1235 self.name))
1143 1236
1144 def Sync_LocalHalf(self, syncbuf): 1237 def Sync_LocalHalf(self, syncbuf, force_sync=False):
1145 """Perform only the local IO portion of the sync process. 1238 """Perform only the local IO portion of the sync process.
1146 Network access is not required. 1239 Network access is not required.
1147 """ 1240 """
1148 self._InitWorkTree() 1241 self._InitWorkTree(force_sync=force_sync)
1149 all_refs = self.bare_ref.all 1242 all_refs = self.bare_ref.all
1150 self.CleanPublishedCache(all_refs) 1243 self.CleanPublishedCache(all_refs)
1151 revid = self.GetRevisionId(all_refs) 1244 revid = self.GetRevisionId(all_refs)
1152 1245
1153 def _doff(): 1246 def _doff():
1154 self._FastForward(revid) 1247 self._FastForward(revid)
1155 self._CopyFiles() 1248 self._CopyAndLinkFiles()
1156 1249
1157 head = self.work_git.GetHead() 1250 head = self.work_git.GetHead()
1158 if head.startswith(R_HEADS): 1251 if head.startswith(R_HEADS):
@@ -1188,7 +1281,7 @@ class Project(object):
1188 except GitError as e: 1281 except GitError as e:
1189 syncbuf.fail(self, e) 1282 syncbuf.fail(self, e)
1190 return 1283 return
1191 self._CopyFiles() 1284 self._CopyAndLinkFiles()
1192 return 1285 return
1193 1286
1194 if head == revid: 1287 if head == revid:
@@ -1210,7 +1303,7 @@ class Project(object):
1210 except GitError as e: 1303 except GitError as e:
1211 syncbuf.fail(self, e) 1304 syncbuf.fail(self, e)
1212 return 1305 return
1213 self._CopyFiles() 1306 self._CopyAndLinkFiles()
1214 return 1307 return
1215 1308
1216 upstream_gain = self._revlist(not_rev(HEAD), revid) 1309 upstream_gain = self._revlist(not_rev(HEAD), revid)
@@ -1278,17 +1371,19 @@ class Project(object):
1278 if not ID_RE.match(self.revisionExpr): 1371 if not ID_RE.match(self.revisionExpr):
1279 # in case of manifest sync the revisionExpr might be a SHA1 1372 # in case of manifest sync the revisionExpr might be a SHA1
1280 branch.merge = self.revisionExpr 1373 branch.merge = self.revisionExpr
1374 if not branch.merge.startswith('refs/'):
1375 branch.merge = R_HEADS + branch.merge
1281 branch.Save() 1376 branch.Save()
1282 1377
1283 if cnt_mine > 0 and self.rebase: 1378 if cnt_mine > 0 and self.rebase:
1284 def _dorebase(): 1379 def _dorebase():
1285 self._Rebase(upstream = '%s^1' % last_mine, onto = revid) 1380 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
1286 self._CopyFiles() 1381 self._CopyAndLinkFiles()
1287 syncbuf.later2(self, _dorebase) 1382 syncbuf.later2(self, _dorebase)
1288 elif local_changes: 1383 elif local_changes:
1289 try: 1384 try:
1290 self._ResetHard(revid) 1385 self._ResetHard(revid)
1291 self._CopyFiles() 1386 self._CopyAndLinkFiles()
1292 except GitError as e: 1387 except GitError as e:
1293 syncbuf.fail(self, e) 1388 syncbuf.fail(self, e)
1294 return 1389 return
@@ -1301,6 +1396,13 @@ class Project(object):
1301 abssrc = os.path.join(self.worktree, src) 1396 abssrc = os.path.join(self.worktree, src)
1302 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest)) 1397 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
1303 1398
1399 def AddLinkFile(self, src, dest, absdest):
1400 # dest should already be an absolute path, but src is project relative
1401 # make src relative path to dest
1402 absdestdir = os.path.dirname(absdest)
1403 relsrc = os.path.relpath(os.path.join(self.worktree, src), absdestdir)
1404 self.linkfiles.append(_LinkFile(self.worktree, src, dest, relsrc, absdest))
1405
1304 def AddAnnotation(self, name, value, keep): 1406 def AddAnnotation(self, name, value, keep):
1305 self.annotations.append(_Annotation(name, value, keep)) 1407 self.annotations.append(_Annotation(name, value, keep))
1306 1408
@@ -1331,15 +1433,17 @@ class Project(object):
1331 return True 1433 return True
1332 1434
1333 all_refs = self.bare_ref.all 1435 all_refs = self.bare_ref.all
1334 if (R_HEADS + name) in all_refs: 1436 if R_HEADS + name in all_refs:
1335 return GitCommand(self, 1437 return GitCommand(self,
1336 ['checkout', name, '--'], 1438 ['checkout', name, '--'],
1337 capture_stdout = True, 1439 capture_stdout=True,
1338 capture_stderr = True).Wait() == 0 1440 capture_stderr=True).Wait() == 0
1339 1441
1340 branch = self.GetBranch(name) 1442 branch = self.GetBranch(name)
1341 branch.remote = self.GetRemote(self.remote.name) 1443 branch.remote = self.GetRemote(self.remote.name)
1342 branch.merge = self.revisionExpr 1444 branch.merge = self.revisionExpr
1445 if not branch.merge.startswith('refs/') and not ID_RE.match(self.revisionExpr):
1446 branch.merge = R_HEADS + self.revisionExpr
1343 revid = self.GetRevisionId(all_refs) 1447 revid = self.GetRevisionId(all_refs)
1344 1448
1345 if head.startswith(R_HEADS): 1449 if head.startswith(R_HEADS):
@@ -1362,8 +1466,8 @@ class Project(object):
1362 1466
1363 if GitCommand(self, 1467 if GitCommand(self,
1364 ['checkout', '-b', branch.name, revid], 1468 ['checkout', '-b', branch.name, revid],
1365 capture_stdout = True, 1469 capture_stdout=True,
1366 capture_stderr = True).Wait() == 0: 1470 capture_stderr=True).Wait() == 0:
1367 branch.Save() 1471 branch.Save()
1368 return True 1472 return True
1369 return False 1473 return False
@@ -1409,8 +1513,8 @@ class Project(object):
1409 1513
1410 return GitCommand(self, 1514 return GitCommand(self,
1411 ['checkout', name, '--'], 1515 ['checkout', name, '--'],
1412 capture_stdout = True, 1516 capture_stdout=True,
1413 capture_stderr = True).Wait() == 0 1517 capture_stderr=True).Wait() == 0
1414 1518
1415 def AbandonBranch(self, name): 1519 def AbandonBranch(self, name):
1416 """Destroy a local topic branch. 1520 """Destroy a local topic branch.
@@ -1444,8 +1548,8 @@ class Project(object):
1444 1548
1445 return GitCommand(self, 1549 return GitCommand(self,
1446 ['branch', '-D', name], 1550 ['branch', '-D', name],
1447 capture_stdout = True, 1551 capture_stdout=True,
1448 capture_stderr = True).Wait() == 0 1552 capture_stderr=True).Wait() == 0
1449 1553
1450 def PruneHeads(self): 1554 def PruneHeads(self):
1451 """Prune any topic branches already merged into upstream. 1555 """Prune any topic branches already merged into upstream.
@@ -1462,7 +1566,7 @@ class Project(object):
1462 rev = self.GetRevisionId(left) 1566 rev = self.GetRevisionId(left)
1463 if cb is not None \ 1567 if cb is not None \
1464 and not self._revlist(HEAD + '...' + rev) \ 1568 and not self._revlist(HEAD + '...' + rev) \
1465 and not self.IsDirty(consider_untracked = False): 1569 and not self.IsDirty(consider_untracked=False):
1466 self.work_git.DetachHead(HEAD) 1570 self.work_git.DetachHead(HEAD)
1467 kill.append(cb) 1571 kill.append(cb)
1468 1572
@@ -1495,7 +1599,7 @@ class Project(object):
1495 1599
1496 kept = [] 1600 kept = []
1497 for branch in kill: 1601 for branch in kill:
1498 if (R_HEADS + branch) in left: 1602 if R_HEADS + branch in left:
1499 branch = self.GetBranch(branch) 1603 branch = self.GetBranch(branch)
1500 base = branch.LocalMerge 1604 base = branch.LocalMerge
1501 if not base: 1605 if not base:
@@ -1545,8 +1649,8 @@ class Project(object):
1545 def parse_gitmodules(gitdir, rev): 1649 def parse_gitmodules(gitdir, rev):
1546 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev] 1650 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1547 try: 1651 try:
1548 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True, 1652 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1549 bare = True, gitdir = gitdir) 1653 bare=True, gitdir=gitdir)
1550 except GitError: 1654 except GitError:
1551 return [], [] 1655 return [], []
1552 if p.Wait() != 0: 1656 if p.Wait() != 0:
@@ -1558,8 +1662,8 @@ class Project(object):
1558 os.write(fd, p.stdout) 1662 os.write(fd, p.stdout)
1559 os.close(fd) 1663 os.close(fd)
1560 cmd = ['config', '--file', temp_gitmodules_path, '--list'] 1664 cmd = ['config', '--file', temp_gitmodules_path, '--list']
1561 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True, 1665 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1562 bare = True, gitdir = gitdir) 1666 bare=True, gitdir=gitdir)
1563 if p.Wait() != 0: 1667 if p.Wait() != 0:
1564 return [], [] 1668 return [], []
1565 gitmodules_lines = p.stdout.split('\n') 1669 gitmodules_lines = p.stdout.split('\n')
@@ -1592,8 +1696,8 @@ class Project(object):
1592 cmd = ['ls-tree', rev, '--'] 1696 cmd = ['ls-tree', rev, '--']
1593 cmd.extend(paths) 1697 cmd.extend(paths)
1594 try: 1698 try:
1595 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True, 1699 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1596 bare = True, gitdir = gitdir) 1700 bare=True, gitdir=gitdir)
1597 except GitError: 1701 except GitError:
1598 return [] 1702 return []
1599 if p.Wait() != 0: 1703 if p.Wait() != 0:
@@ -1628,23 +1732,24 @@ class Project(object):
1628 continue 1732 continue
1629 1733
1630 remote = RemoteSpec(self.remote.name, 1734 remote = RemoteSpec(self.remote.name,
1631 url = url, 1735 url=url,
1632 review = self.remote.review) 1736 review=self.remote.review,
1633 subproject = Project(manifest = self.manifest, 1737 revision=self.remote.revision)
1634 name = name, 1738 subproject = Project(manifest=self.manifest,
1635 remote = remote, 1739 name=name,
1636 gitdir = gitdir, 1740 remote=remote,
1637 objdir = objdir, 1741 gitdir=gitdir,
1638 worktree = worktree, 1742 objdir=objdir,
1639 relpath = relpath, 1743 worktree=worktree,
1640 revisionExpr = self.revisionExpr, 1744 relpath=relpath,
1641 revisionId = rev, 1745 revisionExpr=self.revisionExpr,
1642 rebase = self.rebase, 1746 revisionId=rev,
1643 groups = self.groups, 1747 rebase=self.rebase,
1644 sync_c = self.sync_c, 1748 groups=self.groups,
1645 sync_s = self.sync_s, 1749 sync_c=self.sync_c,
1646 parent = self, 1750 sync_s=self.sync_s,
1647 is_derived = True) 1751 parent=self,
1752 is_derived=True)
1648 result.append(subproject) 1753 result.append(subproject)
1649 result.extend(subproject.GetDerivedSubprojects()) 1754 result.extend(subproject.GetDerivedSubprojects())
1650 return result 1755 return result
@@ -1674,6 +1779,7 @@ class Project(object):
1674 if command.Wait() != 0: 1779 if command.Wait() != 0:
1675 raise GitError('git archive %s: %s' % (self.name, command.stderr)) 1780 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1676 1781
1782
1677 def _RemoteFetch(self, name=None, 1783 def _RemoteFetch(self, name=None,
1678 current_branch_only=False, 1784 current_branch_only=False,
1679 initial=False, 1785 initial=False,
@@ -1683,26 +1789,43 @@ class Project(object):
1683 1789
1684 is_sha1 = False 1790 is_sha1 = False
1685 tag_name = None 1791 tag_name = None
1792 depth = None
1793
1794 # The depth should not be used when fetching to a mirror because
1795 # it will result in a shallow repository that cannot be cloned or
1796 # fetched from.
1797 if not self.manifest.IsMirror:
1798 if self.clone_depth:
1799 depth = self.clone_depth
1800 else:
1801 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1802 # The repo project should never be synced with partial depth
1803 if self.relpath == '.repo/repo':
1804 depth = None
1686 1805
1687 if self.clone_depth:
1688 depth = self.clone_depth
1689 else:
1690 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1691 if depth: 1806 if depth:
1692 current_branch_only = True 1807 current_branch_only = True
1693 1808
1809 if ID_RE.match(self.revisionExpr) is not None:
1810 is_sha1 = True
1811
1694 if current_branch_only: 1812 if current_branch_only:
1695 if ID_RE.match(self.revisionExpr) is not None: 1813 if self.revisionExpr.startswith(R_TAGS):
1696 is_sha1 = True
1697 elif self.revisionExpr.startswith(R_TAGS):
1698 # this is a tag and its sha1 value should never change 1814 # this is a tag and its sha1 value should never change
1699 tag_name = self.revisionExpr[len(R_TAGS):] 1815 tag_name = self.revisionExpr[len(R_TAGS):]
1700 1816
1701 if is_sha1 or tag_name is not None: 1817 if is_sha1 or tag_name is not None:
1702 if self._CheckForSha1(): 1818 if self._CheckForSha1():
1703 return True 1819 return True
1704 if is_sha1 and (not self.upstream or ID_RE.match(self.upstream)): 1820 if is_sha1 and not depth:
1705 current_branch_only = False 1821 # When syncing a specific commit and --depth is not set:
1822 # * if upstream is explicitly specified and is not a sha1, fetch only
1823 # upstream as users expect only upstream to be fetch.
1824 # Note: The commit might not be in upstream in which case the sync
1825 # will fail.
1826 # * otherwise, fetch all branches to make sure we end up with the
1827 # specific commit.
1828 current_branch_only = self.upstream and not ID_RE.match(self.upstream)
1706 1829
1707 if not name: 1830 if not name:
1708 name = self.remote.name 1831 name = self.remote.name
@@ -1752,9 +1875,7 @@ class Project(object):
1752 1875
1753 cmd = ['fetch'] 1876 cmd = ['fetch']
1754 1877
1755 # The --depth option only affects the initial fetch; after that we'll do 1878 if depth:
1756 # full fetches of changes.
1757 if depth and initial:
1758 cmd.append('--depth=%s' % depth) 1879 cmd.append('--depth=%s' % depth)
1759 1880
1760 if quiet: 1881 if quiet:
@@ -1763,46 +1884,74 @@ class Project(object):
1763 cmd.append('--update-head-ok') 1884 cmd.append('--update-head-ok')
1764 cmd.append(name) 1885 cmd.append(name)
1765 1886
1887 # If using depth then we should not get all the tags since they may
1888 # be outside of the depth.
1889 if no_tags or depth:
1890 cmd.append('--no-tags')
1891 else:
1892 cmd.append('--tags')
1893
1894 spec = []
1766 if not current_branch_only: 1895 if not current_branch_only:
1767 # Fetch whole repo 1896 # Fetch whole repo
1768 # If using depth then we should not get all the tags since they may 1897 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
1769 # be outside of the depth.
1770 if no_tags or depth:
1771 cmd.append('--no-tags')
1772 else:
1773 cmd.append('--tags')
1774
1775 cmd.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
1776 elif tag_name is not None: 1898 elif tag_name is not None:
1777 cmd.append('tag') 1899 spec.append('tag')
1778 cmd.append(tag_name) 1900 spec.append(tag_name)
1779 else: 1901
1902 if not self.manifest.IsMirror:
1780 branch = self.revisionExpr 1903 branch = self.revisionExpr
1781 if is_sha1: 1904 if is_sha1 and depth and git_require((1, 8, 3)):
1782 branch = self.upstream 1905 # Shallow checkout of a specific commit, fetch from that commit and not
1783 if branch.startswith(R_HEADS): 1906 # the heads only as the commit might be deeper in the history.
1784 branch = branch[len(R_HEADS):] 1907 spec.append(branch)
1785 cmd.append(str((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch))) 1908 else:
1909 if is_sha1:
1910 branch = self.upstream
1911 if branch is not None and branch.strip():
1912 if not branch.startswith('refs/'):
1913 branch = R_HEADS + branch
1914 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
1915 cmd.extend(spec)
1916
1917 shallowfetch = self.config.GetString('repo.shallowfetch')
1918 if shallowfetch and shallowfetch != ' '.join(spec):
1919 GitCommand(self, ['fetch', '--depth=2147483647', name]
1920 + shallowfetch.split(),
1921 bare=True, ssh_proxy=ssh_proxy).Wait()
1922 if depth:
1923 self.config.SetString('repo.shallowfetch', ' '.join(spec))
1924 else:
1925 self.config.SetString('repo.shallowfetch', None)
1786 1926
1787 ok = False 1927 ok = False
1788 for _i in range(2): 1928 for _i in range(2):
1789 ret = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait() 1929 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy)
1930 ret = gitcmd.Wait()
1790 if ret == 0: 1931 if ret == 0:
1791 ok = True 1932 ok = True
1792 break 1933 break
1934 # If needed, run the 'git remote prune' the first time through the loop
1935 elif (not _i and
1936 "error:" in gitcmd.stderr and
1937 "git remote prune" in gitcmd.stderr):
1938 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
1939 ssh_proxy=ssh_proxy)
1940 ret = prunecmd.Wait()
1941 if ret:
1942 break
1943 continue
1793 elif current_branch_only and is_sha1 and ret == 128: 1944 elif current_branch_only and is_sha1 and ret == 128:
1794 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1 1945 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1795 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus 1946 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1796 # abort the optimization attempt and do a full sync. 1947 # abort the optimization attempt and do a full sync.
1797 break 1948 break
1949 elif ret < 0:
1950 # Git died with a signal, exit immediately
1951 break
1798 time.sleep(random.randint(30, 45)) 1952 time.sleep(random.randint(30, 45))
1799 1953
1800 if initial: 1954 if initial:
1801 # Ensure that some refs exist. Otherwise, we probably aren't looking
1802 # at a real git repository and may have a bad url.
1803 if not self.bare_ref.all:
1804 ok = False
1805
1806 if alt_dir: 1955 if alt_dir:
1807 if old_packed != '': 1956 if old_packed != '':
1808 _lwrite(packed_refs, old_packed) 1957 _lwrite(packed_refs, old_packed)
@@ -1815,8 +1964,15 @@ class Project(object):
1815 # got what we wanted, else trigger a second run of all 1964 # got what we wanted, else trigger a second run of all
1816 # refs. 1965 # refs.
1817 if not self._CheckForSha1(): 1966 if not self._CheckForSha1():
1818 return self._RemoteFetch(name=name, current_branch_only=False, 1967 if not depth:
1819 initial=False, quiet=quiet, alt_dir=alt_dir) 1968 # Avoid infinite recursion when depth is True (since depth implies
1969 # current_branch_only)
1970 return self._RemoteFetch(name=name, current_branch_only=False,
1971 initial=False, quiet=quiet, alt_dir=alt_dir)
1972 if self.clone_depth:
1973 self.clone_depth = None
1974 return self._RemoteFetch(name=name, current_branch_only=current_branch_only,
1975 initial=False, quiet=quiet, alt_dir=alt_dir)
1820 1976
1821 return ok 1977 return ok
1822 1978
@@ -1877,34 +2033,34 @@ class Project(object):
1877 os.remove(tmpPath) 2033 os.remove(tmpPath)
1878 if 'http_proxy' in os.environ and 'darwin' == sys.platform: 2034 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1879 cmd += ['--proxy', os.environ['http_proxy']] 2035 cmd += ['--proxy', os.environ['http_proxy']]
1880 cookiefile = self._GetBundleCookieFile(srcUrl) 2036 with self._GetBundleCookieFile(srcUrl, quiet) as cookiefile:
1881 if cookiefile: 2037 if cookiefile:
1882 cmd += ['--cookie', cookiefile] 2038 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
1883 if srcUrl.startswith('persistent-'): 2039 if srcUrl.startswith('persistent-'):
1884 srcUrl = srcUrl[len('persistent-'):] 2040 srcUrl = srcUrl[len('persistent-'):]
1885 cmd += [srcUrl] 2041 cmd += [srcUrl]
1886 2042
1887 if IsTrace(): 2043 if IsTrace():
1888 Trace('%s', ' '.join(cmd)) 2044 Trace('%s', ' '.join(cmd))
1889 try: 2045 try:
1890 proc = subprocess.Popen(cmd) 2046 proc = subprocess.Popen(cmd)
1891 except OSError: 2047 except OSError:
1892 return False 2048 return False
1893 2049
1894 curlret = proc.wait() 2050 curlret = proc.wait()
1895 2051
1896 if curlret == 22: 2052 if curlret == 22:
1897 # From curl man page: 2053 # From curl man page:
1898 # 22: HTTP page not retrieved. The requested url was not found or 2054 # 22: HTTP page not retrieved. The requested url was not found or
1899 # returned another error with the HTTP error code being 400 or above. 2055 # returned another error with the HTTP error code being 400 or above.
1900 # This return code only appears if -f, --fail is used. 2056 # This return code only appears if -f, --fail is used.
1901 if not quiet: 2057 if not quiet:
1902 print("Server does not provide clone.bundle; ignoring.", 2058 print("Server does not provide clone.bundle; ignoring.",
1903 file=sys.stderr) 2059 file=sys.stderr)
1904 return False 2060 return False
1905 2061
1906 if os.path.exists(tmpPath): 2062 if os.path.exists(tmpPath):
1907 if curlret == 0 and self._IsValidBundle(tmpPath): 2063 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
1908 os.rename(tmpPath, dstPath) 2064 os.rename(tmpPath, dstPath)
1909 return True 2065 return True
1910 else: 2066 else:
@@ -1913,45 +2069,51 @@ class Project(object):
1913 else: 2069 else:
1914 return False 2070 return False
1915 2071
1916 def _IsValidBundle(self, path): 2072 def _IsValidBundle(self, path, quiet):
1917 try: 2073 try:
1918 with open(path) as f: 2074 with open(path) as f:
1919 if f.read(16) == '# v2 git bundle\n': 2075 if f.read(16) == '# v2 git bundle\n':
1920 return True 2076 return True
1921 else: 2077 else:
1922 print("Invalid clone.bundle file; ignoring.", file=sys.stderr) 2078 if not quiet:
2079 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
1923 return False 2080 return False
1924 except OSError: 2081 except OSError:
1925 return False 2082 return False
1926 2083
1927 def _GetBundleCookieFile(self, url): 2084 @contextlib.contextmanager
2085 def _GetBundleCookieFile(self, url, quiet):
1928 if url.startswith('persistent-'): 2086 if url.startswith('persistent-'):
1929 try: 2087 try:
1930 p = subprocess.Popen( 2088 p = subprocess.Popen(
1931 ['git-remote-persistent-https', '-print_config', url], 2089 ['git-remote-persistent-https', '-print_config', url],
1932 stdin=subprocess.PIPE, stdout=subprocess.PIPE, 2090 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1933 stderr=subprocess.PIPE) 2091 stderr=subprocess.PIPE)
1934 p.stdin.close() # Tell subprocess it's ok to close. 2092 try:
1935 prefix = 'http.cookiefile=' 2093 prefix = 'http.cookiefile='
1936 cookiefile = None 2094 cookiefile = None
1937 for line in p.stdout: 2095 for line in p.stdout:
1938 line = line.strip() 2096 line = line.strip()
1939 if line.startswith(prefix): 2097 if line.startswith(prefix):
1940 cookiefile = line[len(prefix):] 2098 cookiefile = line[len(prefix):]
1941 break 2099 break
1942 if p.wait(): 2100 # Leave subprocess open, as cookie file may be transient.
1943 err_msg = p.stderr.read() 2101 if cookiefile:
1944 if ' -print_config' in err_msg: 2102 yield cookiefile
1945 pass # Persistent proxy doesn't support -print_config. 2103 return
1946 else: 2104 finally:
1947 print(err_msg, file=sys.stderr) 2105 p.stdin.close()
1948 if cookiefile: 2106 if p.wait():
1949 return cookiefile 2107 err_msg = p.stderr.read()
2108 if ' -print_config' in err_msg:
2109 pass # Persistent proxy doesn't support -print_config.
2110 elif not quiet:
2111 print(err_msg, file=sys.stderr)
1950 except OSError as e: 2112 except OSError as e:
1951 if e.errno == errno.ENOENT: 2113 if e.errno == errno.ENOENT:
1952 pass # No persistent proxy. 2114 pass # No persistent proxy.
1953 raise 2115 raise
1954 return GitConfig.ForUser().GetString('http.cookiefile') 2116 yield GitConfig.ForUser().GetString('http.cookiefile')
1955 2117
1956 def _Checkout(self, rev, quiet=False): 2118 def _Checkout(self, rev, quiet=False):
1957 cmd = ['checkout'] 2119 cmd = ['checkout']
@@ -1963,7 +2125,7 @@ class Project(object):
1963 if self._allrefs: 2125 if self._allrefs:
1964 raise GitError('%s checkout %s ' % (self.name, rev)) 2126 raise GitError('%s checkout %s ' % (self.name, rev))
1965 2127
1966 def _CherryPick(self, rev, quiet=False): 2128 def _CherryPick(self, rev):
1967 cmd = ['cherry-pick'] 2129 cmd = ['cherry-pick']
1968 cmd.append(rev) 2130 cmd.append(rev)
1969 cmd.append('--') 2131 cmd.append('--')
@@ -1971,7 +2133,7 @@ class Project(object):
1971 if self._allrefs: 2133 if self._allrefs:
1972 raise GitError('%s cherry-pick %s ' % (self.name, rev)) 2134 raise GitError('%s cherry-pick %s ' % (self.name, rev))
1973 2135
1974 def _Revert(self, rev, quiet=False): 2136 def _Revert(self, rev):
1975 cmd = ['revert'] 2137 cmd = ['revert']
1976 cmd.append('--no-edit') 2138 cmd.append('--no-edit')
1977 cmd.append(rev) 2139 cmd.append(rev)
@@ -1988,7 +2150,7 @@ class Project(object):
1988 if GitCommand(self, cmd).Wait() != 0: 2150 if GitCommand(self, cmd).Wait() != 0:
1989 raise GitError('%s reset --hard %s ' % (self.name, rev)) 2151 raise GitError('%s reset --hard %s ' % (self.name, rev))
1990 2152
1991 def _Rebase(self, upstream, onto = None): 2153 def _Rebase(self, upstream, onto=None):
1992 cmd = ['rebase'] 2154 cmd = ['rebase']
1993 if onto is not None: 2155 if onto is not None:
1994 cmd.extend(['--onto', onto]) 2156 cmd.extend(['--onto', onto])
@@ -2003,64 +2165,80 @@ class Project(object):
2003 if GitCommand(self, cmd).Wait() != 0: 2165 if GitCommand(self, cmd).Wait() != 0:
2004 raise GitError('%s merge %s ' % (self.name, head)) 2166 raise GitError('%s merge %s ' % (self.name, head))
2005 2167
2006 def _InitGitDir(self, mirror_git=None): 2168 def _InitGitDir(self, mirror_git=None, force_sync=False):
2007 if not os.path.exists(self.gitdir): 2169 init_git_dir = not os.path.exists(self.gitdir)
2008 2170 init_obj_dir = not os.path.exists(self.objdir)
2171 try:
2009 # Initialize the bare repository, which contains all of the objects. 2172 # Initialize the bare repository, which contains all of the objects.
2010 if not os.path.exists(self.objdir): 2173 if init_obj_dir:
2011 os.makedirs(self.objdir) 2174 os.makedirs(self.objdir)
2012 self.bare_objdir.init() 2175 self.bare_objdir.init()
2013 2176
2014 # If we have a separate directory to hold refs, initialize it as well. 2177 # If we have a separate directory to hold refs, initialize it as well.
2015 if self.objdir != self.gitdir: 2178 if self.objdir != self.gitdir:
2016 os.makedirs(self.gitdir) 2179 if init_git_dir:
2017 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False, 2180 os.makedirs(self.gitdir)
2018 copy_all=True)
2019 2181
2020 mp = self.manifest.manifestProject 2182 if init_obj_dir or init_git_dir:
2021 ref_dir = mp.config.GetString('repo.reference') or '' 2183 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2184 copy_all=True)
2185 try:
2186 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2187 except GitError as e:
2188 print("Retrying clone after deleting %s" % force_sync, file=sys.stderr)
2189 if force_sync:
2190 try:
2191 shutil.rmtree(os.path.realpath(self.gitdir))
2192 if self.worktree and os.path.exists(
2193 os.path.realpath(self.worktree)):
2194 shutil.rmtree(os.path.realpath(self.worktree))
2195 return self._InitGitDir(mirror_git=mirror_git, force_sync=False)
2196 except:
2197 raise e
2198 raise e
2199
2200 if init_git_dir:
2201 mp = self.manifest.manifestProject
2202 ref_dir = mp.config.GetString('repo.reference') or ''
2203
2204 if ref_dir or mirror_git:
2205 if not mirror_git:
2206 mirror_git = os.path.join(ref_dir, self.name + '.git')
2207 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2208 self.relpath + '.git')
2209
2210 if os.path.exists(mirror_git):
2211 ref_dir = mirror_git
2212
2213 elif os.path.exists(repo_git):
2214 ref_dir = repo_git
2022 2215
2023 if ref_dir or mirror_git: 2216 else:
2024 if not mirror_git: 2217 ref_dir = None
2025 mirror_git = os.path.join(ref_dir, self.name + '.git')
2026 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2027 self.relpath + '.git')
2028 2218
2029 if os.path.exists(mirror_git): 2219 if ref_dir:
2030 ref_dir = mirror_git 2220 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2221 os.path.join(ref_dir, 'objects') + '\n')
2031 2222
2032 elif os.path.exists(repo_git): 2223 self._UpdateHooks()
2033 ref_dir = repo_git
2034 2224
2225 m = self.manifest.manifestProject.config
2226 for key in ['user.name', 'user.email']:
2227 if m.Has(key, include_defaults=False):
2228 self.config.SetString(key, m.GetString(key))
2229 if self.manifest.IsMirror:
2230 self.config.SetString('core.bare', 'true')
2035 else: 2231 else:
2036 ref_dir = None 2232 self.config.SetString('core.bare', None)
2037 2233 except Exception:
2038 if ref_dir: 2234 if init_obj_dir and os.path.exists(self.objdir):
2039 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'), 2235 shutil.rmtree(self.objdir)
2040 os.path.join(ref_dir, 'objects') + '\n') 2236 if init_git_dir and os.path.exists(self.gitdir):
2041 2237 shutil.rmtree(self.gitdir)
2042 self._UpdateHooks() 2238 raise
2043
2044 m = self.manifest.manifestProject.config
2045 for key in ['user.name', 'user.email']:
2046 if m.Has(key, include_defaults = False):
2047 self.config.SetString(key, m.GetString(key))
2048 if self.manifest.IsMirror:
2049 self.config.SetString('core.bare', 'true')
2050 else:
2051 self.config.SetString('core.bare', None)
2052 2239
2053 def _UpdateHooks(self): 2240 def _UpdateHooks(self):
2054 if os.path.exists(self.gitdir): 2241 if os.path.exists(self.gitdir):
2055 # Always recreate hooks since they can have been changed
2056 # since the latest update.
2057 hooks = self._gitdir_path('hooks')
2058 try:
2059 to_rm = os.listdir(hooks)
2060 except OSError:
2061 to_rm = []
2062 for old_hook in to_rm:
2063 os.remove(os.path.join(hooks, old_hook))
2064 self._InitHooks() 2242 self._InitHooks()
2065 2243
2066 def _InitHooks(self): 2244 def _InitHooks(self):
@@ -2123,7 +2301,7 @@ class Project(object):
2123 if cur != '' or self.bare_ref.get(ref) != self.revisionId: 2301 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2124 msg = 'manifest set to %s' % self.revisionId 2302 msg = 'manifest set to %s' % self.revisionId
2125 dst = self.revisionId + '^0' 2303 dst = self.revisionId + '^0'
2126 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True) 2304 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
2127 else: 2305 else:
2128 remote = self.GetRemote(self.remote.name) 2306 remote = self.GetRemote(self.remote.name)
2129 dst = remote.ToLocal(self.revisionExpr) 2307 dst = remote.ToLocal(self.revisionExpr)
@@ -2131,6 +2309,22 @@ class Project(object):
2131 msg = 'manifest set to %s' % self.revisionExpr 2309 msg = 'manifest set to %s' % self.revisionExpr
2132 self.bare_git.symbolic_ref('-m', msg, ref, dst) 2310 self.bare_git.symbolic_ref('-m', msg, ref, dst)
2133 2311
2312 def _CheckDirReference(self, srcdir, destdir, share_refs):
2313 symlink_files = self.shareable_files
2314 symlink_dirs = self.shareable_dirs
2315 if share_refs:
2316 symlink_files += self.working_tree_files
2317 symlink_dirs += self.working_tree_dirs
2318 to_symlink = symlink_files + symlink_dirs
2319 for name in set(to_symlink):
2320 dst = os.path.realpath(os.path.join(destdir, name))
2321 if os.path.lexists(dst):
2322 src = os.path.realpath(os.path.join(srcdir, name))
2323 # Fail if the links are pointing to the wrong place
2324 if src != dst:
2325 raise GitError('--force-sync not enabled; cannot overwrite a local '
2326 'work tree')
2327
2134 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all): 2328 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2135 """Update |dotgit| to reference |gitdir|, using symlinks where possible. 2329 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2136 2330
@@ -2142,13 +2336,11 @@ class Project(object):
2142 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|. 2336 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2143 This saves you the effort of initializing |dotgit| yourself. 2337 This saves you the effort of initializing |dotgit| yourself.
2144 """ 2338 """
2145 # These objects can be shared between several working trees. 2339 symlink_files = self.shareable_files
2146 symlink_files = ['description', 'info'] 2340 symlink_dirs = self.shareable_dirs
2147 symlink_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
2148 if share_refs: 2341 if share_refs:
2149 # These objects can only be used by a single working tree. 2342 symlink_files += self.working_tree_files
2150 symlink_files += ['config', 'packed-refs'] 2343 symlink_dirs += self.working_tree_dirs
2151 symlink_dirs += ['logs', 'refs']
2152 to_symlink = symlink_files + symlink_dirs 2344 to_symlink = symlink_files + symlink_dirs
2153 2345
2154 to_copy = [] 2346 to_copy = []
@@ -2160,13 +2352,21 @@ class Project(object):
2160 src = os.path.realpath(os.path.join(gitdir, name)) 2352 src = os.path.realpath(os.path.join(gitdir, name))
2161 dst = os.path.realpath(os.path.join(dotgit, name)) 2353 dst = os.path.realpath(os.path.join(dotgit, name))
2162 2354
2163 if os.path.lexists(dst) and not os.path.islink(dst): 2355 if os.path.lexists(dst):
2164 raise GitError('cannot overwrite a local work tree') 2356 continue
2165 2357
2166 # If the source dir doesn't exist, create an empty dir. 2358 # If the source dir doesn't exist, create an empty dir.
2167 if name in symlink_dirs and not os.path.lexists(src): 2359 if name in symlink_dirs and not os.path.lexists(src):
2168 os.makedirs(src) 2360 os.makedirs(src)
2169 2361
2362 # If the source file doesn't exist, ensure the destination
2363 # file doesn't either.
2364 if name in symlink_files and not os.path.lexists(src):
2365 try:
2366 os.remove(dst)
2367 except OSError:
2368 pass
2369
2170 if name in to_symlink: 2370 if name in to_symlink:
2171 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst) 2371 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2172 elif copy_all and not os.path.islink(dst): 2372 elif copy_all and not os.path.islink(dst):
@@ -2176,26 +2376,44 @@ class Project(object):
2176 shutil.copy(src, dst) 2376 shutil.copy(src, dst)
2177 except OSError as e: 2377 except OSError as e:
2178 if e.errno == errno.EPERM: 2378 if e.errno == errno.EPERM:
2179 raise GitError('filesystem must support symlinks') 2379 raise DownloadError('filesystem must support symlinks')
2180 else: 2380 else:
2181 raise 2381 raise
2182 2382
2183 def _InitWorkTree(self): 2383 def _InitWorkTree(self, force_sync=False):
2184 dotgit = os.path.join(self.worktree, '.git') 2384 dotgit = os.path.join(self.worktree, '.git')
2185 if not os.path.exists(dotgit): 2385 init_dotgit = not os.path.exists(dotgit)
2186 os.makedirs(dotgit) 2386 try:
2187 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True, 2387 if init_dotgit:
2188 copy_all=False) 2388 os.makedirs(dotgit)
2189 2389 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2190 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId()) 2390 copy_all=False)
2191
2192 cmd = ['read-tree', '--reset', '-u']
2193 cmd.append('-v')
2194 cmd.append(HEAD)
2195 if GitCommand(self, cmd).Wait() != 0:
2196 raise GitError("cannot initialize work tree")
2197 2391
2198 self._CopyFiles() 2392 try:
2393 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2394 except GitError as e:
2395 if force_sync:
2396 try:
2397 shutil.rmtree(dotgit)
2398 return self._InitWorkTree(force_sync=False)
2399 except:
2400 raise e
2401 raise e
2402
2403 if init_dotgit:
2404 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
2405
2406 cmd = ['read-tree', '--reset', '-u']
2407 cmd.append('-v')
2408 cmd.append(HEAD)
2409 if GitCommand(self, cmd).Wait() != 0:
2410 raise GitError("cannot initialize work tree")
2411
2412 self._CopyAndLinkFiles()
2413 except Exception:
2414 if init_dotgit:
2415 shutil.rmtree(dotgit)
2416 raise
2199 2417
2200 def _gitdir_path(self, path): 2418 def _gitdir_path(self, path):
2201 return os.path.realpath(os.path.join(self.gitdir, path)) 2419 return os.path.realpath(os.path.join(self.gitdir, path))
@@ -2259,10 +2477,10 @@ class Project(object):
2259 '-z', 2477 '-z',
2260 '--others', 2478 '--others',
2261 '--exclude-standard'], 2479 '--exclude-standard'],
2262 bare = False, 2480 bare=False,
2263 gitdir=self._gitdir, 2481 gitdir=self._gitdir,
2264 capture_stdout = True, 2482 capture_stdout=True,
2265 capture_stderr = True) 2483 capture_stderr=True)
2266 if p.Wait() == 0: 2484 if p.Wait() == 0:
2267 out = p.stdout 2485 out = p.stdout
2268 if out: 2486 if out:
@@ -2277,9 +2495,9 @@ class Project(object):
2277 p = GitCommand(self._project, 2495 p = GitCommand(self._project,
2278 cmd, 2496 cmd,
2279 gitdir=self._gitdir, 2497 gitdir=self._gitdir,
2280 bare = False, 2498 bare=False,
2281 capture_stdout = True, 2499 capture_stdout=True,
2282 capture_stderr = True) 2500 capture_stderr=True)
2283 try: 2501 try:
2284 out = p.process.stdout.read() 2502 out = p.process.stdout.read()
2285 r = {} 2503 r = {}
@@ -2287,8 +2505,8 @@ class Project(object):
2287 out = iter(out[:-1].split('\0')) # pylint: disable=W1401 2505 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
2288 while out: 2506 while out:
2289 try: 2507 try:
2290 info = out.next() 2508 info = next(out)
2291 path = out.next() 2509 path = next(out)
2292 except StopIteration: 2510 except StopIteration:
2293 break 2511 break
2294 2512
@@ -2314,7 +2532,7 @@ class Project(object):
2314 info = _Info(path, *info) 2532 info = _Info(path, *info)
2315 if info.status in ('R', 'C'): 2533 if info.status in ('R', 'C'):
2316 info.src_path = info.path 2534 info.src_path = info.path
2317 info.path = out.next() 2535 info.path = next(out)
2318 r[info.path] = info 2536 r[info.path] = info
2319 return r 2537 return r
2320 finally: 2538 finally:
@@ -2385,10 +2603,10 @@ class Project(object):
2385 cmdv.extend(args) 2603 cmdv.extend(args)
2386 p = GitCommand(self._project, 2604 p = GitCommand(self._project,
2387 cmdv, 2605 cmdv,
2388 bare = self._bare, 2606 bare=self._bare,
2389 gitdir=self._gitdir, 2607 gitdir=self._gitdir,
2390 capture_stdout = True, 2608 capture_stdout=True,
2391 capture_stderr = True) 2609 capture_stderr=True)
2392 r = [] 2610 r = []
2393 for line in p.process.stdout: 2611 for line in p.process.stdout:
2394 if line[-1] == '\n': 2612 if line[-1] == '\n':
@@ -2438,10 +2656,10 @@ class Project(object):
2438 cmdv.extend(args) 2656 cmdv.extend(args)
2439 p = GitCommand(self._project, 2657 p = GitCommand(self._project,
2440 cmdv, 2658 cmdv,
2441 bare = self._bare, 2659 bare=self._bare,
2442 gitdir=self._gitdir, 2660 gitdir=self._gitdir,
2443 capture_stdout = True, 2661 capture_stdout=True,
2444 capture_stderr = True) 2662 capture_stderr=True)
2445 if p.Wait() != 0: 2663 if p.Wait() != 0:
2446 raise GitError('%s %s: %s' % ( 2664 raise GitError('%s %s: %s' % (
2447 self._project.name, 2665 self._project.name,
@@ -2506,9 +2724,9 @@ class _Later(object):
2506class _SyncColoring(Coloring): 2724class _SyncColoring(Coloring):
2507 def __init__(self, config): 2725 def __init__(self, config):
2508 Coloring.__init__(self, config, 'reposync') 2726 Coloring.__init__(self, config, 'reposync')
2509 self.project = self.printer('header', attr = 'bold') 2727 self.project = self.printer('header', attr='bold')
2510 self.info = self.printer('info') 2728 self.info = self.printer('info')
2511 self.fail = self.printer('fail', fg='red') 2729 self.fail = self.printer('fail', fg='red')
2512 2730
2513class SyncBuffer(object): 2731class SyncBuffer(object):
2514 def __init__(self, config, detach_head=False): 2732 def __init__(self, config, detach_head=False):
@@ -2570,16 +2788,16 @@ class MetaProject(Project):
2570 """ 2788 """
2571 def __init__(self, manifest, name, gitdir, worktree): 2789 def __init__(self, manifest, name, gitdir, worktree):
2572 Project.__init__(self, 2790 Project.__init__(self,
2573 manifest = manifest, 2791 manifest=manifest,
2574 name = name, 2792 name=name,
2575 gitdir = gitdir, 2793 gitdir=gitdir,
2576 objdir = gitdir, 2794 objdir=gitdir,
2577 worktree = worktree, 2795 worktree=worktree,
2578 remote = RemoteSpec('origin'), 2796 remote=RemoteSpec('origin'),
2579 relpath = '.repo/%s' % name, 2797 relpath='.repo/%s' % name,
2580 revisionExpr = 'refs/heads/master', 2798 revisionExpr='refs/heads/master',
2581 revisionId = None, 2799 revisionId=None,
2582 groups = None) 2800 groups=None)
2583 2801
2584 def PreSync(self): 2802 def PreSync(self):
2585 if self.Exists: 2803 if self.Exists:
@@ -2590,20 +2808,20 @@ class MetaProject(Project):
2590 self.revisionExpr = base 2808 self.revisionExpr = base
2591 self.revisionId = None 2809 self.revisionId = None
2592 2810
2593 def MetaBranchSwitch(self, target): 2811 def MetaBranchSwitch(self):
2594 """ Prepare MetaProject for manifest branch switch 2812 """ Prepare MetaProject for manifest branch switch
2595 """ 2813 """
2596 2814
2597 # detach and delete manifest branch, allowing a new 2815 # detach and delete manifest branch, allowing a new
2598 # branch to take over 2816 # branch to take over
2599 syncbuf = SyncBuffer(self.config, detach_head = True) 2817 syncbuf = SyncBuffer(self.config, detach_head=True)
2600 self.Sync_LocalHalf(syncbuf) 2818 self.Sync_LocalHalf(syncbuf)
2601 syncbuf.Finish() 2819 syncbuf.Finish()
2602 2820
2603 return GitCommand(self, 2821 return GitCommand(self,
2604 ['update-ref', '-d', 'refs/heads/default'], 2822 ['update-ref', '-d', 'refs/heads/default'],
2605 capture_stdout = True, 2823 capture_stdout=True,
2606 capture_stderr = True).Wait() == 0 2824 capture_stderr=True).Wait() == 0
2607 2825
2608 2826
2609 @property 2827 @property