summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorShawn O. Pearce <sop@google.com>2012-10-26 12:30:38 -0700
committerGerrit Code Review <noreply-gerritcodereview@google.com>2012-10-26 12:30:38 -0700
commitde7eae482664d4bda61238b6adb679b3b92535d2 (patch)
treefab047f944470100c621b39da83bd80991f7bdbf
parent2fe99e8820a84f545ad45d53921dc94b8bc9d4f3 (diff)
parentcd81dd6403fc8dbe6ec5920c517d9083902c3c1f (diff)
downloadgit-repo-de7eae482664d4bda61238b6adb679b3b92535d2.tar.gz
Merge "Revert "Represent git-submodule as nested projects""
-rw-r--r--command.py72
-rw-r--r--docs/manifest-format.txt15
-rw-r--r--manifest_xml.py108
-rw-r--r--project.py179
-rw-r--r--subcmds/sync.py21
5 files changed, 55 insertions, 340 deletions
diff --git a/command.py b/command.py
index d543e3a8..0c3b360c 100644
--- a/command.py
+++ b/command.py
@@ -60,32 +60,6 @@ class Command(object):
60 """ 60 """
61 raise NotImplementedError 61 raise NotImplementedError
62 62
63 def _ResetPathToProjectMap(self, projects):
64 self._by_path = dict((p.worktree, p) for p in projects)
65
66 def _UpdatePathToProjectMap(self, project):
67 self._by_path[project.worktree] = project
68
69 def _GetProjectByPath(self, path):
70 project = None
71 if os.path.exists(path):
72 oldpath = None
73 while path \
74 and path != oldpath \
75 and path != self.manifest.topdir:
76 try:
77 project = self._by_path[path]
78 break
79 except KeyError:
80 oldpath = path
81 path = os.path.dirname(path)
82 else:
83 try:
84 project = self._by_path[path]
85 except KeyError:
86 pass
87 return project
88
89 def GetProjects(self, args, missing_ok=False): 63 def GetProjects(self, args, missing_ok=False):
90 """A list of projects that match the arguments. 64 """A list of projects that match the arguments.
91 """ 65 """
@@ -100,38 +74,40 @@ class Command(object):
100 groups = [x for x in re.split('[,\s]+', groups) if x] 74 groups = [x for x in re.split('[,\s]+', groups) if x]
101 75
102 if not args: 76 if not args:
103 all_projects_list = all_projects.values() 77 for project in all_projects.values():
104 derived_projects = []
105 for project in all_projects_list:
106 if project.Registered:
107 # Do not search registered subproject for derived projects
108 # since its parent has been searched already
109 continue
110 derived_projects.extend(project.GetDerivedSubprojects())
111 all_projects_list.extend(derived_projects)
112 for project in all_projects_list:
113 if ((missing_ok or project.Exists) and 78 if ((missing_ok or project.Exists) and
114 project.MatchesGroups(groups)): 79 project.MatchesGroups(groups)):
115 result.append(project) 80 result.append(project)
116 else: 81 else:
117 self._ResetPathToProjectMap(all_projects.values()) 82 by_path = None
118 83
119 for arg in args: 84 for arg in args:
120 project = all_projects.get(arg) 85 project = all_projects.get(arg)
121 86
122 if not project: 87 if not project:
123 path = os.path.abspath(arg).replace('\\', '/') 88 path = os.path.abspath(arg).replace('\\', '/')
124 project = self._GetProjectByPath(path) 89
125 90 if not by_path:
126 # If it's not a derived project, update path->project mapping and 91 by_path = dict()
127 # search again, as arg might actually point to a derived subproject. 92 for p in all_projects.values():
128 if project and not project.Derived: 93 by_path[p.worktree] = p
129 search_again = False 94
130 for subproject in project.GetDerivedSubprojects(): 95 if os.path.exists(path):
131 self._UpdatePathToProjectMap(subproject) 96 oldpath = None
132 search_again = True 97 while path \
133 if search_again: 98 and path != oldpath \
134 project = self._GetProjectByPath(path) or project 99 and path != self.manifest.topdir:
100 try:
101 project = by_path[path]
102 break
103 except KeyError:
104 oldpath = path
105 path = os.path.dirname(path)
106 else:
107 try:
108 project = by_path[path]
109 except KeyError:
110 pass
135 111
136 if not project: 112 if not project:
137 raise NoSuchProjectError(arg) 113 raise NoSuchProjectError(arg)
diff --git a/docs/manifest-format.txt b/docs/manifest-format.txt
index a36af67c..f499868c 100644
--- a/docs/manifest-format.txt
+++ b/docs/manifest-format.txt
@@ -45,8 +45,7 @@ following DTD:
45 <!ELEMENT manifest-server (EMPTY)> 45 <!ELEMENT manifest-server (EMPTY)>
46 <!ATTLIST url CDATA #REQUIRED> 46 <!ATTLIST url CDATA #REQUIRED>
47 47
48 <!ELEMENT project (annotation?, 48 <!ELEMENT project (annotation?)>
49 project*)>
50 <!ATTLIST project name CDATA #REQUIRED> 49 <!ATTLIST project name CDATA #REQUIRED>
51 <!ATTLIST project path CDATA #IMPLIED> 50 <!ATTLIST project path CDATA #IMPLIED>
52 <!ATTLIST project remote IDREF #IMPLIED> 51 <!ATTLIST project remote IDREF #IMPLIED>
@@ -153,10 +152,7 @@ Element project
153 152
154One or more project elements may be specified. Each element 153One or more project elements may be specified. Each element
155describes a single Git repository to be cloned into the repo 154describes a single Git repository to be cloned into the repo
156client workspace. You may specify Git-submodules by creating a 155client workspace.
157nested project. Git-submodules will be automatically
158recognized and inherit their parent's attributes, but those
159may be overridden by an explicitly specified project element.
160 156
161Attribute `name`: A unique name for this project. The project's 157Attribute `name`: A unique name for this project. The project's
162name is appended onto its remote's fetch URL to generate the actual 158name is appended onto its remote's fetch URL to generate the actual
@@ -167,8 +163,7 @@ URL to configure the Git remote with. The URL gets formed as:
167where ${remote_fetch} is the remote's fetch attribute and 163where ${remote_fetch} is the remote's fetch attribute and
168${project_name} is the project's name attribute. The suffix ".git" 164${project_name} is the project's name attribute. The suffix ".git"
169is always appended as repo assumes the upstream is a forest of 165is always appended as repo assumes the upstream is a forest of
170bare Git repositories. If the project has a parent element, its 166bare Git repositories.
171name will be prefixed by the parent's.
172 167
173The project name must match the name Gerrit knows, if Gerrit is 168The project name must match the name Gerrit knows, if Gerrit is
174being used for code reviews. 169being used for code reviews.
@@ -176,8 +171,6 @@ being used for code reviews.
176Attribute `path`: An optional path relative to the top directory 171Attribute `path`: An optional path relative to the top directory
177of the repo client where the Git working directory for this project 172of the repo client where the Git working directory for this project
178should be placed. If not supplied the project name is used. 173should be placed. If not supplied the project name is used.
179If the project has a parent element, its path will be prefixed
180by the parent's.
181 174
182Attribute `remote`: Name of a previously defined remote element. 175Attribute `remote`: Name of a previously defined remote element.
183If not supplied the remote given by the default element is used. 176If not supplied the remote given by the default element is used.
@@ -197,8 +190,6 @@ its name:`name` and path:`path`. E.g. for
197definition is implicitly in the following manifest groups: 190definition is implicitly in the following manifest groups:
198default, name:monkeys, and path:barrel-of. If you place a project in the 191default, name:monkeys, and path:barrel-of. If you place a project in the
199group "notdefault", it will not be automatically downloaded by repo. 192group "notdefault", it will not be automatically downloaded by repo.
200If the project has a parent element, the `name` and `path` here
201are the prefixed ones.
202 193
203Element annotation 194Element annotation
204------------------ 195------------------
diff --git a/manifest_xml.py b/manifest_xml.py
index 11e4ee54..dd163bed 100644
--- a/manifest_xml.py
+++ b/manifest_xml.py
@@ -180,25 +180,20 @@ class XmlManifest(object):
180 root.appendChild(e) 180 root.appendChild(e)
181 root.appendChild(doc.createTextNode('')) 181 root.appendChild(doc.createTextNode(''))
182 182
183 def output_projects(parent, parent_node, projects): 183 sort_projects = list(self.projects.keys())
184 for p in projects: 184 sort_projects.sort()
185 output_project(parent, parent_node, self.projects[p])
186 185
187 def output_project(parent, parent_node, p): 186 for p in sort_projects:
188 if not p.MatchesGroups(groups): 187 p = self.projects[p]
189 return
190 188
191 name = p.name 189 if not p.MatchesGroups(groups):
192 relpath = p.relpath 190 continue
193 if parent:
194 name = self._UnjoinName(parent.name, name)
195 relpath = self._UnjoinRelpath(parent.relpath, relpath)
196 191
197 e = doc.createElement('project') 192 e = doc.createElement('project')
198 parent_node.appendChild(e) 193 root.appendChild(e)
199 e.setAttribute('name', name) 194 e.setAttribute('name', p.name)
200 if relpath != name: 195 if p.relpath != p.name:
201 e.setAttribute('path', relpath) 196 e.setAttribute('path', p.relpath)
202 if not d.remote or p.remote.name != d.remote.name: 197 if not d.remote or p.remote.name != d.remote.name:
203 e.setAttribute('remote', p.remote.name) 198 e.setAttribute('remote', p.remote.name)
204 if peg_rev: 199 if peg_rev:
@@ -236,16 +231,6 @@ class XmlManifest(object):
236 if p.sync_c: 231 if p.sync_c:
237 e.setAttribute('sync-c', 'true') 232 e.setAttribute('sync-c', 'true')
238 233
239 if p.subprojects:
240 sort_projects = [subp.name for subp in p.subprojects]
241 sort_projects.sort()
242 output_projects(p, e, sort_projects)
243
244 sort_projects = [key for key in self.projects.keys()
245 if not self.projects[key].parent]
246 sort_projects.sort()
247 output_projects(None, root, sort_projects)
248
249 if self._repo_hooks_project: 234 if self._repo_hooks_project:
250 root.appendChild(doc.createTextNode('')) 235 root.appendChild(doc.createTextNode(''))
251 e = doc.createElement('repo-hooks') 236 e = doc.createElement('repo-hooks')
@@ -398,15 +383,11 @@ class XmlManifest(object):
398 for node in itertools.chain(*node_list): 383 for node in itertools.chain(*node_list):
399 if node.nodeName == 'project': 384 if node.nodeName == 'project':
400 project = self._ParseProject(node) 385 project = self._ParseProject(node)
401 def recursively_add_projects(project): 386 if self._projects.get(project.name):
402 if self._projects.get(project.name): 387 raise ManifestParseError(
403 raise ManifestParseError( 388 'duplicate project %s in %s' %
404 'duplicate project %s in %s' % 389 (project.name, self.manifestFile))
405 (project.name, self.manifestFile)) 390 self._projects[project.name] = project
406 self._projects[project.name] = project
407 for subproject in project.subprojects:
408 recursively_add_projects(subproject)
409 recursively_add_projects(project)
410 if node.nodeName == 'repo-hooks': 391 if node.nodeName == 'repo-hooks':
411 # Get the name of the project and the (space-separated) list of enabled. 392 # Get the name of the project and the (space-separated) list of enabled.
412 repo_hooks_project = self._reqatt(node, 'in-project') 393 repo_hooks_project = self._reqatt(node, 'in-project')
@@ -556,19 +537,11 @@ class XmlManifest(object):
556 537
557 return '\n'.join(cleanLines) 538 return '\n'.join(cleanLines)
558 539
559 def _JoinName(self, parent_name, name): 540 def _ParseProject(self, node):
560 return os.path.join(parent_name, name)
561
562 def _UnjoinName(self, parent_name, name):
563 return os.path.relpath(name, parent_name)
564
565 def _ParseProject(self, node, parent = None):
566 """ 541 """
567 reads a <project> element from the manifest file 542 reads a <project> element from the manifest file
568 """ 543 """
569 name = self._reqatt(node, 'name') 544 name = self._reqatt(node, 'name')
570 if parent:
571 name = self._JoinName(parent.name, name)
572 545
573 remote = self._get_remote(node) 546 remote = self._get_remote(node)
574 if remote is None: 547 if remote is None:
@@ -613,66 +586,37 @@ class XmlManifest(object):
613 groups = node.getAttribute('groups') 586 groups = node.getAttribute('groups')
614 groups = [x for x in re.split('[,\s]+', groups) if x] 587 groups = [x for x in re.split('[,\s]+', groups) if x]
615 588
616 if parent is None: 589 default_groups = ['all', 'name:%s' % name, 'path:%s' % path]
617 relpath, worktree, gitdir = self.GetProjectPaths(name, path)
618 else:
619 relpath, worktree, gitdir = self.GetSubprojectPaths(parent, path)
620
621 default_groups = ['all', 'name:%s' % name, 'path:%s' % relpath]
622 groups.extend(set(default_groups).difference(groups)) 590 groups.extend(set(default_groups).difference(groups))
623 591
592 if self.IsMirror:
593 worktree = None
594 gitdir = os.path.join(self.topdir, '%s.git' % name)
595 else:
596 worktree = os.path.join(self.topdir, path).replace('\\', '/')
597 gitdir = os.path.join(self.repodir, 'projects/%s.git' % path)
598
624 project = Project(manifest = self, 599 project = Project(manifest = self,
625 name = name, 600 name = name,
626 remote = remote.ToRemoteSpec(name), 601 remote = remote.ToRemoteSpec(name),
627 gitdir = gitdir, 602 gitdir = gitdir,
628 worktree = worktree, 603 worktree = worktree,
629 relpath = relpath, 604 relpath = path,
630 revisionExpr = revisionExpr, 605 revisionExpr = revisionExpr,
631 revisionId = None, 606 revisionId = None,
632 rebase = rebase, 607 rebase = rebase,
633 groups = groups, 608 groups = groups,
634 sync_c = sync_c, 609 sync_c = sync_c,
635 upstream = upstream, 610 upstream = upstream)
636 parent = parent)
637 611
638 for n in node.childNodes: 612 for n in node.childNodes:
639 if n.nodeName == 'copyfile': 613 if n.nodeName == 'copyfile':
640 self._ParseCopyFile(project, n) 614 self._ParseCopyFile(project, n)
641 if n.nodeName == 'annotation': 615 if n.nodeName == 'annotation':
642 self._ParseAnnotation(project, n) 616 self._ParseAnnotation(project, n)
643 if n.nodeName == 'project':
644 project.subprojects.append(self._ParseProject(n, parent = project))
645 617
646 return project 618 return project
647 619
648 def GetProjectPaths(self, name, path):
649 relpath = path
650 if self.IsMirror:
651 worktree = None
652 gitdir = os.path.join(self.topdir, '%s.git' % name)
653 else:
654 worktree = os.path.join(self.topdir, path).replace('\\', '/')
655 gitdir = os.path.join(self.repodir, 'projects', '%s.git' % path)
656 return relpath, worktree, gitdir
657
658 def GetSubprojectName(self, parent, submodule_path):
659 return os.path.join(parent.name, submodule_path)
660
661 def _JoinRelpath(self, parent_relpath, relpath):
662 return os.path.join(parent_relpath, relpath)
663
664 def _UnjoinRelpath(self, parent_relpath, relpath):
665 return os.path.relpath(relpath, parent_relpath)
666
667 def GetSubprojectPaths(self, parent, path):
668 relpath = self._JoinRelpath(parent.relpath, path)
669 gitdir = os.path.join(parent.gitdir, 'subprojects', '%s.git' % path)
670 if self.IsMirror:
671 worktree = None
672 else:
673 worktree = os.path.join(parent.worktree, path).replace('\\', '/')
674 return relpath, worktree, gitdir
675
676 def _ParseCopyFile(self, project, node): 620 def _ParseCopyFile(self, project, node):
677 src = self._reqatt(node, 'src') 621 src = self._reqatt(node, 'src')
678 dest = self._reqatt(node, 'dest') 622 dest = self._reqatt(node, 'dest')
diff --git a/project.py b/project.py
index c5ee50fc..2f471692 100644
--- a/project.py
+++ b/project.py
@@ -22,7 +22,6 @@ import shutil
22import stat 22import stat
23import subprocess 23import subprocess
24import sys 24import sys
25import tempfile
26import time 25import time
27 26
28from color import Coloring 27from color import Coloring
@@ -485,28 +484,7 @@ class Project(object):
485 rebase = True, 484 rebase = True,
486 groups = None, 485 groups = None,
487 sync_c = False, 486 sync_c = False,
488 upstream = None, 487 upstream = None):
489 parent = None,
490 is_derived = False):
491 """Init a Project object.
492
493 Args:
494 manifest: The XmlManifest object.
495 name: The `name` attribute of manifest.xml's project element.
496 remote: RemoteSpec object specifying its remote's properties.
497 gitdir: Absolute path of git directory.
498 worktree: Absolute path of git working tree.
499 relpath: Relative path of git working tree to repo's top directory.
500 revisionExpr: The `revision` attribute of manifest.xml's project element.
501 revisionId: git commit id for checking out.
502 rebase: The `rebase` attribute of manifest.xml's project element.
503 groups: The `groups` attribute of manifest.xml's project element.
504 sync_c: The `sync-c` attribute of manifest.xml's project element.
505 upstream: The `upstream` attribute of manifest.xml's project element.
506 parent: The parent Project object.
507 is_derived: False if the project was explicitly defined in the manifest;
508 True if the project is a discovered submodule.
509 """
510 self.manifest = manifest 488 self.manifest = manifest
511 self.name = name 489 self.name = name
512 self.remote = remote 490 self.remote = remote
@@ -529,9 +507,6 @@ class Project(object):
529 self.groups = groups 507 self.groups = groups
530 self.sync_c = sync_c 508 self.sync_c = sync_c
531 self.upstream = upstream 509 self.upstream = upstream
532 self.parent = parent
533 self.is_derived = is_derived
534 self.subprojects = []
535 510
536 self.snapshots = {} 511 self.snapshots = {}
537 self.copyfiles = [] 512 self.copyfiles = []
@@ -552,14 +527,6 @@ class Project(object):
552 self.enabled_repo_hooks = [] 527 self.enabled_repo_hooks = []
553 528
554 @property 529 @property
555 def Registered(self):
556 return self.parent and not self.is_derived
557
558 @property
559 def Derived(self):
560 return self.is_derived
561
562 @property
563 def Exists(self): 530 def Exists(self):
564 return os.path.isdir(self.gitdir) 531 return os.path.isdir(self.gitdir)
565 532
@@ -1403,150 +1370,6 @@ class Project(object):
1403 return kept 1370 return kept
1404 1371
1405 1372
1406## Submodule Management ##
1407
1408 def GetRegisteredSubprojects(self):
1409 result = []
1410 def rec(subprojects):
1411 if not subprojects:
1412 return
1413 result.extend(subprojects)
1414 for p in subprojects:
1415 rec(p.subprojects)
1416 rec(self.subprojects)
1417 return result
1418
1419 def _GetSubmodules(self):
1420 # Unfortunately we cannot call `git submodule status --recursive` here
1421 # because the working tree might not exist yet, and it cannot be used
1422 # without a working tree in its current implementation.
1423
1424 def get_submodules(gitdir, rev):
1425 # Parse .gitmodules for submodule sub_paths and sub_urls
1426 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1427 if not sub_paths:
1428 return []
1429 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1430 # revision of submodule repository
1431 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1432 submodules = []
1433 for sub_path, sub_url in zip(sub_paths, sub_urls):
1434 try:
1435 sub_rev = sub_revs[sub_path]
1436 except KeyError:
1437 # Ignore non-exist submodules
1438 continue
1439 submodules.append((sub_rev, sub_path, sub_url))
1440 return submodules
1441
1442 re_path = re.compile(r'submodule.(\w+).path')
1443 re_url = re.compile(r'submodule.(\w+).url')
1444 def parse_gitmodules(gitdir, rev):
1445 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1446 try:
1447 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1448 bare = True, gitdir = gitdir)
1449 except GitError:
1450 return [], []
1451 if p.Wait() != 0:
1452 return [], []
1453
1454 gitmodules_lines = []
1455 fd, temp_gitmodules_path = tempfile.mkstemp()
1456 try:
1457 os.write(fd, p.stdout)
1458 os.close(fd)
1459 cmd = ['config', '--file', temp_gitmodules_path, '--list']
1460 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1461 bare = True, gitdir = gitdir)
1462 if p.Wait() != 0:
1463 return [], []
1464 gitmodules_lines = p.stdout.split('\n')
1465 except GitError:
1466 return [], []
1467 finally:
1468 os.remove(temp_gitmodules_path)
1469
1470 names = set()
1471 paths = {}
1472 urls = {}
1473 for line in gitmodules_lines:
1474 if not line:
1475 continue
1476 key, value = line.split('=')
1477 m = re_path.match(key)
1478 if m:
1479 names.add(m.group(1))
1480 paths[m.group(1)] = value
1481 continue
1482 m = re_url.match(key)
1483 if m:
1484 names.add(m.group(1))
1485 urls[m.group(1)] = value
1486 continue
1487 names = sorted(names)
1488 return [paths[name] for name in names], [urls[name] for name in names]
1489
1490 def git_ls_tree(gitdir, rev, paths):
1491 cmd = ['ls-tree', rev, '--']
1492 cmd.extend(paths)
1493 try:
1494 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1495 bare = True, gitdir = gitdir)
1496 except GitError:
1497 return []
1498 if p.Wait() != 0:
1499 return []
1500 objects = {}
1501 for line in p.stdout.split('\n'):
1502 if not line.strip():
1503 continue
1504 object_rev, object_path = line.split()[2:4]
1505 objects[object_path] = object_rev
1506 return objects
1507
1508 try:
1509 rev = self.GetRevisionId()
1510 except GitError:
1511 return []
1512 return get_submodules(self.gitdir, rev)
1513
1514 def GetDerivedSubprojects(self):
1515 result = []
1516 if not self.Exists:
1517 # If git repo does not exist yet, querying its submodules will
1518 # mess up its states; so return here.
1519 return result
1520 for rev, path, url in self._GetSubmodules():
1521 name = self.manifest.GetSubprojectName(self, path)
1522 project = self.manifest.projects.get(name)
1523 if project and project.Registered:
1524 # If it has been registered, skip it because we are searching
1525 # derived subprojects, but search for its derived subprojects.
1526 result.extend(project.GetDerivedSubprojects())
1527 continue
1528 relpath, worktree, gitdir = self.manifest.GetSubprojectPaths(self, path)
1529 remote = RemoteSpec(self.remote.name,
1530 url = url,
1531 review = self.remote.review)
1532 subproject = Project(manifest = self.manifest,
1533 name = name,
1534 remote = remote,
1535 gitdir = gitdir,
1536 worktree = worktree,
1537 relpath = relpath,
1538 revisionExpr = self.revisionExpr,
1539 revisionId = rev,
1540 rebase = self.rebase,
1541 groups = self.groups,
1542 sync_c = self.sync_c,
1543 parent = self,
1544 is_derived = True)
1545 result.append(subproject)
1546 result.extend(subproject.GetDerivedSubprojects())
1547 return result
1548
1549
1550## Direct Git Commands ## 1373## Direct Git Commands ##
1551 1374
1552 def _RemoteFetch(self, name=None, 1375 def _RemoteFetch(self, name=None,
diff --git a/subcmds/sync.py b/subcmds/sync.py
index 7416f4c3..d4637d0c 100644
--- a/subcmds/sync.py
+++ b/subcmds/sync.py
@@ -563,31 +563,12 @@ uncommitted changes are present' % project.relpath
563 to_fetch.extend(all_projects) 563 to_fetch.extend(all_projects)
564 to_fetch.sort(key=self._fetch_times.Get, reverse=True) 564 to_fetch.sort(key=self._fetch_times.Get, reverse=True)
565 565
566 fetched = self._Fetch(to_fetch, opt) 566 self._Fetch(to_fetch, opt)
567 _PostRepoFetch(rp, opt.no_repo_verify) 567 _PostRepoFetch(rp, opt.no_repo_verify)
568 if opt.network_only: 568 if opt.network_only:
569 # bail out now; the rest touches the working tree 569 # bail out now; the rest touches the working tree
570 return 570 return
571 571
572 # Iteratively fetch missing and/or nested unregistered submodules
573 previously_missing_set = set()
574 while True:
575 self.manifest._Unload()
576 all_projects = self.GetProjects(args, missing_ok=True)
577 missing = []
578 for project in all_projects:
579 if project.gitdir not in fetched:
580 missing.append(project)
581 if not missing:
582 break
583 # Stop us from non-stopped fetching actually-missing repos: If set of
584 # missing repos has not been changed from last fetch, we break.
585 missing_set = set(p.name for p in missing)
586 if previously_missing_set == missing_set:
587 break
588 previously_missing_set = missing_set
589 fetched.update(self._Fetch(missing, opt))
590
591 if self.manifest.IsMirror: 572 if self.manifest.IsMirror:
592 # bail out now, we have no working tree 573 # bail out now, we have no working tree
593 return 574 return