summaryrefslogtreecommitdiffstats
path: root/project.py
diff options
context:
space:
mode:
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 96feb94b..a0889b3f 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,