summaryrefslogtreecommitdiffstats
path: root/project.py
diff options
context:
space:
mode:
Diffstat (limited to 'project.py')
-rw-r--r--project.py185
1 files changed, 146 insertions, 39 deletions
diff --git a/project.py b/project.py
index b01a52ba..46f3b8f7 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):]
@@ -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,
@@ -1905,8 +1973,17 @@ class Project(object):
1905 1973
1906 def _InitGitDir(self, mirror_git=None): 1974 def _InitGitDir(self, mirror_git=None):
1907 if not os.path.exists(self.gitdir): 1975 if not os.path.exists(self.gitdir):
1908 os.makedirs(self.gitdir) 1976
1909 self.bare_git.init() 1977 # Initialize the bare repository, which contains all of the objects.
1978 if not os.path.exists(self.objdir):
1979 os.makedirs(self.objdir)
1980 self.bare_objdir.init()
1981
1982 # If we have a separate directory to hold refs, initialize it as well.
1983 if self.objdir != self.gitdir:
1984 os.makedirs(self.gitdir)
1985 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
1986 copy_all=True)
1910 1987
1911 mp = self.manifest.manifestProject 1988 mp = self.manifest.manifestProject
1912 ref_dir = mp.config.GetString('repo.reference') or '' 1989 ref_dir = mp.config.GetString('repo.reference') or ''
@@ -1955,7 +2032,7 @@ class Project(object):
1955 self._InitHooks() 2032 self._InitHooks()
1956 2033
1957 def _InitHooks(self): 2034 def _InitHooks(self):
1958 hooks = self._gitdir_path('hooks') 2035 hooks = os.path.realpath(self._gitdir_path('hooks'))
1959 if not os.path.exists(hooks): 2036 if not os.path.exists(hooks):
1960 os.makedirs(hooks) 2037 os.makedirs(hooks)
1961 for stock_hook in _ProjectHooks(): 2038 for stock_hook in _ProjectHooks():
@@ -2022,33 +2099,61 @@ class Project(object):
2022 msg = 'manifest set to %s' % self.revisionExpr 2099 msg = 'manifest set to %s' % self.revisionExpr
2023 self.bare_git.symbolic_ref('-m', msg, ref, dst) 2100 self.bare_git.symbolic_ref('-m', msg, ref, dst)
2024 2101
2102 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2103 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2104
2105 Args:
2106 gitdir: The bare git repository. Must already be initialized.
2107 dotgit: The repository you would like to initialize.
2108 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2109 Only one work tree can store refs under a given |gitdir|.
2110 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2111 This saves you the effort of initializing |dotgit| yourself.
2112 """
2113 # These objects can be shared between several working trees.
2114 symlink_files = ['description', 'info']
2115 symlink_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
2116 if share_refs:
2117 # These objects can only be used by a single working tree.
2118 symlink_files += ['config', 'packed-refs']
2119 symlink_dirs += ['logs', 'refs']
2120 to_symlink = symlink_files + symlink_dirs
2121
2122 to_copy = []
2123 if copy_all:
2124 to_copy = os.listdir(gitdir)
2125
2126 for name in set(to_copy).union(to_symlink):
2127 try:
2128 src = os.path.realpath(os.path.join(gitdir, name))
2129 dst = os.path.realpath(os.path.join(dotgit, name))
2130
2131 if os.path.lexists(dst) and not os.path.islink(dst):
2132 raise GitError('cannot overwrite a local work tree')
2133
2134 # If the source dir doesn't exist, create an empty dir.
2135 if name in symlink_dirs and not os.path.lexists(src):
2136 os.makedirs(src)
2137
2138 if name in to_symlink:
2139 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2140 elif copy_all and not os.path.islink(dst):
2141 if os.path.isdir(src):
2142 shutil.copytree(src, dst)
2143 elif os.path.isfile(src):
2144 shutil.copy(src, dst)
2145 except OSError as e:
2146 if e.errno == errno.EPERM:
2147 raise GitError('filesystem must support symlinks')
2148 else:
2149 raise
2150
2025 def _InitWorkTree(self): 2151 def _InitWorkTree(self):
2026 dotgit = os.path.join(self.worktree, '.git') 2152 dotgit = os.path.join(self.worktree, '.git')
2027 if not os.path.exists(dotgit): 2153 if not os.path.exists(dotgit):
2028 os.makedirs(dotgit) 2154 os.makedirs(dotgit)
2029 2155 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2030 for name in ['config', 2156 copy_all=False)
2031 'description',
2032 'hooks',
2033 'info',
2034 'logs',
2035 'objects',
2036 'packed-refs',
2037 'refs',
2038 'rr-cache',
2039 'svn']:
2040 try:
2041 src = os.path.join(self.gitdir, name)
2042 dst = os.path.join(dotgit, name)
2043 if os.path.islink(dst) or not os.path.exists(dst):
2044 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2045 else:
2046 raise GitError('cannot overwrite a local work tree')
2047 except OSError as e:
2048 if e.errno == errno.EPERM:
2049 raise GitError('filesystem must support symlinks')
2050 else:
2051 raise
2052 2157
2053 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId()) 2158 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
2054 2159
@@ -2058,14 +2163,10 @@ class Project(object):
2058 if GitCommand(self, cmd).Wait() != 0: 2163 if GitCommand(self, cmd).Wait() != 0:
2059 raise GitError("cannot initialize work tree") 2164 raise GitError("cannot initialize work tree")
2060 2165
2061 rr_cache = os.path.join(self.gitdir, 'rr-cache')
2062 if not os.path.exists(rr_cache):
2063 os.makedirs(rr_cache)
2064
2065 self._CopyFiles() 2166 self._CopyFiles()
2066 2167
2067 def _gitdir_path(self, path): 2168 def _gitdir_path(self, path):
2068 return os.path.join(self.gitdir, path) 2169 return os.path.realpath(os.path.join(self.gitdir, path))
2069 2170
2070 def _revlist(self, *args, **kw): 2171 def _revlist(self, *args, **kw):
2071 a = [] 2172 a = []
@@ -2078,9 +2179,10 @@ class Project(object):
2078 return self.bare_ref.all 2179 return self.bare_ref.all
2079 2180
2080 class _GitGetByExec(object): 2181 class _GitGetByExec(object):
2081 def __init__(self, project, bare): 2182 def __init__(self, project, bare, gitdir):
2082 self._project = project 2183 self._project = project
2083 self._bare = bare 2184 self._bare = bare
2185 self._gitdir = gitdir
2084 2186
2085 def LsOthers(self): 2187 def LsOthers(self):
2086 p = GitCommand(self._project, 2188 p = GitCommand(self._project,
@@ -2089,6 +2191,7 @@ class Project(object):
2089 '--others', 2191 '--others',
2090 '--exclude-standard'], 2192 '--exclude-standard'],
2091 bare = False, 2193 bare = False,
2194 gitdir=self._gitdir,
2092 capture_stdout = True, 2195 capture_stdout = True,
2093 capture_stderr = True) 2196 capture_stderr = True)
2094 if p.Wait() == 0: 2197 if p.Wait() == 0:
@@ -2104,6 +2207,7 @@ class Project(object):
2104 cmd.extend(args) 2207 cmd.extend(args)
2105 p = GitCommand(self._project, 2208 p = GitCommand(self._project,
2106 cmd, 2209 cmd,
2210 gitdir=self._gitdir,
2107 bare = False, 2211 bare = False,
2108 capture_stdout = True, 2212 capture_stdout = True,
2109 capture_stderr = True) 2213 capture_stderr = True)
@@ -2213,6 +2317,7 @@ class Project(object):
2213 p = GitCommand(self._project, 2317 p = GitCommand(self._project,
2214 cmdv, 2318 cmdv,
2215 bare = self._bare, 2319 bare = self._bare,
2320 gitdir=self._gitdir,
2216 capture_stdout = True, 2321 capture_stdout = True,
2217 capture_stderr = True) 2322 capture_stderr = True)
2218 r = [] 2323 r = []
@@ -2265,6 +2370,7 @@ class Project(object):
2265 p = GitCommand(self._project, 2370 p = GitCommand(self._project,
2266 cmdv, 2371 cmdv,
2267 bare = self._bare, 2372 bare = self._bare,
2373 gitdir=self._gitdir,
2268 capture_stdout = True, 2374 capture_stdout = True,
2269 capture_stderr = True) 2375 capture_stderr = True)
2270 if p.Wait() != 0: 2376 if p.Wait() != 0:
@@ -2398,6 +2504,7 @@ class MetaProject(Project):
2398 manifest = manifest, 2504 manifest = manifest,
2399 name = name, 2505 name = name,
2400 gitdir = gitdir, 2506 gitdir = gitdir,
2507 objdir = gitdir,
2401 worktree = worktree, 2508 worktree = worktree,
2402 remote = RemoteSpec('origin'), 2509 remote = RemoteSpec('origin'),
2403 relpath = '.repo/%s' % name, 2510 relpath = '.repo/%s' % name,