summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid James <davidjames@google.com>2013-10-11 17:03:19 -0700
committerDavid James <davidjames@google.com>2013-10-14 15:34:32 -0700
commit8d20116038ff78b22069dd4e993b5819775f03d1 (patch)
tree4c7ec381f2452d3ae4ed5332230a8d7a61e6ec2b
parentb25ea555c39cd500740acb74fa9f1dab71588266 (diff)
downloadgit-repo-8d20116038ff78b22069dd4e993b5819775f03d1.tar.gz
repo: Support multiple branches for the same project.
It is often useful to be able to include the same project more than once, but with different branches and placed in different paths in the workspace. Add this feature. This CL adds the concept of an object directory. The object directory stores objects that can be shared amongst several working trees. For newly synced repositories, we set up the git repo now to share its objects with an object repo. Each worktree for a given repo shares objects, but has an independent set of references and branches. This ensures that repo only has to update the objects once; however the references for each worktree are updated separately. Storing the references separately is needed to ensure that commits to a branch on one worktree will not change the HEAD commits of the others. One nice side effect of sharing objects between different worktrees is that you can easily cherry-pick changes between the two worktrees without needing to fetch them. Bug: Issue 141 Change-Id: I5e2f4e1a7abb56f9d3f310fa6fd0c17019330ecd
-rw-r--r--command.py26
-rw-r--r--manifest_xml.py64
-rw-r--r--project.py118
-rw-r--r--subcmds/rebase.py3
-rw-r--r--subcmds/sync.py54
-rw-r--r--subcmds/upload.py4
6 files changed, 190 insertions, 79 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 bdbb1d40..647e89f9 100644
--- a/manifest_xml.py
+++ b/manifest_xml.py
@@ -209,8 +209,9 @@ class XmlManifest(object):
209 root.appendChild(doc.createTextNode('')) 209 root.appendChild(doc.createTextNode(''))
210 210
211 def output_projects(parent, parent_node, projects): 211 def output_projects(parent, parent_node, projects):
212 for p in projects: 212 for project_name in projects:
213 output_project(parent, parent_node, self.projects[p]) 213 for project in self._projects[project_name]:
214 output_project(parent, parent_node, project)
214 215
215 def output_project(parent, parent_node, p): 216 def output_project(parent, parent_node, p):
216 if not p.MatchesGroups(groups): 217 if not p.MatchesGroups(groups):
@@ -269,13 +270,11 @@ class XmlManifest(object):
269 e.setAttribute('sync-s', 'true') 270 e.setAttribute('sync-s', 'true')
270 271
271 if p.subprojects: 272 if p.subprojects:
272 sort_projects = list(sorted([subp.name for subp in p.subprojects])) 273 subprojects = set(subp.name for subp in p.subprojects)
273 output_projects(p, e, sort_projects) 274 output_projects(p, e, list(sorted(subprojects)))
274 275
275 sort_projects = list(sorted([key for key, value in self.projects.items() 276 projects = set(p.name for p in self._paths.values() if not p.parent)
276 if not value.parent])) 277 output_projects(None, root, list(sorted(projects)))
277 sort_projects.sort()
278 output_projects(None, root, sort_projects)
279 278
280 if self._repo_hooks_project: 279 if self._repo_hooks_project:
281 root.appendChild(doc.createTextNode('')) 280 root.appendChild(doc.createTextNode(''))
@@ -288,9 +287,14 @@ class XmlManifest(object):
288 doc.writexml(fd, '', ' ', '\n', 'UTF-8') 287 doc.writexml(fd, '', ' ', '\n', 'UTF-8')
289 288
290 @property 289 @property
290 def paths(self):
291 self._Load()
292 return self._paths
293
294 @property
291 def projects(self): 295 def projects(self):
292 self._Load() 296 self._Load()
293 return self._projects 297 return self._paths.values()
294 298
295 @property 299 @property
296 def remotes(self): 300 def remotes(self):
@@ -324,6 +328,7 @@ class XmlManifest(object):
324 def _Unload(self): 328 def _Unload(self):
325 self._loaded = False 329 self._loaded = False
326 self._projects = {} 330 self._projects = {}
331 self._paths = {}
327 self._remotes = {} 332 self._remotes = {}
328 self._default = None 333 self._default = None
329 self._repo_hooks_project = None 334 self._repo_hooks_project = None
@@ -453,11 +458,17 @@ class XmlManifest(object):
453 self._manifest_server = url 458 self._manifest_server = url
454 459
455 def recursively_add_projects(project): 460 def recursively_add_projects(project):
456 if self._projects.get(project.name): 461 projects = self._projects.setdefault(project.name, [])
462 if project.relpath is None:
457 raise ManifestParseError( 463 raise ManifestParseError(
458 'duplicate project %s in %s' % 464 'missing path for %s in %s' %
459 (project.name, self.manifestFile)) 465 (project.name, self.manifestFile))
460 self._projects[project.name] = project 466 if project.relpath in self._paths:
467 raise ManifestParseError(
468 'duplicate path %s in %s' %
469 (project.relpath, self.manifestFile))
470 self._paths[project.relpath] = project
471 projects.append(project)
461 for subproject in project.subprojects: 472 for subproject in project.subprojects:
462 recursively_add_projects(subproject) 473 recursively_add_projects(subproject)
463 474
@@ -478,12 +489,18 @@ class XmlManifest(object):
478 489
479 # Store a reference to the Project. 490 # Store a reference to the Project.
480 try: 491 try:
481 self._repo_hooks_project = self._projects[repo_hooks_project] 492 repo_hooks_projects = self._projects[repo_hooks_project]
482 except KeyError: 493 except KeyError:
483 raise ManifestParseError( 494 raise ManifestParseError(
484 'project %s not found for repo-hooks' % 495 'project %s not found for repo-hooks' %
485 (repo_hooks_project)) 496 (repo_hooks_project))
486 497
498 if len(repo_hooks_projects) != 1:
499 raise ManifestParseError(
500 'internal error parsing repo-hooks in %s' %
501 (self.manifestFile))
502 self._repo_hooks_project = repo_hooks_projects[0]
503
487 # Store the enabled hooks in the Project object. 504 # Store the enabled hooks in the Project object.
488 self._repo_hooks_project.enabled_repo_hooks = enabled_repo_hooks 505 self._repo_hooks_project.enabled_repo_hooks = enabled_repo_hooks
489 if node.nodeName == 'remove-project': 506 if node.nodeName == 'remove-project':
@@ -530,11 +547,12 @@ class XmlManifest(object):
530 name = name, 547 name = name,
531 remote = remote.ToRemoteSpec(name), 548 remote = remote.ToRemoteSpec(name),
532 gitdir = gitdir, 549 gitdir = gitdir,
550 objdir = gitdir,
533 worktree = None, 551 worktree = None,
534 relpath = None, 552 relpath = None,
535 revisionExpr = m.revisionExpr, 553 revisionExpr = m.revisionExpr,
536 revisionId = None) 554 revisionId = None)
537 self._projects[project.name] = project 555 self._projects[project.name] = [project]
538 556
539 def _ParseRemote(self, node): 557 def _ParseRemote(self, node):
540 """ 558 """
@@ -694,9 +712,10 @@ class XmlManifest(object):
694 groups = [x for x in re.split(r'[,\s]+', groups) if x] 712 groups = [x for x in re.split(r'[,\s]+', groups) if x]
695 713
696 if parent is None: 714 if parent is None:
697 relpath, worktree, gitdir = self.GetProjectPaths(name, path) 715 relpath, worktree, gitdir, objdir = self.GetProjectPaths(name, path)
698 else: 716 else:
699 relpath, worktree, gitdir = self.GetSubprojectPaths(parent, path) 717 relpath, worktree, gitdir, objdir = \
718 self.GetSubprojectPaths(parent, name, path)
700 719
701 default_groups = ['all', 'name:%s' % name, 'path:%s' % relpath] 720 default_groups = ['all', 'name:%s' % name, 'path:%s' % relpath]
702 groups.extend(set(default_groups).difference(groups)) 721 groups.extend(set(default_groups).difference(groups))
@@ -709,6 +728,7 @@ class XmlManifest(object):
709 name = name, 728 name = name,
710 remote = remote.ToRemoteSpec(name), 729 remote = remote.ToRemoteSpec(name),
711 gitdir = gitdir, 730 gitdir = gitdir,
731 objdir = objdir,
712 worktree = worktree, 732 worktree = worktree,
713 relpath = relpath, 733 relpath = relpath,
714 revisionExpr = revisionExpr, 734 revisionExpr = revisionExpr,
@@ -737,10 +757,15 @@ class XmlManifest(object):
737 if self.IsMirror: 757 if self.IsMirror:
738 worktree = None 758 worktree = None
739 gitdir = os.path.join(self.topdir, '%s.git' % name) 759 gitdir = os.path.join(self.topdir, '%s.git' % name)
760 objdir = gitdir
740 else: 761 else:
741 worktree = os.path.join(self.topdir, path).replace('\\', '/') 762 worktree = os.path.join(self.topdir, path).replace('\\', '/')
742 gitdir = os.path.join(self.repodir, 'projects', '%s.git' % path) 763 gitdir = os.path.join(self.repodir, 'projects', '%s.git' % path)
743 return relpath, worktree, gitdir 764 objdir = os.path.join(self.repodir, 'project-objects', '%s.git' % name)
765 return relpath, worktree, gitdir, objdir
766
767 def GetProjectsWithName(self, name):
768 return self._projects.get(name, [])
744 769
745 def GetSubprojectName(self, parent, submodule_path): 770 def GetSubprojectName(self, parent, submodule_path):
746 return os.path.join(parent.name, submodule_path) 771 return os.path.join(parent.name, submodule_path)
@@ -751,14 +776,15 @@ class XmlManifest(object):
751 def _UnjoinRelpath(self, parent_relpath, relpath): 776 def _UnjoinRelpath(self, parent_relpath, relpath):
752 return os.path.relpath(relpath, parent_relpath) 777 return os.path.relpath(relpath, parent_relpath)
753 778
754 def GetSubprojectPaths(self, parent, path): 779 def GetSubprojectPaths(self, parent, name, path):
755 relpath = self._JoinRelpath(parent.relpath, path) 780 relpath = self._JoinRelpath(parent.relpath, path)
756 gitdir = os.path.join(parent.gitdir, 'subprojects', '%s.git' % path) 781 gitdir = os.path.join(parent.gitdir, 'subprojects', '%s.git' % path)
782 objdir = os.path.join(parent.gitdir, 'subproject-objects', '%s.git' % name)
757 if self.IsMirror: 783 if self.IsMirror:
758 worktree = None 784 worktree = None
759 else: 785 else:
760 worktree = os.path.join(parent.worktree, path).replace('\\', '/') 786 worktree = os.path.join(parent.worktree, path).replace('\\', '/')
761 return relpath, worktree, gitdir 787 return relpath, worktree, gitdir, objdir
762 788
763 def _ParseCopyFile(self, project, node): 789 def _ParseCopyFile(self, project, node):
764 src = self._reqatt(node, 'src') 790 src = self._reqatt(node, 'src')
diff --git a/project.py b/project.py
index dec21ab1..f9f1f75d 100644
--- a/project.py
+++ b/project.py
@@ -487,6 +487,7 @@ class Project(object):
487 name, 487 name,
488 remote, 488 remote,
489 gitdir, 489 gitdir,
490 objdir,
490 worktree, 491 worktree,
491 relpath, 492 relpath,
492 revisionExpr, 493 revisionExpr,
@@ -507,6 +508,7 @@ class Project(object):
507 name: The `name` attribute of manifest.xml's project element. 508 name: The `name` attribute of manifest.xml's project element.
508 remote: RemoteSpec object specifying its remote's properties. 509 remote: RemoteSpec object specifying its remote's properties.
509 gitdir: Absolute path of git directory. 510 gitdir: Absolute path of git directory.
511 objdir: Absolute path of directory to store git objects.
510 worktree: Absolute path of git working tree. 512 worktree: Absolute path of git working tree.
511 relpath: Relative path of git working tree to repo's top directory. 513 relpath: Relative path of git working tree to repo's top directory.
512 revisionExpr: The `revision` attribute of manifest.xml's project element. 514 revisionExpr: The `revision` attribute of manifest.xml's project element.
@@ -525,6 +527,7 @@ class Project(object):
525 self.name = name 527 self.name = name
526 self.remote = remote 528 self.remote = remote
527 self.gitdir = gitdir.replace('\\', '/') 529 self.gitdir = gitdir.replace('\\', '/')
530 self.objdir = objdir.replace('\\', '/')
528 if worktree: 531 if worktree:
529 self.worktree = worktree.replace('\\', '/') 532 self.worktree = worktree.replace('\\', '/')
530 else: 533 else:
@@ -557,11 +560,12 @@ class Project(object):
557 defaults = self.manifest.globalConfig) 560 defaults = self.manifest.globalConfig)
558 561
559 if self.worktree: 562 if self.worktree:
560 self.work_git = self._GitGetByExec(self, bare=False) 563 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
561 else: 564 else:
562 self.work_git = None 565 self.work_git = None
563 self.bare_git = self._GitGetByExec(self, bare=True) 566 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
564 self.bare_ref = GitRefs(gitdir) 567 self.bare_ref = GitRefs(gitdir)
568 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
565 self.dest_branch = dest_branch 569 self.dest_branch = dest_branch
566 570
567 # This will be filled in if a project is later identified to be the 571 # This will be filled in if a project is later identified to be the
@@ -1069,6 +1073,7 @@ class Project(object):
1069 """Perform only the local IO portion of the sync process. 1073 """Perform only the local IO portion of the sync process.
1070 Network access is not required. 1074 Network access is not required.
1071 """ 1075 """
1076 self._InitWorkTree()
1072 all_refs = self.bare_ref.all 1077 all_refs = self.bare_ref.all
1073 self.CleanPublishedCache(all_refs) 1078 self.CleanPublishedCache(all_refs)
1074 revid = self.GetRevisionId(all_refs) 1079 revid = self.GetRevisionId(all_refs)
@@ -1077,7 +1082,6 @@ class Project(object):
1077 self._FastForward(revid) 1082 self._FastForward(revid)
1078 self._CopyFiles() 1083 self._CopyFiles()
1079 1084
1080 self._InitWorkTree()
1081 head = self.work_git.GetHead() 1085 head = self.work_git.GetHead()
1082 if head.startswith(R_HEADS): 1086 if head.startswith(R_HEADS):
1083 branch = head[len(R_HEADS):] 1087 branch = head[len(R_HEADS):]
@@ -1544,11 +1548,13 @@ class Project(object):
1544 return result 1548 return result
1545 for rev, path, url in self._GetSubmodules(): 1549 for rev, path, url in self._GetSubmodules():
1546 name = self.manifest.GetSubprojectName(self, path) 1550 name = self.manifest.GetSubprojectName(self, path)
1547 project = self.manifest.projects.get(name) 1551 relpath, worktree, gitdir, objdir = \
1552 self.manifest.GetSubprojectPaths(self, name, path)
1553 project = self.manifest.paths.get(relpath)
1548 if project: 1554 if project:
1549 result.extend(project.GetDerivedSubprojects()) 1555 result.extend(project.GetDerivedSubprojects())
1550 continue 1556 continue
1551 relpath, worktree, gitdir = self.manifest.GetSubprojectPaths(self, path) 1557
1552 remote = RemoteSpec(self.remote.name, 1558 remote = RemoteSpec(self.remote.name,
1553 url = url, 1559 url = url,
1554 review = self.remote.review) 1560 review = self.remote.review)
@@ -1556,6 +1562,7 @@ class Project(object):
1556 name = name, 1562 name = name,
1557 remote = remote, 1563 remote = remote,
1558 gitdir = gitdir, 1564 gitdir = gitdir,
1565 objdir = objdir,
1559 worktree = worktree, 1566 worktree = worktree,
1560 relpath = relpath, 1567 relpath = relpath,
1561 revisionExpr = self.revisionExpr, 1568 revisionExpr = self.revisionExpr,
@@ -1905,8 +1912,17 @@ class Project(object):
1905 1912
1906 def _InitGitDir(self, mirror_git=None): 1913 def _InitGitDir(self, mirror_git=None):
1907 if not os.path.exists(self.gitdir): 1914 if not os.path.exists(self.gitdir):
1908 os.makedirs(self.gitdir) 1915
1909 self.bare_git.init() 1916 # Initialize the bare repository, which contains all of the objects.
1917 if not os.path.exists(self.objdir):
1918 os.makedirs(self.objdir)
1919 self.bare_objdir.init()
1920
1921 # If we have a separate directory to hold refs, initialize it as well.
1922 if self.objdir != self.gitdir:
1923 os.makedirs(self.gitdir)
1924 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
1925 copy_all=True)
1910 1926
1911 mp = self.manifest.manifestProject 1927 mp = self.manifest.manifestProject
1912 ref_dir = mp.config.GetString('repo.reference') or '' 1928 ref_dir = mp.config.GetString('repo.reference') or ''
@@ -2022,33 +2038,61 @@ class Project(object):
2022 msg = 'manifest set to %s' % self.revisionExpr 2038 msg = 'manifest set to %s' % self.revisionExpr
2023 self.bare_git.symbolic_ref('-m', msg, ref, dst) 2039 self.bare_git.symbolic_ref('-m', msg, ref, dst)
2024 2040
2041 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2042 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2043
2044 Args:
2045 gitdir: The bare git repository. Must already be initialized.
2046 dotgit: The repository you would like to initialize.
2047 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2048 Only one work tree can store refs under a given |gitdir|.
2049 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2050 This saves you the effort of initializing |dotgit| yourself.
2051 """
2052 # These objects can be shared between several working trees.
2053 symlink_files = ['description', 'info']
2054 symlink_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
2055 if share_refs:
2056 # These objects can only be used by a single working tree.
2057 symlink_files += ['config', 'packed-refs']
2058 symlink_dirs += ['logs', 'refs']
2059 to_symlink = symlink_files + symlink_dirs
2060
2061 to_copy = []
2062 if copy_all:
2063 to_copy = os.listdir(gitdir)
2064
2065 for name in set(to_copy).union(to_symlink):
2066 try:
2067 src = os.path.realpath(os.path.join(gitdir, name))
2068 dst = os.path.realpath(os.path.join(dotgit, name))
2069
2070 if os.path.lexists(dst) and not os.path.islink(dst):
2071 raise GitError('cannot overwrite a local work tree')
2072
2073 # If the source dir doesn't exist, create an empty dir.
2074 if name in symlink_dirs and not os.path.lexists(src):
2075 os.makedirs(src)
2076
2077 if name in to_symlink:
2078 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2079 elif copy_all and not os.path.islink(dst):
2080 if os.path.isdir(src):
2081 shutil.copytree(src, dst)
2082 elif os.path.isfile(src):
2083 shutil.copy(src, dst)
2084 except OSError as e:
2085 if e.errno == errno.EPERM:
2086 raise GitError('filesystem must support symlinks')
2087 else:
2088 raise
2089
2025 def _InitWorkTree(self): 2090 def _InitWorkTree(self):
2026 dotgit = os.path.join(self.worktree, '.git') 2091 dotgit = os.path.join(self.worktree, '.git')
2027 if not os.path.exists(dotgit): 2092 if not os.path.exists(dotgit):
2028 os.makedirs(dotgit) 2093 os.makedirs(dotgit)
2029 2094 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2030 for name in ['config', 2095 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 2096
2053 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId()) 2097 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
2054 2098
@@ -2058,14 +2102,10 @@ class Project(object):
2058 if GitCommand(self, cmd).Wait() != 0: 2102 if GitCommand(self, cmd).Wait() != 0:
2059 raise GitError("cannot initialize work tree") 2103 raise GitError("cannot initialize work tree")
2060 2104
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() 2105 self._CopyFiles()
2066 2106
2067 def _gitdir_path(self, path): 2107 def _gitdir_path(self, path):
2068 return os.path.join(self.gitdir, path) 2108 return os.path.realpath(os.path.join(self.gitdir, path))
2069 2109
2070 def _revlist(self, *args, **kw): 2110 def _revlist(self, *args, **kw):
2071 a = [] 2111 a = []
@@ -2078,9 +2118,10 @@ class Project(object):
2078 return self.bare_ref.all 2118 return self.bare_ref.all
2079 2119
2080 class _GitGetByExec(object): 2120 class _GitGetByExec(object):
2081 def __init__(self, project, bare): 2121 def __init__(self, project, bare, gitdir):
2082 self._project = project 2122 self._project = project
2083 self._bare = bare 2123 self._bare = bare
2124 self._gitdir = gitdir
2084 2125
2085 def LsOthers(self): 2126 def LsOthers(self):
2086 p = GitCommand(self._project, 2127 p = GitCommand(self._project,
@@ -2089,6 +2130,7 @@ class Project(object):
2089 '--others', 2130 '--others',
2090 '--exclude-standard'], 2131 '--exclude-standard'],
2091 bare = False, 2132 bare = False,
2133 gitdir=self._gitdir,
2092 capture_stdout = True, 2134 capture_stdout = True,
2093 capture_stderr = True) 2135 capture_stderr = True)
2094 if p.Wait() == 0: 2136 if p.Wait() == 0:
@@ -2104,6 +2146,7 @@ class Project(object):
2104 cmd.extend(args) 2146 cmd.extend(args)
2105 p = GitCommand(self._project, 2147 p = GitCommand(self._project,
2106 cmd, 2148 cmd,
2149 gitdir=self._gitdir,
2107 bare = False, 2150 bare = False,
2108 capture_stdout = True, 2151 capture_stdout = True,
2109 capture_stderr = True) 2152 capture_stderr = True)
@@ -2213,6 +2256,7 @@ class Project(object):
2213 p = GitCommand(self._project, 2256 p = GitCommand(self._project,
2214 cmdv, 2257 cmdv,
2215 bare = self._bare, 2258 bare = self._bare,
2259 gitdir=self._gitdir,
2216 capture_stdout = True, 2260 capture_stdout = True,
2217 capture_stderr = True) 2261 capture_stderr = True)
2218 r = [] 2262 r = []
@@ -2265,6 +2309,7 @@ class Project(object):
2265 p = GitCommand(self._project, 2309 p = GitCommand(self._project,
2266 cmdv, 2310 cmdv,
2267 bare = self._bare, 2311 bare = self._bare,
2312 gitdir=self._gitdir,
2268 capture_stdout = True, 2313 capture_stdout = True,
2269 capture_stderr = True) 2314 capture_stderr = True)
2270 if p.Wait() != 0: 2315 if p.Wait() != 0:
@@ -2398,6 +2443,7 @@ class MetaProject(Project):
2398 manifest = manifest, 2443 manifest = manifest,
2399 name = name, 2444 name = name,
2400 gitdir = gitdir, 2445 gitdir = gitdir,
2446 objdir = gitdir,
2401 worktree = worktree, 2447 worktree = worktree,
2402 remote = RemoteSpec('origin'), 2448 remote = RemoteSpec('origin'),
2403 relpath = '.repo/%s' % name, 2449 relpath = '.repo/%s' % name,
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 e9d52b7b..d1a06412 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
@@ -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))
@@ -303,20 +324,24 @@ later is required to fix a server side protocol bug.
303 else: 324 else:
304 sys.exit(1) 325 sys.exit(1)
305 else: 326 else:
327 objdir_project_map = dict()
328 for project in projects:
329 objdir_project_map.setdefault(project.objdir, []).append(project)
330
306 threads = set() 331 threads = set()
307 lock = _threading.Lock() 332 lock = _threading.Lock()
308 sem = _threading.Semaphore(self.jobs) 333 sem = _threading.Semaphore(self.jobs)
309 err_event = _threading.Event() 334 err_event = _threading.Event()
310 for project in projects: 335 for project_list in objdir_project_map.values():
311 # Check for any errors before starting any new threads. 336 # Check for any errors before starting any new threads.
312 # ...we'll let existing threads finish, though. 337 # ...we'll let existing threads finish, though.
313 if err_event.isSet(): 338 if err_event.isSet():
314 break 339 break
315 340
316 sem.acquire() 341 sem.acquire()
317 t = _threading.Thread(target = self._FetchHelper, 342 t = _threading.Thread(target = self._FetchProjectList,
318 args = (opt, 343 args = (opt,
319 project, 344 project_list,
320 lock, 345 lock,
321 fetched, 346 fetched,
322 pm, 347 pm,
@@ -342,6 +367,10 @@ later is required to fix a server side protocol bug.
342 return fetched 367 return fetched
343 368
344 def _GCProjects(self, projects): 369 def _GCProjects(self, projects):
370 gitdirs = {}
371 for project in projects:
372 gitdirs[project.gitdir] = project.bare_git
373
345 has_dash_c = git_require((1, 7, 2)) 374 has_dash_c = git_require((1, 7, 2))
346 if multiprocessing and has_dash_c: 375 if multiprocessing and has_dash_c:
347 cpu_count = multiprocessing.cpu_count() 376 cpu_count = multiprocessing.cpu_count()
@@ -350,8 +379,8 @@ later is required to fix a server side protocol bug.
350 jobs = min(self.jobs, cpu_count) 379 jobs = min(self.jobs, cpu_count)
351 380
352 if jobs < 2: 381 if jobs < 2:
353 for project in projects: 382 for bare_git in gitdirs.values():
354 project.bare_git.gc('--auto') 383 bare_git.gc('--auto')
355 return 384 return
356 385
357 config = {'pack.threads': cpu_count / jobs if cpu_count > jobs else 1} 386 config = {'pack.threads': cpu_count / jobs if cpu_count > jobs else 1}
@@ -360,10 +389,10 @@ later is required to fix a server side protocol bug.
360 sem = _threading.Semaphore(jobs) 389 sem = _threading.Semaphore(jobs)
361 err_event = _threading.Event() 390 err_event = _threading.Event()
362 391
363 def GC(project): 392 def GC(bare_git):
364 try: 393 try:
365 try: 394 try:
366 project.bare_git.gc('--auto', config=config) 395 bare_git.gc('--auto', config=config)
367 except GitError: 396 except GitError:
368 err_event.set() 397 err_event.set()
369 except: 398 except:
@@ -372,11 +401,11 @@ later is required to fix a server side protocol bug.
372 finally: 401 finally:
373 sem.release() 402 sem.release()
374 403
375 for project in projects: 404 for bare_git in gitdirs.values():
376 if err_event.isSet(): 405 if err_event.isSet():
377 break 406 break
378 sem.acquire() 407 sem.acquire()
379 t = _threading.Thread(target=GC, args=(project,)) 408 t = _threading.Thread(target=GC, args=(bare_git,))
380 t.daemon = True 409 t.daemon = True
381 threads.add(t) 410 threads.add(t)
382 t.start() 411 t.start()
@@ -416,12 +445,13 @@ later is required to fix a server side protocol bug.
416 if path not in new_project_paths: 445 if path not in new_project_paths:
417 # If the path has already been deleted, we don't need to do it 446 # If the path has already been deleted, we don't need to do it
418 if os.path.exists(self.manifest.topdir + '/' + path): 447 if os.path.exists(self.manifest.topdir + '/' + path):
448 gitdir = os.path.join(self.manifest.topdir, path, '.git')
419 project = Project( 449 project = Project(
420 manifest = self.manifest, 450 manifest = self.manifest,
421 name = path, 451 name = path,
422 remote = RemoteSpec('origin'), 452 remote = RemoteSpec('origin'),
423 gitdir = os.path.join(self.manifest.topdir, 453 gitdir = gitdir,
424 path, '.git'), 454 objdir = gitdir,
425 worktree = os.path.join(self.manifest.topdir, path), 455 worktree = os.path.join(self.manifest.topdir, path),
426 relpath = path, 456 relpath = path,
427 revisionExpr = 'HEAD', 457 revisionExpr = 'HEAD',
diff --git a/subcmds/upload.py b/subcmds/upload.py
index 8d801e08..9ad55d79 100644
--- a/subcmds/upload.py
+++ b/subcmds/upload.py
@@ -431,8 +431,10 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
431 hook = RepoHook('pre-upload', self.manifest.repo_hooks_project, 431 hook = RepoHook('pre-upload', self.manifest.repo_hooks_project,
432 self.manifest.topdir, abort_if_user_denies=True) 432 self.manifest.topdir, abort_if_user_denies=True)
433 pending_proj_names = [project.name for (project, avail) in pending] 433 pending_proj_names = [project.name for (project, avail) in pending]
434 pending_worktrees = [project.worktree for (project, avail) in pending]
434 try: 435 try:
435 hook.Run(opt.allow_all_hooks, project_list=pending_proj_names) 436 hook.Run(opt.allow_all_hooks, project_list=pending_proj_names,
437 worktree_list=pending_worktrees)
436 except HookError as e: 438 except HookError as e:
437 print("ERROR: %s" % str(e), file=sys.stderr) 439 print("ERROR: %s" % str(e), file=sys.stderr)
438 return 440 return