summaryrefslogtreecommitdiffstats
path: root/project.py
diff options
context:
space:
mode:
Diffstat (limited to 'project.py')
-rw-r--r--project.py260
1 files changed, 223 insertions, 37 deletions
diff --git a/project.py b/project.py
index 2f471692..ba7898ed 100644
--- a/project.py
+++ b/project.py
@@ -12,6 +12,7 @@
12# See the License for the specific language governing permissions and 12# See the License for the specific language governing permissions and
13# limitations under the License. 13# limitations under the License.
14 14
15from __future__ import print_function
15import traceback 16import traceback
16import errno 17import errno
17import filecmp 18import filecmp
@@ -22,13 +23,15 @@ import shutil
22import stat 23import stat
23import subprocess 24import subprocess
24import sys 25import sys
26import tempfile
25import time 27import time
26 28
27from color import Coloring 29from color import Coloring
28from git_command import GitCommand 30from git_command import GitCommand, git_require
29from git_config import GitConfig, IsId, GetSchemeFromUrl, ID_RE 31from git_config import GitConfig, IsId, GetSchemeFromUrl, ID_RE
30from error import GitError, HookError, UploadError 32from error import GitError, HookError, UploadError
31from error import ManifestInvalidRevisionError 33from error import ManifestInvalidRevisionError
34from error import NoManifestException
32from trace import IsTrace, Trace 35from trace import IsTrace, Trace
33 36
34from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M 37from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
@@ -50,7 +53,7 @@ def _lwrite(path, content):
50 53
51def _error(fmt, *args): 54def _error(fmt, *args):
52 msg = fmt % args 55 msg = fmt % args
53 print >>sys.stderr, 'error: %s' % msg 56 print('error: %s' % msg, file=sys.stderr)
54 57
55def not_rev(r): 58def not_rev(r):
56 return '^' + r 59 return '^' + r
@@ -359,7 +362,7 @@ class RepoHook(object):
359 '(yes/yes-never-ask-again/NO)? ') % ( 362 '(yes/yes-never-ask-again/NO)? ') % (
360 self._GetMustVerb(), self._script_fullpath) 363 self._GetMustVerb(), self._script_fullpath)
361 response = raw_input(prompt).lower() 364 response = raw_input(prompt).lower()
362 print 365 print()
363 366
364 # User is doing a one-time approval. 367 # User is doing a one-time approval.
365 if response in ('y', 'yes'): 368 if response in ('y', 'yes'):
@@ -484,7 +487,30 @@ class Project(object):
484 rebase = True, 487 rebase = True,
485 groups = None, 488 groups = None,
486 sync_c = False, 489 sync_c = False,
487 upstream = None): 490 sync_s = False,
491 upstream = None,
492 parent = None,
493 is_derived = False):
494 """Init a Project object.
495
496 Args:
497 manifest: The XmlManifest object.
498 name: The `name` attribute of manifest.xml's project element.
499 remote: RemoteSpec object specifying its remote's properties.
500 gitdir: Absolute path of git directory.
501 worktree: Absolute path of git working tree.
502 relpath: Relative path of git working tree to repo's top directory.
503 revisionExpr: The `revision` attribute of manifest.xml's project element.
504 revisionId: git commit id for checking out.
505 rebase: The `rebase` attribute of manifest.xml's project element.
506 groups: The `groups` attribute of manifest.xml's project element.
507 sync_c: The `sync-c` attribute of manifest.xml's project element.
508 sync_s: The `sync-s` attribute of manifest.xml's project element.
509 upstream: The `upstream` attribute of manifest.xml's project element.
510 parent: The parent Project object.
511 is_derived: False if the project was explicitly defined in the manifest;
512 True if the project is a discovered submodule.
513 """
488 self.manifest = manifest 514 self.manifest = manifest
489 self.name = name 515 self.name = name
490 self.remote = remote 516 self.remote = remote
@@ -506,7 +532,11 @@ class Project(object):
506 self.rebase = rebase 532 self.rebase = rebase
507 self.groups = groups 533 self.groups = groups
508 self.sync_c = sync_c 534 self.sync_c = sync_c
535 self.sync_s = sync_s
509 self.upstream = upstream 536 self.upstream = upstream
537 self.parent = parent
538 self.is_derived = is_derived
539 self.subprojects = []
510 540
511 self.snapshots = {} 541 self.snapshots = {}
512 self.copyfiles = [] 542 self.copyfiles = []
@@ -527,6 +557,10 @@ class Project(object):
527 self.enabled_repo_hooks = [] 557 self.enabled_repo_hooks = []
528 558
529 @property 559 @property
560 def Derived(self):
561 return self.is_derived
562
563 @property
530 def Exists(self): 564 def Exists(self):
531 return os.path.isdir(self.gitdir) 565 return os.path.isdir(self.gitdir)
532 566
@@ -555,7 +589,7 @@ class Project(object):
555 '--unmerged', 589 '--unmerged',
556 '--ignore-missing', 590 '--ignore-missing',
557 '--refresh') 591 '--refresh')
558 if self.work_git.DiffZ('diff-index','-M','--cached',HEAD): 592 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
559 return True 593 return True
560 if self.work_git.DiffZ('diff-files'): 594 if self.work_git.DiffZ('diff-files'):
561 return True 595 return True
@@ -584,14 +618,14 @@ class Project(object):
584 return self._userident_email 618 return self._userident_email
585 619
586 def _LoadUserIdentity(self): 620 def _LoadUserIdentity(self):
587 u = self.bare_git.var('GIT_COMMITTER_IDENT') 621 u = self.bare_git.var('GIT_COMMITTER_IDENT')
588 m = re.compile("^(.*) <([^>]*)> ").match(u) 622 m = re.compile("^(.*) <([^>]*)> ").match(u)
589 if m: 623 if m:
590 self._userident_name = m.group(1) 624 self._userident_name = m.group(1)
591 self._userident_email = m.group(2) 625 self._userident_email = m.group(2)
592 else: 626 else:
593 self._userident_name = '' 627 self._userident_name = ''
594 self._userident_email = '' 628 self._userident_email = ''
595 629
596 def GetRemote(self, name): 630 def GetRemote(self, name):
597 """Get the configuration for a single remote. 631 """Get the configuration for a single remote.
@@ -683,9 +717,9 @@ class Project(object):
683 if not os.path.isdir(self.worktree): 717 if not os.path.isdir(self.worktree):
684 if output_redir == None: 718 if output_redir == None:
685 output_redir = sys.stdout 719 output_redir = sys.stdout
686 print >>output_redir, '' 720 print(file=output_redir)
687 print >>output_redir, 'project %s/' % self.relpath 721 print('project %s/' % self.relpath, file=output_redir)
688 print >>output_redir, ' missing (run "repo sync")' 722 print(' missing (run "repo sync")', file=output_redir)
689 return 723 return
690 724
691 self.work_git.update_index('-q', 725 self.work_git.update_index('-q',
@@ -785,7 +819,7 @@ class Project(object):
785 out.project('project %s/' % self.relpath) 819 out.project('project %s/' % self.relpath)
786 out.nl() 820 out.nl()
787 has_diff = True 821 has_diff = True
788 print line[:-1] 822 print(line[:-1])
789 p.Wait() 823 p.Wait()
790 824
791 825
@@ -1012,6 +1046,10 @@ class Project(object):
1012 self.CleanPublishedCache(all_refs) 1046 self.CleanPublishedCache(all_refs)
1013 revid = self.GetRevisionId(all_refs) 1047 revid = self.GetRevisionId(all_refs)
1014 1048
1049 def _doff():
1050 self._FastForward(revid)
1051 self._CopyFiles()
1052
1015 self._InitWorkTree() 1053 self._InitWorkTree()
1016 head = self.work_git.GetHead() 1054 head = self.work_git.GetHead()
1017 if head.startswith(R_HEADS): 1055 if head.startswith(R_HEADS):
@@ -1090,9 +1128,6 @@ class Project(object):
1090 # All published commits are merged, and thus we are a 1128 # All published commits are merged, and thus we are a
1091 # strict subset. We can fast-forward safely. 1129 # strict subset. We can fast-forward safely.
1092 # 1130 #
1093 def _doff():
1094 self._FastForward(revid)
1095 self._CopyFiles()
1096 syncbuf.later1(self, _doff) 1131 syncbuf.later1(self, _doff)
1097 return 1132 return
1098 1133
@@ -1155,9 +1190,6 @@ class Project(object):
1155 syncbuf.fail(self, e) 1190 syncbuf.fail(self, e)
1156 return 1191 return
1157 else: 1192 else:
1158 def _doff():
1159 self._FastForward(revid)
1160 self._CopyFiles()
1161 syncbuf.later1(self, _doff) 1193 syncbuf.later1(self, _doff)
1162 1194
1163 def AddCopyFile(self, src, dest, absdest): 1195 def AddCopyFile(self, src, dest, absdest):
@@ -1177,7 +1209,7 @@ class Project(object):
1177 cmd = ['fetch', remote.name] 1209 cmd = ['fetch', remote.name]
1178 cmd.append('refs/changes/%2.2d/%d/%d' \ 1210 cmd.append('refs/changes/%2.2d/%d/%d' \
1179 % (change_id % 100, change_id, patch_id)) 1211 % (change_id % 100, change_id, patch_id))
1180 cmd.extend(map(lambda x: str(x), remote.fetch)) 1212 cmd.extend(map(str, remote.fetch))
1181 if GitCommand(self, cmd, bare=True).Wait() != 0: 1213 if GitCommand(self, cmd, bare=True).Wait() != 0:
1182 return None 1214 return None
1183 return DownloadedChange(self, 1215 return DownloadedChange(self,
@@ -1370,6 +1402,149 @@ class Project(object):
1370 return kept 1402 return kept
1371 1403
1372 1404
1405## Submodule Management ##
1406
1407 def GetRegisteredSubprojects(self):
1408 result = []
1409 def rec(subprojects):
1410 if not subprojects:
1411 return
1412 result.extend(subprojects)
1413 for p in subprojects:
1414 rec(p.subprojects)
1415 rec(self.subprojects)
1416 return result
1417
1418 def _GetSubmodules(self):
1419 # Unfortunately we cannot call `git submodule status --recursive` here
1420 # because the working tree might not exist yet, and it cannot be used
1421 # without a working tree in its current implementation.
1422
1423 def get_submodules(gitdir, rev):
1424 # Parse .gitmodules for submodule sub_paths and sub_urls
1425 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1426 if not sub_paths:
1427 return []
1428 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1429 # revision of submodule repository
1430 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1431 submodules = []
1432 for sub_path, sub_url in zip(sub_paths, sub_urls):
1433 try:
1434 sub_rev = sub_revs[sub_path]
1435 except KeyError:
1436 # Ignore non-exist submodules
1437 continue
1438 submodules.append((sub_rev, sub_path, sub_url))
1439 return submodules
1440
1441 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1442 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1443 def parse_gitmodules(gitdir, rev):
1444 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1445 try:
1446 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1447 bare = True, gitdir = gitdir)
1448 except GitError:
1449 return [], []
1450 if p.Wait() != 0:
1451 return [], []
1452
1453 gitmodules_lines = []
1454 fd, temp_gitmodules_path = tempfile.mkstemp()
1455 try:
1456 os.write(fd, p.stdout)
1457 os.close(fd)
1458 cmd = ['config', '--file', temp_gitmodules_path, '--list']
1459 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1460 bare = True, gitdir = gitdir)
1461 if p.Wait() != 0:
1462 return [], []
1463 gitmodules_lines = p.stdout.split('\n')
1464 except GitError:
1465 return [], []
1466 finally:
1467 os.remove(temp_gitmodules_path)
1468
1469 names = set()
1470 paths = {}
1471 urls = {}
1472 for line in gitmodules_lines:
1473 if not line:
1474 continue
1475 m = re_path.match(line)
1476 if m:
1477 names.add(m.group(1))
1478 paths[m.group(1)] = m.group(2)
1479 continue
1480 m = re_url.match(line)
1481 if m:
1482 names.add(m.group(1))
1483 urls[m.group(1)] = m.group(2)
1484 continue
1485 names = sorted(names)
1486 return ([paths.get(name, '') for name in names],
1487 [urls.get(name, '') for name in names])
1488
1489 def git_ls_tree(gitdir, rev, paths):
1490 cmd = ['ls-tree', rev, '--']
1491 cmd.extend(paths)
1492 try:
1493 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1494 bare = True, gitdir = gitdir)
1495 except GitError:
1496 return []
1497 if p.Wait() != 0:
1498 return []
1499 objects = {}
1500 for line in p.stdout.split('\n'):
1501 if not line.strip():
1502 continue
1503 object_rev, object_path = line.split()[2:4]
1504 objects[object_path] = object_rev
1505 return objects
1506
1507 try:
1508 rev = self.GetRevisionId()
1509 except GitError:
1510 return []
1511 return get_submodules(self.gitdir, rev)
1512
1513 def GetDerivedSubprojects(self):
1514 result = []
1515 if not self.Exists:
1516 # If git repo does not exist yet, querying its submodules will
1517 # mess up its states; so return here.
1518 return result
1519 for rev, path, url in self._GetSubmodules():
1520 name = self.manifest.GetSubprojectName(self, path)
1521 project = self.manifest.projects.get(name)
1522 if project:
1523 result.extend(project.GetDerivedSubprojects())
1524 continue
1525 relpath, worktree, gitdir = self.manifest.GetSubprojectPaths(self, path)
1526 remote = RemoteSpec(self.remote.name,
1527 url = url,
1528 review = self.remote.review)
1529 subproject = Project(manifest = self.manifest,
1530 name = name,
1531 remote = remote,
1532 gitdir = gitdir,
1533 worktree = worktree,
1534 relpath = relpath,
1535 revisionExpr = self.revisionExpr,
1536 revisionId = rev,
1537 rebase = self.rebase,
1538 groups = self.groups,
1539 sync_c = self.sync_c,
1540 sync_s = self.sync_s,
1541 parent = self,
1542 is_derived = True)
1543 result.append(subproject)
1544 result.extend(subproject.GetDerivedSubprojects())
1545 return result
1546
1547
1373## Direct Git Commands ## 1548## Direct Git Commands ##
1374 1549
1375 def _RemoteFetch(self, name=None, 1550 def _RemoteFetch(self, name=None,
@@ -1382,14 +1557,14 @@ class Project(object):
1382 tag_name = None 1557 tag_name = None
1383 1558
1384 def CheckForSha1(): 1559 def CheckForSha1():
1385 try: 1560 try:
1386 # if revision (sha or tag) is not present then following function 1561 # if revision (sha or tag) is not present then following function
1387 # throws an error. 1562 # throws an error.
1388 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr) 1563 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1389 return True 1564 return True
1390 except GitError: 1565 except GitError:
1391 # There is no such persistent revision. We have to fetch it. 1566 # There is no such persistent revision. We have to fetch it.
1392 return False 1567 return False
1393 1568
1394 if current_branch_only: 1569 if current_branch_only:
1395 if ID_RE.match(self.revisionExpr) is not None: 1570 if ID_RE.match(self.revisionExpr) is not None:
@@ -1571,6 +1746,9 @@ class Project(object):
1571 os.remove(tmpPath) 1746 os.remove(tmpPath)
1572 if 'http_proxy' in os.environ and 'darwin' == sys.platform: 1747 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1573 cmd += ['--proxy', os.environ['http_proxy']] 1748 cmd += ['--proxy', os.environ['http_proxy']]
1749 cookiefile = GitConfig.ForUser().GetString('http.cookiefile')
1750 if cookiefile:
1751 cmd += ['--cookie', cookiefile]
1574 cmd += [srcUrl] 1752 cmd += [srcUrl]
1575 1753
1576 if IsTrace(): 1754 if IsTrace():
@@ -1588,7 +1766,8 @@ class Project(object):
1588 # returned another error with the HTTP error code being 400 or above. 1766 # returned another error with the HTTP error code being 400 or above.
1589 # This return code only appears if -f, --fail is used. 1767 # This return code only appears if -f, --fail is used.
1590 if not quiet: 1768 if not quiet:
1591 print >> sys.stderr, "Server does not provide clone.bundle; ignoring." 1769 print("Server does not provide clone.bundle; ignoring.",
1770 file=sys.stderr)
1592 return False 1771 return False
1593 1772
1594 if os.path.exists(tmpPath): 1773 if os.path.exists(tmpPath):
@@ -1836,7 +2015,8 @@ class Project(object):
1836 if p.Wait() == 0: 2015 if p.Wait() == 0:
1837 out = p.stdout 2016 out = p.stdout
1838 if out: 2017 if out:
1839 return out[:-1].split("\0") 2018 return out[:-1].split('\0') # pylint: disable=W1401
2019 # Backslash is not anomalous
1840 return [] 2020 return []
1841 2021
1842 def DiffZ(self, name, *args): 2022 def DiffZ(self, name, *args):
@@ -1852,7 +2032,7 @@ class Project(object):
1852 out = p.process.stdout.read() 2032 out = p.process.stdout.read()
1853 r = {} 2033 r = {}
1854 if out: 2034 if out:
1855 out = iter(out[:-1].split('\0')) 2035 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
1856 while out: 2036 while out:
1857 try: 2037 try:
1858 info = out.next() 2038 info = out.next()
@@ -1879,7 +2059,7 @@ class Project(object):
1879 self.level = self.level[1:] 2059 self.level = self.level[1:]
1880 2060
1881 info = info[1:].split(' ') 2061 info = info[1:].split(' ')
1882 info =_Info(path, *info) 2062 info = _Info(path, *info)
1883 if info.status in ('R', 'C'): 2063 if info.status in ('R', 'C'):
1884 info.src_path = info.path 2064 info.src_path = info.path
1885 info.path = out.next() 2065 info.path = out.next()
@@ -1893,7 +2073,10 @@ class Project(object):
1893 path = os.path.join(self._project.gitdir, HEAD) 2073 path = os.path.join(self._project.gitdir, HEAD)
1894 else: 2074 else:
1895 path = os.path.join(self._project.worktree, '.git', HEAD) 2075 path = os.path.join(self._project.worktree, '.git', HEAD)
1896 fd = open(path, 'rb') 2076 try:
2077 fd = open(path, 'rb')
2078 except IOError:
2079 raise NoManifestException(path)
1897 try: 2080 try:
1898 line = fd.read() 2081 line = fd.read()
1899 finally: 2082 finally:
@@ -1988,6 +2171,9 @@ class Project(object):
1988 raise TypeError('%s() got an unexpected keyword argument %r' 2171 raise TypeError('%s() got an unexpected keyword argument %r'
1989 % (name, k)) 2172 % (name, k))
1990 if config is not None: 2173 if config is not None:
2174 if not git_require((1, 7, 2)):
2175 raise ValueError('cannot set config on command line for %s()'
2176 % name)
1991 for k, v in config.iteritems(): 2177 for k, v in config.iteritems():
1992 cmdv.append('-c') 2178 cmdv.append('-c')
1993 cmdv.append('%s=%s' % (k, v)) 2179 cmdv.append('%s=%s' % (k, v))