diff options
Diffstat (limited to 'project.py')
-rw-r--r-- | project.py | 193 |
1 files changed, 150 insertions, 43 deletions
@@ -23,6 +23,7 @@ import shutil | |||
23 | import stat | 23 | import stat |
24 | import subprocess | 24 | import subprocess |
25 | import sys | 25 | import sys |
26 | import tarfile | ||
26 | import tempfile | 27 | import tempfile |
27 | import time | 28 | import 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, |