summaryrefslogtreecommitdiffstats
path: root/project.py
diff options
context:
space:
mode:
Diffstat (limited to 'project.py')
-rw-r--r--project.py189
1 files changed, 183 insertions, 6 deletions
diff --git a/project.py b/project.py
index 099d0b5a..c5ee50fc 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
@@ -1044,7 +1077,7 @@ class Project(object):
1044 1077
1045 try: 1078 try:
1046 self._Checkout(revid, quiet=True) 1079 self._Checkout(revid, quiet=True)
1047 except GitError, e: 1080 except GitError as e:
1048 syncbuf.fail(self, e) 1081 syncbuf.fail(self, e)
1049 return 1082 return
1050 self._CopyFiles() 1083 self._CopyFiles()
@@ -1066,7 +1099,7 @@ class Project(object):
1066 branch.name) 1099 branch.name)
1067 try: 1100 try:
1068 self._Checkout(revid, quiet=True) 1101 self._Checkout(revid, quiet=True)
1069 except GitError, e: 1102 except GitError as e:
1070 syncbuf.fail(self, e) 1103 syncbuf.fail(self, e)
1071 return 1104 return
1072 self._CopyFiles() 1105 self._CopyFiles()
@@ -1151,7 +1184,7 @@ class Project(object):
1151 try: 1184 try:
1152 self._ResetHard(revid) 1185 self._ResetHard(revid)
1153 self._CopyFiles() 1186 self._CopyFiles()
1154 except GitError, e: 1187 except GitError as e:
1155 syncbuf.fail(self, e) 1188 syncbuf.fail(self, e)
1156 return 1189 return
1157 else: 1190 else:
@@ -1370,6 +1403,150 @@ 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):
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
1373## Direct Git Commands ## 1550## Direct Git Commands ##
1374 1551
1375 def _RemoteFetch(self, name=None, 1552 def _RemoteFetch(self, name=None,
@@ -1723,7 +1900,7 @@ class Project(object):
1723 continue 1900 continue
1724 try: 1901 try:
1725 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst) 1902 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
1726 except OSError, e: 1903 except OSError as e:
1727 if e.errno == errno.EPERM: 1904 if e.errno == errno.EPERM:
1728 raise GitError('filesystem must support symlinks') 1905 raise GitError('filesystem must support symlinks')
1729 else: 1906 else:
@@ -1786,7 +1963,7 @@ class Project(object):
1786 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst) 1963 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
1787 else: 1964 else:
1788 raise GitError('cannot overwrite a local work tree') 1965 raise GitError('cannot overwrite a local work tree')
1789 except OSError, e: 1966 except OSError as e:
1790 if e.errno == errno.EPERM: 1967 if e.errno == errno.EPERM:
1791 raise GitError('filesystem must support symlinks') 1968 raise GitError('filesystem must support symlinks')
1792 else: 1969 else: