diff options
-rw-r--r-- | command.py | 26 | ||||
-rw-r--r-- | docs/manifest-format.txt | 12 | ||||
-rw-r--r-- | git_command.py | 2 | ||||
-rw-r--r-- | git_config.py | 4 | ||||
-rw-r--r-- | git_refs.py | 2 | ||||
-rwxr-xr-x | hooks/commit-msg | 4 | ||||
-rwxr-xr-x | hooks/pre-auto-gc | 2 | ||||
-rw-r--r-- | manifest_xml.py | 70 | ||||
-rw-r--r-- | project.py | 193 | ||||
-rwxr-xr-x | repo | 32 | ||||
-rw-r--r-- | subcmds/branches.py | 2 | ||||
-rw-r--r-- | subcmds/init.py | 21 | ||||
-rw-r--r-- | subcmds/rebase.py | 3 | ||||
-rw-r--r-- | subcmds/sync.py | 67 | ||||
-rw-r--r-- | subcmds/upload.py | 15 |
15 files changed, 334 insertions, 121 deletions
@@ -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 | |||
35 | then | 35 | then |
36 | exit 0 | 36 | exit 0 |
37 | elif test -x /usr/bin/pmset && /usr/bin/pmset -g batt | | 37 | elif 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'" |
39 | then | 39 | then |
40 | exit 0 | 40 | exit 0 |
41 | elif test -d /sys/bus/acpi/drivers/battery && test 0 = \ | 41 | elif 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') |
@@ -23,6 +23,7 @@ import shutil | |||
23 | import stat | 23 | import stat |
24 | import subprocess | 24 | import subprocess |
25 | import sys | 25 | import sys |
26 | import tarfile | ||
26 | import tempfile | 27 | import tempfile |
27 | import time | 28 | import 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, |
@@ -110,6 +110,7 @@ REPO_MAIN = S_repo + '/main.py' # main script | |||
110 | MIN_PYTHON_VERSION = (2, 6) # minimum supported python version | 110 | MIN_PYTHON_VERSION = (2, 6) # minimum supported python version |
111 | 111 | ||
112 | 112 | ||
113 | import errno | ||
113 | import optparse | 114 | import optparse |
114 | import os | 115 | import os |
115 | import re | 116 | import re |
@@ -138,10 +139,9 @@ def _print(*objects, **kwargs): | |||
138 | # Python version check | 139 | # Python version check |
139 | ver = sys.version_info | 140 | ver = sys.version_info |
140 | if ver[0] == 3: | 141 | if 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) | ||
145 | if (ver[0], ver[1]) < MIN_PYTHON_VERSION: | 145 | if (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', | |||
181 | group.add_option('--depth', type='int', default=None, | 181 | group.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') |
184 | group.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.') | ||
184 | group.add_option('-g', '--groups', | 188 | group.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 | ||
324 | def SetupGnuPG(quiet): | 328 | def 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 |