summaryrefslogtreecommitdiffstats
path: root/project.py
diff options
context:
space:
mode:
authorChe-Liang Chiou <clchiou@google.com>2012-01-11 11:28:42 +0800
committerChe-Liang Chiou <clchiou@google.com>2012-10-23 16:08:58 -0700
commit69998b0c6ff724bf620480140ccce648fec7d6a9 (patch)
treeb6f9c4c00b04a0f140074c4c2dba91ed4f055b11 /project.py
parent5c6eeac8f0350fd6b14cf226ffcff655f1dd9582 (diff)
downloadgit-repo-69998b0c6ff724bf620480140ccce648fec7d6a9.tar.gz
Represent git-submodule as nested projects
We need a representation of git-submodule in repo; otherwise repo will not sync submodules, and leave workspace in a broken state. Of course this will not be a problem if all projects are owned by the owner of the manifest file, who may simply choose not to use git-submodule in all projects. However, this is not possible in practice because manifest file owner is unlikely to own all upstream projects. As git submodules are simply git repositories, it is natural to treat them as plain repo projects that live inside a repo project. That is, we could use recursively declared projects to denote the is-submodule relation of git repositories. The behavior of repo remains the same to projects that do not have a sub-project within. As for parent projects, repo fetches them and their sub-projects as normal projects, and then checks out subprojects at the commit specified in parent's commit object. The sub-project is fetched at a path relative to parent project's working directory; so the path specified in manifest file should match that of .gitmodules file. If a submodule is not registered in repo manifest, repo will derive its properties from itself and its parent project, which might not always be correct. In such cases, the subproject is called a derived subproject. To a user, a sub-project is merely a git-submodule; so all tips of working with a git-submodule apply here, too. For example, you should not run `repo sync` in a parent repository if its submodule is dirty. Change-Id: I541e9e2ac1a70304272dbe09724572aa1004eb5c
Diffstat (limited to 'project.py')
-rw-r--r--project.py180
1 files changed, 179 insertions, 1 deletions
diff --git a/project.py b/project.py
index 472b1d32..2989d380 100644
--- a/project.py
+++ b/project.py
@@ -22,6 +22,7 @@ import shutil
22import stat 22import stat
23import subprocess 23import subprocess
24import sys 24import sys
25import tempfile
25import time 26import time
26 27
27from color import Coloring 28from color import Coloring
@@ -484,7 +485,28 @@ class Project(object):
484 rebase = True, 485 rebase = True,
485 groups = None, 486 groups = None,
486 sync_c = False, 487 sync_c = False,
487 upstream = None): 488 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 """
488 self.manifest = manifest 510 self.manifest = manifest
489 self.name = name 511 self.name = name
490 self.remote = remote 512 self.remote = remote
@@ -507,6 +529,9 @@ class Project(object):
507 self.groups = groups 529 self.groups = groups
508 self.sync_c = sync_c 530 self.sync_c = sync_c
509 self.upstream = upstream 531 self.upstream = upstream
532 self.parent = parent
533 self.is_derived = is_derived
534 self.subprojects = []
510 535
511 self.snapshots = {} 536 self.snapshots = {}
512 self.copyfiles = [] 537 self.copyfiles = []
@@ -527,6 +552,14 @@ class Project(object):
527 self.enabled_repo_hooks = [] 552 self.enabled_repo_hooks = []
528 553
529 @property 554 @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
530 def Exists(self): 563 def Exists(self):
531 return os.path.isdir(self.gitdir) 564 return os.path.isdir(self.gitdir)
532 565
@@ -1370,6 +1403,151 @@ class Project(object):
1370 return kept 1403 return kept
1371 1404
1372 1405
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, path):
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 sub_gitdir = self.manifest.GetSubprojectPaths(self, sub_path)[2]
1440 submodules.append((sub_rev, sub_path, sub_url))
1441 return submodules
1442
1443 re_path = re.compile(r'submodule.(\w+).path')
1444 re_url = re.compile(r'submodule.(\w+).url')
1445 def parse_gitmodules(gitdir, rev):
1446 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1447 try:
1448 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1449 bare = True, gitdir = gitdir)
1450 except GitError as e:
1451 return [], []
1452 if p.Wait() != 0:
1453 return [], []
1454
1455 gitmodules_lines = []
1456 fd, temp_gitmodules_path = tempfile.mkstemp()
1457 try:
1458 os.write(fd, p.stdout)
1459 os.close(fd)
1460 cmd = ['config', '--file', temp_gitmodules_path, '--list']
1461 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1462 bare = True, gitdir = gitdir)
1463 if p.Wait() != 0:
1464 return [], []
1465 gitmodules_lines = p.stdout.split('\n')
1466 except GitError as e:
1467 return [], []
1468 finally:
1469 os.remove(temp_gitmodules_path)
1470
1471 names = set()
1472 paths = {}
1473 urls = {}
1474 for line in gitmodules_lines:
1475 if not line:
1476 continue
1477 key, value = line.split('=')
1478 m = re_path.match(key)
1479 if m:
1480 names.add(m.group(1))
1481 paths[m.group(1)] = value
1482 continue
1483 m = re_url.match(key)
1484 if m:
1485 names.add(m.group(1))
1486 urls[m.group(1)] = value
1487 continue
1488 names = sorted(names)
1489 return [paths[name] for name in names], [urls[name] for name in names]
1490
1491 def git_ls_tree(gitdir, rev, paths):
1492 cmd = ['ls-tree', rev, '--']
1493 cmd.extend(paths)
1494 try:
1495 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1496 bare = True, gitdir = gitdir)
1497 except GitError:
1498 return []
1499 if p.Wait() != 0:
1500 return []
1501 objects = {}
1502 for line in p.stdout.split('\n'):
1503 if not line.strip():
1504 continue
1505 object_rev, object_path = line.split()[2:4]
1506 objects[object_path] = object_rev
1507 return objects
1508
1509 try:
1510 rev = self.GetRevisionId()
1511 except GitError:
1512 return []
1513 return get_submodules(self.gitdir, rev, '')
1514
1515 def GetDerivedSubprojects(self):
1516 result = []
1517 if not self.Exists:
1518 # If git repo does not exist yet, querying its submodules will
1519 # mess up its states; so return here.
1520 return result
1521 for rev, path, url in self._GetSubmodules():
1522 name = self.manifest.GetSubprojectName(self, path)
1523 project = self.manifest.projects.get(name)
1524 if project and project.Registered:
1525 # If it has been registered, skip it because we are searching
1526 # derived subprojects, but search for its derived subprojects.
1527 result.extend(project.GetDerivedSubprojects())
1528 continue
1529 relpath, worktree, gitdir = self.manifest.GetSubprojectPaths(self, path)
1530 remote = RemoteSpec(self.remote.name,
1531 url = url,
1532 review = self.remote.review)
1533 subproject = Project(manifest = self.manifest,
1534 name = name,
1535 remote = remote,
1536 gitdir = gitdir,
1537 worktree = worktree,
1538 relpath = relpath,
1539 revisionExpr = self.revisionExpr,
1540 revisionId = rev,
1541 rebase = self.rebase,
1542 groups = self.groups,
1543 sync_c = self.sync_c,
1544 parent = self,
1545 is_derived = True)
1546 result.append(subproject)
1547 result.extend(subproject.GetDerivedSubprojects())
1548 return result
1549
1550
1373## Direct Git Commands ## 1551## Direct Git Commands ##
1374 1552
1375 def _RemoteFetch(self, name=None, 1553 def _RemoteFetch(self, name=None,