summaryrefslogtreecommitdiffstats
path: root/project.py
diff options
context:
space:
mode:
Diffstat (limited to 'project.py')
-rw-r--r--project.py193
1 files changed, 150 insertions, 43 deletions
diff --git a/project.py b/project.py
index a6a88607..a1249a86 100644
--- a/project.py
+++ b/project.py
@@ -23,6 +23,7 @@ import shutil
23import stat 23import stat
24import subprocess 24import subprocess
25import sys 25import sys
26import tarfile
26import tempfile 27import tempfile
27import time 28import time
28 29
@@ -82,7 +83,7 @@ def _ProjectHooks():
82 """ 83 """
83 global _project_hook_list 84 global _project_hook_list
84 if _project_hook_list is None: 85 if _project_hook_list is None:
85 d = os.path.abspath(os.path.dirname(__file__)) 86 d = os.path.realpath(os.path.abspath(os.path.dirname(__file__)))
86 d = os.path.join(d , 'hooks') 87 d = os.path.join(d , 'hooks')
87 _project_hook_list = [os.path.join(d, x) for x in os.listdir(d)] 88 _project_hook_list = [os.path.join(d, x) for x in os.listdir(d)]
88 return _project_hook_list 89 return _project_hook_list
@@ -487,6 +488,7 @@ class Project(object):
487 name, 488 name,
488 remote, 489 remote,
489 gitdir, 490 gitdir,
491 objdir,
490 worktree, 492 worktree,
491 relpath, 493 relpath,
492 revisionExpr, 494 revisionExpr,
@@ -507,6 +509,7 @@ class Project(object):
507 name: The `name` attribute of manifest.xml's project element. 509 name: The `name` attribute of manifest.xml's project element.
508 remote: RemoteSpec object specifying its remote's properties. 510 remote: RemoteSpec object specifying its remote's properties.
509 gitdir: Absolute path of git directory. 511 gitdir: Absolute path of git directory.
512 objdir: Absolute path of directory to store git objects.
510 worktree: Absolute path of git working tree. 513 worktree: Absolute path of git working tree.
511 relpath: Relative path of git working tree to repo's top directory. 514 relpath: Relative path of git working tree to repo's top directory.
512 revisionExpr: The `revision` attribute of manifest.xml's project element. 515 revisionExpr: The `revision` attribute of manifest.xml's project element.
@@ -525,6 +528,7 @@ class Project(object):
525 self.name = name 528 self.name = name
526 self.remote = remote 529 self.remote = remote
527 self.gitdir = gitdir.replace('\\', '/') 530 self.gitdir = gitdir.replace('\\', '/')
531 self.objdir = objdir.replace('\\', '/')
528 if worktree: 532 if worktree:
529 self.worktree = worktree.replace('\\', '/') 533 self.worktree = worktree.replace('\\', '/')
530 else: 534 else:
@@ -557,11 +561,12 @@ class Project(object):
557 defaults = self.manifest.globalConfig) 561 defaults = self.manifest.globalConfig)
558 562
559 if self.worktree: 563 if self.worktree:
560 self.work_git = self._GitGetByExec(self, bare=False) 564 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
561 else: 565 else:
562 self.work_git = None 566 self.work_git = None
563 self.bare_git = self._GitGetByExec(self, bare=True) 567 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
564 self.bare_ref = GitRefs(gitdir) 568 self.bare_ref = GitRefs(gitdir)
569 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
565 self.dest_branch = dest_branch 570 self.dest_branch = dest_branch
566 571
567 # This will be filled in if a project is later identified to be the 572 # This will be filled in if a project is later identified to be the
@@ -982,15 +987,62 @@ class Project(object):
982 987
983## Sync ## 988## Sync ##
984 989
990 def _ExtractArchive(self, tarpath, path=None):
991 """Extract the given tar on its current location
992
993 Args:
994 - tarpath: The path to the actual tar file
995
996 """
997 try:
998 with tarfile.open(tarpath, 'r') as tar:
999 tar.extractall(path=path)
1000 return True
1001 except (IOError, tarfile.TarError) as e:
1002 print("error: Cannot extract archive %s: "
1003 "%s" % (tarpath, str(e)), file=sys.stderr)
1004 return False
1005
985 def Sync_NetworkHalf(self, 1006 def Sync_NetworkHalf(self,
986 quiet=False, 1007 quiet=False,
987 is_new=None, 1008 is_new=None,
988 current_branch_only=False, 1009 current_branch_only=False,
989 clone_bundle=True, 1010 clone_bundle=True,
990 no_tags=False): 1011 no_tags=False,
1012 archive=False):
991 """Perform only the network IO portion of the sync process. 1013 """Perform only the network IO portion of the sync process.
992 Local working directory/branch state is not affected. 1014 Local working directory/branch state is not affected.
993 """ 1015 """
1016 if archive and not isinstance(self, MetaProject):
1017 if self.remote.url.startswith(('http://', 'https://')):
1018 print("error: %s: Cannot fetch archives from http/https "
1019 "remotes." % self.name, file=sys.stderr)
1020 return False
1021
1022 name = self.relpath.replace('\\', '/')
1023 name = name.replace('/', '_')
1024 tarpath = '%s.tar' % name
1025 topdir = self.manifest.topdir
1026
1027 try:
1028 self._FetchArchive(tarpath, cwd=topdir)
1029 except GitError as e:
1030 print('error: %s' % str(e), file=sys.stderr)
1031 return False
1032
1033 # From now on, we only need absolute tarpath
1034 tarpath = os.path.join(topdir, tarpath)
1035
1036 if not self._ExtractArchive(tarpath, path=topdir):
1037 return False
1038 try:
1039 os.remove(tarpath)
1040 except OSError as e:
1041 print("warn: Cannot remove archive %s: "
1042 "%s" % (tarpath, str(e)), file=sys.stderr)
1043 self._CopyFiles()
1044 return True
1045
994 if is_new is None: 1046 if is_new is None:
995 is_new = not self.Exists 1047 is_new = not self.Exists
996 if is_new: 1048 if is_new:
@@ -1069,6 +1121,7 @@ class Project(object):
1069 """Perform only the local IO portion of the sync process. 1121 """Perform only the local IO portion of the sync process.
1070 Network access is not required. 1122 Network access is not required.
1071 """ 1123 """
1124 self._InitWorkTree()
1072 all_refs = self.bare_ref.all 1125 all_refs = self.bare_ref.all
1073 self.CleanPublishedCache(all_refs) 1126 self.CleanPublishedCache(all_refs)
1074 revid = self.GetRevisionId(all_refs) 1127 revid = self.GetRevisionId(all_refs)
@@ -1077,7 +1130,6 @@ class Project(object):
1077 self._FastForward(revid) 1130 self._FastForward(revid)
1078 self._CopyFiles() 1131 self._CopyFiles()
1079 1132
1080 self._InitWorkTree()
1081 head = self.work_git.GetHead() 1133 head = self.work_git.GetHead()
1082 if head.startswith(R_HEADS): 1134 if head.startswith(R_HEADS):
1083 branch = head[len(R_HEADS):] 1135 branch = head[len(R_HEADS):]
@@ -1165,7 +1217,7 @@ class Project(object):
1165 last_mine = None 1217 last_mine = None
1166 cnt_mine = 0 1218 cnt_mine = 0
1167 for commit in local_changes: 1219 for commit in local_changes:
1168 commit_id, committer_email = commit.split(' ', 1) 1220 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
1169 if committer_email == self.UserEmail: 1221 if committer_email == self.UserEmail:
1170 last_mine = commit_id 1222 last_mine = commit_id
1171 cnt_mine += 1 1223 cnt_mine += 1
@@ -1544,11 +1596,13 @@ class Project(object):
1544 return result 1596 return result
1545 for rev, path, url in self._GetSubmodules(): 1597 for rev, path, url in self._GetSubmodules():
1546 name = self.manifest.GetSubprojectName(self, path) 1598 name = self.manifest.GetSubprojectName(self, path)
1547 project = self.manifest.projects.get(name) 1599 relpath, worktree, gitdir, objdir = \
1600 self.manifest.GetSubprojectPaths(self, name, path)
1601 project = self.manifest.paths.get(relpath)
1548 if project: 1602 if project:
1549 result.extend(project.GetDerivedSubprojects()) 1603 result.extend(project.GetDerivedSubprojects())
1550 continue 1604 continue
1551 relpath, worktree, gitdir = self.manifest.GetSubprojectPaths(self, path) 1605
1552 remote = RemoteSpec(self.remote.name, 1606 remote = RemoteSpec(self.remote.name,
1553 url = url, 1607 url = url,
1554 review = self.remote.review) 1608 review = self.remote.review)
@@ -1556,6 +1610,7 @@ class Project(object):
1556 name = name, 1610 name = name,
1557 remote = remote, 1611 remote = remote,
1558 gitdir = gitdir, 1612 gitdir = gitdir,
1613 objdir = objdir,
1559 worktree = worktree, 1614 worktree = worktree,
1560 relpath = relpath, 1615 relpath = relpath,
1561 revisionExpr = self.revisionExpr, 1616 revisionExpr = self.revisionExpr,
@@ -1573,6 +1628,19 @@ class Project(object):
1573 1628
1574## Direct Git Commands ## 1629## Direct Git Commands ##
1575 1630
1631 def _FetchArchive(self, tarpath, cwd=None):
1632 cmd = ['archive', '-v', '-o', tarpath]
1633 cmd.append('--remote=%s' % self.remote.url)
1634 cmd.append('--prefix=%s/' % self.relpath)
1635 cmd.append(self.revisionExpr)
1636
1637 command = GitCommand(self, cmd, cwd=cwd,
1638 capture_stdout=True,
1639 capture_stderr=True)
1640
1641 if command.Wait() != 0:
1642 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1643
1576 def _RemoteFetch(self, name=None, 1644 def _RemoteFetch(self, name=None,
1577 current_branch_only=False, 1645 current_branch_only=False,
1578 initial=False, 1646 initial=False,
@@ -1843,11 +1911,11 @@ class Project(object):
1843 cookiefile = line[len(prefix):] 1911 cookiefile = line[len(prefix):]
1844 break 1912 break
1845 if p.wait(): 1913 if p.wait():
1846 line = iter(p.stderr).next() 1914 err_msg = p.stderr.read()
1847 if ' -print_config' in line: 1915 if ' -print_config' in err_msg:
1848 pass # Persistent proxy doesn't support -print_config. 1916 pass # Persistent proxy doesn't support -print_config.
1849 else: 1917 else:
1850 print(line + p.stderr.read(), file=sys.stderr) 1918 print(err_msg, file=sys.stderr)
1851 if cookiefile: 1919 if cookiefile:
1852 return cookiefile 1920 return cookiefile
1853 except OSError as e: 1921 except OSError as e:
@@ -1908,8 +1976,17 @@ class Project(object):
1908 1976
1909 def _InitGitDir(self, mirror_git=None): 1977 def _InitGitDir(self, mirror_git=None):
1910 if not os.path.exists(self.gitdir): 1978 if not os.path.exists(self.gitdir):
1911 os.makedirs(self.gitdir) 1979
1912 self.bare_git.init() 1980 # Initialize the bare repository, which contains all of the objects.
1981 if not os.path.exists(self.objdir):
1982 os.makedirs(self.objdir)
1983 self.bare_objdir.init()
1984
1985 # If we have a separate directory to hold refs, initialize it as well.
1986 if self.objdir != self.gitdir:
1987 os.makedirs(self.gitdir)
1988 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
1989 copy_all=True)
1913 1990
1914 mp = self.manifest.manifestProject 1991 mp = self.manifest.manifestProject
1915 ref_dir = mp.config.GetString('repo.reference') or '' 1992 ref_dir = mp.config.GetString('repo.reference') or ''
@@ -1958,7 +2035,7 @@ class Project(object):
1958 self._InitHooks() 2035 self._InitHooks()
1959 2036
1960 def _InitHooks(self): 2037 def _InitHooks(self):
1961 hooks = self._gitdir_path('hooks') 2038 hooks = os.path.realpath(self._gitdir_path('hooks'))
1962 if not os.path.exists(hooks): 2039 if not os.path.exists(hooks):
1963 os.makedirs(hooks) 2040 os.makedirs(hooks)
1964 for stock_hook in _ProjectHooks(): 2041 for stock_hook in _ProjectHooks():
@@ -2025,33 +2102,61 @@ class Project(object):
2025 msg = 'manifest set to %s' % self.revisionExpr 2102 msg = 'manifest set to %s' % self.revisionExpr
2026 self.bare_git.symbolic_ref('-m', msg, ref, dst) 2103 self.bare_git.symbolic_ref('-m', msg, ref, dst)
2027 2104
2105 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2106 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2107
2108 Args:
2109 gitdir: The bare git repository. Must already be initialized.
2110 dotgit: The repository you would like to initialize.
2111 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2112 Only one work tree can store refs under a given |gitdir|.
2113 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2114 This saves you the effort of initializing |dotgit| yourself.
2115 """
2116 # These objects can be shared between several working trees.
2117 symlink_files = ['description', 'info']
2118 symlink_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
2119 if share_refs:
2120 # These objects can only be used by a single working tree.
2121 symlink_files += ['config', 'packed-refs']
2122 symlink_dirs += ['logs', 'refs']
2123 to_symlink = symlink_files + symlink_dirs
2124
2125 to_copy = []
2126 if copy_all:
2127 to_copy = os.listdir(gitdir)
2128
2129 for name in set(to_copy).union(to_symlink):
2130 try:
2131 src = os.path.realpath(os.path.join(gitdir, name))
2132 dst = os.path.realpath(os.path.join(dotgit, name))
2133
2134 if os.path.lexists(dst) and not os.path.islink(dst):
2135 raise GitError('cannot overwrite a local work tree')
2136
2137 # If the source dir doesn't exist, create an empty dir.
2138 if name in symlink_dirs and not os.path.lexists(src):
2139 os.makedirs(src)
2140
2141 if name in to_symlink:
2142 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2143 elif copy_all and not os.path.islink(dst):
2144 if os.path.isdir(src):
2145 shutil.copytree(src, dst)
2146 elif os.path.isfile(src):
2147 shutil.copy(src, dst)
2148 except OSError as e:
2149 if e.errno == errno.EPERM:
2150 raise GitError('filesystem must support symlinks')
2151 else:
2152 raise
2153
2028 def _InitWorkTree(self): 2154 def _InitWorkTree(self):
2029 dotgit = os.path.join(self.worktree, '.git') 2155 dotgit = os.path.join(self.worktree, '.git')
2030 if not os.path.exists(dotgit): 2156 if not os.path.exists(dotgit):
2031 os.makedirs(dotgit) 2157 os.makedirs(dotgit)
2032 2158 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2033 for name in ['config', 2159 copy_all=False)
2034 'description',
2035 'hooks',
2036 'info',
2037 'logs',
2038 'objects',
2039 'packed-refs',
2040 'refs',
2041 'rr-cache',
2042 'svn']:
2043 try:
2044 src = os.path.join(self.gitdir, name)
2045 dst = os.path.join(dotgit, name)
2046 if os.path.islink(dst) or not os.path.exists(dst):
2047 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2048 else:
2049 raise GitError('cannot overwrite a local work tree')
2050 except OSError as e:
2051 if e.errno == errno.EPERM:
2052 raise GitError('filesystem must support symlinks')
2053 else:
2054 raise
2055 2160
2056 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId()) 2161 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
2057 2162
@@ -2061,14 +2166,10 @@ class Project(object):
2061 if GitCommand(self, cmd).Wait() != 0: 2166 if GitCommand(self, cmd).Wait() != 0:
2062 raise GitError("cannot initialize work tree") 2167 raise GitError("cannot initialize work tree")
2063 2168
2064 rr_cache = os.path.join(self.gitdir, 'rr-cache')
2065 if not os.path.exists(rr_cache):
2066 os.makedirs(rr_cache)
2067
2068 self._CopyFiles() 2169 self._CopyFiles()
2069 2170
2070 def _gitdir_path(self, path): 2171 def _gitdir_path(self, path):
2071 return os.path.join(self.gitdir, path) 2172 return os.path.realpath(os.path.join(self.gitdir, path))
2072 2173
2073 def _revlist(self, *args, **kw): 2174 def _revlist(self, *args, **kw):
2074 a = [] 2175 a = []
@@ -2081,9 +2182,10 @@ class Project(object):
2081 return self.bare_ref.all 2182 return self.bare_ref.all
2082 2183
2083 class _GitGetByExec(object): 2184 class _GitGetByExec(object):
2084 def __init__(self, project, bare): 2185 def __init__(self, project, bare, gitdir):
2085 self._project = project 2186 self._project = project
2086 self._bare = bare 2187 self._bare = bare
2188 self._gitdir = gitdir
2087 2189
2088 def LsOthers(self): 2190 def LsOthers(self):
2089 p = GitCommand(self._project, 2191 p = GitCommand(self._project,
@@ -2092,6 +2194,7 @@ class Project(object):
2092 '--others', 2194 '--others',
2093 '--exclude-standard'], 2195 '--exclude-standard'],
2094 bare = False, 2196 bare = False,
2197 gitdir=self._gitdir,
2095 capture_stdout = True, 2198 capture_stdout = True,
2096 capture_stderr = True) 2199 capture_stderr = True)
2097 if p.Wait() == 0: 2200 if p.Wait() == 0:
@@ -2107,6 +2210,7 @@ class Project(object):
2107 cmd.extend(args) 2210 cmd.extend(args)
2108 p = GitCommand(self._project, 2211 p = GitCommand(self._project,
2109 cmd, 2212 cmd,
2213 gitdir=self._gitdir,
2110 bare = False, 2214 bare = False,
2111 capture_stdout = True, 2215 capture_stdout = True,
2112 capture_stderr = True) 2216 capture_stderr = True)
@@ -2216,6 +2320,7 @@ class Project(object):
2216 p = GitCommand(self._project, 2320 p = GitCommand(self._project,
2217 cmdv, 2321 cmdv,
2218 bare = self._bare, 2322 bare = self._bare,
2323 gitdir=self._gitdir,
2219 capture_stdout = True, 2324 capture_stdout = True,
2220 capture_stderr = True) 2325 capture_stderr = True)
2221 r = [] 2326 r = []
@@ -2268,6 +2373,7 @@ class Project(object):
2268 p = GitCommand(self._project, 2373 p = GitCommand(self._project,
2269 cmdv, 2374 cmdv,
2270 bare = self._bare, 2375 bare = self._bare,
2376 gitdir=self._gitdir,
2271 capture_stdout = True, 2377 capture_stdout = True,
2272 capture_stderr = True) 2378 capture_stderr = True)
2273 if p.Wait() != 0: 2379 if p.Wait() != 0:
@@ -2401,6 +2507,7 @@ class MetaProject(Project):
2401 manifest = manifest, 2507 manifest = manifest,
2402 name = name, 2508 name = name,
2403 gitdir = gitdir, 2509 gitdir = gitdir,
2510 objdir = gitdir,
2404 worktree = worktree, 2511 worktree = worktree,
2405 remote = RemoteSpec('origin'), 2512 remote = RemoteSpec('origin'),
2406 relpath = '.repo/%s' % name, 2513 relpath = '.repo/%s' % name,