summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--command.py27
-rw-r--r--docs/manifest_submodule.txt130
-rw-r--r--docs/manifest_xml.txt (renamed from docs/manifest-format.txt)0
-rw-r--r--git_config.py14
-rw-r--r--git_refs.py1
-rwxr-xr-xmain.py6
-rw-r--r--manifest.py51
-rw-r--r--manifest_loader.py34
-rw-r--r--manifest_submodule.py474
-rw-r--r--manifest_xml.py80
-rw-r--r--project.py71
-rwxr-xr-xrepo9
-rw-r--r--subcmds/help.py3
-rw-r--r--subcmds/init.py78
-rw-r--r--subcmds/manifest.py81
-rw-r--r--subcmds/selfupdate.py1
-rw-r--r--subcmds/sync.py12
17 files changed, 947 insertions, 125 deletions
diff --git a/command.py b/command.py
index a941b95a..6e4e2c57 100644
--- a/command.py
+++ b/command.py
@@ -17,6 +17,8 @@ import os
17import optparse 17import optparse
18import sys 18import sys
19 19
20import manifest_loader
21
20from error import NoSuchProjectError 22from error import NoSuchProjectError
21 23
22class Command(object): 24class Command(object):
@@ -24,7 +26,6 @@ class Command(object):
24 """ 26 """
25 27
26 common = False 28 common = False
27 manifest = None
28 _optparse = None 29 _optparse = None
29 30
30 def WantPager(self, opt): 31 def WantPager(self, opt):
@@ -57,10 +58,25 @@ class Command(object):
57 """ 58 """
58 raise NotImplementedError 59 raise NotImplementedError
59 60
61 @property
62 def manifest(self):
63 return self.GetManifest()
64
65 def GetManifest(self, reparse=False, type=None):
66 return manifest_loader.GetManifest(self.repodir,
67 reparse=reparse,
68 type=type)
69
60 def GetProjects(self, args, missing_ok=False): 70 def GetProjects(self, args, missing_ok=False):
61 """A list of projects that match the arguments. 71 """A list of projects that match the arguments.
62 """ 72 """
63 all = self.manifest.projects 73 all = self.manifest.projects
74
75 mp = self.manifest.manifestProject
76 if mp.relpath == '.':
77 all = dict(all)
78 all[mp.name] = mp
79
64 result = [] 80 result = []
65 81
66 if not args: 82 if not args:
@@ -81,7 +97,9 @@ class Command(object):
81 for p in all.values(): 97 for p in all.values():
82 by_path[p.worktree] = p 98 by_path[p.worktree] = p
83 99
84 if os.path.exists(path): 100 try:
101 project = by_path[path]
102 except KeyError:
85 while path \ 103 while path \
86 and path != '/' \ 104 and path != '/' \
87 and path != self.manifest.topdir: 105 and path != self.manifest.topdir:
@@ -90,11 +108,6 @@ class Command(object):
90 break 108 break
91 except KeyError: 109 except KeyError:
92 path = os.path.dirname(path) 110 path = os.path.dirname(path)
93 else:
94 try:
95 project = by_path[path]
96 except KeyError:
97 pass
98 111
99 if not project: 112 if not project:
100 raise NoSuchProjectError(arg) 113 raise NoSuchProjectError(arg)
diff --git a/docs/manifest_submodule.txt b/docs/manifest_submodule.txt
new file mode 100644
index 00000000..e7d1f643
--- /dev/null
+++ b/docs/manifest_submodule.txt
@@ -0,0 +1,130 @@
1repo Manifest Format (submodule)
2================================
3
4A repo manifest describes the structure of a repo client; that is
5the directories that are visible and where they should be obtained
6from with git.
7
8The basic structure of a manifest is a bare Git repository holding
9a 'gitmodules' file in the top level directory, and one or more
10gitlink references pointing at commits from the referenced projects.
11This is the same structure as used by 'git submodule'.
12
13Manifests are inherently version controlled, since they are kept
14within a Git repository. Updates to manifests are automatically
15obtained by clients during `repo sync`.
16
17.gitmodules
18===========
19
20The '.gitmodules' file, located in the top-level directory of the
21client's working tree (or manifest repository), is a text file with
22a syntax matching the requirements of 'git config'.
23
24This file contains one subsection per project (also called a
25submodule by git), and the subsection value is a unique name to
26describe the project. Each submodule section must contain the
27following required keys:
28
29 * path
30 * url
31
32submodule.<name>.path
33---------------------
34
35Defines the path, relative to the top-level directory of the client's
36working tree, where the project is expected to be checked out. The
37path name must not end with a '/'. All paths must be unique within
38the .gitmodules file.
39
40At the specified path within the manifest repository a gitlink
41tree entry (an entry with file mode 160000) must exist referencing
42a commit SHA-1 from the project. This tree entry specifies the
43exact version of the project that `repo sync` will synchronize the
44client's working tree to.
45
46submodule.<name>.url
47--------------------
48
49Defines a URL from where the project repository can be cloned.
50By default `repo sync` will clone from this URL whenever a user
51needs to access this project.
52
53submodule.<name>.revision
54-------------------------
55
56Name of the branch in the project repository that Gerrit Code Review
57should automatically refresh the project's gitlink entry from.
58
59If set, during submit of a change within the referenced project,
60Gerrit Code Review will automatically update the manifest
61repository's corresponding gitlink to the new commit SHA-1 of
62this branch.
63
64Valid values are a short branch name (e.g. 'master'), a full ref
65name (e.g. 'refs/heads/master'), or '.' to request using the same
66branch name as the manifest branch itself. Since '.' automatically
67uses the manifest branch, '.' is the recommended value.
68
69If this key is not set, Gerrit Code Review will NOT automatically
70update the gitlink. An unset key requires the manifest maintainer
71to manually update the gitlink when it is necessary to reference
72a different revision of the project.
73
74submodule.<name>.update
75-----------------------
76
77This key is not supported by repo. If set, it will be ignored.
78
79.review
80=======
81
82The optional '.review' file, located in the top-level directory of
83the client's working tree (or manifest repository), is a text file
84with a syntax matching the requirements of 'git config'.
85
86This file describes how `repo upload` should interact with the
87project's preferred code review system.
88
89review.url
90----------
91
92URL of the default Gerrit Code Review server. If a project does
93not have a specific URL in the '.review' file, this default URL
94will be used instead.
95
96review.<name>.url
97-----------------
98
99Project specific URL of the Gerrit Code Review server, for the
100submodule whose project name is <name>.
101
102Example
103=======
104
105 $ cat .gitmodules
106 [submodule "app/Clock"]
107 path = clock
108 url = git://vcs.example.com/ClockWidget.git
109 revision = .
110 [submodule "app/Browser"]
111 path = net/browser
112 url = git://netgroup.example.com/network/web/Browser.git
113 revision = .
114
115 $ cat .review
116 [review]
117 url = vcs-gerrit.example.com
118 [review "app/Browser"]
119 url = netgroup.example.com
120
121In the above example, the app/Clock project will send its code
122reviews to the default server, vcs-gerrit.example.com, while
123app/Browser will send its code reviews to netgroup.example.com.
124
125See Also
126========
127
128 * http://www.kernel.org/pub/software/scm/git/docs/gitmodules.html
129 * http://www.kernel.org/pub/software/scm/git/docs/git-config.html
130 * http://code.google.com/p/gerrit/
diff --git a/docs/manifest-format.txt b/docs/manifest_xml.txt
index da0e69ff..da0e69ff 100644
--- a/docs/manifest-format.txt
+++ b/docs/manifest_xml.txt
diff --git a/git_config.py b/git_config.py
index fe983c4a..9dba699a 100644
--- a/git_config.py
+++ b/git_config.py
@@ -71,6 +71,14 @@ class GitConfig(object):
71 else: 71 else:
72 self._pickle = pickleFile 72 self._pickle = pickleFile
73 73
74 def ClearCache(self):
75 if os.path.exists(self._pickle):
76 os.remove(self._pickle)
77 self._cache_dict = None
78 self._section_dict = None
79 self._remotes = {}
80 self._branches = {}
81
74 def Has(self, name, include_defaults = True): 82 def Has(self, name, include_defaults = True):
75 """Return true if this configuration file has the key. 83 """Return true if this configuration file has the key.
76 """ 84 """
@@ -254,9 +262,11 @@ class GitConfig(object):
254 finally: 262 finally:
255 fd.close() 263 fd.close()
256 except IOError: 264 except IOError:
257 os.remove(self._pickle) 265 if os.path.exists(self._pickle):
266 os.remove(self._pickle)
258 except cPickle.PickleError: 267 except cPickle.PickleError:
259 os.remove(self._pickle) 268 if os.path.exists(self._pickle):
269 os.remove(self._pickle)
260 270
261 def _ReadGit(self): 271 def _ReadGit(self):
262 """ 272 """
diff --git a/git_refs.py b/git_refs.py
index ac8ed0c1..b24a0b4e 100644
--- a/git_refs.py
+++ b/git_refs.py
@@ -21,7 +21,6 @@ HEAD = 'HEAD'
21R_HEADS = 'refs/heads/' 21R_HEADS = 'refs/heads/'
22R_TAGS = 'refs/tags/' 22R_TAGS = 'refs/tags/'
23R_PUB = 'refs/published/' 23R_PUB = 'refs/published/'
24R_M = 'refs/remotes/m/'
25 24
26 25
27class GitRefs(object): 26class GitRefs(object):
diff --git a/main.py b/main.py
index 70ddeffa..2cc7e447 100755
--- a/main.py
+++ b/main.py
@@ -32,11 +32,9 @@ from git_config import close_ssh
32from command import InteractiveCommand 32from command import InteractiveCommand
33from command import MirrorSafeCommand 33from command import MirrorSafeCommand
34from command import PagedCommand 34from command import PagedCommand
35from editor import Editor
36from error import ManifestInvalidRevisionError 35from error import ManifestInvalidRevisionError
37from error import NoSuchProjectError 36from error import NoSuchProjectError
38from error import RepoChangedException 37from error import RepoChangedException
39from manifest_xml import XmlManifest
40from pager import RunPager 38from pager import RunPager
41 39
42from subcmds import all as all_commands 40from subcmds import all as all_commands
@@ -61,6 +59,8 @@ class _Repo(object):
61 def __init__(self, repodir): 59 def __init__(self, repodir):
62 self.repodir = repodir 60 self.repodir = repodir
63 self.commands = all_commands 61 self.commands = all_commands
62 # add 'branch' as an alias for 'branches'
63 all_commands['branch'] = all_commands['branches']
64 64
65 def _Run(self, argv): 65 def _Run(self, argv):
66 name = None 66 name = None
@@ -97,8 +97,6 @@ class _Repo(object):
97 sys.exit(1) 97 sys.exit(1)
98 98
99 cmd.repodir = self.repodir 99 cmd.repodir = self.repodir
100 cmd.manifest = XmlManifest(cmd.repodir)
101 Editor.globalConfig = cmd.manifest.globalConfig
102 100
103 if not isinstance(cmd, MirrorSafeCommand) and cmd.manifest.IsMirror: 101 if not isinstance(cmd, MirrorSafeCommand) and cmd.manifest.IsMirror:
104 print >>sys.stderr, \ 102 print >>sys.stderr, \
diff --git a/manifest.py b/manifest.py
new file mode 100644
index 00000000..f737e866
--- /dev/null
+++ b/manifest.py
@@ -0,0 +1,51 @@
1#
2# Copyright (C) 2009 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import os
17
18from error import ManifestParseError
19from editor import Editor
20from git_config import GitConfig
21from project import MetaProject
22
23class Manifest(object):
24 """any manifest format"""
25
26 def __init__(self, repodir):
27 self.repodir = os.path.abspath(repodir)
28 self.topdir = os.path.dirname(self.repodir)
29 self.globalConfig = GitConfig.ForUser()
30 Editor.globalConfig = self.globalConfig
31
32 self.repoProject = MetaProject(self, 'repo',
33 gitdir = os.path.join(repodir, 'repo/.git'),
34 worktree = os.path.join(repodir, 'repo'))
35
36 @property
37 def IsMirror(self):
38 return self.manifestProject.config.GetBoolean('repo.mirror')
39
40 @property
41 def projects(self):
42 return {}
43
44 def InitBranch(self):
45 pass
46
47 def SetMRefs(self, project):
48 pass
49
50 def Upgrade_Local(self, old):
51 raise ManifestParseError, 'unsupported upgrade path'
diff --git a/manifest_loader.py b/manifest_loader.py
new file mode 100644
index 00000000..467cb42a
--- /dev/null
+++ b/manifest_loader.py
@@ -0,0 +1,34 @@
1#
2# Copyright (C) 2009 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16from manifest_submodule import SubmoduleManifest
17from manifest_xml import XmlManifest
18
19def ParseManifest(repodir, type=None):
20 if type:
21 return type(repodir)
22 if SubmoduleManifest.Is(repodir):
23 return SubmoduleManifest(repodir)
24 return XmlManifest(repodir)
25
26_manifest = None
27
28def GetManifest(repodir, reparse=False, type=None):
29 global _manifest
30 if _manifest is None \
31 or reparse \
32 or (type and _manifest.__class__ != type):
33 _manifest = ParseManifest(repodir, type=type)
34 return _manifest
diff --git a/manifest_submodule.py b/manifest_submodule.py
new file mode 100644
index 00000000..92f187a0
--- /dev/null
+++ b/manifest_submodule.py
@@ -0,0 +1,474 @@
1#
2# Copyright (C) 2009 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import sys
17import os
18import shutil
19
20from error import GitError
21from error import ManifestParseError
22from git_command import GitCommand
23from git_config import GitConfig
24from git_config import IsId
25from manifest import Manifest
26from progress import Progress
27from project import RemoteSpec
28from project import Project
29from project import MetaProject
30from project import R_HEADS
31from project import HEAD
32from project import _lwrite
33
34import manifest_xml
35
36GITLINK = '160000'
37
38def _rmdir(dir, top):
39 while dir != top:
40 try:
41 os.rmdir(dir)
42 except OSError:
43 break
44 dir = os.path.dirname(dir)
45
46def _rmref(gitdir, ref):
47 os.remove(os.path.join(gitdir, ref))
48 log = os.path.join(gitdir, 'logs', ref)
49 if os.path.exists(log):
50 os.remove(log)
51 _rmdir(os.path.dirname(log), gitdir)
52
53def _has_gitmodules(d):
54 return os.path.exists(os.path.join(d, '.gitmodules'))
55
56class SubmoduleManifest(Manifest):
57 """manifest from .gitmodules file"""
58
59 @classmethod
60 def Is(cls, repodir):
61 return _has_gitmodules(os.path.dirname(repodir)) \
62 or _has_gitmodules(os.path.join(repodir, 'manifest')) \
63 or _has_gitmodules(os.path.join(repodir, 'manifests'))
64
65 @classmethod
66 def IsBare(cls, p):
67 try:
68 p.bare_git.cat_file('-e', '%s:.gitmodules' % p.GetRevisionId())
69 except GitError:
70 return False
71 return True
72
73 def __init__(self, repodir):
74 Manifest.__init__(self, repodir)
75
76 gitdir = os.path.join(repodir, 'manifest.git')
77 config = GitConfig.ForRepository(gitdir = gitdir)
78
79 if config.GetBoolean('repo.mirror'):
80 worktree = os.path.join(repodir, 'manifest')
81 relpath = None
82 else:
83 worktree = self.topdir
84 relpath = '.'
85
86 self.manifestProject = MetaProject(self, '__manifest__',
87 gitdir = gitdir,
88 worktree = worktree,
89 relpath = relpath)
90 self._modules = GitConfig(os.path.join(worktree, '.gitmodules'),
91 pickleFile = os.path.join(
92 repodir, '.repopickle_gitmodules'
93 ))
94 self._review = GitConfig(os.path.join(worktree, '.review'),
95 pickleFile = os.path.join(
96 repodir, '.repopickle_review'
97 ))
98 self._Unload()
99
100 @property
101 def projects(self):
102 self._Load()
103 return self._projects
104
105 def InitBranch(self):
106 m = self.manifestProject
107 if m.CurrentBranch is None:
108 b = m.revisionExpr
109 if b.startswith(R_HEADS):
110 b = b[len(R_HEADS):]
111 return m.StartBranch(b)
112 return True
113
114 def SetMRefs(self, project):
115 if project.revisionId is None:
116 # Special project, e.g. the manifest or repo executable.
117 #
118 return
119
120 ref = 'refs/remotes/m'
121 cur = project.bare_ref.get(ref)
122 exp = project.revisionId
123 if cur != exp:
124 msg = 'manifest set to %s' % exp
125 project.bare_git.UpdateRef(ref, exp, message = msg, detach = True)
126
127 ref = 'refs/remotes/m-revision'
128 cur = project.bare_ref.symref(ref)
129 exp = project.revisionExpr
130 if exp is None:
131 if cur:
132 _rmref(project.gitdir, ref)
133 elif cur != exp:
134 remote = project.GetRemote(project.remote.name)
135 dst = remote.ToLocal(exp)
136 msg = 'manifest set to %s (%s)' % (exp, dst)
137 project.bare_git.symbolic_ref('-m', msg, ref, dst)
138
139 def Upgrade_Local(self, old):
140 if isinstance(old, manifest_xml.XmlManifest):
141 self.FromXml_Local_1(old, checkout=True)
142 self.FromXml_Local_2(old)
143 else:
144 raise ManifestParseError, 'cannot upgrade manifest'
145
146 def FromXml_Local_1(self, old, checkout):
147 os.rename(old.manifestProject.gitdir,
148 os.path.join(old.repodir, 'manifest.git'))
149
150 oldmp = old.manifestProject
151 oldBranch = oldmp.CurrentBranch
152 b = oldmp.GetBranch(oldBranch).merge
153 if not b:
154 raise ManifestParseError, 'cannot upgrade manifest'
155 if b.startswith(R_HEADS):
156 b = b[len(R_HEADS):]
157
158 newmp = self.manifestProject
159 self._CleanOldMRefs(newmp)
160 if oldBranch != b:
161 newmp.bare_git.branch('-m', oldBranch, b)
162 newmp.config.ClearCache()
163
164 old_remote = newmp.GetBranch(b).remote.name
165 act_remote = self._GuessRemoteName(old)
166 if old_remote != act_remote:
167 newmp.bare_git.remote('rename', old_remote, act_remote)
168 newmp.config.ClearCache()
169 newmp.remote.name = act_remote
170 print >>sys.stderr, "Assuming remote named '%s'" % act_remote
171
172 if checkout:
173 for p in old.projects.values():
174 for c in p.copyfiles:
175 if os.path.exists(c.abs_dest):
176 os.remove(c.abs_dest)
177 newmp._InitWorkTree()
178 else:
179 newmp._LinkWorkTree()
180
181 _lwrite(os.path.join(newmp.worktree,'.git',HEAD),
182 'ref: refs/heads/%s\n' % b)
183
184 def _GuessRemoteName(self, old):
185 used = {}
186 for p in old.projects.values():
187 n = p.remote.name
188 used[n] = used.get(n, 0) + 1
189
190 remote_name = 'origin'
191 remote_used = 0
192 for n in used.keys():
193 if remote_used < used[n]:
194 remote_used = used[n]
195 remote_name = n
196 return remote_name
197
198 def FromXml_Local_2(self, old):
199 shutil.rmtree(old.manifestProject.worktree)
200 os.remove(old._manifestFile)
201
202 my_remote = self._Remote().name
203 new_base = os.path.join(self.repodir, 'projects')
204 old_base = os.path.join(self.repodir, 'projects.old')
205 os.rename(new_base, old_base)
206 os.makedirs(new_base)
207
208 info = []
209 pm = Progress('Converting projects', len(self.projects))
210 for p in self.projects.values():
211 pm.update()
212
213 old_p = old.projects.get(p.name)
214 old_gitdir = os.path.join(old_base, '%s.git' % p.relpath)
215 if not os.path.isdir(old_gitdir):
216 continue
217
218 parent = os.path.dirname(p.gitdir)
219 if not os.path.isdir(parent):
220 os.makedirs(parent)
221 os.rename(old_gitdir, p.gitdir)
222 _rmdir(os.path.dirname(old_gitdir), self.repodir)
223
224 if not os.path.isdir(p.worktree):
225 os.makedirs(p.worktree)
226
227 if os.path.isdir(os.path.join(p.worktree, '.git')):
228 p._LinkWorkTree(relink=True)
229
230 self._CleanOldMRefs(p)
231 if old_p and old_p.remote.name != my_remote:
232 info.append("%s/: renamed remote '%s' to '%s'" \
233 % (p.relpath, old_p.remote.name, my_remote))
234 p.bare_git.remote('rename', old_p.remote.name, my_remote)
235 p.config.ClearCache()
236
237 self.SetMRefs(p)
238 pm.end()
239 for i in info:
240 print >>sys.stderr, i
241
242 def _CleanOldMRefs(self, p):
243 all_refs = p._allrefs
244 for ref in all_refs.keys():
245 if ref.startswith(manifest_xml.R_M):
246 if p.bare_ref.symref(ref) != '':
247 _rmref(p.gitdir, ref)
248 else:
249 p.bare_git.DeleteRef(ref, all_refs[ref])
250
251 def FromXml_Definition(self, old):
252 """Convert another manifest representation to this one.
253 """
254 mp = self.manifestProject
255 gm = self._modules
256 gr = self._review
257
258 fd = open(os.path.join(mp.worktree, '.gitignore'), 'ab')
259 fd.write('/.repo\n')
260 fd.close()
261
262 sort_projects = list(old.projects.keys())
263 sort_projects.sort()
264
265 b = mp.GetBranch(mp.CurrentBranch).merge
266 if b.startswith(R_HEADS):
267 b = b[len(R_HEADS):]
268
269 info = []
270 pm = Progress('Converting manifest', len(sort_projects))
271 for p in sort_projects:
272 pm.update()
273 p = old.projects[p]
274
275 gm.SetString('submodule.%s.path' % p.name, p.relpath)
276 gm.SetString('submodule.%s.url' % p.name, p.remote.url)
277
278 if gr.GetString('review.url') is None:
279 gr.SetString('review.url', p.remote.review)
280 elif gr.GetString('review.url') != p.remote.review:
281 gr.SetString('review.%s.url' % p.name, p.remote.review)
282
283 r = p.revisionExpr
284 if r and not IsId(r):
285 if r.startswith(R_HEADS):
286 r = r[len(R_HEADS):]
287 if r == b:
288 r = '.'
289 gm.SetString('submodule.%s.revision' % p.name, r)
290
291 for c in p.copyfiles:
292 info.append('Moved %s out of %s' % (c.src, p.relpath))
293 c._Copy()
294 p.work_git.rm(c.src)
295 mp.work_git.add(c.dest)
296
297 self.SetRevisionId(p.relpath, p.GetRevisionId())
298 mp.work_git.add('.gitignore', '.gitmodules', '.review')
299 pm.end()
300 for i in info:
301 print >>sys.stderr, i
302
303 def _Unload(self):
304 self._loaded = False
305 self._projects = {}
306 self._revisionIds = None
307 self.branch = None
308
309 def _Load(self):
310 if not self._loaded:
311 f = os.path.join(self.repodir, manifest_xml.LOCAL_MANIFEST_NAME)
312 if os.path.exists(f):
313 print >>sys.stderr, 'warning: ignoring %s' % f
314
315 m = self.manifestProject
316 b = m.CurrentBranch
317 if not b:
318 raise ManifestParseError, 'manifest cannot be on detached HEAD'
319 b = m.GetBranch(b).merge
320 if b.startswith(R_HEADS):
321 b = b[len(R_HEADS):]
322 self.branch = b
323 m.remote.name = self._Remote().name
324
325 self._ParseModules()
326
327 if self.IsMirror:
328 self._AddMetaProjectMirror(self.repoProject)
329 self._AddMetaProjectMirror(self.manifestProject)
330
331 self._loaded = True
332
333 def _ParseModules(self):
334 byPath = dict()
335 for name in self._modules.GetSubSections('submodule'):
336 p = self._ParseProject(name)
337 if self._projects.get(p.name):
338 raise ManifestParseError, 'duplicate project "%s"' % p.name
339 if byPath.get(p.relpath):
340 raise ManifestParseError, 'duplicate path "%s"' % p.relpath
341 self._projects[p.name] = p
342 byPath[p.relpath] = p
343
344 for relpath in self._allRevisionIds.keys():
345 if relpath not in byPath:
346 raise ManifestParseError, \
347 'project "%s" not in .gitmodules' \
348 % relpath
349
350 def _Remote(self):
351 m = self.manifestProject
352 b = m.GetBranch(m.CurrentBranch)
353 return b.remote
354
355 def _ResolveUrl(self, url):
356 if url.startswith('./') or url.startswith('../'):
357 base = self._Remote().url
358 try:
359 base = base[:base.rindex('/')+1]
360 except ValueError:
361 base = base[:base.rindex(':')+1]
362 if url.startswith('./'):
363 url = url[2:]
364 while '/' in base and url.startswith('../'):
365 base = base[:base.rindex('/')+1]
366 url = url[3:]
367 return base + url
368 return url
369
370 def _GetRevisionId(self, path):
371 return self._allRevisionIds.get(path)
372
373 @property
374 def _allRevisionIds(self):
375 if self._revisionIds is None:
376 a = dict()
377 p = GitCommand(self.manifestProject,
378 ['ls-files','-z','--stage'],
379 capture_stdout = True)
380 for line in p.process.stdout.read().split('\0')[:-1]:
381 l_info, l_path = line.split('\t', 2)
382 l_mode, l_id, l_stage = l_info.split(' ', 2)
383 if l_mode == GITLINK and l_stage == '0':
384 a[l_path] = l_id
385 p.Wait()
386 self._revisionIds = a
387 return self._revisionIds
388
389 def SetRevisionId(self, path, id):
390 self.manifestProject.work_git.update_index(
391 '--add','--cacheinfo', GITLINK, id, path)
392
393 def _ParseProject(self, name):
394 gm = self._modules
395 gr = self._review
396
397 path = gm.GetString('submodule.%s.path' % name)
398 if not path:
399 path = name
400
401 revId = self._GetRevisionId(path)
402 if not revId:
403 raise ManifestParseError(
404 'submodule "%s" has no revision at "%s"' \
405 % (name, path))
406
407 url = gm.GetString('submodule.%s.url' % name)
408 if not url:
409 url = name
410 url = self._ResolveUrl(url)
411
412 review = gr.GetString('review.%s.url' % name)
413 if not review:
414 review = gr.GetString('review.url')
415 if not review:
416 review = self._Remote().review
417
418 remote = RemoteSpec(self._Remote().name, url, review)
419 revExpr = gm.GetString('submodule.%s.revision' % name)
420 if revExpr == '.':
421 revExpr = self.branch
422
423 if self.IsMirror:
424 relpath = None
425 worktree = None
426 gitdir = os.path.join(self.topdir, '%s.git' % name)
427 else:
428 worktree = os.path.join(self.topdir, path)
429 gitdir = os.path.join(self.repodir, 'projects/%s.git' % name)
430
431 return Project(manifest = self,
432 name = name,
433 remote = remote,
434 gitdir = gitdir,
435 worktree = worktree,
436 relpath = path,
437 revisionExpr = revExpr,
438 revisionId = revId)
439
440 def _AddMetaProjectMirror(self, m):
441 m_url = m.GetRemote(m.remote.name).url
442 if m_url.endswith('/.git'):
443 raise ManifestParseError, 'refusing to mirror %s' % m_url
444
445 name = self._GuessMetaName(m_url)
446 if name.endswith('.git'):
447 name = name[:-4]
448
449 if name not in self._projects:
450 m.PreSync()
451 gitdir = os.path.join(self.topdir, '%s.git' % name)
452 project = Project(manifest = self,
453 name = name,
454 remote = RemoteSpec(self._Remote().name, m_url),
455 gitdir = gitdir,
456 worktree = None,
457 relpath = None,
458 revisionExpr = m.revisionExpr,
459 revisionId = None)
460 self._projects[project.name] = project
461
462 def _GuessMetaName(self, m_url):
463 parts = m_url.split('/')
464 name = parts[-1]
465 parts = parts[0:-1]
466 s = len(parts) - 1
467 while s > 0:
468 l = '/'.join(parts[0:s]) + '/'
469 r = '/'.join(parts[s:]) + '/'
470 for p in self._projects.values():
471 if p.name.startswith(r) and p.remote.url.startswith(l):
472 return r + name
473 s -= 1
474 return m_url[m_url.rindex('/') + 1:]
diff --git a/manifest_xml.py b/manifest_xml.py
index 7d02f9d6..d888653d 100644
--- a/manifest_xml.py
+++ b/manifest_xml.py
@@ -17,12 +17,19 @@ import os
17import sys 17import sys
18import xml.dom.minidom 18import xml.dom.minidom
19 19
20from git_config import GitConfig, IsId 20from git_config import GitConfig
21from project import RemoteSpec, Project, MetaProject, R_HEADS, HEAD 21from git_config import IsId
22from manifest import Manifest
23from project import RemoteSpec
24from project import Project
25from project import MetaProject
26from project import R_HEADS
27from project import HEAD
22from error import ManifestParseError 28from error import ManifestParseError
23 29
24MANIFEST_FILE_NAME = 'manifest.xml' 30MANIFEST_FILE_NAME = 'manifest.xml'
25LOCAL_MANIFEST_NAME = 'local_manifest.xml' 31LOCAL_MANIFEST_NAME = 'local_manifest.xml'
32R_M = 'refs/remotes/m/'
26 33
27class _Default(object): 34class _Default(object):
28 """Project defaults within the manifest.""" 35 """Project defaults within the manifest."""
@@ -46,19 +53,13 @@ class _XmlRemote(object):
46 url += '/%s.git' % projectName 53 url += '/%s.git' % projectName
47 return RemoteSpec(self.name, url, self.reviewUrl) 54 return RemoteSpec(self.name, url, self.reviewUrl)
48 55
49class XmlManifest(object): 56class XmlManifest(Manifest):
50 """manages the repo configuration file""" 57 """manages the repo configuration file"""
51 58
52 def __init__(self, repodir): 59 def __init__(self, repodir):
53 self.repodir = os.path.abspath(repodir) 60 Manifest.__init__(self, repodir)
54 self.topdir = os.path.dirname(self.repodir)
55 self.manifestFile = os.path.join(self.repodir, MANIFEST_FILE_NAME)
56 self.globalConfig = GitConfig.ForUser()
57
58 self.repoProject = MetaProject(self, 'repo',
59 gitdir = os.path.join(repodir, 'repo/.git'),
60 worktree = os.path.join(repodir, 'repo'))
61 61
62 self._manifestFile = os.path.join(repodir, MANIFEST_FILE_NAME)
62 self.manifestProject = MetaProject(self, 'manifests', 63 self.manifestProject = MetaProject(self, 'manifests',
63 gitdir = os.path.join(repodir, 'manifests.git'), 64 gitdir = os.path.join(repodir, 'manifests.git'),
64 worktree = os.path.join(repodir, 'manifests')) 65 worktree = os.path.join(repodir, 'manifests'))
@@ -72,18 +73,18 @@ class XmlManifest(object):
72 if not os.path.isfile(path): 73 if not os.path.isfile(path):
73 raise ManifestParseError('manifest %s not found' % name) 74 raise ManifestParseError('manifest %s not found' % name)
74 75
75 old = self.manifestFile 76 old = self._manifestFile
76 try: 77 try:
77 self.manifestFile = path 78 self._manifestFile = path
78 self._Unload() 79 self._Unload()
79 self._Load() 80 self._Load()
80 finally: 81 finally:
81 self.manifestFile = old 82 self._manifestFile = old
82 83
83 try: 84 try:
84 if os.path.exists(self.manifestFile): 85 if os.path.exists(self._manifestFile):
85 os.remove(self.manifestFile) 86 os.remove(self._manifestFile)
86 os.symlink('manifests/%s' % name, self.manifestFile) 87 os.symlink('manifests/%s' % name, self._manifestFile)
87 except OSError, e: 88 except OSError, e:
88 raise ManifestParseError('cannot link manifest %s' % name) 89 raise ManifestParseError('cannot link manifest %s' % name)
89 90
@@ -168,9 +169,15 @@ class XmlManifest(object):
168 self._Load() 169 self._Load()
169 return self._default 170 return self._default
170 171
171 @property 172 def InitBranch(self):
172 def IsMirror(self): 173 m = self.manifestProject
173 return self.manifestProject.config.GetBoolean('repo.mirror') 174 if m.CurrentBranch is None:
175 return m.StartBranch('default')
176 return True
177
178 def SetMRefs(self, project):
179 if self.branch:
180 project._InitAnyMRef(R_M + self.branch)
174 181
175 def _Unload(self): 182 def _Unload(self):
176 self._loaded = False 183 self._loaded = False
@@ -182,7 +189,10 @@ class XmlManifest(object):
182 def _Load(self): 189 def _Load(self):
183 if not self._loaded: 190 if not self._loaded:
184 m = self.manifestProject 191 m = self.manifestProject
185 b = m.GetBranch(m.CurrentBranch).merge 192 b = m.GetBranch(m.CurrentBranch)
193 if b.remote and b.remote.name:
194 m.remote.name = b.remote.name
195 b = b.merge
186 if b is not None and b.startswith(R_HEADS): 196 if b is not None and b.startswith(R_HEADS):
187 b = b[len(R_HEADS):] 197 b = b[len(R_HEADS):]
188 self.branch = b 198 self.branch = b
@@ -192,11 +202,11 @@ class XmlManifest(object):
192 local = os.path.join(self.repodir, LOCAL_MANIFEST_NAME) 202 local = os.path.join(self.repodir, LOCAL_MANIFEST_NAME)
193 if os.path.exists(local): 203 if os.path.exists(local):
194 try: 204 try:
195 real = self.manifestFile 205 real = self._manifestFile
196 self.manifestFile = local 206 self._manifestFile = local
197 self._ParseManifest(False) 207 self._ParseManifest(False)
198 finally: 208 finally:
199 self.manifestFile = real 209 self._manifestFile = real
200 210
201 if self.IsMirror: 211 if self.IsMirror:
202 self._AddMetaProjectMirror(self.repoProject) 212 self._AddMetaProjectMirror(self.repoProject)
@@ -205,17 +215,17 @@ class XmlManifest(object):
205 self._loaded = True 215 self._loaded = True
206 216
207 def _ParseManifest(self, is_root_file): 217 def _ParseManifest(self, is_root_file):
208 root = xml.dom.minidom.parse(self.manifestFile) 218 root = xml.dom.minidom.parse(self._manifestFile)
209 if not root or not root.childNodes: 219 if not root or not root.childNodes:
210 raise ManifestParseError, \ 220 raise ManifestParseError, \
211 "no root node in %s" % \ 221 "no root node in %s" % \
212 self.manifestFile 222 self._manifestFile
213 223
214 config = root.childNodes[0] 224 config = root.childNodes[0]
215 if config.nodeName != 'manifest': 225 if config.nodeName != 'manifest':
216 raise ManifestParseError, \ 226 raise ManifestParseError, \
217 "no <manifest> in %s" % \ 227 "no <manifest> in %s" % \
218 self.manifestFile 228 self._manifestFile
219 229
220 for node in config.childNodes: 230 for node in config.childNodes:
221 if node.nodeName == 'remove-project': 231 if node.nodeName == 'remove-project':
@@ -233,7 +243,7 @@ class XmlManifest(object):
233 if self._remotes.get(remote.name): 243 if self._remotes.get(remote.name):
234 raise ManifestParseError, \ 244 raise ManifestParseError, \
235 'duplicate remote %s in %s' % \ 245 'duplicate remote %s in %s' % \
236 (remote.name, self.manifestFile) 246 (remote.name, self._manifestFile)
237 self._remotes[remote.name] = remote 247 self._remotes[remote.name] = remote
238 248
239 for node in config.childNodes: 249 for node in config.childNodes:
@@ -241,7 +251,7 @@ class XmlManifest(object):
241 if self._default is not None: 251 if self._default is not None:
242 raise ManifestParseError, \ 252 raise ManifestParseError, \
243 'duplicate default in %s' % \ 253 'duplicate default in %s' % \
244 (self.manifestFile) 254 (self._manifestFile)
245 self._default = self._ParseDefault(node) 255 self._default = self._ParseDefault(node)
246 if self._default is None: 256 if self._default is None:
247 self._default = _Default() 257 self._default = _Default()
@@ -252,7 +262,7 @@ class XmlManifest(object):
252 if self._projects.get(project.name): 262 if self._projects.get(project.name):
253 raise ManifestParseError, \ 263 raise ManifestParseError, \
254 'duplicate project %s in %s' % \ 264 'duplicate project %s in %s' % \
255 (project.name, self.manifestFile) 265 (project.name, self._manifestFile)
256 self._projects[project.name] = project 266 self._projects[project.name] = project
257 267
258 def _AddMetaProjectMirror(self, m): 268 def _AddMetaProjectMirror(self, m):
@@ -324,7 +334,7 @@ class XmlManifest(object):
324 if remote is None: 334 if remote is None:
325 raise ManifestParseError, \ 335 raise ManifestParseError, \
326 "no remote for project %s within %s" % \ 336 "no remote for project %s within %s" % \
327 (name, self.manifestFile) 337 (name, self._manifestFile)
328 338
329 revisionExpr = node.getAttribute('revision') 339 revisionExpr = node.getAttribute('revision')
330 if not revisionExpr: 340 if not revisionExpr:
@@ -332,7 +342,7 @@ class XmlManifest(object):
332 if not revisionExpr: 342 if not revisionExpr:
333 raise ManifestParseError, \ 343 raise ManifestParseError, \
334 "no revision for project %s within %s" % \ 344 "no revision for project %s within %s" % \
335 (name, self.manifestFile) 345 (name, self._manifestFile)
336 346
337 path = node.getAttribute('path') 347 path = node.getAttribute('path')
338 if not path: 348 if not path:
@@ -340,7 +350,7 @@ class XmlManifest(object):
340 if path.startswith('/'): 350 if path.startswith('/'):
341 raise ManifestParseError, \ 351 raise ManifestParseError, \
342 "project %s path cannot be absolute in %s" % \ 352 "project %s path cannot be absolute in %s" % \
343 (name, self.manifestFile) 353 (name, self._manifestFile)
344 354
345 if self.IsMirror: 355 if self.IsMirror:
346 relpath = None 356 relpath = None
@@ -382,7 +392,7 @@ class XmlManifest(object):
382 if not v: 392 if not v:
383 raise ManifestParseError, \ 393 raise ManifestParseError, \
384 "remote %s not defined in %s" % \ 394 "remote %s not defined in %s" % \
385 (name, self.manifestFile) 395 (name, self._manifestFile)
386 return v 396 return v
387 397
388 def _reqatt(self, node, attname): 398 def _reqatt(self, node, attname):
@@ -393,5 +403,5 @@ class XmlManifest(object):
393 if not v: 403 if not v:
394 raise ManifestParseError, \ 404 raise ManifestParseError, \
395 "no %s in <%s> within %s" % \ 405 "no %s in <%s> within %s" % \
396 (attname, node.nodeName, self.manifestFile) 406 (attname, node.nodeName, self._manifestFile)
397 return v 407 return v
diff --git a/project.py b/project.py
index 1beee2a6..beacc92f 100644
--- a/project.py
+++ b/project.py
@@ -27,7 +27,7 @@ from git_config import GitConfig, IsId
27from error import GitError, ImportError, UploadError 27from error import GitError, ImportError, UploadError
28from error import ManifestInvalidRevisionError 28from error import ManifestInvalidRevisionError
29 29
30from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M 30from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB
31 31
32def _lwrite(path, content): 32def _lwrite(path, content):
33 lock = '%s.lock' % path 33 lock = '%s.lock' % path
@@ -598,7 +598,7 @@ class Project(object):
598 return False 598 return False
599 599
600 if self.worktree: 600 if self.worktree:
601 self._InitMRef() 601 self.manifest.SetMRefs(self)
602 else: 602 else:
603 self._InitMirrorHead() 603 self._InitMirrorHead()
604 try: 604 try:
@@ -1080,10 +1080,6 @@ class Project(object):
1080 remote.ResetFetch(mirror=True) 1080 remote.ResetFetch(mirror=True)
1081 remote.Save() 1081 remote.Save()
1082 1082
1083 def _InitMRef(self):
1084 if self.manifest.branch:
1085 self._InitAnyMRef(R_M + self.manifest.branch)
1086
1087 def _InitMirrorHead(self): 1083 def _InitMirrorHead(self):
1088 self._InitAnyMRef(HEAD) 1084 self._InitAnyMRef(HEAD)
1089 1085
@@ -1102,30 +1098,37 @@ class Project(object):
1102 msg = 'manifest set to %s' % self.revisionExpr 1098 msg = 'manifest set to %s' % self.revisionExpr
1103 self.bare_git.symbolic_ref('-m', msg, ref, dst) 1099 self.bare_git.symbolic_ref('-m', msg, ref, dst)
1104 1100
1105 def _InitWorkTree(self): 1101 def _LinkWorkTree(self, relink=False):
1106 dotgit = os.path.join(self.worktree, '.git') 1102 dotgit = os.path.join(self.worktree, '.git')
1107 if not os.path.exists(dotgit): 1103 if not relink:
1108 os.makedirs(dotgit) 1104 os.makedirs(dotgit)
1109 1105
1110 for name in ['config', 1106 for name in ['config',
1111 'description', 1107 'description',
1112 'hooks', 1108 'hooks',
1113 'info', 1109 'info',
1114 'logs', 1110 'logs',
1115 'objects', 1111 'objects',
1116 'packed-refs', 1112 'packed-refs',
1117 'refs', 1113 'refs',
1118 'rr-cache', 1114 'rr-cache',
1119 'svn']: 1115 'svn']:
1120 try: 1116 try:
1121 src = os.path.join(self.gitdir, name) 1117 src = os.path.join(self.gitdir, name)
1122 dst = os.path.join(dotgit, name) 1118 dst = os.path.join(dotgit, name)
1123 os.symlink(relpath(src, dst), dst) 1119 if relink:
1124 except OSError, e: 1120 os.remove(dst)
1125 if e.errno == errno.EPERM: 1121 os.symlink(relpath(src, dst), dst)
1126 raise GitError('filesystem must support symlinks') 1122 except OSError, e:
1127 else: 1123 if e.errno == errno.EPERM:
1128 raise 1124 raise GitError('filesystem must support symlinks')
1125 else:
1126 raise
1127
1128 def _InitWorkTree(self):
1129 dotgit = os.path.join(self.worktree, '.git')
1130 if not os.path.exists(dotgit):
1131 self._LinkWorkTree()
1129 1132
1130 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId()) 1133 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
1131 1134
@@ -1423,15 +1426,17 @@ class SyncBuffer(object):
1423class MetaProject(Project): 1426class MetaProject(Project):
1424 """A special project housed under .repo. 1427 """A special project housed under .repo.
1425 """ 1428 """
1426 def __init__(self, manifest, name, gitdir, worktree): 1429 def __init__(self, manifest, name, gitdir, worktree, relpath=None):
1427 repodir = manifest.repodir 1430 repodir = manifest.repodir
1431 if relpath is None:
1432 relpath = '.repo/%s' % name
1428 Project.__init__(self, 1433 Project.__init__(self,
1429 manifest = manifest, 1434 manifest = manifest,
1430 name = name, 1435 name = name,
1431 gitdir = gitdir, 1436 gitdir = gitdir,
1432 worktree = worktree, 1437 worktree = worktree,
1433 remote = RemoteSpec('origin'), 1438 remote = RemoteSpec('origin'),
1434 relpath = '.repo/%s' % name, 1439 relpath = relpath,
1435 revisionExpr = 'refs/heads/master', 1440 revisionExpr = 'refs/heads/master',
1436 revisionId = None) 1441 revisionId = None)
1437 1442
@@ -1439,10 +1444,12 @@ class MetaProject(Project):
1439 if self.Exists: 1444 if self.Exists:
1440 cb = self.CurrentBranch 1445 cb = self.CurrentBranch
1441 if cb: 1446 if cb:
1442 base = self.GetBranch(cb).merge 1447 cb = self.GetBranch(cb)
1443 if base: 1448 if cb.merge:
1444 self.revisionExpr = base 1449 self.revisionExpr = cb.merge
1445 self.revisionId = None 1450 self.revisionId = None
1451 if cb.remote and cb.remote.name:
1452 self.remote.name = cb.remote.name
1446 1453
1447 @property 1454 @property
1448 def LastFetch(self): 1455 def LastFetch(self):
diff --git a/repo b/repo
index 13742559..ff7c4188 100755
--- a/repo
+++ b/repo
@@ -28,7 +28,7 @@ if __name__ == '__main__':
28del magic 28del magic
29 29
30# increment this whenever we make important changes to this script 30# increment this whenever we make important changes to this script
31VERSION = (1, 8) 31VERSION = (1, 9)
32 32
33# increment this if the MAINTAINER_KEYS block is modified 33# increment this if the MAINTAINER_KEYS block is modified
34KEYRING_VERSION = (1,0) 34KEYRING_VERSION = (1,0)
@@ -109,12 +109,17 @@ group = init_optparse.add_option_group('Manifest options')
109group.add_option('-u', '--manifest-url', 109group.add_option('-u', '--manifest-url',
110 dest='manifest_url', 110 dest='manifest_url',
111 help='manifest repository location', metavar='URL') 111 help='manifest repository location', metavar='URL')
112group.add_option('-o', '--origin',
113 dest='manifest_origin',
114 help="use REMOTE instead of 'origin' to track upstream",
115 metavar='REMOTE')
112group.add_option('-b', '--manifest-branch', 116group.add_option('-b', '--manifest-branch',
113 dest='manifest_branch', 117 dest='manifest_branch',
114 help='manifest branch or revision', metavar='REVISION') 118 help='manifest branch or revision', metavar='REVISION')
115group.add_option('-m', '--manifest-name', 119group.add_option('-m', '--manifest-name',
116 dest='manifest_name', 120 dest='manifest_name',
117 help='initial manifest file', metavar='NAME.xml') 121 help='initial manifest file (deprecated)',
122 metavar='NAME.xml')
118group.add_option('--mirror', 123group.add_option('--mirror',
119 dest='mirror', action='store_true', 124 dest='mirror', action='store_true',
120 help='mirror the forrest') 125 help='mirror the forrest')
diff --git a/subcmds/help.py b/subcmds/help.py
index c5979fd6..e2f3074c 100644
--- a/subcmds/help.py
+++ b/subcmds/help.py
@@ -94,6 +94,8 @@ See 'repo help --all' for a complete list of recognized commands.
94 body = getattr(cmd, bodyAttr) 94 body = getattr(cmd, bodyAttr)
95 except AttributeError: 95 except AttributeError:
96 return 96 return
97 if body == '' or body is None:
98 return
97 99
98 self.nl() 100 self.nl()
99 101
@@ -163,6 +165,7 @@ See 'repo help --all' for a complete list of recognized commands.
163 print >>sys.stderr, "repo: '%s' is not a repo command." % name 165 print >>sys.stderr, "repo: '%s' is not a repo command." % name
164 sys.exit(1) 166 sys.exit(1)
165 167
168 cmd.repodir = self.repodir
166 self._PrintCommandHelp(cmd) 169 self._PrintCommandHelp(cmd)
167 170
168 else: 171 else:
diff --git a/subcmds/init.py b/subcmds/init.py
index 75a58f11..cdbbfdf7 100644
--- a/subcmds/init.py
+++ b/subcmds/init.py
@@ -21,6 +21,9 @@ from command import InteractiveCommand, MirrorSafeCommand
21from error import ManifestParseError 21from error import ManifestParseError
22from project import SyncBuffer 22from project import SyncBuffer
23from git_command import git_require, MIN_GIT_VERSION 23from git_command import git_require, MIN_GIT_VERSION
24from manifest_submodule import SubmoduleManifest
25from manifest_xml import XmlManifest
26from subcmds.sync import _ReloadManifest
24 27
25class Init(InteractiveCommand, MirrorSafeCommand): 28class Init(InteractiveCommand, MirrorSafeCommand):
26 common = True 29 common = True
@@ -37,10 +40,6 @@ current working directory.
37The optional -b argument can be used to select the manifest branch 40The optional -b argument can be used to select the manifest branch
38to checkout and use. If no branch is specified, master is assumed. 41to checkout and use. If no branch is specified, master is assumed.
39 42
40The optional -m argument can be used to specify an alternate manifest
41to be used. If no manifest is specified, the manifest default.xml
42will be used.
43
44Switching Manifest Branches 43Switching Manifest Branches
45--------------------------- 44---------------------------
46 45
@@ -65,9 +64,15 @@ to update the working directory files.
65 g.add_option('-b', '--manifest-branch', 64 g.add_option('-b', '--manifest-branch',
66 dest='manifest_branch', 65 dest='manifest_branch',
67 help='manifest branch or revision', metavar='REVISION') 66 help='manifest branch or revision', metavar='REVISION')
68 g.add_option('-m', '--manifest-name', 67 g.add_option('-o', '--origin',
69 dest='manifest_name', default='default.xml', 68 dest='manifest_origin',
70 help='initial manifest file', metavar='NAME.xml') 69 help="use REMOTE instead of 'origin' to track upstream",
70 metavar='REMOTE')
71 if isinstance(self.manifest, XmlManifest) \
72 or not self.manifest.manifestProject.Exists:
73 g.add_option('-m', '--manifest-name',
74 dest='manifest_name', default='default.xml',
75 help='initial manifest file', metavar='NAME.xml')
71 g.add_option('--mirror', 76 g.add_option('--mirror',
72 dest='mirror', action='store_true', 77 dest='mirror', action='store_true',
73 help='mirror the forrest') 78 help='mirror the forrest')
@@ -85,30 +90,42 @@ to update the working directory files.
85 dest='no_repo_verify', action='store_true', 90 dest='no_repo_verify', action='store_true',
86 help='do not verify repo source code') 91 help='do not verify repo source code')
87 92
88 def _SyncManifest(self, opt): 93 def _ApplyOptions(self, opt, is_new):
89 m = self.manifest.manifestProject 94 m = self.manifest.manifestProject
90 is_new = not m.Exists
91 95
92 if is_new: 96 if is_new:
93 if not opt.manifest_url: 97 if opt.manifest_origin:
94 print >>sys.stderr, 'fatal: manifest url (-u) is required.' 98 m.remote.name = opt.manifest_origin
95 sys.exit(1)
96
97 if not opt.quiet:
98 print >>sys.stderr, 'Getting manifest ...'
99 print >>sys.stderr, ' from %s' % opt.manifest_url
100 m._InitGitDir()
101 99
102 if opt.manifest_branch: 100 if opt.manifest_branch:
103 m.revisionExpr = opt.manifest_branch 101 m.revisionExpr = opt.manifest_branch
104 else: 102 else:
105 m.revisionExpr = 'refs/heads/master' 103 m.revisionExpr = 'refs/heads/master'
106 else: 104 else:
105 if opt.manifest_origin:
106 print >>sys.stderr, 'fatal: cannot change origin name'
107 sys.exit(1)
108
107 if opt.manifest_branch: 109 if opt.manifest_branch:
108 m.revisionExpr = opt.manifest_branch 110 m.revisionExpr = opt.manifest_branch
109 else: 111 else:
110 m.PreSync() 112 m.PreSync()
111 113
114 def _SyncManifest(self, opt):
115 m = self.manifest.manifestProject
116 is_new = not m.Exists
117
118 if is_new:
119 if not opt.manifest_url:
120 print >>sys.stderr, 'fatal: manifest url (-u) is required.'
121 sys.exit(1)
122
123 if not opt.quiet:
124 print >>sys.stderr, 'Getting manifest ...'
125 print >>sys.stderr, ' from %s' % opt.manifest_url
126 m._InitGitDir()
127
128 self._ApplyOptions(opt, is_new)
112 if opt.manifest_url: 129 if opt.manifest_url:
113 r = m.GetRemote(m.remote.name) 130 r = m.GetRemote(m.remote.name)
114 r.url = opt.manifest_url 131 r.url = opt.manifest_url
@@ -118,6 +135,7 @@ to update the working directory files.
118 if opt.mirror: 135 if opt.mirror:
119 if is_new: 136 if is_new:
120 m.config.SetString('repo.mirror', 'true') 137 m.config.SetString('repo.mirror', 'true')
138 m.config.ClearCache()
121 else: 139 else:
122 print >>sys.stderr, 'fatal: --mirror not supported on existing client' 140 print >>sys.stderr, 'fatal: --mirror not supported on existing client'
123 sys.exit(1) 141 sys.exit(1)
@@ -127,14 +145,29 @@ to update the working directory files.
127 print >>sys.stderr, 'fatal: cannot obtain manifest %s' % r.url 145 print >>sys.stderr, 'fatal: cannot obtain manifest %s' % r.url
128 sys.exit(1) 146 sys.exit(1)
129 147
148 if is_new and SubmoduleManifest.IsBare(m):
149 new = self.GetManifest(reparse=True, type=SubmoduleManifest)
150 if m.gitdir != new.manifestProject.gitdir:
151 os.rename(m.gitdir, new.manifestProject.gitdir)
152 new = self.GetManifest(reparse=True, type=SubmoduleManifest)
153 m = new.manifestProject
154 self._ApplyOptions(opt, is_new)
155
156 if not is_new:
157 # Force the manifest to load if it exists, the old graph
158 # may be needed inside of _ReloadManifest().
159 #
160 self.manifest.projects
161
130 syncbuf = SyncBuffer(m.config) 162 syncbuf = SyncBuffer(m.config)
131 m.Sync_LocalHalf(syncbuf) 163 m.Sync_LocalHalf(syncbuf)
132 syncbuf.Finish() 164 syncbuf.Finish()
165 _ReloadManifest(self)
166 self._ApplyOptions(opt, is_new)
133 167
134 if is_new or m.CurrentBranch is None: 168 if not self.manifest.InitBranch():
135 if not m.StartBranch('default'): 169 print >>sys.stderr, 'fatal: cannot create branch in manifest'
136 print >>sys.stderr, 'fatal: cannot create default in manifest' 170 sys.exit(1)
137 sys.exit(1)
138 171
139 def _LinkManifest(self, name): 172 def _LinkManifest(self, name):
140 if not name: 173 if not name:
@@ -216,7 +249,8 @@ to update the working directory files.
216 def Execute(self, opt, args): 249 def Execute(self, opt, args):
217 git_require(MIN_GIT_VERSION, fail=True) 250 git_require(MIN_GIT_VERSION, fail=True)
218 self._SyncManifest(opt) 251 self._SyncManifest(opt)
219 self._LinkManifest(opt.manifest_name) 252 if isinstance(self.manifest, XmlManifest):
253 self._LinkManifest(opt.manifest_name)
220 254
221 if os.isatty(0) and os.isatty(1) and not self.manifest.IsMirror: 255 if os.isatty(0) and os.isatty(1) and not self.manifest.IsMirror:
222 self._ConfigureUser() 256 self._ConfigureUser()
diff --git a/subcmds/manifest.py b/subcmds/manifest.py
index 4374a9d0..dcd3df17 100644
--- a/subcmds/manifest.py
+++ b/subcmds/manifest.py
@@ -17,14 +17,25 @@ import os
17import sys 17import sys
18 18
19from command import PagedCommand 19from command import PagedCommand
20from manifest_submodule import SubmoduleManifest
21from manifest_xml import XmlManifest
22
23def _doc(name):
24 r = os.path.dirname(__file__)
25 r = os.path.dirname(r)
26 fd = open(os.path.join(r, 'docs', name))
27 try:
28 return fd.read()
29 finally:
30 fd.close()
20 31
21class Manifest(PagedCommand): 32class Manifest(PagedCommand):
22 common = False 33 common = False
23 helpSummary = "Manifest inspection utility" 34 helpSummary = "Manifest inspection utility"
24 helpUsage = """ 35 helpUsage = """
25%prog [-o {-|NAME.xml} [-r]] 36%prog [options]
26""" 37"""
27 _helpDescription = """ 38 _xmlHelp = """
28 39
29With the -o option, exports the current manifest for inspection. 40With the -o option, exports the current manifest for inspection.
30The manifest and (if present) local_manifest.xml are combined 41The manifest and (if present) local_manifest.xml are combined
@@ -35,23 +46,30 @@ in a Git repository for use during future 'repo init' invocations.
35 46
36 @property 47 @property
37 def helpDescription(self): 48 def helpDescription(self):
38 help = self._helpDescription + '\n' 49 help = ''
39 r = os.path.dirname(__file__) 50 if isinstance(self.manifest, XmlManifest):
40 r = os.path.dirname(r) 51 help += self._xmlHelp + '\n' + _doc('manifest_xml.txt')
41 fd = open(os.path.join(r, 'docs', 'manifest-format.txt')) 52 if isinstance(self.manifest, SubmoduleManifest):
42 for line in fd: 53 help += _doc('manifest_submodule.txt')
43 help += line
44 fd.close()
45 return help 54 return help
46 55
47 def _Options(self, p): 56 def _Options(self, p):
48 p.add_option('-r', '--revision-as-HEAD', 57 if isinstance(self.manifest, XmlManifest):
49 dest='peg_rev', action='store_true', 58 p.add_option('--upgrade',
50 help='Save revisions as current HEAD') 59 dest='upgrade', action='store_true',
51 p.add_option('-o', '--output-file', 60 help='Upgrade XML manifest to submodule')
52 dest='output_file', 61 p.add_option('-r', '--revision-as-HEAD',
53 help='File to save the manifest to', 62 dest='peg_rev', action='store_true',
54 metavar='-|NAME.xml') 63 help='Save revisions as current HEAD')
64 p.add_option('-o', '--output-file',
65 dest='output_file',
66 help='File to save the manifest to',
67 metavar='-|NAME.xml')
68
69 def WantPager(self, opt):
70 if isinstance(self.manifest, XmlManifest) and opt.upgrade:
71 return False
72 return True
55 73
56 def _Output(self, opt): 74 def _Output(self, opt):
57 if opt.output_file == '-': 75 if opt.output_file == '-':
@@ -64,13 +82,38 @@ in a Git repository for use during future 'repo init' invocations.
64 if opt.output_file != '-': 82 if opt.output_file != '-':
65 print >>sys.stderr, 'Saved manifest to %s' % opt.output_file 83 print >>sys.stderr, 'Saved manifest to %s' % opt.output_file
66 84
85 def _Upgrade(self):
86 old = self.manifest
87
88 if isinstance(old, SubmoduleManifest):
89 print >>sys.stderr, 'error: already upgraded'
90 sys.exit(1)
91
92 old._Load()
93 for p in old.projects.values():
94 if not os.path.exists(p.gitdir) \
95 or not os.path.exists(p.worktree):
96 print >>sys.stderr, 'fatal: project "%s" missing' % p.relpath
97 sys.exit(1)
98
99 new = SubmoduleManifest(old.repodir)
100 new.FromXml_Local_1(old, checkout=False)
101 new.FromXml_Definition(old)
102 new.FromXml_Local_2(old)
103 print >>sys.stderr, 'upgraded manifest; commit result manually'
104
67 def Execute(self, opt, args): 105 def Execute(self, opt, args):
68 if args: 106 if args:
69 self.Usage() 107 self.Usage()
70 108
71 if opt.output_file is not None: 109 if isinstance(self.manifest, XmlManifest):
72 self._Output(opt) 110 if opt.upgrade:
73 return 111 self._Upgrade()
112 return
113
114 if opt.output_file is not None:
115 self._Output(opt)
116 return
74 117
75 print >>sys.stderr, 'error: no operation to perform' 118 print >>sys.stderr, 'error: no operation to perform'
76 print >>sys.stderr, 'error: see repo help manifest' 119 print >>sys.stderr, 'error: see repo help manifest'
diff --git a/subcmds/selfupdate.py b/subcmds/selfupdate.py
index 4f46a129..46aa3a19 100644
--- a/subcmds/selfupdate.py
+++ b/subcmds/selfupdate.py
@@ -55,6 +55,7 @@ need to be performed by an end-user.
55 print >>sys.stderr, "error: can't update repo" 55 print >>sys.stderr, "error: can't update repo"
56 sys.exit(1) 56 sys.exit(1)
57 57
58 rp.bare_git.gc('--auto')
58 _PostRepoFetch(rp, 59 _PostRepoFetch(rp,
59 no_repo_verify = opt.no_repo_verify, 60 no_repo_verify = opt.no_repo_verify,
60 verbose = True) 61 verbose = True)
diff --git a/subcmds/sync.py b/subcmds/sync.py
index bd07dd9f..5fc834d0 100644
--- a/subcmds/sync.py
+++ b/subcmds/sync.py
@@ -118,6 +118,8 @@ later is required to fix a server side protocol bug.
118 print >>sys.stderr, 'error: Cannot fetch %s' % project.name 118 print >>sys.stderr, 'error: Cannot fetch %s' % project.name
119 sys.exit(1) 119 sys.exit(1)
120 pm.end() 120 pm.end()
121 for project in projects:
122 project.bare_git.gc('--auto')
121 return fetched 123 return fetched
122 124
123 def UpdateProjectList(self): 125 def UpdateProjectList(self):
@@ -213,8 +215,9 @@ uncommitted changes are present' % project.relpath
213 mp.Sync_LocalHalf(syncbuf) 215 mp.Sync_LocalHalf(syncbuf)
214 if not syncbuf.Finish(): 216 if not syncbuf.Finish():
215 sys.exit(1) 217 sys.exit(1)
218 _ReloadManifest(self)
219 mp = self.manifest.manifestProject
216 220
217 self.manifest._Unload()
218 all = self.GetProjects(args, missing_ok=True) 221 all = self.GetProjects(args, missing_ok=True)
219 missing = [] 222 missing = []
220 for project in all: 223 for project in all:
@@ -241,6 +244,13 @@ uncommitted changes are present' % project.relpath
241 if not syncbuf.Finish(): 244 if not syncbuf.Finish():
242 sys.exit(1) 245 sys.exit(1)
243 246
247def _ReloadManifest(cmd):
248 old = cmd.manifest
249 new = cmd.GetManifest(reparse=True)
250
251 if old.__class__ != new.__class__:
252 print >>sys.stderr, 'NOTICE: manifest format has changed ***'
253 new.Upgrade_Local(old)
244 254
245def _PostRepoUpgrade(manifest): 255def _PostRepoUpgrade(manifest):
246 for project in manifest.projects.values(): 256 for project in manifest.projects.values():