summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorConley Owens <cco3@android.com>2014-01-10 01:20:12 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2014-01-10 01:20:13 +0000
commite695338e21080d3cbe20935db572e4cd0c7a620c (patch)
treecc525f53929fa4ea24f108d24545c68c7c6cb5ba
parentbd80f7eedd5a74f6c5fad745091dcda53a6415bf (diff)
parent8d20116038ff78b22069dd4e993b5819775f03d1 (diff)
downloadgit-repo-e695338e21080d3cbe20935db572e4cd0c7a620c.tar.gz
Merge "repo: Support multiple branches for the same project."
-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 785976bc..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):
@@ -336,6 +340,7 @@ class XmlManifest(object):
336 def _Unload(self): 340 def _Unload(self):
337 self._loaded = False 341 self._loaded = False
338 self._projects = {} 342 self._projects = {}
343 self._paths = {}
339 self._remotes = {} 344 self._remotes = {}
340 self._default = None 345 self._default = None
341 self._repo_hooks_project = None 346 self._repo_hooks_project = None
@@ -467,11 +472,17 @@ class XmlManifest(object):
467 self._manifest_server = url 472 self._manifest_server = url
468 473
469 def recursively_add_projects(project): 474 def recursively_add_projects(project):
470 if self._projects.get(project.name): 475 projects = self._projects.setdefault(project.name, [])
476 if project.relpath is None:
471 raise ManifestParseError( 477 raise ManifestParseError(
472 'duplicate project %s in %s' % 478 'missing path for %s in %s' %
473 (project.name, self.manifestFile)) 479 (project.name, self.manifestFile))
474 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)
475 for subproject in project.subprojects: 486 for subproject in project.subprojects:
476 recursively_add_projects(subproject) 487 recursively_add_projects(subproject)
477 488
@@ -492,12 +503,18 @@ class XmlManifest(object):
492 503
493 # Store a reference to the Project. 504 # Store a reference to the Project.
494 try: 505 try:
495 self._repo_hooks_project = self._projects[repo_hooks_project] 506 repo_hooks_projects = self._projects[repo_hooks_project]
496 except KeyError: 507 except KeyError:
497 raise ManifestParseError( 508 raise ManifestParseError(
498 'project %s not found for repo-hooks' % 509 'project %s not found for repo-hooks' %
499 (repo_hooks_project)) 510 (repo_hooks_project))
500 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
501 # Store the enabled hooks in the Project object. 518 # Store the enabled hooks in the Project object.
502 self._repo_hooks_project.enabled_repo_hooks = enabled_repo_hooks 519 self._repo_hooks_project.enabled_repo_hooks = enabled_repo_hooks
503 if node.nodeName == 'remove-project': 520 if node.nodeName == 'remove-project':
@@ -544,11 +561,12 @@ class XmlManifest(object):
544 name = name, 561 name = name,
545 remote = remote.ToRemoteSpec(name), 562 remote = remote.ToRemoteSpec(name),
546 gitdir = gitdir, 563 gitdir = gitdir,
564 objdir = gitdir,
547 worktree = None, 565 worktree = None,
548 relpath = None, 566 relpath = None,
549 revisionExpr = m.revisionExpr, 567 revisionExpr = m.revisionExpr,
550 revisionId = None) 568 revisionId = None)
551 self._projects[project.name] = project 569 self._projects[project.name] = [project]
552 570
553 def _ParseRemote(self, node): 571 def _ParseRemote(self, node):
554 """ 572 """
@@ -708,9 +726,10 @@ class XmlManifest(object):
708 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]
709 727
710 if parent is None: 728 if parent is None:
711 relpath, worktree, gitdir = self.GetProjectPaths(name, path) 729 relpath, worktree, gitdir, objdir = self.GetProjectPaths(name, path)
712 else: 730 else:
713 relpath, worktree, gitdir = self.GetSubprojectPaths(parent, path) 731 relpath, worktree, gitdir, objdir = \
732 self.GetSubprojectPaths(parent, name, path)
714 733
715 default_groups = ['all', 'name:%s' % name, 'path:%s' % relpath] 734 default_groups = ['all', 'name:%s' % name, 'path:%s' % relpath]
716 groups.extend(set(default_groups).difference(groups)) 735 groups.extend(set(default_groups).difference(groups))
@@ -723,6 +742,7 @@ class XmlManifest(object):
723 name = name, 742 name = name,
724 remote = remote.ToRemoteSpec(name), 743 remote = remote.ToRemoteSpec(name),
725 gitdir = gitdir, 744 gitdir = gitdir,
745 objdir = objdir,
726 worktree = worktree, 746 worktree = worktree,
727 relpath = relpath, 747 relpath = relpath,
728 revisionExpr = revisionExpr, 748 revisionExpr = revisionExpr,
@@ -751,10 +771,15 @@ class XmlManifest(object):
751 if self.IsMirror: 771 if self.IsMirror:
752 worktree = None 772 worktree = None
753 gitdir = os.path.join(self.topdir, '%s.git' % name) 773 gitdir = os.path.join(self.topdir, '%s.git' % name)
774 objdir = gitdir
754 else: 775 else:
755 worktree = os.path.join(self.topdir, path).replace('\\', '/') 776 worktree = os.path.join(self.topdir, path).replace('\\', '/')
756 gitdir = os.path.join(self.repodir, 'projects', '%s.git' % path) 777 gitdir = os.path.join(self.repodir, 'projects', '%s.git' % path)
757 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, [])
758 783
759 def GetSubprojectName(self, parent, submodule_path): 784 def GetSubprojectName(self, parent, submodule_path):
760 return os.path.join(parent.name, submodule_path) 785 return os.path.join(parent.name, submodule_path)
@@ -765,14 +790,15 @@ class XmlManifest(object):
765 def _UnjoinRelpath(self, parent_relpath, relpath): 790 def _UnjoinRelpath(self, parent_relpath, relpath):
766 return os.path.relpath(relpath, parent_relpath) 791 return os.path.relpath(relpath, parent_relpath)
767 792
768 def GetSubprojectPaths(self, parent, path): 793 def GetSubprojectPaths(self, parent, name, path):
769 relpath = self._JoinRelpath(parent.relpath, path) 794 relpath = self._JoinRelpath(parent.relpath, path)
770 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)
771 if self.IsMirror: 797 if self.IsMirror:
772 worktree = None 798 worktree = None
773 else: 799 else:
774 worktree = os.path.join(parent.worktree, path).replace('\\', '/') 800 worktree = os.path.join(parent.worktree, path).replace('\\', '/')
775 return relpath, worktree, gitdir 801 return relpath, worktree, gitdir, objdir
776 802
777 def _ParseCopyFile(self, project, node): 803 def _ParseCopyFile(self, project, node):
778 src = self._reqatt(node, 'src') 804 src = self._reqatt(node, 'src')
diff --git a/project.py b/project.py
index eb8bad3e..46f3b8f7 100644
--- a/project.py
+++ b/project.py
@@ -488,6 +488,7 @@ class Project(object):
488 name, 488 name,
489 remote, 489 remote,
490 gitdir, 490 gitdir,
491 objdir,
491 worktree, 492 worktree,
492 relpath, 493 relpath,
493 revisionExpr, 494 revisionExpr,
@@ -508,6 +509,7 @@ class Project(object):
508 name: The `name` attribute of manifest.xml's project element. 509 name: The `name` attribute of manifest.xml's project element.
509 remote: RemoteSpec object specifying its remote's properties. 510 remote: RemoteSpec object specifying its remote's properties.
510 gitdir: Absolute path of git directory. 511 gitdir: Absolute path of git directory.
512 objdir: Absolute path of directory to store git objects.
511 worktree: Absolute path of git working tree. 513 worktree: Absolute path of git working tree.
512 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.
513 revisionExpr: The `revision` attribute of manifest.xml's project element. 515 revisionExpr: The `revision` attribute of manifest.xml's project element.
@@ -526,6 +528,7 @@ class Project(object):
526 self.name = name 528 self.name = name
527 self.remote = remote 529 self.remote = remote
528 self.gitdir = gitdir.replace('\\', '/') 530 self.gitdir = gitdir.replace('\\', '/')
531 self.objdir = objdir.replace('\\', '/')
529 if worktree: 532 if worktree:
530 self.worktree = worktree.replace('\\', '/') 533 self.worktree = worktree.replace('\\', '/')
531 else: 534 else:
@@ -558,11 +561,12 @@ class Project(object):
558 defaults = self.manifest.globalConfig) 561 defaults = self.manifest.globalConfig)
559 562
560 if self.worktree: 563 if self.worktree:
561 self.work_git = self._GitGetByExec(self, bare=False) 564 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
562 else: 565 else:
563 self.work_git = None 566 self.work_git = None
564 self.bare_git = self._GitGetByExec(self, bare=True) 567 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
565 self.bare_ref = GitRefs(gitdir) 568 self.bare_ref = GitRefs(gitdir)
569 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
566 self.dest_branch = dest_branch 570 self.dest_branch = dest_branch
567 571
568 # 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
@@ -1117,6 +1121,7 @@ class Project(object):
1117 """Perform only the local IO portion of the sync process. 1121 """Perform only the local IO portion of the sync process.
1118 Network access is not required. 1122 Network access is not required.
1119 """ 1123 """
1124 self._InitWorkTree()
1120 all_refs = self.bare_ref.all 1125 all_refs = self.bare_ref.all
1121 self.CleanPublishedCache(all_refs) 1126 self.CleanPublishedCache(all_refs)
1122 revid = self.GetRevisionId(all_refs) 1127 revid = self.GetRevisionId(all_refs)
@@ -1125,7 +1130,6 @@ class Project(object):
1125 self._FastForward(revid) 1130 self._FastForward(revid)
1126 self._CopyFiles() 1131 self._CopyFiles()
1127 1132
1128 self._InitWorkTree()
1129 head = self.work_git.GetHead() 1133 head = self.work_git.GetHead()
1130 if head.startswith(R_HEADS): 1134 if head.startswith(R_HEADS):
1131 branch = head[len(R_HEADS):] 1135 branch = head[len(R_HEADS):]
@@ -1592,11 +1596,13 @@ class Project(object):
1592 return result 1596 return result
1593 for rev, path, url in self._GetSubmodules(): 1597 for rev, path, url in self._GetSubmodules():
1594 name = self.manifest.GetSubprojectName(self, path) 1598 name = self.manifest.GetSubprojectName(self, path)
1595 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)
1596 if project: 1602 if project:
1597 result.extend(project.GetDerivedSubprojects()) 1603 result.extend(project.GetDerivedSubprojects())
1598 continue 1604 continue
1599 relpath, worktree, gitdir = self.manifest.GetSubprojectPaths(self, path) 1605
1600 remote = RemoteSpec(self.remote.name, 1606 remote = RemoteSpec(self.remote.name,
1601 url = url, 1607 url = url,
1602 review = self.remote.review) 1608 review = self.remote.review)
@@ -1604,6 +1610,7 @@ class Project(object):
1604 name = name, 1610 name = name,
1605 remote = remote, 1611 remote = remote,
1606 gitdir = gitdir, 1612 gitdir = gitdir,
1613 objdir = objdir,
1607 worktree = worktree, 1614 worktree = worktree,
1608 relpath = relpath, 1615 relpath = relpath,
1609 revisionExpr = self.revisionExpr, 1616 revisionExpr = self.revisionExpr,
@@ -1966,8 +1973,17 @@ class Project(object):
1966 1973
1967 def _InitGitDir(self, mirror_git=None): 1974 def _InitGitDir(self, mirror_git=None):
1968 if not os.path.exists(self.gitdir): 1975 if not os.path.exists(self.gitdir):
1969 os.makedirs(self.gitdir) 1976
1970 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)
1971 1987
1972 mp = self.manifest.manifestProject 1988 mp = self.manifest.manifestProject
1973 ref_dir = mp.config.GetString('repo.reference') or '' 1989 ref_dir = mp.config.GetString('repo.reference') or ''
@@ -2083,33 +2099,61 @@ class Project(object):
2083 msg = 'manifest set to %s' % self.revisionExpr 2099 msg = 'manifest set to %s' % self.revisionExpr
2084 self.bare_git.symbolic_ref('-m', msg, ref, dst) 2100 self.bare_git.symbolic_ref('-m', msg, ref, dst)
2085 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
2086 def _InitWorkTree(self): 2151 def _InitWorkTree(self):
2087 dotgit = os.path.join(self.worktree, '.git') 2152 dotgit = os.path.join(self.worktree, '.git')
2088 if not os.path.exists(dotgit): 2153 if not os.path.exists(dotgit):
2089 os.makedirs(dotgit) 2154 os.makedirs(dotgit)
2090 2155 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2091 for name in ['config', 2156 copy_all=False)
2092 'description',
2093 'hooks',
2094 'info',
2095 'logs',
2096 'objects',
2097 'packed-refs',
2098 'refs',
2099 'rr-cache',
2100 'svn']:
2101 try:
2102 src = os.path.join(self.gitdir, name)
2103 dst = os.path.join(dotgit, name)
2104 if os.path.islink(dst) or not os.path.exists(dst):
2105 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2106 else:
2107 raise GitError('cannot overwrite a local work tree')
2108 except OSError as e:
2109 if e.errno == errno.EPERM:
2110 raise GitError('filesystem must support symlinks')
2111 else:
2112 raise
2113 2157
2114 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId()) 2158 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
2115 2159
@@ -2119,14 +2163,10 @@ class Project(object):
2119 if GitCommand(self, cmd).Wait() != 0: 2163 if GitCommand(self, cmd).Wait() != 0:
2120 raise GitError("cannot initialize work tree") 2164 raise GitError("cannot initialize work tree")
2121 2165
2122 rr_cache = os.path.join(self.gitdir, 'rr-cache')
2123 if not os.path.exists(rr_cache):
2124 os.makedirs(rr_cache)
2125
2126 self._CopyFiles() 2166 self._CopyFiles()
2127 2167
2128 def _gitdir_path(self, path): 2168 def _gitdir_path(self, path):
2129 return os.path.join(self.gitdir, path) 2169 return os.path.realpath(os.path.join(self.gitdir, path))
2130 2170
2131 def _revlist(self, *args, **kw): 2171 def _revlist(self, *args, **kw):
2132 a = [] 2172 a = []
@@ -2139,9 +2179,10 @@ class Project(object):
2139 return self.bare_ref.all 2179 return self.bare_ref.all
2140 2180
2141 class _GitGetByExec(object): 2181 class _GitGetByExec(object):
2142 def __init__(self, project, bare): 2182 def __init__(self, project, bare, gitdir):
2143 self._project = project 2183 self._project = project
2144 self._bare = bare 2184 self._bare = bare
2185 self._gitdir = gitdir
2145 2186
2146 def LsOthers(self): 2187 def LsOthers(self):
2147 p = GitCommand(self._project, 2188 p = GitCommand(self._project,
@@ -2150,6 +2191,7 @@ class Project(object):
2150 '--others', 2191 '--others',
2151 '--exclude-standard'], 2192 '--exclude-standard'],
2152 bare = False, 2193 bare = False,
2194 gitdir=self._gitdir,
2153 capture_stdout = True, 2195 capture_stdout = True,
2154 capture_stderr = True) 2196 capture_stderr = True)
2155 if p.Wait() == 0: 2197 if p.Wait() == 0:
@@ -2165,6 +2207,7 @@ class Project(object):
2165 cmd.extend(args) 2207 cmd.extend(args)
2166 p = GitCommand(self._project, 2208 p = GitCommand(self._project,
2167 cmd, 2209 cmd,
2210 gitdir=self._gitdir,
2168 bare = False, 2211 bare = False,
2169 capture_stdout = True, 2212 capture_stdout = True,
2170 capture_stderr = True) 2213 capture_stderr = True)
@@ -2274,6 +2317,7 @@ class Project(object):
2274 p = GitCommand(self._project, 2317 p = GitCommand(self._project,
2275 cmdv, 2318 cmdv,
2276 bare = self._bare, 2319 bare = self._bare,
2320 gitdir=self._gitdir,
2277 capture_stdout = True, 2321 capture_stdout = True,
2278 capture_stderr = True) 2322 capture_stderr = True)
2279 r = [] 2323 r = []
@@ -2326,6 +2370,7 @@ class Project(object):
2326 p = GitCommand(self._project, 2370 p = GitCommand(self._project,
2327 cmdv, 2371 cmdv,
2328 bare = self._bare, 2372 bare = self._bare,
2373 gitdir=self._gitdir,
2329 capture_stdout = True, 2374 capture_stdout = True,
2330 capture_stderr = True) 2375 capture_stderr = True)
2331 if p.Wait() != 0: 2376 if p.Wait() != 0:
@@ -2459,6 +2504,7 @@ class MetaProject(Project):
2459 manifest = manifest, 2504 manifest = manifest,
2460 name = name, 2505 name = name,
2461 gitdir = gitdir, 2506 gitdir = gitdir,
2507 objdir = gitdir,
2462 worktree = worktree, 2508 worktree = worktree,
2463 remote = RemoteSpec('origin'), 2509 remote = RemoteSpec('origin'),
2464 relpath = '.repo/%s' % name, 2510 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 0279ff60..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
@@ -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))
@@ -304,20 +325,24 @@ later is required to fix a server side protocol bug.
304 else: 325 else:
305 sys.exit(1) 326 sys.exit(1)
306 else: 327 else:
328 objdir_project_map = dict()
329 for project in projects:
330 objdir_project_map.setdefault(project.objdir, []).append(project)
331
307 threads = set() 332 threads = set()
308 lock = _threading.Lock() 333 lock = _threading.Lock()
309 sem = _threading.Semaphore(self.jobs) 334 sem = _threading.Semaphore(self.jobs)
310 err_event = _threading.Event() 335 err_event = _threading.Event()
311 for project in projects: 336 for project_list in objdir_project_map.values():
312 # Check for any errors before starting any new threads. 337 # Check for any errors before starting any new threads.
313 # ...we'll let existing threads finish, though. 338 # ...we'll let existing threads finish, though.
314 if err_event.isSet(): 339 if err_event.isSet():
315 break 340 break
316 341
317 sem.acquire() 342 sem.acquire()
318 t = _threading.Thread(target = self._FetchHelper, 343 t = _threading.Thread(target = self._FetchProjectList,
319 args = (opt, 344 args = (opt,
320 project, 345 project_list,
321 lock, 346 lock,
322 fetched, 347 fetched,
323 pm, 348 pm,
@@ -345,6 +370,10 @@ later is required to fix a server side protocol bug.
345 return fetched 370 return fetched
346 371
347 def _GCProjects(self, projects): 372 def _GCProjects(self, projects):
373 gitdirs = {}
374 for project in projects:
375 gitdirs[project.gitdir] = project.bare_git
376
348 has_dash_c = git_require((1, 7, 2)) 377 has_dash_c = git_require((1, 7, 2))
349 if multiprocessing and has_dash_c: 378 if multiprocessing and has_dash_c:
350 cpu_count = multiprocessing.cpu_count() 379 cpu_count = multiprocessing.cpu_count()
@@ -353,8 +382,8 @@ later is required to fix a server side protocol bug.
353 jobs = min(self.jobs, cpu_count) 382 jobs = min(self.jobs, cpu_count)
354 383
355 if jobs < 2: 384 if jobs < 2:
356 for project in projects: 385 for bare_git in gitdirs.values():
357 project.bare_git.gc('--auto') 386 bare_git.gc('--auto')
358 return 387 return
359 388
360 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}
@@ -363,10 +392,10 @@ later is required to fix a server side protocol bug.
363 sem = _threading.Semaphore(jobs) 392 sem = _threading.Semaphore(jobs)
364 err_event = _threading.Event() 393 err_event = _threading.Event()
365 394
366 def GC(project): 395 def GC(bare_git):
367 try: 396 try:
368 try: 397 try:
369 project.bare_git.gc('--auto', config=config) 398 bare_git.gc('--auto', config=config)
370 except GitError: 399 except GitError:
371 err_event.set() 400 err_event.set()
372 except: 401 except:
@@ -375,11 +404,11 @@ later is required to fix a server side protocol bug.
375 finally: 404 finally:
376 sem.release() 405 sem.release()
377 406
378 for project in projects: 407 for bare_git in gitdirs.values():
379 if err_event.isSet(): 408 if err_event.isSet():
380 break 409 break
381 sem.acquire() 410 sem.acquire()
382 t = _threading.Thread(target=GC, args=(project,)) 411 t = _threading.Thread(target=GC, args=(bare_git,))
383 t.daemon = True 412 t.daemon = True
384 threads.add(t) 413 threads.add(t)
385 t.start() 414 t.start()
@@ -419,12 +448,13 @@ later is required to fix a server side protocol bug.
419 if path not in new_project_paths: 448 if path not in new_project_paths:
420 # 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
421 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')
422 project = Project( 452 project = Project(
423 manifest = self.manifest, 453 manifest = self.manifest,
424 name = path, 454 name = path,
425 remote = RemoteSpec('origin'), 455 remote = RemoteSpec('origin'),
426 gitdir = os.path.join(self.manifest.topdir, 456 gitdir = gitdir,
427 path, '.git'), 457 objdir = gitdir,
428 worktree = os.path.join(self.manifest.topdir, path), 458 worktree = os.path.join(self.manifest.topdir, path),
429 relpath = path, 459 relpath = path,
430 revisionExpr = 'HEAD', 460 revisionExpr = 'HEAD',
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