summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--command.py26
-rw-r--r--manifest_xml.py68
-rw-r--r--project.py185
-rwxr-xr-xrepo29
-rw-r--r--subcmds/init.py21
-rw-r--r--subcmds/rebase.py3
-rw-r--r--subcmds/sync.py65
-rw-r--r--subcmds/upload.py4
8 files changed, 303 insertions, 98 deletions
diff --git a/command.py b/command.py
index 287f4d30..207ef46b 100644
--- a/command.py
+++ b/command.py
@@ -129,7 +129,7 @@ class Command(object):
129 def GetProjects(self, args, missing_ok=False, submodules_ok=False): 129 def GetProjects(self, args, missing_ok=False, submodules_ok=False):
130 """A list of projects that match the arguments. 130 """A list of projects that match the arguments.
131 """ 131 """
132 all_projects = self.manifest.projects 132 all_projects_list = self.manifest.projects
133 result = [] 133 result = []
134 134
135 mp = self.manifest.manifestProject 135 mp = self.manifest.manifestProject
@@ -140,7 +140,6 @@ class Command(object):
140 groups = [x for x in re.split(r'[,\s]+', groups) if x] 140 groups = [x for x in re.split(r'[,\s]+', groups) if x]
141 141
142 if not args: 142 if not args:
143 all_projects_list = list(all_projects.values())
144 derived_projects = {} 143 derived_projects = {}
145 for project in all_projects_list: 144 for project in all_projects_list:
146 if submodules_ok or project.sync_s: 145 if submodules_ok or project.sync_s:
@@ -152,12 +151,12 @@ class Command(object):
152 project.MatchesGroups(groups)): 151 project.MatchesGroups(groups)):
153 result.append(project) 152 result.append(project)
154 else: 153 else:
155 self._ResetPathToProjectMap(all_projects.values()) 154 self._ResetPathToProjectMap(all_projects_list)
156 155
157 for arg in args: 156 for arg in args:
158 project = all_projects.get(arg) 157 projects = self.manifest.GetProjectsWithName(arg)
159 158
160 if not project: 159 if not projects:
161 path = os.path.abspath(arg).replace('\\', '/') 160 path = os.path.abspath(arg).replace('\\', '/')
162 project = self._GetProjectByPath(path) 161 project = self._GetProjectByPath(path)
163 162
@@ -172,14 +171,19 @@ class Command(object):
172 if search_again: 171 if search_again:
173 project = self._GetProjectByPath(path) or project 172 project = self._GetProjectByPath(path) or project
174 173
175 if not project: 174 if project:
176 raise NoSuchProjectError(arg) 175 projects = [project]
177 if not missing_ok and not project.Exists: 176
177 if not projects:
178 raise NoSuchProjectError(arg) 178 raise NoSuchProjectError(arg)
179 if not project.MatchesGroups(groups):
180 raise InvalidProjectGroupsError(arg)
181 179
182 result.append(project) 180 for project in projects:
181 if not missing_ok and not project.Exists:
182 raise NoSuchProjectError(arg)
183 if not project.MatchesGroups(groups):
184 raise InvalidProjectGroupsError(arg)
185
186 result.extend(projects)
183 187
184 def _getpath(x): 188 def _getpath(x):
185 return x.relpath 189 return x.relpath
diff --git a/manifest_xml.py b/manifest_xml.py
index 457d5ab0..c5f3bcc9 100644
--- a/manifest_xml.py
+++ b/manifest_xml.py
@@ -215,8 +215,9 @@ class XmlManifest(object):
215 root.appendChild(doc.createTextNode('')) 215 root.appendChild(doc.createTextNode(''))
216 216
217 def output_projects(parent, parent_node, projects): 217 def output_projects(parent, parent_node, projects):
218 for p in projects: 218 for project_name in projects:
219 output_project(parent, parent_node, self.projects[p]) 219 for project in self._projects[project_name]:
220 output_project(parent, parent_node, project)
220 221
221 def output_project(parent, parent_node, p): 222 def output_project(parent, parent_node, p):
222 if not p.MatchesGroups(groups): 223 if not p.MatchesGroups(groups):
@@ -277,13 +278,11 @@ class XmlManifest(object):
277 e.setAttribute('sync-s', 'true') 278 e.setAttribute('sync-s', 'true')
278 279
279 if p.subprojects: 280 if p.subprojects:
280 sort_projects = list(sorted([subp.name for subp in p.subprojects])) 281 subprojects = set(subp.name for subp in p.subprojects)
281 output_projects(p, e, sort_projects) 282 output_projects(p, e, list(sorted(subprojects)))
282 283
283 sort_projects = list(sorted([key for key, value in self.projects.items() 284 projects = set(p.name for p in self._paths.values() if not p.parent)
284 if not value.parent])) 285 output_projects(None, root, list(sorted(projects)))
285 sort_projects.sort()
286 output_projects(None, root, sort_projects)
287 286
288 if self._repo_hooks_project: 287 if self._repo_hooks_project:
289 root.appendChild(doc.createTextNode('')) 288 root.appendChild(doc.createTextNode(''))
@@ -296,9 +295,14 @@ class XmlManifest(object):
296 doc.writexml(fd, '', ' ', '\n', 'UTF-8') 295 doc.writexml(fd, '', ' ', '\n', 'UTF-8')
297 296
298 @property 297 @property
298 def paths(self):
299 self._Load()
300 return self._paths
301
302 @property
299 def projects(self): 303 def projects(self):
300 self._Load() 304 self._Load()
301 return self._projects 305 return self._paths.values()
302 306
303 @property 307 @property
304 def remotes(self): 308 def remotes(self):
@@ -329,9 +333,14 @@ class XmlManifest(object):
329 def IsMirror(self): 333 def IsMirror(self):
330 return self.manifestProject.config.GetBoolean('repo.mirror') 334 return self.manifestProject.config.GetBoolean('repo.mirror')
331 335
336 @property
337 def IsArchive(self):
338 return self.manifestProject.config.GetBoolean('repo.archive')
339
332 def _Unload(self): 340 def _Unload(self):
333 self._loaded = False 341 self._loaded = False
334 self._projects = {} 342 self._projects = {}
343 self._paths = {}
335 self._remotes = {} 344 self._remotes = {}
336 self._default = None 345 self._default = None
337 self._repo_hooks_project = None 346 self._repo_hooks_project = None
@@ -463,11 +472,17 @@ class XmlManifest(object):
463 self._manifest_server = url 472 self._manifest_server = url
464 473
465 def recursively_add_projects(project): 474 def recursively_add_projects(project):
466 if self._projects.get(project.name): 475 projects = self._projects.setdefault(project.name, [])
476 if project.relpath is None:
467 raise ManifestParseError( 477 raise ManifestParseError(
468 'duplicate project %s in %s' % 478 'missing path for %s in %s' %
469 (project.name, self.manifestFile)) 479 (project.name, self.manifestFile))
470 self._projects[project.name] = project 480 if project.relpath in self._paths:
481 raise ManifestParseError(
482 'duplicate path %s in %s' %
483 (project.relpath, self.manifestFile))
484 self._paths[project.relpath] = project
485 projects.append(project)
471 for subproject in project.subprojects: 486 for subproject in project.subprojects:
472 recursively_add_projects(subproject) 487 recursively_add_projects(subproject)
473 488
@@ -488,12 +503,18 @@ class XmlManifest(object):
488 503
489 # Store a reference to the Project. 504 # Store a reference to the Project.
490 try: 505 try:
491 self._repo_hooks_project = self._projects[repo_hooks_project] 506 repo_hooks_projects = self._projects[repo_hooks_project]
492 except KeyError: 507 except KeyError:
493 raise ManifestParseError( 508 raise ManifestParseError(
494 'project %s not found for repo-hooks' % 509 'project %s not found for repo-hooks' %
495 (repo_hooks_project)) 510 (repo_hooks_project))
496 511
512 if len(repo_hooks_projects) != 1:
513 raise ManifestParseError(
514 'internal error parsing repo-hooks in %s' %
515 (self.manifestFile))
516 self._repo_hooks_project = repo_hooks_projects[0]
517
497 # Store the enabled hooks in the Project object. 518 # Store the enabled hooks in the Project object.
498 self._repo_hooks_project.enabled_repo_hooks = enabled_repo_hooks 519 self._repo_hooks_project.enabled_repo_hooks = enabled_repo_hooks
499 if node.nodeName == 'remove-project': 520 if node.nodeName == 'remove-project':
@@ -540,11 +561,12 @@ class XmlManifest(object):
540 name = name, 561 name = name,
541 remote = remote.ToRemoteSpec(name), 562 remote = remote.ToRemoteSpec(name),
542 gitdir = gitdir, 563 gitdir = gitdir,
564 objdir = gitdir,
543 worktree = None, 565 worktree = None,
544 relpath = None, 566 relpath = None,
545 revisionExpr = m.revisionExpr, 567 revisionExpr = m.revisionExpr,
546 revisionId = None) 568 revisionId = None)
547 self._projects[project.name] = project 569 self._projects[project.name] = [project]
548 570
549 def _ParseRemote(self, node): 571 def _ParseRemote(self, node):
550 """ 572 """
@@ -704,9 +726,10 @@ class XmlManifest(object):
704 groups = [x for x in re.split(r'[,\s]+', groups) if x] 726 groups = [x for x in re.split(r'[,\s]+', groups) if x]
705 727
706 if parent is None: 728 if parent is None:
707 relpath, worktree, gitdir = self.GetProjectPaths(name, path) 729 relpath, worktree, gitdir, objdir = self.GetProjectPaths(name, path)
708 else: 730 else:
709 relpath, worktree, gitdir = self.GetSubprojectPaths(parent, path) 731 relpath, worktree, gitdir, objdir = \
732 self.GetSubprojectPaths(parent, name, path)
710 733
711 default_groups = ['all', 'name:%s' % name, 'path:%s' % relpath] 734 default_groups = ['all', 'name:%s' % name, 'path:%s' % relpath]
712 groups.extend(set(default_groups).difference(groups)) 735 groups.extend(set(default_groups).difference(groups))
@@ -719,6 +742,7 @@ class XmlManifest(object):
719 name = name, 742 name = name,
720 remote = remote.ToRemoteSpec(name), 743 remote = remote.ToRemoteSpec(name),
721 gitdir = gitdir, 744 gitdir = gitdir,
745 objdir = objdir,
722 worktree = worktree, 746 worktree = worktree,
723 relpath = relpath, 747 relpath = relpath,
724 revisionExpr = revisionExpr, 748 revisionExpr = revisionExpr,
@@ -747,10 +771,15 @@ class XmlManifest(object):
747 if self.IsMirror: 771 if self.IsMirror:
748 worktree = None 772 worktree = None
749 gitdir = os.path.join(self.topdir, '%s.git' % name) 773 gitdir = os.path.join(self.topdir, '%s.git' % name)
774 objdir = gitdir
750 else: 775 else:
751 worktree = os.path.join(self.topdir, path).replace('\\', '/') 776 worktree = os.path.join(self.topdir, path).replace('\\', '/')
752 gitdir = os.path.join(self.repodir, 'projects', '%s.git' % path) 777 gitdir = os.path.join(self.repodir, 'projects', '%s.git' % path)
753 return relpath, worktree, gitdir 778 objdir = os.path.join(self.repodir, 'project-objects', '%s.git' % name)
779 return relpath, worktree, gitdir, objdir
780
781 def GetProjectsWithName(self, name):
782 return self._projects.get(name, [])
754 783
755 def GetSubprojectName(self, parent, submodule_path): 784 def GetSubprojectName(self, parent, submodule_path):
756 return os.path.join(parent.name, submodule_path) 785 return os.path.join(parent.name, submodule_path)
@@ -761,14 +790,15 @@ class XmlManifest(object):
761 def _UnjoinRelpath(self, parent_relpath, relpath): 790 def _UnjoinRelpath(self, parent_relpath, relpath):
762 return os.path.relpath(relpath, parent_relpath) 791 return os.path.relpath(relpath, parent_relpath)
763 792
764 def GetSubprojectPaths(self, parent, path): 793 def GetSubprojectPaths(self, parent, name, path):
765 relpath = self._JoinRelpath(parent.relpath, path) 794 relpath = self._JoinRelpath(parent.relpath, path)
766 gitdir = os.path.join(parent.gitdir, 'subprojects', '%s.git' % path) 795 gitdir = os.path.join(parent.gitdir, 'subprojects', '%s.git' % path)
796 objdir = os.path.join(parent.gitdir, 'subproject-objects', '%s.git' % name)
767 if self.IsMirror: 797 if self.IsMirror:
768 worktree = None 798 worktree = None
769 else: 799 else:
770 worktree = os.path.join(parent.worktree, path).replace('\\', '/') 800 worktree = os.path.join(parent.worktree, path).replace('\\', '/')
771 return relpath, worktree, gitdir 801 return relpath, worktree, gitdir, objdir
772 802
773 def _ParseCopyFile(self, project, node): 803 def _ParseCopyFile(self, project, node):
774 src = self._reqatt(node, 'src') 804 src = self._reqatt(node, 'src')
diff --git a/project.py b/project.py
index b01a52ba..46f3b8f7 100644
--- a/project.py
+++ b/project.py
@@ -23,6 +23,7 @@ import shutil
23import stat 23import stat
24import subprocess 24import subprocess
25import sys 25import sys
26import tarfile
26import tempfile 27import tempfile
27import time 28import time
28 29
@@ -82,7 +83,7 @@ def _ProjectHooks():
82 """ 83 """
83 global _project_hook_list 84 global _project_hook_list
84 if _project_hook_list is None: 85 if _project_hook_list is None:
85 d = os.path.abspath(os.path.dirname(__file__)) 86 d = os.path.realpath(os.path.abspath(os.path.dirname(__file__)))
86 d = os.path.join(d , 'hooks') 87 d = os.path.join(d , 'hooks')
87 _project_hook_list = [os.path.join(d, x) for x in os.listdir(d)] 88 _project_hook_list = [os.path.join(d, x) for x in os.listdir(d)]
88 return _project_hook_list 89 return _project_hook_list
@@ -487,6 +488,7 @@ class Project(object):
487 name, 488 name,
488 remote, 489 remote,
489 gitdir, 490 gitdir,
491 objdir,
490 worktree, 492 worktree,
491 relpath, 493 relpath,
492 revisionExpr, 494 revisionExpr,
@@ -507,6 +509,7 @@ class Project(object):
507 name: The `name` attribute of manifest.xml's project element. 509 name: The `name` attribute of manifest.xml's project element.
508 remote: RemoteSpec object specifying its remote's properties. 510 remote: RemoteSpec object specifying its remote's properties.
509 gitdir: Absolute path of git directory. 511 gitdir: Absolute path of git directory.
512 objdir: Absolute path of directory to store git objects.
510 worktree: Absolute path of git working tree. 513 worktree: Absolute path of git working tree.
511 relpath: Relative path of git working tree to repo's top directory. 514 relpath: Relative path of git working tree to repo's top directory.
512 revisionExpr: The `revision` attribute of manifest.xml's project element. 515 revisionExpr: The `revision` attribute of manifest.xml's project element.
@@ -525,6 +528,7 @@ class Project(object):
525 self.name = name 528 self.name = name
526 self.remote = remote 529 self.remote = remote
527 self.gitdir = gitdir.replace('\\', '/') 530 self.gitdir = gitdir.replace('\\', '/')
531 self.objdir = objdir.replace('\\', '/')
528 if worktree: 532 if worktree:
529 self.worktree = worktree.replace('\\', '/') 533 self.worktree = worktree.replace('\\', '/')
530 else: 534 else:
@@ -557,11 +561,12 @@ class Project(object):
557 defaults = self.manifest.globalConfig) 561 defaults = self.manifest.globalConfig)
558 562
559 if self.worktree: 563 if self.worktree:
560 self.work_git = self._GitGetByExec(self, bare=False) 564 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
561 else: 565 else:
562 self.work_git = None 566 self.work_git = None
563 self.bare_git = self._GitGetByExec(self, bare=True) 567 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
564 self.bare_ref = GitRefs(gitdir) 568 self.bare_ref = GitRefs(gitdir)
569 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
565 self.dest_branch = dest_branch 570 self.dest_branch = dest_branch
566 571
567 # This will be filled in if a project is later identified to be the 572 # This will be filled in if a project is later identified to be the
@@ -982,15 +987,62 @@ class Project(object):
982 987
983## Sync ## 988## Sync ##
984 989
990 def _ExtractArchive(self, tarpath, path=None):
991 """Extract the given tar on its current location
992
993 Args:
994 - tarpath: The path to the actual tar file
995
996 """
997 try:
998 with tarfile.open(tarpath, 'r') as tar:
999 tar.extractall(path=path)
1000 return True
1001 except (IOError, tarfile.TarError) as e:
1002 print("error: Cannot extract archive %s: "
1003 "%s" % (tarpath, str(e)), file=sys.stderr)
1004 return False
1005
985 def Sync_NetworkHalf(self, 1006 def Sync_NetworkHalf(self,
986 quiet=False, 1007 quiet=False,
987 is_new=None, 1008 is_new=None,
988 current_branch_only=False, 1009 current_branch_only=False,
989 clone_bundle=True, 1010 clone_bundle=True,
990 no_tags=False): 1011 no_tags=False,
1012 archive=False):
991 """Perform only the network IO portion of the sync process. 1013 """Perform only the network IO portion of the sync process.
992 Local working directory/branch state is not affected. 1014 Local working directory/branch state is not affected.
993 """ 1015 """
1016 if archive and not isinstance(self, MetaProject):
1017 if self.remote.url.startswith(('http://', 'https://')):
1018 print("error: %s: Cannot fetch archives from http/https "
1019 "remotes." % self.name, file=sys.stderr)
1020 return False
1021
1022 name = self.relpath.replace('\\', '/')
1023 name = name.replace('/', '_')
1024 tarpath = '%s.tar' % name
1025 topdir = self.manifest.topdir
1026
1027 try:
1028 self._FetchArchive(tarpath, cwd=topdir)
1029 except GitError as e:
1030 print('error: %s' % str(e), file=sys.stderr)
1031 return False
1032
1033 # From now on, we only need absolute tarpath
1034 tarpath = os.path.join(topdir, tarpath)
1035
1036 if not self._ExtractArchive(tarpath, path=topdir):
1037 return False
1038 try:
1039 os.remove(tarpath)
1040 except OSError as e:
1041 print("warn: Cannot remove archive %s: "
1042 "%s" % (tarpath, str(e)), file=sys.stderr)
1043 self._CopyFiles()
1044 return True
1045
994 if is_new is None: 1046 if is_new is None:
995 is_new = not self.Exists 1047 is_new = not self.Exists
996 if is_new: 1048 if is_new:
@@ -1069,6 +1121,7 @@ class Project(object):
1069 """Perform only the local IO portion of the sync process. 1121 """Perform only the local IO portion of the sync process.
1070 Network access is not required. 1122 Network access is not required.
1071 """ 1123 """
1124 self._InitWorkTree()
1072 all_refs = self.bare_ref.all 1125 all_refs = self.bare_ref.all
1073 self.CleanPublishedCache(all_refs) 1126 self.CleanPublishedCache(all_refs)
1074 revid = self.GetRevisionId(all_refs) 1127 revid = self.GetRevisionId(all_refs)
@@ -1077,7 +1130,6 @@ class Project(object):
1077 self._FastForward(revid) 1130 self._FastForward(revid)
1078 self._CopyFiles() 1131 self._CopyFiles()
1079 1132
1080 self._InitWorkTree()
1081 head = self.work_git.GetHead() 1133 head = self.work_git.GetHead()
1082 if head.startswith(R_HEADS): 1134 if head.startswith(R_HEADS):
1083 branch = head[len(R_HEADS):] 1135 branch = head[len(R_HEADS):]
@@ -1544,11 +1596,13 @@ class Project(object):
1544 return result 1596 return result
1545 for rev, path, url in self._GetSubmodules(): 1597 for rev, path, url in self._GetSubmodules():
1546 name = self.manifest.GetSubprojectName(self, path) 1598 name = self.manifest.GetSubprojectName(self, path)
1547 project = self.manifest.projects.get(name) 1599 relpath, worktree, gitdir, objdir = \
1600 self.manifest.GetSubprojectPaths(self, name, path)
1601 project = self.manifest.paths.get(relpath)
1548 if project: 1602 if project:
1549 result.extend(project.GetDerivedSubprojects()) 1603 result.extend(project.GetDerivedSubprojects())
1550 continue 1604 continue
1551 relpath, worktree, gitdir = self.manifest.GetSubprojectPaths(self, path) 1605
1552 remote = RemoteSpec(self.remote.name, 1606 remote = RemoteSpec(self.remote.name,
1553 url = url, 1607 url = url,
1554 review = self.remote.review) 1608 review = self.remote.review)
@@ -1556,6 +1610,7 @@ class Project(object):
1556 name = name, 1610 name = name,
1557 remote = remote, 1611 remote = remote,
1558 gitdir = gitdir, 1612 gitdir = gitdir,
1613 objdir = objdir,
1559 worktree = worktree, 1614 worktree = worktree,
1560 relpath = relpath, 1615 relpath = relpath,
1561 revisionExpr = self.revisionExpr, 1616 revisionExpr = self.revisionExpr,
@@ -1573,6 +1628,19 @@ class Project(object):
1573 1628
1574## Direct Git Commands ## 1629## Direct Git Commands ##
1575 1630
1631 def _FetchArchive(self, tarpath, cwd=None):
1632 cmd = ['archive', '-v', '-o', tarpath]
1633 cmd.append('--remote=%s' % self.remote.url)
1634 cmd.append('--prefix=%s/' % self.relpath)
1635 cmd.append(self.revisionExpr)
1636
1637 command = GitCommand(self, cmd, cwd=cwd,
1638 capture_stdout=True,
1639 capture_stderr=True)
1640
1641 if command.Wait() != 0:
1642 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1643
1576 def _RemoteFetch(self, name=None, 1644 def _RemoteFetch(self, name=None,
1577 current_branch_only=False, 1645 current_branch_only=False,
1578 initial=False, 1646 initial=False,
@@ -1905,8 +1973,17 @@ class Project(object):
1905 1973
1906 def _InitGitDir(self, mirror_git=None): 1974 def _InitGitDir(self, mirror_git=None):
1907 if not os.path.exists(self.gitdir): 1975 if not os.path.exists(self.gitdir):
1908 os.makedirs(self.gitdir) 1976
1909 self.bare_git.init() 1977 # Initialize the bare repository, which contains all of the objects.
1978 if not os.path.exists(self.objdir):
1979 os.makedirs(self.objdir)
1980 self.bare_objdir.init()
1981
1982 # If we have a separate directory to hold refs, initialize it as well.
1983 if self.objdir != self.gitdir:
1984 os.makedirs(self.gitdir)
1985 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
1986 copy_all=True)
1910 1987
1911 mp = self.manifest.manifestProject 1988 mp = self.manifest.manifestProject
1912 ref_dir = mp.config.GetString('repo.reference') or '' 1989 ref_dir = mp.config.GetString('repo.reference') or ''
@@ -1955,7 +2032,7 @@ class Project(object):
1955 self._InitHooks() 2032 self._InitHooks()
1956 2033
1957 def _InitHooks(self): 2034 def _InitHooks(self):
1958 hooks = self._gitdir_path('hooks') 2035 hooks = os.path.realpath(self._gitdir_path('hooks'))
1959 if not os.path.exists(hooks): 2036 if not os.path.exists(hooks):
1960 os.makedirs(hooks) 2037 os.makedirs(hooks)
1961 for stock_hook in _ProjectHooks(): 2038 for stock_hook in _ProjectHooks():
@@ -2022,33 +2099,61 @@ class Project(object):
2022 msg = 'manifest set to %s' % self.revisionExpr 2099 msg = 'manifest set to %s' % self.revisionExpr
2023 self.bare_git.symbolic_ref('-m', msg, ref, dst) 2100 self.bare_git.symbolic_ref('-m', msg, ref, dst)
2024 2101
2102 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2103 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2104
2105 Args:
2106 gitdir: The bare git repository. Must already be initialized.
2107 dotgit: The repository you would like to initialize.
2108 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2109 Only one work tree can store refs under a given |gitdir|.
2110 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2111 This saves you the effort of initializing |dotgit| yourself.
2112 """
2113 # These objects can be shared between several working trees.
2114 symlink_files = ['description', 'info']
2115 symlink_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
2116 if share_refs:
2117 # These objects can only be used by a single working tree.
2118 symlink_files += ['config', 'packed-refs']
2119 symlink_dirs += ['logs', 'refs']
2120 to_symlink = symlink_files + symlink_dirs
2121
2122 to_copy = []
2123 if copy_all:
2124 to_copy = os.listdir(gitdir)
2125
2126 for name in set(to_copy).union(to_symlink):
2127 try:
2128 src = os.path.realpath(os.path.join(gitdir, name))
2129 dst = os.path.realpath(os.path.join(dotgit, name))
2130
2131 if os.path.lexists(dst) and not os.path.islink(dst):
2132 raise GitError('cannot overwrite a local work tree')
2133
2134 # If the source dir doesn't exist, create an empty dir.
2135 if name in symlink_dirs and not os.path.lexists(src):
2136 os.makedirs(src)
2137
2138 if name in to_symlink:
2139 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2140 elif copy_all and not os.path.islink(dst):
2141 if os.path.isdir(src):
2142 shutil.copytree(src, dst)
2143 elif os.path.isfile(src):
2144 shutil.copy(src, dst)
2145 except OSError as e:
2146 if e.errno == errno.EPERM:
2147 raise GitError('filesystem must support symlinks')
2148 else:
2149 raise
2150
2025 def _InitWorkTree(self): 2151 def _InitWorkTree(self):
2026 dotgit = os.path.join(self.worktree, '.git') 2152 dotgit = os.path.join(self.worktree, '.git')
2027 if not os.path.exists(dotgit): 2153 if not os.path.exists(dotgit):
2028 os.makedirs(dotgit) 2154 os.makedirs(dotgit)
2029 2155 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2030 for name in ['config', 2156 copy_all=False)
2031 'description',
2032 'hooks',
2033 'info',
2034 'logs',
2035 'objects',
2036 'packed-refs',
2037 'refs',
2038 'rr-cache',
2039 'svn']:
2040 try:
2041 src = os.path.join(self.gitdir, name)
2042 dst = os.path.join(dotgit, name)
2043 if os.path.islink(dst) or not os.path.exists(dst):
2044 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2045 else:
2046 raise GitError('cannot overwrite a local work tree')
2047 except OSError as e:
2048 if e.errno == errno.EPERM:
2049 raise GitError('filesystem must support symlinks')
2050 else:
2051 raise
2052 2157
2053 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId()) 2158 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
2054 2159
@@ -2058,14 +2163,10 @@ class Project(object):
2058 if GitCommand(self, cmd).Wait() != 0: 2163 if GitCommand(self, cmd).Wait() != 0:
2059 raise GitError("cannot initialize work tree") 2164 raise GitError("cannot initialize work tree")
2060 2165
2061 rr_cache = os.path.join(self.gitdir, 'rr-cache')
2062 if not os.path.exists(rr_cache):
2063 os.makedirs(rr_cache)
2064
2065 self._CopyFiles() 2166 self._CopyFiles()
2066 2167
2067 def _gitdir_path(self, path): 2168 def _gitdir_path(self, path):
2068 return os.path.join(self.gitdir, path) 2169 return os.path.realpath(os.path.join(self.gitdir, path))
2069 2170
2070 def _revlist(self, *args, **kw): 2171 def _revlist(self, *args, **kw):
2071 a = [] 2172 a = []
@@ -2078,9 +2179,10 @@ class Project(object):
2078 return self.bare_ref.all 2179 return self.bare_ref.all
2079 2180
2080 class _GitGetByExec(object): 2181 class _GitGetByExec(object):
2081 def __init__(self, project, bare): 2182 def __init__(self, project, bare, gitdir):
2082 self._project = project 2183 self._project = project
2083 self._bare = bare 2184 self._bare = bare
2185 self._gitdir = gitdir
2084 2186
2085 def LsOthers(self): 2187 def LsOthers(self):
2086 p = GitCommand(self._project, 2188 p = GitCommand(self._project,
@@ -2089,6 +2191,7 @@ class Project(object):
2089 '--others', 2191 '--others',
2090 '--exclude-standard'], 2192 '--exclude-standard'],
2091 bare = False, 2193 bare = False,
2194 gitdir=self._gitdir,
2092 capture_stdout = True, 2195 capture_stdout = True,
2093 capture_stderr = True) 2196 capture_stderr = True)
2094 if p.Wait() == 0: 2197 if p.Wait() == 0:
@@ -2104,6 +2207,7 @@ class Project(object):
2104 cmd.extend(args) 2207 cmd.extend(args)
2105 p = GitCommand(self._project, 2208 p = GitCommand(self._project,
2106 cmd, 2209 cmd,
2210 gitdir=self._gitdir,
2107 bare = False, 2211 bare = False,
2108 capture_stdout = True, 2212 capture_stdout = True,
2109 capture_stderr = True) 2213 capture_stderr = True)
@@ -2213,6 +2317,7 @@ class Project(object):
2213 p = GitCommand(self._project, 2317 p = GitCommand(self._project,
2214 cmdv, 2318 cmdv,
2215 bare = self._bare, 2319 bare = self._bare,
2320 gitdir=self._gitdir,
2216 capture_stdout = True, 2321 capture_stdout = True,
2217 capture_stderr = True) 2322 capture_stderr = True)
2218 r = [] 2323 r = []
@@ -2265,6 +2370,7 @@ class Project(object):
2265 p = GitCommand(self._project, 2370 p = GitCommand(self._project,
2266 cmdv, 2371 cmdv,
2267 bare = self._bare, 2372 bare = self._bare,
2373 gitdir=self._gitdir,
2268 capture_stdout = True, 2374 capture_stdout = True,
2269 capture_stderr = True) 2375 capture_stderr = True)
2270 if p.Wait() != 0: 2376 if p.Wait() != 0:
@@ -2398,6 +2504,7 @@ class MetaProject(Project):
2398 manifest = manifest, 2504 manifest = manifest,
2399 name = name, 2505 name = name,
2400 gitdir = gitdir, 2506 gitdir = gitdir,
2507 objdir = gitdir,
2401 worktree = worktree, 2508 worktree = worktree,
2402 remote = RemoteSpec('origin'), 2509 remote = RemoteSpec('origin'),
2403 relpath = '.repo/%s' % name, 2510 relpath = '.repo/%s' % name,
diff --git a/repo b/repo
index 277bbc6e..56d784fb 100755
--- a/repo
+++ b/repo
@@ -110,6 +110,7 @@ REPO_MAIN = S_repo + '/main.py' # main script
110MIN_PYTHON_VERSION = (2, 6) # minimum supported python version 110MIN_PYTHON_VERSION = (2, 6) # minimum supported python version
111 111
112 112
113import errno
113import optparse 114import optparse
114import os 115import os
115import re 116import re
@@ -180,6 +181,10 @@ group.add_option('--reference',
180group.add_option('--depth', type='int', default=None, 181group.add_option('--depth', type='int', default=None,
181 dest='depth', 182 dest='depth',
182 help='create a shallow clone with given depth; see git clone') 183 help='create a shallow clone with given depth; see git clone')
184group.add_option('--archive',
185 dest='archive', action='store_true',
186 help='checkout an archive instead of a git repository for '
187 'each project. See git archive.')
183group.add_option('-g', '--groups', 188group.add_option('-g', '--groups',
184 dest='groups', default='default', 189 dest='groups', default='default',
185 help='restrict manifest projects to ones with specified ' 190 help='restrict manifest projects to ones with specified '
@@ -239,10 +244,10 @@ def _Init(args):
239 _print("fatal: invalid branch name '%s'" % branch, file=sys.stderr) 244 _print("fatal: invalid branch name '%s'" % branch, file=sys.stderr)
240 raise CloneFailure() 245 raise CloneFailure()
241 246
242 if not os.path.isdir(repodir): 247 try:
243 try: 248 os.mkdir(repodir)
244 os.mkdir(repodir) 249 except OSError as e:
245 except OSError as e: 250 if e.errno != errno.EEXIST:
246 _print('fatal: cannot make %s directory: %s' 251 _print('fatal: cannot make %s directory: %s'
247 % (repodir, e.strerror), file=sys.stderr) 252 % (repodir, e.strerror), file=sys.stderr)
248 # Don't raise CloneFailure; that would delete the 253 # Don't raise CloneFailure; that would delete the
@@ -321,18 +326,18 @@ def NeedSetupGnuPG():
321 326
322 327
323def SetupGnuPG(quiet): 328def SetupGnuPG(quiet):
324 if not os.path.isdir(home_dot_repo): 329 try:
325 try: 330 os.mkdir(home_dot_repo)
326 os.mkdir(home_dot_repo) 331 except OSError as e:
327 except OSError as e: 332 if e.errno != errno.EEXIST:
328 _print('fatal: cannot make %s directory: %s' 333 _print('fatal: cannot make %s directory: %s'
329 % (home_dot_repo, e.strerror), file=sys.stderr) 334 % (home_dot_repo, e.strerror), file=sys.stderr)
330 sys.exit(1) 335 sys.exit(1)
331 336
332 if not os.path.isdir(gpg_dir): 337 try:
333 try: 338 os.mkdir(gpg_dir, stat.S_IRWXU)
334 os.mkdir(gpg_dir, stat.S_IRWXU) 339 except OSError as e:
335 except OSError as e: 340 if e.errno != errno.EEXIST:
336 _print('fatal: cannot make %s directory: %s' % (gpg_dir, e.strerror), 341 _print('fatal: cannot make %s directory: %s' % (gpg_dir, e.strerror),
337 file=sys.stderr) 342 file=sys.stderr)
338 sys.exit(1) 343 sys.exit(1)
diff --git a/subcmds/init.py b/subcmds/init.py
index a44fb7a9..b1fcb69c 100644
--- a/subcmds/init.py
+++ b/subcmds/init.py
@@ -99,6 +99,10 @@ to update the working directory files.
99 g.add_option('--depth', type='int', default=None, 99 g.add_option('--depth', type='int', default=None,
100 dest='depth', 100 dest='depth',
101 help='create a shallow clone with given depth; see git clone') 101 help='create a shallow clone with given depth; see git clone')
102 g.add_option('--archive',
103 dest='archive', action='store_true',
104 help='checkout an archive instead of a git repository for '
105 'each project. See git archive.')
102 g.add_option('-g', '--groups', 106 g.add_option('-g', '--groups',
103 dest='groups', default='default', 107 dest='groups', default='default',
104 help='restrict manifest projects to ones with specified ' 108 help='restrict manifest projects to ones with specified '
@@ -198,6 +202,16 @@ to update the working directory files.
198 if opt.reference: 202 if opt.reference:
199 m.config.SetString('repo.reference', opt.reference) 203 m.config.SetString('repo.reference', opt.reference)
200 204
205 if opt.archive:
206 if is_new:
207 m.config.SetString('repo.archive', 'true')
208 else:
209 print('fatal: --archive is only supported when initializing a new '
210 'workspace.', file=sys.stderr)
211 print('Either delete the .repo folder in this workspace, or initialize '
212 'in another location.', file=sys.stderr)
213 sys.exit(1)
214
201 if opt.mirror: 215 if opt.mirror:
202 if is_new: 216 if is_new:
203 m.config.SetString('repo.mirror', 'true') 217 m.config.SetString('repo.mirror', 'true')
@@ -366,6 +380,13 @@ to update the working directory files.
366 if opt.reference: 380 if opt.reference:
367 opt.reference = os.path.expanduser(opt.reference) 381 opt.reference = os.path.expanduser(opt.reference)
368 382
383 # Check this here, else manifest will be tagged "not new" and init won't be
384 # possible anymore without removing the .repo/manifests directory.
385 if opt.archive and opt.mirror:
386 print('fatal: --mirror and --archive cannot be used together.',
387 file=sys.stderr)
388 sys.exit(1)
389
369 self._SyncManifest(opt) 390 self._SyncManifest(opt)
370 self._LinkManifest(opt.manifest_name) 391 self._LinkManifest(opt.manifest_name)
371 392
diff --git a/subcmds/rebase.py b/subcmds/rebase.py
index b9a7774d..1bdc1f0b 100644
--- a/subcmds/rebase.py
+++ b/subcmds/rebase.py
@@ -62,6 +62,9 @@ branch but need to incorporate new upstream changes "underneath" them.
62 if opt.interactive and not one_project: 62 if opt.interactive and not one_project:
63 print('error: interactive rebase not supported with multiple projects', 63 print('error: interactive rebase not supported with multiple projects',
64 file=sys.stderr) 64 file=sys.stderr)
65 if len(args) == 1:
66 print('note: project %s is mapped to more than one path' % (args[0],),
67 file=sys.stderr)
65 return -1 68 return -1
66 69
67 for project in all_projects: 70 for project in all_projects:
diff --git a/subcmds/sync.py b/subcmds/sync.py
index d8aec59b..5e7385db 100644
--- a/subcmds/sync.py
+++ b/subcmds/sync.py
@@ -219,9 +219,25 @@ later is required to fix a server side protocol bug.
219 dest='repo_upgraded', action='store_true', 219 dest='repo_upgraded', action='store_true',
220 help=SUPPRESS_HELP) 220 help=SUPPRESS_HELP)
221 221
222 def _FetchHelper(self, opt, project, lock, fetched, pm, sem, err_event): 222 def _FetchProjectList(self, opt, projects, *args):
223 """Main function of the fetch threads when jobs are > 1. 223 """Main function of the fetch threads when jobs are > 1.
224 224
225 Delegates most of the work to _FetchHelper.
226
227 Args:
228 opt: Program options returned from optparse. See _Options().
229 projects: Projects to fetch.
230 *args: Remaining arguments to pass to _FetchHelper. See the
231 _FetchHelper docstring for details.
232 """
233 for project in projects:
234 success = self._FetchHelper(opt, project, *args)
235 if not success and not opt.force_broken:
236 break
237
238 def _FetchHelper(self, opt, project, lock, fetched, pm, sem, err_event):
239 """Fetch git objects for a single project.
240
225 Args: 241 Args:
226 opt: Program options returned from optparse. See _Options(). 242 opt: Program options returned from optparse. See _Options().
227 project: Project object for the project to fetch. 243 project: Project object for the project to fetch.
@@ -235,6 +251,9 @@ later is required to fix a server side protocol bug.
235 can be started up. 251 can be started up.
236 err_event: We'll set this event in the case of an error (after printing 252 err_event: We'll set this event in the case of an error (after printing
237 out info about the error). 253 out info about the error).
254
255 Returns:
256 Whether the fetch was successful.
238 """ 257 """
239 # We'll set to true once we've locked the lock. 258 # We'll set to true once we've locked the lock.
240 did_lock = False 259 did_lock = False
@@ -253,7 +272,7 @@ later is required to fix a server side protocol bug.
253 quiet=opt.quiet, 272 quiet=opt.quiet,
254 current_branch_only=opt.current_branch_only, 273 current_branch_only=opt.current_branch_only,
255 clone_bundle=not opt.no_clone_bundle, 274 clone_bundle=not opt.no_clone_bundle,
256 no_tags=opt.no_tags) 275 no_tags=opt.no_tags, archive=self.manifest.IsArchive)
257 self._fetch_times.Set(project, time.time() - start) 276 self._fetch_times.Set(project, time.time() - start)
258 277
259 # Lock around all the rest of the code, since printing, updating a set 278 # Lock around all the rest of the code, since printing, updating a set
@@ -281,6 +300,8 @@ later is required to fix a server side protocol bug.
281 lock.release() 300 lock.release()
282 sem.release() 301 sem.release()
283 302
303 return success
304
284 def _Fetch(self, projects, opt): 305 def _Fetch(self, projects, opt):
285 fetched = set() 306 fetched = set()
286 pm = Progress('Fetching projects', len(projects)) 307 pm = Progress('Fetching projects', len(projects))
@@ -294,7 +315,8 @@ later is required to fix a server side protocol bug.
294 quiet=opt.quiet, 315 quiet=opt.quiet,
295 current_branch_only=opt.current_branch_only, 316 current_branch_only=opt.current_branch_only,
296 clone_bundle=not opt.no_clone_bundle, 317 clone_bundle=not opt.no_clone_bundle,
297 no_tags=opt.no_tags): 318 no_tags=opt.no_tags,
319 archive=self.manifest.IsArchive):
298 fetched.add(project.gitdir) 320 fetched.add(project.gitdir)
299 else: 321 else:
300 print('error: Cannot fetch %s' % project.name, file=sys.stderr) 322 print('error: Cannot fetch %s' % project.name, file=sys.stderr)
@@ -303,20 +325,24 @@ later is required to fix a server side protocol bug.
303 else: 325 else:
304 sys.exit(1) 326 sys.exit(1)
305 else: 327 else:
328 objdir_project_map = dict()
329 for project in projects:
330 objdir_project_map.setdefault(project.objdir, []).append(project)
331
306 threads = set() 332 threads = set()
307 lock = _threading.Lock() 333 lock = _threading.Lock()
308 sem = _threading.Semaphore(self.jobs) 334 sem = _threading.Semaphore(self.jobs)
309 err_event = _threading.Event() 335 err_event = _threading.Event()
310 for project in projects: 336 for project_list in objdir_project_map.values():
311 # Check for any errors before starting any new threads. 337 # Check for any errors before starting any new threads.
312 # ...we'll let existing threads finish, though. 338 # ...we'll let existing threads finish, though.
313 if err_event.isSet(): 339 if err_event.isSet():
314 break 340 break
315 341
316 sem.acquire() 342 sem.acquire()
317 t = _threading.Thread(target = self._FetchHelper, 343 t = _threading.Thread(target = self._FetchProjectList,
318 args = (opt, 344 args = (opt,
319 project, 345 project_list,
320 lock, 346 lock,
321 fetched, 347 fetched,
322 pm, 348 pm,
@@ -338,10 +364,16 @@ later is required to fix a server side protocol bug.
338 pm.end() 364 pm.end()
339 self._fetch_times.Save() 365 self._fetch_times.Save()
340 366
341 self._GCProjects(projects) 367 if not self.manifest.IsArchive:
368 self._GCProjects(projects)
369
342 return fetched 370 return fetched
343 371
344 def _GCProjects(self, projects): 372 def _GCProjects(self, projects):
373 gitdirs = {}
374 for project in projects:
375 gitdirs[project.gitdir] = project.bare_git
376
345 has_dash_c = git_require((1, 7, 2)) 377 has_dash_c = git_require((1, 7, 2))
346 if multiprocessing and has_dash_c: 378 if multiprocessing and has_dash_c:
347 cpu_count = multiprocessing.cpu_count() 379 cpu_count = multiprocessing.cpu_count()
@@ -350,8 +382,8 @@ later is required to fix a server side protocol bug.
350 jobs = min(self.jobs, cpu_count) 382 jobs = min(self.jobs, cpu_count)
351 383
352 if jobs < 2: 384 if jobs < 2:
353 for project in projects: 385 for bare_git in gitdirs.values():
354 project.bare_git.gc('--auto') 386 bare_git.gc('--auto')
355 return 387 return
356 388
357 config = {'pack.threads': cpu_count / jobs if cpu_count > jobs else 1} 389 config = {'pack.threads': cpu_count / jobs if cpu_count > jobs else 1}
@@ -360,10 +392,10 @@ later is required to fix a server side protocol bug.
360 sem = _threading.Semaphore(jobs) 392 sem = _threading.Semaphore(jobs)
361 err_event = _threading.Event() 393 err_event = _threading.Event()
362 394
363 def GC(project): 395 def GC(bare_git):
364 try: 396 try:
365 try: 397 try:
366 project.bare_git.gc('--auto', config=config) 398 bare_git.gc('--auto', config=config)
367 except GitError: 399 except GitError:
368 err_event.set() 400 err_event.set()
369 except: 401 except:
@@ -372,11 +404,11 @@ later is required to fix a server side protocol bug.
372 finally: 404 finally:
373 sem.release() 405 sem.release()
374 406
375 for project in projects: 407 for bare_git in gitdirs.values():
376 if err_event.isSet(): 408 if err_event.isSet():
377 break 409 break
378 sem.acquire() 410 sem.acquire()
379 t = _threading.Thread(target=GC, args=(project,)) 411 t = _threading.Thread(target=GC, args=(bare_git,))
380 t.daemon = True 412 t.daemon = True
381 threads.add(t) 413 threads.add(t)
382 t.start() 414 t.start()
@@ -416,12 +448,13 @@ later is required to fix a server side protocol bug.
416 if path not in new_project_paths: 448 if path not in new_project_paths:
417 # If the path has already been deleted, we don't need to do it 449 # If the path has already been deleted, we don't need to do it
418 if os.path.exists(self.manifest.topdir + '/' + path): 450 if os.path.exists(self.manifest.topdir + '/' + path):
451 gitdir = os.path.join(self.manifest.topdir, path, '.git')
419 project = Project( 452 project = Project(
420 manifest = self.manifest, 453 manifest = self.manifest,
421 name = path, 454 name = path,
422 remote = RemoteSpec('origin'), 455 remote = RemoteSpec('origin'),
423 gitdir = os.path.join(self.manifest.topdir, 456 gitdir = gitdir,
424 path, '.git'), 457 objdir = gitdir,
425 worktree = os.path.join(self.manifest.topdir, path), 458 worktree = os.path.join(self.manifest.topdir, path),
426 relpath = path, 459 relpath = path,
427 revisionExpr = 'HEAD', 460 revisionExpr = 'HEAD',
@@ -641,7 +674,7 @@ later is required to fix a server side protocol bug.
641 previously_missing_set = missing_set 674 previously_missing_set = missing_set
642 fetched.update(self._Fetch(missing, opt)) 675 fetched.update(self._Fetch(missing, opt))
643 676
644 if self.manifest.IsMirror: 677 if self.manifest.IsMirror or self.manifest.IsArchive:
645 # bail out now, we have no working tree 678 # bail out now, we have no working tree
646 return 679 return
647 680
diff --git a/subcmds/upload.py b/subcmds/upload.py
index 7f7585ae..56212408 100644
--- a/subcmds/upload.py
+++ b/subcmds/upload.py
@@ -432,8 +432,10 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
432 hook = RepoHook('pre-upload', self.manifest.repo_hooks_project, 432 hook = RepoHook('pre-upload', self.manifest.repo_hooks_project,
433 self.manifest.topdir, abort_if_user_denies=True) 433 self.manifest.topdir, abort_if_user_denies=True)
434 pending_proj_names = [project.name for (project, avail) in pending] 434 pending_proj_names = [project.name for (project, avail) in pending]
435 pending_worktrees = [project.worktree for (project, avail) in pending]
435 try: 436 try:
436 hook.Run(opt.allow_all_hooks, project_list=pending_proj_names) 437 hook.Run(opt.allow_all_hooks, project_list=pending_proj_names,
438 worktree_list=pending_worktrees)
437 except HookError as e: 439 except HookError as e:
438 print("ERROR: %s" % str(e), file=sys.stderr) 440 print("ERROR: %s" % str(e), file=sys.stderr)
439 return 441 return