summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--command.py26
-rw-r--r--docs/manifest-format.txt12
-rw-r--r--git_command.py2
-rw-r--r--git_config.py4
-rw-r--r--git_refs.py2
-rwxr-xr-xhooks/commit-msg4
-rwxr-xr-xhooks/pre-auto-gc2
-rw-r--r--manifest_xml.py70
-rw-r--r--project.py193
-rwxr-xr-xrepo32
-rw-r--r--subcmds/branches.py2
-rw-r--r--subcmds/init.py21
-rw-r--r--subcmds/rebase.py3
-rw-r--r--subcmds/sync.py67
-rw-r--r--subcmds/upload.py15
15 files changed, 334 insertions, 121 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/docs/manifest-format.txt b/docs/manifest-format.txt
index dcc90d07..e48b75fe 100644
--- a/docs/manifest-format.txt
+++ b/docs/manifest-format.txt
@@ -27,15 +27,15 @@ following DTD:
27 remove-project*, 27 remove-project*,
28 project*, 28 project*,
29 repo-hooks?)> 29 repo-hooks?)>
30 30
31 <!ELEMENT notice (#PCDATA)> 31 <!ELEMENT notice (#PCDATA)>
32 32
33 <!ELEMENT remote (EMPTY)> 33 <!ELEMENT remote (EMPTY)>
34 <!ATTLIST remote name ID #REQUIRED> 34 <!ATTLIST remote name ID #REQUIRED>
35 <!ATTLIST remote alias CDATA #IMPLIED> 35 <!ATTLIST remote alias CDATA #IMPLIED>
36 <!ATTLIST remote fetch CDATA #REQUIRED> 36 <!ATTLIST remote fetch CDATA #REQUIRED>
37 <!ATTLIST remote review CDATA #IMPLIED> 37 <!ATTLIST remote review CDATA #IMPLIED>
38 38
39 <!ELEMENT default (EMPTY)> 39 <!ELEMENT default (EMPTY)>
40 <!ATTLIST default remote IDREF #IMPLIED> 40 <!ATTLIST default remote IDREF #IMPLIED>
41 <!ATTLIST default revision CDATA #IMPLIED> 41 <!ATTLIST default revision CDATA #IMPLIED>
@@ -46,8 +46,8 @@ following DTD:
46 46
47 <!ELEMENT manifest-server (EMPTY)> 47 <!ELEMENT manifest-server (EMPTY)>
48 <!ATTLIST url CDATA #REQUIRED> 48 <!ATTLIST url CDATA #REQUIRED>
49 49
50 <!ELEMENT project (annotation?, 50 <!ELEMENT project (annotation*,
51 project*)> 51 project*)>
52 <!ATTLIST project name CDATA #REQUIRED> 52 <!ATTLIST project name CDATA #REQUIRED>
53 <!ATTLIST project path CDATA #IMPLIED> 53 <!ATTLIST project path CDATA #IMPLIED>
@@ -65,7 +65,7 @@ following DTD:
65 <!ATTLIST annotation name CDATA #REQUIRED> 65 <!ATTLIST annotation name CDATA #REQUIRED>
66 <!ATTLIST annotation value CDATA #REQUIRED> 66 <!ATTLIST annotation value CDATA #REQUIRED>
67 <!ATTLIST annotation keep CDATA "true"> 67 <!ATTLIST annotation keep CDATA "true">
68 68
69 <!ELEMENT remove-project (EMPTY)> 69 <!ELEMENT remove-project (EMPTY)>
70 <!ATTLIST remove-project name CDATA #REQUIRED> 70 <!ATTLIST remove-project name CDATA #REQUIRED>
71 71
diff --git a/git_command.py b/git_command.py
index d347dd61..51f5e3c0 100644
--- a/git_command.py
+++ b/git_command.py
@@ -86,7 +86,7 @@ class _GitCall(object):
86 global _git_version 86 global _git_version
87 87
88 if _git_version is None: 88 if _git_version is None:
89 ver_str = git.version() 89 ver_str = git.version().decode('utf-8')
90 if ver_str.startswith('git version '): 90 if ver_str.startswith('git version '):
91 _git_version = tuple( 91 _git_version = tuple(
92 map(int, 92 map(int,
diff --git a/git_config.py b/git_config.py
index a294a0b6..f6093a25 100644
--- a/git_config.py
+++ b/git_config.py
@@ -304,8 +304,8 @@ class GitConfig(object):
304 d = self._do('--null', '--list') 304 d = self._do('--null', '--list')
305 if d is None: 305 if d is None:
306 return c 306 return c
307 for line in d.rstrip('\0').split('\0'): # pylint: disable=W1401 307 for line in d.decode('utf-8').rstrip('\0').split('\0'): # pylint: disable=W1401
308 # Backslash is not anomalous 308 # Backslash is not anomalous
309 if '\n' in line: 309 if '\n' in line:
310 key, val = line.split('\n', 1) 310 key, val = line.split('\n', 1)
311 else: 311 else:
diff --git a/git_refs.py b/git_refs.py
index 4dd68769..3c266061 100644
--- a/git_refs.py
+++ b/git_refs.py
@@ -100,7 +100,7 @@ class GitRefs(object):
100 def _ReadPackedRefs(self): 100 def _ReadPackedRefs(self):
101 path = os.path.join(self._gitdir, 'packed-refs') 101 path = os.path.join(self._gitdir, 'packed-refs')
102 try: 102 try:
103 fd = open(path, 'rb') 103 fd = open(path, 'r')
104 mtime = os.path.getmtime(path) 104 mtime = os.path.getmtime(path)
105 except IOError: 105 except IOError:
106 return 106 return
diff --git a/hooks/commit-msg b/hooks/commit-msg
index b37dfaa4..5ca2b112 100755
--- a/hooks/commit-msg
+++ b/hooks/commit-msg
@@ -1,5 +1,5 @@
1#!/bin/sh 1#!/bin/sh
2# From Gerrit Code Review 2.5.2 2# From Gerrit Code Review 2.6
3# 3#
4# Part of Gerrit Code Review (http://code.google.com/p/gerrit/) 4# Part of Gerrit Code Review (http://code.google.com/p/gerrit/)
5# 5#
@@ -154,7 +154,7 @@ add_ChangeId() {
154 if (unprinted) { 154 if (unprinted) {
155 print "Change-Id: I'"$id"'" 155 print "Change-Id: I'"$id"'"
156 } 156 }
157 }' "$MSG" > $T && mv $T "$MSG" || rm -f $T 157 }' "$MSG" > "$T" && mv "$T" "$MSG" || rm -f "$T"
158} 158}
159_gen_ChangeIdInput() { 159_gen_ChangeIdInput() {
160 echo "tree `git write-tree`" 160 echo "tree `git write-tree`"
diff --git a/hooks/pre-auto-gc b/hooks/pre-auto-gc
index 360e5e1f..43403022 100755
--- a/hooks/pre-auto-gc
+++ b/hooks/pre-auto-gc
@@ -35,7 +35,7 @@ elif grep -q "AC Power \+: 1" /proc/pmu/info 2>/dev/null
35then 35then
36 exit 0 36 exit 0
37elif test -x /usr/bin/pmset && /usr/bin/pmset -g batt | 37elif test -x /usr/bin/pmset && /usr/bin/pmset -g batt |
38 grep -q "Currently drawing from 'AC Power'" 38 grep -q "drawing from 'AC Power'"
39then 39then
40 exit 0 40 exit 0
41elif test -d /sys/bus/acpi/drivers/battery && test 0 = \ 41elif test -d /sys/bus/acpi/drivers/battery && test 0 = \
diff --git a/manifest_xml.py b/manifest_xml.py
index f1d3edaa..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):
@@ -235,7 +236,7 @@ class XmlManifest(object):
235 e.setAttribute('path', relpath) 236 e.setAttribute('path', relpath)
236 remoteName = None 237 remoteName = None
237 if d.remote: 238 if d.remote:
238 remoteName = d.remote.remoteAlias or d.remote.name 239 remoteName = d.remote.remoteAlias or d.remote.name
239 if not d.remote or p.remote.name != remoteName: 240 if not d.remote or p.remote.name != remoteName:
240 e.setAttribute('remote', p.remote.name) 241 e.setAttribute('remote', p.remote.name)
241 if peg_rev: 242 if peg_rev:
@@ -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 a6a88607..a1249a86 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):]
@@ -1165,7 +1217,7 @@ class Project(object):
1165 last_mine = None 1217 last_mine = None
1166 cnt_mine = 0 1218 cnt_mine = 0
1167 for commit in local_changes: 1219 for commit in local_changes:
1168 commit_id, committer_email = commit.split(' ', 1) 1220 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
1169 if committer_email == self.UserEmail: 1221 if committer_email == self.UserEmail:
1170 last_mine = commit_id 1222 last_mine = commit_id
1171 cnt_mine += 1 1223 cnt_mine += 1
@@ -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,
@@ -1843,11 +1911,11 @@ class Project(object):
1843 cookiefile = line[len(prefix):] 1911 cookiefile = line[len(prefix):]
1844 break 1912 break
1845 if p.wait(): 1913 if p.wait():
1846 line = iter(p.stderr).next() 1914 err_msg = p.stderr.read()
1847 if ' -print_config' in line: 1915 if ' -print_config' in err_msg:
1848 pass # Persistent proxy doesn't support -print_config. 1916 pass # Persistent proxy doesn't support -print_config.
1849 else: 1917 else:
1850 print(line + p.stderr.read(), file=sys.stderr) 1918 print(err_msg, file=sys.stderr)
1851 if cookiefile: 1919 if cookiefile:
1852 return cookiefile 1920 return cookiefile
1853 except OSError as e: 1921 except OSError as e:
@@ -1908,8 +1976,17 @@ class Project(object):
1908 1976
1909 def _InitGitDir(self, mirror_git=None): 1977 def _InitGitDir(self, mirror_git=None):
1910 if not os.path.exists(self.gitdir): 1978 if not os.path.exists(self.gitdir):
1911 os.makedirs(self.gitdir) 1979
1912 self.bare_git.init() 1980 # Initialize the bare repository, which contains all of the objects.
1981 if not os.path.exists(self.objdir):
1982 os.makedirs(self.objdir)
1983 self.bare_objdir.init()
1984
1985 # If we have a separate directory to hold refs, initialize it as well.
1986 if self.objdir != self.gitdir:
1987 os.makedirs(self.gitdir)
1988 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
1989 copy_all=True)
1913 1990
1914 mp = self.manifest.manifestProject 1991 mp = self.manifest.manifestProject
1915 ref_dir = mp.config.GetString('repo.reference') or '' 1992 ref_dir = mp.config.GetString('repo.reference') or ''
@@ -1958,7 +2035,7 @@ class Project(object):
1958 self._InitHooks() 2035 self._InitHooks()
1959 2036
1960 def _InitHooks(self): 2037 def _InitHooks(self):
1961 hooks = self._gitdir_path('hooks') 2038 hooks = os.path.realpath(self._gitdir_path('hooks'))
1962 if not os.path.exists(hooks): 2039 if not os.path.exists(hooks):
1963 os.makedirs(hooks) 2040 os.makedirs(hooks)
1964 for stock_hook in _ProjectHooks(): 2041 for stock_hook in _ProjectHooks():
@@ -2025,33 +2102,61 @@ class Project(object):
2025 msg = 'manifest set to %s' % self.revisionExpr 2102 msg = 'manifest set to %s' % self.revisionExpr
2026 self.bare_git.symbolic_ref('-m', msg, ref, dst) 2103 self.bare_git.symbolic_ref('-m', msg, ref, dst)
2027 2104
2105 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2106 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2107
2108 Args:
2109 gitdir: The bare git repository. Must already be initialized.
2110 dotgit: The repository you would like to initialize.
2111 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2112 Only one work tree can store refs under a given |gitdir|.
2113 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2114 This saves you the effort of initializing |dotgit| yourself.
2115 """
2116 # These objects can be shared between several working trees.
2117 symlink_files = ['description', 'info']
2118 symlink_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
2119 if share_refs:
2120 # These objects can only be used by a single working tree.
2121 symlink_files += ['config', 'packed-refs']
2122 symlink_dirs += ['logs', 'refs']
2123 to_symlink = symlink_files + symlink_dirs
2124
2125 to_copy = []
2126 if copy_all:
2127 to_copy = os.listdir(gitdir)
2128
2129 for name in set(to_copy).union(to_symlink):
2130 try:
2131 src = os.path.realpath(os.path.join(gitdir, name))
2132 dst = os.path.realpath(os.path.join(dotgit, name))
2133
2134 if os.path.lexists(dst) and not os.path.islink(dst):
2135 raise GitError('cannot overwrite a local work tree')
2136
2137 # If the source dir doesn't exist, create an empty dir.
2138 if name in symlink_dirs and not os.path.lexists(src):
2139 os.makedirs(src)
2140
2141 if name in to_symlink:
2142 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2143 elif copy_all and not os.path.islink(dst):
2144 if os.path.isdir(src):
2145 shutil.copytree(src, dst)
2146 elif os.path.isfile(src):
2147 shutil.copy(src, dst)
2148 except OSError as e:
2149 if e.errno == errno.EPERM:
2150 raise GitError('filesystem must support symlinks')
2151 else:
2152 raise
2153
2028 def _InitWorkTree(self): 2154 def _InitWorkTree(self):
2029 dotgit = os.path.join(self.worktree, '.git') 2155 dotgit = os.path.join(self.worktree, '.git')
2030 if not os.path.exists(dotgit): 2156 if not os.path.exists(dotgit):
2031 os.makedirs(dotgit) 2157 os.makedirs(dotgit)
2032 2158 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2033 for name in ['config', 2159 copy_all=False)
2034 'description',
2035 'hooks',
2036 'info',
2037 'logs',
2038 'objects',
2039 'packed-refs',
2040 'refs',
2041 'rr-cache',
2042 'svn']:
2043 try:
2044 src = os.path.join(self.gitdir, name)
2045 dst = os.path.join(dotgit, name)
2046 if os.path.islink(dst) or not os.path.exists(dst):
2047 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2048 else:
2049 raise GitError('cannot overwrite a local work tree')
2050 except OSError as e:
2051 if e.errno == errno.EPERM:
2052 raise GitError('filesystem must support symlinks')
2053 else:
2054 raise
2055 2160
2056 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId()) 2161 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
2057 2162
@@ -2061,14 +2166,10 @@ class Project(object):
2061 if GitCommand(self, cmd).Wait() != 0: 2166 if GitCommand(self, cmd).Wait() != 0:
2062 raise GitError("cannot initialize work tree") 2167 raise GitError("cannot initialize work tree")
2063 2168
2064 rr_cache = os.path.join(self.gitdir, 'rr-cache')
2065 if not os.path.exists(rr_cache):
2066 os.makedirs(rr_cache)
2067
2068 self._CopyFiles() 2169 self._CopyFiles()
2069 2170
2070 def _gitdir_path(self, path): 2171 def _gitdir_path(self, path):
2071 return os.path.join(self.gitdir, path) 2172 return os.path.realpath(os.path.join(self.gitdir, path))
2072 2173
2073 def _revlist(self, *args, **kw): 2174 def _revlist(self, *args, **kw):
2074 a = [] 2175 a = []
@@ -2081,9 +2182,10 @@ class Project(object):
2081 return self.bare_ref.all 2182 return self.bare_ref.all
2082 2183
2083 class _GitGetByExec(object): 2184 class _GitGetByExec(object):
2084 def __init__(self, project, bare): 2185 def __init__(self, project, bare, gitdir):
2085 self._project = project 2186 self._project = project
2086 self._bare = bare 2187 self._bare = bare
2188 self._gitdir = gitdir
2087 2189
2088 def LsOthers(self): 2190 def LsOthers(self):
2089 p = GitCommand(self._project, 2191 p = GitCommand(self._project,
@@ -2092,6 +2194,7 @@ class Project(object):
2092 '--others', 2194 '--others',
2093 '--exclude-standard'], 2195 '--exclude-standard'],
2094 bare = False, 2196 bare = False,
2197 gitdir=self._gitdir,
2095 capture_stdout = True, 2198 capture_stdout = True,
2096 capture_stderr = True) 2199 capture_stderr = True)
2097 if p.Wait() == 0: 2200 if p.Wait() == 0:
@@ -2107,6 +2210,7 @@ class Project(object):
2107 cmd.extend(args) 2210 cmd.extend(args)
2108 p = GitCommand(self._project, 2211 p = GitCommand(self._project,
2109 cmd, 2212 cmd,
2213 gitdir=self._gitdir,
2110 bare = False, 2214 bare = False,
2111 capture_stdout = True, 2215 capture_stdout = True,
2112 capture_stderr = True) 2216 capture_stderr = True)
@@ -2216,6 +2320,7 @@ class Project(object):
2216 p = GitCommand(self._project, 2320 p = GitCommand(self._project,
2217 cmdv, 2321 cmdv,
2218 bare = self._bare, 2322 bare = self._bare,
2323 gitdir=self._gitdir,
2219 capture_stdout = True, 2324 capture_stdout = True,
2220 capture_stderr = True) 2325 capture_stderr = True)
2221 r = [] 2326 r = []
@@ -2268,6 +2373,7 @@ class Project(object):
2268 p = GitCommand(self._project, 2373 p = GitCommand(self._project,
2269 cmdv, 2374 cmdv,
2270 bare = self._bare, 2375 bare = self._bare,
2376 gitdir=self._gitdir,
2271 capture_stdout = True, 2377 capture_stdout = True,
2272 capture_stderr = True) 2378 capture_stderr = True)
2273 if p.Wait() != 0: 2379 if p.Wait() != 0:
@@ -2401,6 +2507,7 @@ class MetaProject(Project):
2401 manifest = manifest, 2507 manifest = manifest,
2402 name = name, 2508 name = name,
2403 gitdir = gitdir, 2509 gitdir = gitdir,
2510 objdir = gitdir,
2404 worktree = worktree, 2511 worktree = worktree,
2405 remote = RemoteSpec('origin'), 2512 remote = RemoteSpec('origin'),
2406 relpath = '.repo/%s' % name, 2513 relpath = '.repo/%s' % name,
diff --git a/repo b/repo
index 62e6ea51..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
@@ -138,10 +139,9 @@ def _print(*objects, **kwargs):
138# Python version check 139# Python version check
139ver = sys.version_info 140ver = sys.version_info
140if ver[0] == 3: 141if ver[0] == 3:
141 _print('error: Python 3 support is not fully implemented in repo yet.\n' 142 _print('warning: Python 3 support is currently experimental. YMMV.\n'
142 'Please use Python 2.6 - 2.7 instead.', 143 'Please use Python 2.6 - 2.7 instead.',
143 file=sys.stderr) 144 file=sys.stderr)
144 sys.exit(1)
145if (ver[0], ver[1]) < MIN_PYTHON_VERSION: 145if (ver[0], ver[1]) < MIN_PYTHON_VERSION:
146 _print('error: Python version %s unsupported.\n' 146 _print('error: Python version %s unsupported.\n'
147 'Please use Python 2.6 - 2.7 instead.' 147 'Please use Python 2.6 - 2.7 instead.'
@@ -181,6 +181,10 @@ group.add_option('--reference',
181group.add_option('--depth', type='int', default=None, 181group.add_option('--depth', type='int', default=None,
182 dest='depth', 182 dest='depth',
183 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.')
184group.add_option('-g', '--groups', 188group.add_option('-g', '--groups',
185 dest='groups', default='default', 189 dest='groups', default='default',
186 help='restrict manifest projects to ones with specified ' 190 help='restrict manifest projects to ones with specified '
@@ -240,10 +244,10 @@ def _Init(args):
240 _print("fatal: invalid branch name '%s'" % branch, file=sys.stderr) 244 _print("fatal: invalid branch name '%s'" % branch, file=sys.stderr)
241 raise CloneFailure() 245 raise CloneFailure()
242 246
243 if not os.path.isdir(repodir): 247 try:
244 try: 248 os.mkdir(repodir)
245 os.mkdir(repodir) 249 except OSError as e:
246 except OSError as e: 250 if e.errno != errno.EEXIST:
247 _print('fatal: cannot make %s directory: %s' 251 _print('fatal: cannot make %s directory: %s'
248 % (repodir, e.strerror), file=sys.stderr) 252 % (repodir, e.strerror), file=sys.stderr)
249 # Don't raise CloneFailure; that would delete the 253 # Don't raise CloneFailure; that would delete the
@@ -322,18 +326,18 @@ def NeedSetupGnuPG():
322 326
323 327
324def SetupGnuPG(quiet): 328def SetupGnuPG(quiet):
325 if not os.path.isdir(home_dot_repo): 329 try:
326 try: 330 os.mkdir(home_dot_repo)
327 os.mkdir(home_dot_repo) 331 except OSError as e:
328 except OSError as e: 332 if e.errno != errno.EEXIST:
329 _print('fatal: cannot make %s directory: %s' 333 _print('fatal: cannot make %s directory: %s'
330 % (home_dot_repo, e.strerror), file=sys.stderr) 334 % (home_dot_repo, e.strerror), file=sys.stderr)
331 sys.exit(1) 335 sys.exit(1)
332 336
333 if not os.path.isdir(gpg_dir): 337 try:
334 try: 338 os.mkdir(gpg_dir, stat.S_IRWXU)
335 os.mkdir(gpg_dir, stat.S_IRWXU) 339 except OSError as e:
336 except OSError as e: 340 if e.errno != errno.EEXIST:
337 _print('fatal: cannot make %s directory: %s' % (gpg_dir, e.strerror), 341 _print('fatal: cannot make %s directory: %s' % (gpg_dir, e.strerror),
338 file=sys.stderr) 342 file=sys.stderr)
339 sys.exit(1) 343 sys.exit(1)
diff --git a/subcmds/branches.py b/subcmds/branches.py
index c2e7c4b9..f714c1e8 100644
--- a/subcmds/branches.py
+++ b/subcmds/branches.py
@@ -139,7 +139,7 @@ is shown, then the branch appears in all projects.
139 if in_cnt < project_cnt: 139 if in_cnt < project_cnt:
140 fmt = out.write 140 fmt = out.write
141 paths = [] 141 paths = []
142 if in_cnt < project_cnt - in_cnt: 142 if in_cnt < project_cnt - in_cnt:
143 in_type = 'in' 143 in_type = 'in'
144 for b in i.projects: 144 for b in i.projects:
145 paths.append(b.project.relpath) 145 paths.append(b.project.relpath)
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 e9d52b7b..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
@@ -761,7 +794,7 @@ class _FetchTimes(object):
761 def _Load(self): 794 def _Load(self):
762 if self._times is None: 795 if self._times is None:
763 try: 796 try:
764 f = open(self._path) 797 f = open(self._path, 'rb')
765 except IOError: 798 except IOError:
766 self._times = {} 799 self._times = {}
767 return self._times 800 return self._times
diff --git a/subcmds/upload.py b/subcmds/upload.py
index 7f7585ae..526dcd57 100644
--- a/subcmds/upload.py
+++ b/subcmds/upload.py
@@ -422,7 +422,16 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
422 for project in project_list: 422 for project in project_list:
423 if opt.current_branch: 423 if opt.current_branch:
424 cbr = project.CurrentBranch 424 cbr = project.CurrentBranch
425 avail = [project.GetUploadableBranch(cbr)] if cbr else None 425 up_branch = project.GetUploadableBranch(cbr)
426 if up_branch:
427 avail = [up_branch]
428 else:
429 avail = None
430 print('ERROR: Current branch (%s) not uploadable. '
431 'You may be able to type '
432 '"git branch --set-upstream-to m/master" to fix '
433 'your branch.' % str(cbr),
434 file=sys.stderr)
426 else: 435 else:
427 avail = project.GetUploadableBranches(branch) 436 avail = project.GetUploadableBranches(branch)
428 if avail: 437 if avail:
@@ -432,8 +441,10 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
432 hook = RepoHook('pre-upload', self.manifest.repo_hooks_project, 441 hook = RepoHook('pre-upload', self.manifest.repo_hooks_project,
433 self.manifest.topdir, abort_if_user_denies=True) 442 self.manifest.topdir, abort_if_user_denies=True)
434 pending_proj_names = [project.name for (project, avail) in pending] 443 pending_proj_names = [project.name for (project, avail) in pending]
444 pending_worktrees = [project.worktree for (project, avail) in pending]
435 try: 445 try:
436 hook.Run(opt.allow_all_hooks, project_list=pending_proj_names) 446 hook.Run(opt.allow_all_hooks, project_list=pending_proj_names,
447 worktree_list=pending_worktrees)
437 except HookError as e: 448 except HookError as e:
438 print("ERROR: %s" % str(e), file=sys.stderr) 449 print("ERROR: %s" % str(e), file=sys.stderr)
439 return 450 return