summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--command.py27
-rw-r--r--docs/manifest_submodule.txt136
-rw-r--r--docs/manifest_xml.txt (renamed from docs/manifest-format.txt)2
-rw-r--r--git_config.py8
-rw-r--r--git_refs.py1
-rwxr-xr-xmain.py4
-rw-r--r--manifest.py59
-rw-r--r--manifest_loader.py34
-rw-r--r--manifest_submodule.py481
-rw-r--r--manifest_xml.py80
-rw-r--r--project.py77
-rwxr-xr-xrepo7
-rw-r--r--subcmds/help.py1
-rw-r--r--subcmds/init.py76
-rw-r--r--subcmds/manifest.py81
-rw-r--r--subcmds/rebase.py2
-rw-r--r--subcmds/sync.py23
17 files changed, 972 insertions, 127 deletions
diff --git a/command.py b/command.py
index 8e93787e..4e0253fc 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 oldpath = None 103 oldpath = None
86 while path \ 104 while path \
87 and path != oldpath \ 105 and path != oldpath \
@@ -92,11 +110,6 @@ class Command(object):
92 except KeyError: 110 except KeyError:
93 oldpath = path 111 oldpath = path
94 path = os.path.dirname(path) 112 path = os.path.dirname(path)
95 else:
96 try:
97 project = by_path[path]
98 except KeyError:
99 pass
100 113
101 if not project: 114 if not project:
102 raise NoSuchProjectError(arg) 115 raise NoSuchProjectError(arg)
diff --git a/docs/manifest_submodule.txt b/docs/manifest_submodule.txt
new file mode 100644
index 00000000..1718284b
--- /dev/null
+++ b/docs/manifest_submodule.txt
@@ -0,0 +1,136 @@
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
79repo.notice
80-----------
81
82A message displayed when repo sync uses this manifest.
83
84
85.review
86=======
87
88The optional '.review' file, located in the top-level directory of
89the client's working tree (or manifest repository), is a text file
90with a syntax matching the requirements of 'git config'.
91
92This file describes how `repo upload` should interact with the
93project's preferred code review system.
94
95review.url
96----------
97
98URL of the default Gerrit Code Review server. If a project does
99not have a specific URL in the '.review' file, this default URL
100will be used instead.
101
102review.<name>.url
103-----------------
104
105Project specific URL of the Gerrit Code Review server, for the
106submodule whose project name is <name>.
107
108Example
109=======
110
111 $ cat .gitmodules
112 [submodule "app/Clock"]
113 path = clock
114 url = git://vcs.example.com/ClockWidget.git
115 revision = .
116 [submodule "app/Browser"]
117 path = net/browser
118 url = git://netgroup.example.com/network/web/Browser.git
119 revision = .
120
121 $ cat .review
122 [review]
123 url = vcs-gerrit.example.com
124 [review "app/Browser"]
125 url = netgroup.example.com
126
127In the above example, the app/Clock project will send its code
128reviews to the default server, vcs-gerrit.example.com, while
129app/Browser will send its code reviews to netgroup.example.com.
130
131See Also
132========
133
134 * http://www.kernel.org/pub/software/scm/git/docs/gitmodules.html
135 * http://www.kernel.org/pub/software/scm/git/docs/git-config.html
136 * http://code.google.com/p/gerrit/
diff --git a/docs/manifest-format.txt b/docs/manifest_xml.txt
index 2e1c8c35..37fbd5cd 100644
--- a/docs/manifest-format.txt
+++ b/docs/manifest_xml.txt
@@ -37,7 +37,7 @@ following DTD:
37 <!ELEMENT default (EMPTY)> 37 <!ELEMENT default (EMPTY)>
38 <!ATTLIST default remote IDREF #IMPLIED> 38 <!ATTLIST default remote IDREF #IMPLIED>
39 <!ATTLIST default revision CDATA #IMPLIED> 39 <!ATTLIST default revision CDATA #IMPLIED>
40 40
41 <!ELEMENT manifest-server (EMPTY)> 41 <!ELEMENT manifest-server (EMPTY)>
42 <!ATTLIST url CDATA #REQUIRED> 42 <!ATTLIST url CDATA #REQUIRED>
43 43
diff --git a/git_config.py b/git_config.py
index 19c19f19..ff815e35 100644
--- a/git_config.py
+++ b/git_config.py
@@ -80,6 +80,14 @@ class GitConfig(object):
80 else: 80 else:
81 self._pickle = pickleFile 81 self._pickle = pickleFile
82 82
83 def ClearCache(self):
84 if os.path.exists(self._pickle):
85 os.remove(self._pickle)
86 self._cache_dict = None
87 self._section_dict = None
88 self._remotes = {}
89 self._branches = {}
90
83 def Has(self, name, include_defaults = True): 91 def Has(self, name, include_defaults = True):
84 """Return true if this configuration file has the key. 92 """Return true if this configuration file has the key.
85 """ 93 """
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 f068fd47..07b26ef7 100755
--- a/main.py
+++ b/main.py
@@ -32,11 +32,9 @@ from git_config import init_ssh, 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
@@ -99,8 +97,6 @@ class _Repo(object):
99 sys.exit(1) 97 sys.exit(1)
100 98
101 cmd.repodir = self.repodir 99 cmd.repodir = self.repodir
102 cmd.manifest = XmlManifest(cmd.repodir)
103 Editor.globalConfig = cmd.manifest.globalConfig
104 100
105 if not isinstance(cmd, MirrorSafeCommand) and cmd.manifest.IsMirror: 101 if not isinstance(cmd, MirrorSafeCommand) and cmd.manifest.IsMirror:
106 print >>sys.stderr, \ 102 print >>sys.stderr, \
diff --git a/manifest.py b/manifest.py
new file mode 100644
index 00000000..c03cb4a7
--- /dev/null
+++ b/manifest.py
@@ -0,0 +1,59 @@
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 @property
45 def notice(self):
46 return None
47
48 @property
49 def manifest_server(self):
50 return None
51
52 def InitBranch(self):
53 pass
54
55 def SetMRefs(self, project):
56 pass
57
58 def Upgrade_Local(self, old):
59 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..cac271cd
--- /dev/null
+++ b/manifest_submodule.py
@@ -0,0 +1,481 @@
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 @property
106 def notice(self):
107 return self._modules.GetString('repo.notice')
108
109 def InitBranch(self):
110 m = self.manifestProject
111 if m.CurrentBranch is None:
112 b = m.revisionExpr
113 if b.startswith(R_HEADS):
114 b = b[len(R_HEADS):]
115 return m.StartBranch(b)
116 return True
117
118 def SetMRefs(self, project):
119 if project.revisionId is None:
120 # Special project, e.g. the manifest or repo executable.
121 #
122 return
123
124 ref = 'refs/remotes/m'
125 cur = project.bare_ref.get(ref)
126 exp = project.revisionId
127 if cur != exp:
128 msg = 'manifest set to %s' % exp
129 project.bare_git.UpdateRef(ref, exp, message = msg, detach = True)
130
131 ref = 'refs/remotes/m-revision'
132 cur = project.bare_ref.symref(ref)
133 exp = project.revisionExpr
134 if exp is None:
135 if cur:
136 _rmref(project.gitdir, ref)
137 elif cur != exp:
138 remote = project.GetRemote(project.remote.name)
139 dst = remote.ToLocal(exp)
140 msg = 'manifest set to %s (%s)' % (exp, dst)
141 project.bare_git.symbolic_ref('-m', msg, ref, dst)
142
143 def Upgrade_Local(self, old):
144 if isinstance(old, manifest_xml.XmlManifest):
145 self.FromXml_Local_1(old, checkout=True)
146 self.FromXml_Local_2(old)
147 else:
148 raise ManifestParseError, 'cannot upgrade manifest'
149
150 def FromXml_Local_1(self, old, checkout):
151 os.rename(old.manifestProject.gitdir,
152 os.path.join(old.repodir, 'manifest.git'))
153
154 oldmp = old.manifestProject
155 oldBranch = oldmp.CurrentBranch
156 b = oldmp.GetBranch(oldBranch).merge
157 if not b:
158 raise ManifestParseError, 'cannot upgrade manifest'
159 if b.startswith(R_HEADS):
160 b = b[len(R_HEADS):]
161
162 newmp = self.manifestProject
163 self._CleanOldMRefs(newmp)
164 if oldBranch != b:
165 newmp.bare_git.branch('-m', oldBranch, b)
166 newmp.config.ClearCache()
167
168 old_remote = newmp.GetBranch(b).remote.name
169 act_remote = self._GuessRemoteName(old)
170 if old_remote != act_remote:
171 newmp.bare_git.remote('rename', old_remote, act_remote)
172 newmp.config.ClearCache()
173 newmp.remote.name = act_remote
174 print >>sys.stderr, "Assuming remote named '%s'" % act_remote
175
176 if checkout:
177 for p in old.projects.values():
178 for c in p.copyfiles:
179 if os.path.exists(c.abs_dest):
180 os.remove(c.abs_dest)
181 newmp._InitWorkTree()
182 else:
183 newmp._LinkWorkTree()
184
185 _lwrite(os.path.join(newmp.worktree,'.git',HEAD),
186 'ref: refs/heads/%s\n' % b)
187
188 def _GuessRemoteName(self, old):
189 used = {}
190 for p in old.projects.values():
191 n = p.remote.name
192 used[n] = used.get(n, 0) + 1
193
194 remote_name = 'origin'
195 remote_used = 0
196 for n in used.keys():
197 if remote_used < used[n]:
198 remote_used = used[n]
199 remote_name = n
200 return remote_name
201
202 def FromXml_Local_2(self, old):
203 shutil.rmtree(old.manifestProject.worktree)
204 os.remove(old._manifestFile)
205
206 my_remote = self._Remote().name
207 new_base = os.path.join(self.repodir, 'projects')
208 old_base = os.path.join(self.repodir, 'projects.old')
209 os.rename(new_base, old_base)
210 os.makedirs(new_base)
211
212 info = []
213 pm = Progress('Converting projects', len(self.projects))
214 for p in self.projects.values():
215 pm.update()
216
217 old_p = old.projects.get(p.name)
218 old_gitdir = os.path.join(old_base, '%s.git' % p.relpath)
219 if not os.path.isdir(old_gitdir):
220 continue
221
222 parent = os.path.dirname(p.gitdir)
223 if not os.path.isdir(parent):
224 os.makedirs(parent)
225 os.rename(old_gitdir, p.gitdir)
226 _rmdir(os.path.dirname(old_gitdir), self.repodir)
227
228 if not os.path.isdir(p.worktree):
229 os.makedirs(p.worktree)
230
231 if os.path.isdir(os.path.join(p.worktree, '.git')):
232 p._LinkWorkTree(relink=True)
233
234 self._CleanOldMRefs(p)
235 if old_p and old_p.remote.name != my_remote:
236 info.append("%s/: renamed remote '%s' to '%s'" \
237 % (p.relpath, old_p.remote.name, my_remote))
238 p.bare_git.remote('rename', old_p.remote.name, my_remote)
239 p.config.ClearCache()
240
241 self.SetMRefs(p)
242 pm.end()
243 for i in info:
244 print >>sys.stderr, i
245
246 def _CleanOldMRefs(self, p):
247 all_refs = p._allrefs
248 for ref in all_refs.keys():
249 if ref.startswith(manifest_xml.R_M):
250 if p.bare_ref.symref(ref) != '':
251 _rmref(p.gitdir, ref)
252 else:
253 p.bare_git.DeleteRef(ref, all_refs[ref])
254
255 def FromXml_Definition(self, old):
256 """Convert another manifest representation to this one.
257 """
258 mp = self.manifestProject
259 gm = self._modules
260 gr = self._review
261
262 fd = open(os.path.join(mp.worktree, '.gitignore'), 'ab')
263 fd.write('/.repo\n')
264 fd.close()
265
266 sort_projects = list(old.projects.keys())
267 sort_projects.sort()
268
269 b = mp.GetBranch(mp.CurrentBranch).merge
270 if b.startswith(R_HEADS):
271 b = b[len(R_HEADS):]
272
273 if old.notice:
274 gm.SetString('repo.notice', old.notice)
275
276 info = []
277 pm = Progress('Converting manifest', len(sort_projects))
278 for p in sort_projects:
279 pm.update()
280 p = old.projects[p]
281
282 gm.SetString('submodule.%s.path' % p.name, p.relpath)
283 gm.SetString('submodule.%s.url' % p.name, p.remote.url)
284
285 if gr.GetString('review.url') is None:
286 gr.SetString('review.url', p.remote.review)
287 elif gr.GetString('review.url') != p.remote.review:
288 gr.SetString('review.%s.url' % p.name, p.remote.review)
289
290 r = p.revisionExpr
291 if r and not IsId(r):
292 if r.startswith(R_HEADS):
293 r = r[len(R_HEADS):]
294 if r == b:
295 r = '.'
296 gm.SetString('submodule.%s.revision' % p.name, r)
297
298 for c in p.copyfiles:
299 info.append('Moved %s out of %s' % (c.src, p.relpath))
300 c._Copy()
301 p.work_git.rm(c.src)
302 mp.work_git.add(c.dest)
303
304 self.SetRevisionId(p.relpath, p.GetRevisionId())
305 mp.work_git.add('.gitignore', '.gitmodules', '.review')
306 pm.end()
307 for i in info:
308 print >>sys.stderr, i
309
310 def _Unload(self):
311 self._loaded = False
312 self._projects = {}
313 self._revisionIds = None
314 self.branch = None
315
316 def _Load(self):
317 if not self._loaded:
318 f = os.path.join(self.repodir, manifest_xml.LOCAL_MANIFEST_NAME)
319 if os.path.exists(f):
320 print >>sys.stderr, 'warning: ignoring %s' % f
321
322 m = self.manifestProject
323 b = m.CurrentBranch
324 if not b:
325 raise ManifestParseError, 'manifest cannot be on detached HEAD'
326 b = m.GetBranch(b).merge
327 if b.startswith(R_HEADS):
328 b = b[len(R_HEADS):]
329 self.branch = b
330 m.remote.name = self._Remote().name
331
332 self._ParseModules()
333
334 if self.IsMirror:
335 self._AddMetaProjectMirror(self.repoProject)
336 self._AddMetaProjectMirror(self.manifestProject)
337
338 self._loaded = True
339
340 def _ParseModules(self):
341 byPath = dict()
342 for name in self._modules.GetSubSections('submodule'):
343 p = self._ParseProject(name)
344 if self._projects.get(p.name):
345 raise ManifestParseError, 'duplicate project "%s"' % p.name
346 if byPath.get(p.relpath):
347 raise ManifestParseError, 'duplicate path "%s"' % p.relpath
348 self._projects[p.name] = p
349 byPath[p.relpath] = p
350
351 for relpath in self._allRevisionIds.keys():
352 if relpath not in byPath:
353 raise ManifestParseError, \
354 'project "%s" not in .gitmodules' \
355 % relpath
356
357 def _Remote(self):
358 m = self.manifestProject
359 b = m.GetBranch(m.CurrentBranch)
360 return b.remote
361
362 def _ResolveUrl(self, url):
363 if url.startswith('./') or url.startswith('../'):
364 base = self._Remote().url
365 try:
366 base = base[:base.rindex('/')+1]
367 except ValueError:
368 base = base[:base.rindex(':')+1]
369 if url.startswith('./'):
370 url = url[2:]
371 while '/' in base and url.startswith('../'):
372 base = base[:base.rindex('/')+1]
373 url = url[3:]
374 return base + url
375 return url
376
377 def _GetRevisionId(self, path):
378 return self._allRevisionIds.get(path)
379
380 @property
381 def _allRevisionIds(self):
382 if self._revisionIds is None:
383 a = dict()
384 p = GitCommand(self.manifestProject,
385 ['ls-files','-z','--stage'],
386 capture_stdout = True)
387 for line in p.process.stdout.read().split('\0')[:-1]:
388 l_info, l_path = line.split('\t', 2)
389 l_mode, l_id, l_stage = l_info.split(' ', 2)
390 if l_mode == GITLINK and l_stage == '0':
391 a[l_path] = l_id
392 p.Wait()
393 self._revisionIds = a
394 return self._revisionIds
395
396 def SetRevisionId(self, path, id):
397 self.manifestProject.work_git.update_index(
398 '--add','--cacheinfo', GITLINK, id, path)
399
400 def _ParseProject(self, name):
401 gm = self._modules
402 gr = self._review
403
404 path = gm.GetString('submodule.%s.path' % name)
405 if not path:
406 path = name
407
408 revId = self._GetRevisionId(path)
409 if not revId:
410 raise ManifestParseError(
411 'submodule "%s" has no revision at "%s"' \
412 % (name, path))
413
414 url = gm.GetString('submodule.%s.url' % name)
415 if not url:
416 url = name
417 url = self._ResolveUrl(url)
418
419 review = gr.GetString('review.%s.url' % name)
420 if not review:
421 review = gr.GetString('review.url')
422 if not review:
423 review = self._Remote().review
424
425 remote = RemoteSpec(self._Remote().name, url, review)
426 revExpr = gm.GetString('submodule.%s.revision' % name)
427 if revExpr == '.':
428 revExpr = self.branch
429
430 if self.IsMirror:
431 relpath = None
432 worktree = None
433 gitdir = os.path.join(self.topdir, '%s.git' % name)
434 else:
435 worktree = os.path.join(self.topdir, path)
436 gitdir = os.path.join(self.repodir, 'projects/%s.git' % name)
437
438 return Project(manifest = self,
439 name = name,
440 remote = remote,
441 gitdir = gitdir,
442 worktree = worktree,
443 relpath = path,
444 revisionExpr = revExpr,
445 revisionId = revId)
446
447 def _AddMetaProjectMirror(self, m):
448 m_url = m.GetRemote(m.remote.name).url
449 if m_url.endswith('/.git'):
450 raise ManifestParseError, 'refusing to mirror %s' % m_url
451
452 name = self._GuessMetaName(m_url)
453 if name.endswith('.git'):
454 name = name[:-4]
455
456 if name not in self._projects:
457 m.PreSync()
458 gitdir = os.path.join(self.topdir, '%s.git' % name)
459 project = Project(manifest = self,
460 name = name,
461 remote = RemoteSpec(self._Remote().name, m_url),
462 gitdir = gitdir,
463 worktree = None,
464 relpath = None,
465 revisionExpr = m.revisionExpr,
466 revisionId = None)
467 self._projects[project.name] = project
468
469 def _GuessMetaName(self, m_url):
470 parts = m_url.split('/')
471 name = parts[-1]
472 parts = parts[0:-1]
473 s = len(parts) - 1
474 while s > 0:
475 l = '/'.join(parts[0:s]) + '/'
476 r = '/'.join(parts[s:]) + '/'
477 for p in self._projects.values():
478 if p.name.startswith(r) and p.remote.url.startswith(l):
479 return r + name
480 s -= 1
481 return m_url[m_url.rindex('/') + 1:]
diff --git a/manifest_xml.py b/manifest_xml.py
index 0103cf55..1d02f9d4 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,13 +73,13 @@ 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 def Link(self, name): 84 def Link(self, name):
84 """Update the repo metadata to use a different manifest. 85 """Update the repo metadata to use a different manifest.
@@ -86,9 +87,9 @@ class XmlManifest(object):
86 self.Override(name) 87 self.Override(name)
87 88
88 try: 89 try:
89 if os.path.exists(self.manifestFile): 90 if os.path.exists(self._manifestFile):
90 os.remove(self.manifestFile) 91 os.remove(self._manifestFile)
91 os.symlink('manifests/%s' % name, self.manifestFile) 92 os.symlink('manifests/%s' % name, self._manifestFile)
92 except OSError, e: 93 except OSError, e:
93 raise ManifestParseError('cannot link manifest %s' % name) 94 raise ManifestParseError('cannot link manifest %s' % name)
94 95
@@ -198,9 +199,15 @@ class XmlManifest(object):
198 self._Load() 199 self._Load()
199 return self._manifest_server 200 return self._manifest_server
200 201
201 @property 202 def InitBranch(self):
202 def IsMirror(self): 203 m = self.manifestProject
203 return self.manifestProject.config.GetBoolean('repo.mirror') 204 if m.CurrentBranch is None:
205 return m.StartBranch('default')
206 return True
207
208 def SetMRefs(self, project):
209 if self.branch:
210 project._InitAnyMRef(R_M + self.branch)
204 211
205 def _Unload(self): 212 def _Unload(self):
206 self._loaded = False 213 self._loaded = False
@@ -214,7 +221,10 @@ class XmlManifest(object):
214 def _Load(self): 221 def _Load(self):
215 if not self._loaded: 222 if not self._loaded:
216 m = self.manifestProject 223 m = self.manifestProject
217 b = m.GetBranch(m.CurrentBranch).merge 224 b = m.GetBranch(m.CurrentBranch)
225 if b.remote and b.remote.name:
226 m.remote.name = b.remote.name
227 b = b.merge
218 if b is not None and b.startswith(R_HEADS): 228 if b is not None and b.startswith(R_HEADS):
219 b = b[len(R_HEADS):] 229 b = b[len(R_HEADS):]
220 self.branch = b 230 self.branch = b
@@ -224,11 +234,11 @@ class XmlManifest(object):
224 local = os.path.join(self.repodir, LOCAL_MANIFEST_NAME) 234 local = os.path.join(self.repodir, LOCAL_MANIFEST_NAME)
225 if os.path.exists(local): 235 if os.path.exists(local):
226 try: 236 try:
227 real = self.manifestFile 237 real = self._manifestFile
228 self.manifestFile = local 238 self._manifestFile = local
229 self._ParseManifest(False) 239 self._ParseManifest(False)
230 finally: 240 finally:
231 self.manifestFile = real 241 self._manifestFile = real
232 242
233 if self.IsMirror: 243 if self.IsMirror:
234 self._AddMetaProjectMirror(self.repoProject) 244 self._AddMetaProjectMirror(self.repoProject)
@@ -237,17 +247,17 @@ class XmlManifest(object):
237 self._loaded = True 247 self._loaded = True
238 248
239 def _ParseManifest(self, is_root_file): 249 def _ParseManifest(self, is_root_file):
240 root = xml.dom.minidom.parse(self.manifestFile) 250 root = xml.dom.minidom.parse(self._manifestFile)
241 if not root or not root.childNodes: 251 if not root or not root.childNodes:
242 raise ManifestParseError, \ 252 raise ManifestParseError, \
243 "no root node in %s" % \ 253 "no root node in %s" % \
244 self.manifestFile 254 self._manifestFile
245 255
246 config = root.childNodes[0] 256 config = root.childNodes[0]
247 if config.nodeName != 'manifest': 257 if config.nodeName != 'manifest':
248 raise ManifestParseError, \ 258 raise ManifestParseError, \
249 "no <manifest> in %s" % \ 259 "no <manifest> in %s" % \
250 self.manifestFile 260 self._manifestFile
251 261
252 for node in config.childNodes: 262 for node in config.childNodes:
253 if node.nodeName == 'remove-project': 263 if node.nodeName == 'remove-project':
@@ -265,7 +275,7 @@ class XmlManifest(object):
265 if self._remotes.get(remote.name): 275 if self._remotes.get(remote.name):
266 raise ManifestParseError, \ 276 raise ManifestParseError, \
267 'duplicate remote %s in %s' % \ 277 'duplicate remote %s in %s' % \
268 (remote.name, self.manifestFile) 278 (remote.name, self._manifestFile)
269 self._remotes[remote.name] = remote 279 self._remotes[remote.name] = remote
270 280
271 for node in config.childNodes: 281 for node in config.childNodes:
@@ -273,7 +283,7 @@ class XmlManifest(object):
273 if self._default is not None: 283 if self._default is not None:
274 raise ManifestParseError, \ 284 raise ManifestParseError, \
275 'duplicate default in %s' % \ 285 'duplicate default in %s' % \
276 (self.manifestFile) 286 (self._manifestFile)
277 self._default = self._ParseDefault(node) 287 self._default = self._ParseDefault(node)
278 if self._default is None: 288 if self._default is None:
279 self._default = _Default() 289 self._default = _Default()
@@ -301,7 +311,7 @@ class XmlManifest(object):
301 if self._projects.get(project.name): 311 if self._projects.get(project.name):
302 raise ManifestParseError, \ 312 raise ManifestParseError, \
303 'duplicate project %s in %s' % \ 313 'duplicate project %s in %s' % \
304 (project.name, self.manifestFile) 314 (project.name, self._manifestFile)
305 self._projects[project.name] = project 315 self._projects[project.name] = project
306 316
307 def _AddMetaProjectMirror(self, m): 317 def _AddMetaProjectMirror(self, m):
@@ -412,7 +422,7 @@ class XmlManifest(object):
412 if remote is None: 422 if remote is None:
413 raise ManifestParseError, \ 423 raise ManifestParseError, \
414 "no remote for project %s within %s" % \ 424 "no remote for project %s within %s" % \
415 (name, self.manifestFile) 425 (name, self._manifestFile)
416 426
417 revisionExpr = node.getAttribute('revision') 427 revisionExpr = node.getAttribute('revision')
418 if not revisionExpr: 428 if not revisionExpr:
@@ -420,7 +430,7 @@ class XmlManifest(object):
420 if not revisionExpr: 430 if not revisionExpr:
421 raise ManifestParseError, \ 431 raise ManifestParseError, \
422 "no revision for project %s within %s" % \ 432 "no revision for project %s within %s" % \
423 (name, self.manifestFile) 433 (name, self._manifestFile)
424 434
425 path = node.getAttribute('path') 435 path = node.getAttribute('path')
426 if not path: 436 if not path:
@@ -428,7 +438,7 @@ class XmlManifest(object):
428 if path.startswith('/'): 438 if path.startswith('/'):
429 raise ManifestParseError, \ 439 raise ManifestParseError, \
430 "project %s path cannot be absolute in %s" % \ 440 "project %s path cannot be absolute in %s" % \
431 (name, self.manifestFile) 441 (name, self._manifestFile)
432 442
433 if self.IsMirror: 443 if self.IsMirror:
434 relpath = None 444 relpath = None
@@ -470,7 +480,7 @@ class XmlManifest(object):
470 if not v: 480 if not v:
471 raise ManifestParseError, \ 481 raise ManifestParseError, \
472 "remote %s not defined in %s" % \ 482 "remote %s not defined in %s" % \
473 (name, self.manifestFile) 483 (name, self._manifestFile)
474 return v 484 return v
475 485
476 def _reqatt(self, node, attname): 486 def _reqatt(self, node, attname):
@@ -481,5 +491,5 @@ class XmlManifest(object):
481 if not v: 491 if not v:
482 raise ManifestParseError, \ 492 raise ManifestParseError, \
483 "no %s in <%s> within %s" % \ 493 "no %s in <%s> within %s" % \
484 (attname, node.nodeName, self.manifestFile) 494 (attname, node.nodeName, self._manifestFile)
485 return v 495 return v
diff --git a/project.py b/project.py
index 12595cd7..b4044943 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
@@ -643,7 +643,7 @@ class Project(object):
643 self._RemoteFetch(None, rev[len(R_TAGS):], quiet=quiet) 643 self._RemoteFetch(None, rev[len(R_TAGS):], quiet=quiet)
644 644
645 if self.worktree: 645 if self.worktree:
646 self._InitMRef() 646 self.manifest.SetMRefs(self)
647 else: 647 else:
648 self._InitMirrorHead() 648 self._InitMirrorHead()
649 try: 649 try:
@@ -1231,10 +1231,6 @@ class Project(object):
1231 remote.ResetFetch(mirror=True) 1231 remote.ResetFetch(mirror=True)
1232 remote.Save() 1232 remote.Save()
1233 1233
1234 def _InitMRef(self):
1235 if self.manifest.branch:
1236 self._InitAnyMRef(R_M + self.manifest.branch)
1237
1238 def _InitMirrorHead(self): 1234 def _InitMirrorHead(self):
1239 self._InitAnyMRef(HEAD) 1235 self._InitAnyMRef(HEAD)
1240 1236
@@ -1253,33 +1249,40 @@ class Project(object):
1253 msg = 'manifest set to %s' % self.revisionExpr 1249 msg = 'manifest set to %s' % self.revisionExpr
1254 self.bare_git.symbolic_ref('-m', msg, ref, dst) 1250 self.bare_git.symbolic_ref('-m', msg, ref, dst)
1255 1251
1256 def _InitWorkTree(self): 1252 def _LinkWorkTree(self, relink=False):
1257 dotgit = os.path.join(self.worktree, '.git') 1253 dotgit = os.path.join(self.worktree, '.git')
1258 if not os.path.exists(dotgit): 1254 if not relink:
1259 os.makedirs(dotgit) 1255 os.makedirs(dotgit)
1260 1256
1261 for name in ['config', 1257 for name in ['config',
1262 'description', 1258 'description',
1263 'hooks', 1259 'hooks',
1264 'info', 1260 'info',
1265 'logs', 1261 'logs',
1266 'objects', 1262 'objects',
1267 'packed-refs', 1263 'packed-refs',
1268 'refs', 1264 'refs',
1269 'rr-cache', 1265 'rr-cache',
1270 'svn']: 1266 'svn']:
1271 try: 1267 try:
1272 src = os.path.join(self.gitdir, name) 1268 src = os.path.join(self.gitdir, name)
1273 dst = os.path.join(dotgit, name) 1269 dst = os.path.join(dotgit, name)
1274 if os.path.islink(dst) or not os.path.exists(dst): 1270 if relink:
1275 os.symlink(relpath(src, dst), dst) 1271 os.remove(dst)
1276 else: 1272 if os.path.islink(dst) or not os.path.exists(dst):
1277 raise GitError('cannot overwrite a local work tree') 1273 os.symlink(relpath(src, dst), dst)
1278 except OSError, e: 1274 else:
1279 if e.errno == errno.EPERM: 1275 raise GitError('cannot overwrite a local work tree')
1280 raise GitError('filesystem must support symlinks') 1276 except OSError, e:
1281 else: 1277 if e.errno == errno.EPERM:
1282 raise 1278 raise GitError('filesystem must support symlinks')
1279 else:
1280 raise
1281
1282 def _InitWorkTree(self):
1283 dotgit = os.path.join(self.worktree, '.git')
1284 if not os.path.exists(dotgit):
1285 self._LinkWorkTree()
1283 1286
1284 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId()) 1287 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
1285 1288
@@ -1577,15 +1580,17 @@ class SyncBuffer(object):
1577class MetaProject(Project): 1580class MetaProject(Project):
1578 """A special project housed under .repo. 1581 """A special project housed under .repo.
1579 """ 1582 """
1580 def __init__(self, manifest, name, gitdir, worktree): 1583 def __init__(self, manifest, name, gitdir, worktree, relpath=None):
1581 repodir = manifest.repodir 1584 repodir = manifest.repodir
1585 if relpath is None:
1586 relpath = '.repo/%s' % name
1582 Project.__init__(self, 1587 Project.__init__(self,
1583 manifest = manifest, 1588 manifest = manifest,
1584 name = name, 1589 name = name,
1585 gitdir = gitdir, 1590 gitdir = gitdir,
1586 worktree = worktree, 1591 worktree = worktree,
1587 remote = RemoteSpec('origin'), 1592 remote = RemoteSpec('origin'),
1588 relpath = '.repo/%s' % name, 1593 relpath = relpath,
1589 revisionExpr = 'refs/heads/master', 1594 revisionExpr = 'refs/heads/master',
1590 revisionId = None) 1595 revisionId = None)
1591 1596
@@ -1593,10 +1598,12 @@ class MetaProject(Project):
1593 if self.Exists: 1598 if self.Exists:
1594 cb = self.CurrentBranch 1599 cb = self.CurrentBranch
1595 if cb: 1600 if cb:
1596 base = self.GetBranch(cb).merge 1601 cb = self.GetBranch(cb)
1597 if base: 1602 if cb.merge:
1598 self.revisionExpr = base 1603 self.revisionExpr = cb.merge
1599 self.revisionId = None 1604 self.revisionId = None
1605 if cb.remote and cb.remote.name:
1606 self.remote.name = cb.remote.name
1600 1607
1601 @property 1608 @property
1602 def LastFetch(self): 1609 def LastFetch(self):
diff --git a/repo b/repo
index ad9e005f..773ad825 100755
--- a/repo
+++ b/repo
@@ -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 90b817db..e2f3074c 100644
--- a/subcmds/help.py
+++ b/subcmds/help.py
@@ -165,6 +165,7 @@ See 'repo help --all' for a complete list of recognized commands.
165 print >>sys.stderr, "repo: '%s' is not a repo command." % name 165 print >>sys.stderr, "repo: '%s' is not a repo command." % name
166 sys.exit(1) 166 sys.exit(1)
167 167
168 cmd.repodir = self.repodir
168 self._PrintCommandHelp(cmd) 169 self._PrintCommandHelp(cmd)
169 170
170 else: 171 else:
diff --git a/subcmds/init.py b/subcmds/init.py
index 17edfa05..2ca4e163 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
@@ -72,9 +75,15 @@ to update the working directory files.
72 g.add_option('-b', '--manifest-branch', 75 g.add_option('-b', '--manifest-branch',
73 dest='manifest_branch', 76 dest='manifest_branch',
74 help='manifest branch or revision', metavar='REVISION') 77 help='manifest branch or revision', metavar='REVISION')
75 g.add_option('-m', '--manifest-name', 78 g.add_option('-o', '--origin',
76 dest='manifest_name', default='default.xml', 79 dest='manifest_origin',
77 help='initial manifest file', metavar='NAME.xml') 80 help="use REMOTE instead of 'origin' to track upstream",
81 metavar='REMOTE')
82 if isinstance(self.manifest, XmlManifest) \
83 or not self.manifest.manifestProject.Exists:
84 g.add_option('-m', '--manifest-name',
85 dest='manifest_name', default='default.xml',
86 help='initial manifest file', metavar='NAME.xml')
78 g.add_option('--mirror', 87 g.add_option('--mirror',
79 dest='mirror', action='store_true', 88 dest='mirror', action='store_true',
80 help='mirror the forrest') 89 help='mirror the forrest')
@@ -94,30 +103,42 @@ to update the working directory files.
94 dest='no_repo_verify', action='store_true', 103 dest='no_repo_verify', action='store_true',
95 help='do not verify repo source code') 104 help='do not verify repo source code')
96 105
97 def _SyncManifest(self, opt): 106 def _ApplyOptions(self, opt, is_new):
98 m = self.manifest.manifestProject 107 m = self.manifest.manifestProject
99 is_new = not m.Exists
100 108
101 if is_new: 109 if is_new:
102 if not opt.manifest_url: 110 if opt.manifest_origin:
103 print >>sys.stderr, 'fatal: manifest url (-u) is required.' 111 m.remote.name = opt.manifest_origin
104 sys.exit(1)
105
106 if not opt.quiet:
107 print >>sys.stderr, 'Getting manifest ...'
108 print >>sys.stderr, ' from %s' % opt.manifest_url
109 m._InitGitDir()
110 112
111 if opt.manifest_branch: 113 if opt.manifest_branch:
112 m.revisionExpr = opt.manifest_branch 114 m.revisionExpr = opt.manifest_branch
113 else: 115 else:
114 m.revisionExpr = 'refs/heads/master' 116 m.revisionExpr = 'refs/heads/master'
115 else: 117 else:
118 if opt.manifest_origin:
119 print >>sys.stderr, 'fatal: cannot change origin name'
120 sys.exit(1)
121
116 if opt.manifest_branch: 122 if opt.manifest_branch:
117 m.revisionExpr = opt.manifest_branch 123 m.revisionExpr = opt.manifest_branch
118 else: 124 else:
119 m.PreSync() 125 m.PreSync()
120 126
127 def _SyncManifest(self, opt):
128 m = self.manifest.manifestProject
129 is_new = not m.Exists
130
131 if is_new:
132 if not opt.manifest_url:
133 print >>sys.stderr, 'fatal: manifest url (-u) is required.'
134 sys.exit(1)
135
136 if not opt.quiet:
137 print >>sys.stderr, 'Getting manifest ...'
138 print >>sys.stderr, ' from %s' % opt.manifest_url
139 m._InitGitDir()
140
141 self._ApplyOptions(opt, is_new)
121 if opt.manifest_url: 142 if opt.manifest_url:
122 r = m.GetRemote(m.remote.name) 143 r = m.GetRemote(m.remote.name)
123 r.url = opt.manifest_url 144 r.url = opt.manifest_url
@@ -130,6 +151,7 @@ to update the working directory files.
130 if opt.mirror: 151 if opt.mirror:
131 if is_new: 152 if is_new:
132 m.config.SetString('repo.mirror', 'true') 153 m.config.SetString('repo.mirror', 'true')
154 m.config.ClearCache()
133 else: 155 else:
134 print >>sys.stderr, 'fatal: --mirror not supported on existing client' 156 print >>sys.stderr, 'fatal: --mirror not supported on existing client'
135 sys.exit(1) 157 sys.exit(1)
@@ -139,14 +161,33 @@ to update the working directory files.
139 print >>sys.stderr, 'fatal: cannot obtain manifest %s' % r.url 161 print >>sys.stderr, 'fatal: cannot obtain manifest %s' % r.url
140 sys.exit(1) 162 sys.exit(1)
141 163
164 if is_new and SubmoduleManifest.IsBare(m):
165 new = self.GetManifest(reparse=True, type=SubmoduleManifest)
166 if m.gitdir != new.manifestProject.gitdir:
167 os.rename(m.gitdir, new.manifestProject.gitdir)
168 new = self.GetManifest(reparse=True, type=SubmoduleManifest)
169 m = new.manifestProject
170 self._ApplyOptions(opt, is_new)
171
172 if not is_new:
173 # Force the manifest to load if it exists, the old graph
174 # may be needed inside of _ReloadManifest().
175 #
176 self.manifest.projects
177
142 syncbuf = SyncBuffer(m.config) 178 syncbuf = SyncBuffer(m.config)
143 m.Sync_LocalHalf(syncbuf) 179 m.Sync_LocalHalf(syncbuf)
144 syncbuf.Finish() 180 syncbuf.Finish()
145 181
146 if is_new or m.CurrentBranch is None: 182 if isinstance(self.manifest, XmlManifest):
147 if not m.StartBranch('default'): 183 self._LinkManifest(opt.manifest_name)
148 print >>sys.stderr, 'fatal: cannot create default in manifest' 184 _ReloadManifest(self)
149 sys.exit(1) 185
186 self._ApplyOptions(opt, is_new)
187
188 if not self.manifest.InitBranch():
189 print >>sys.stderr, 'fatal: cannot create branch in manifest'
190 sys.exit(1)
150 191
151 def _LinkManifest(self, name): 192 def _LinkManifest(self, name):
152 if not name: 193 if not name:
@@ -229,7 +270,6 @@ to update the working directory files.
229 def Execute(self, opt, args): 270 def Execute(self, opt, args):
230 git_require(MIN_GIT_VERSION, fail=True) 271 git_require(MIN_GIT_VERSION, fail=True)
231 self._SyncManifest(opt) 272 self._SyncManifest(opt)
232 self._LinkManifest(opt.manifest_name)
233 273
234 if os.isatty(0) and os.isatty(1) and not self.manifest.IsMirror: 274 if os.isatty(0) and os.isatty(1) and not self.manifest.IsMirror:
235 self._ConfigureUser() 275 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/rebase.py b/subcmds/rebase.py
index 7c8e9389..e341296d 100644
--- a/subcmds/rebase.py
+++ b/subcmds/rebase.py
@@ -17,7 +17,7 @@ import sys
17 17
18from command import Command 18from command import Command
19from git_command import GitCommand 19from git_command import GitCommand
20from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M 20from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB
21from error import GitError 21from error import GitError
22 22
23class Rebase(Command): 23class Rebase(Command):
diff --git a/subcmds/sync.py b/subcmds/sync.py
index 36ef16db..16f1d189 100644
--- a/subcmds/sync.py
+++ b/subcmds/sync.py
@@ -336,7 +336,14 @@ uncommitted changes are present' % project.relpath
336 # bail out now; the rest touches the working tree 336 # bail out now; the rest touches the working tree
337 return 337 return
338 338
339 self.manifest._Unload() 339 if mp.HasChanges:
340 syncbuf = SyncBuffer(mp.config)
341 mp.Sync_LocalHalf(syncbuf)
342 if not syncbuf.Finish():
343 sys.exit(1)
344 _ReloadManifest(self)
345 mp = self.manifest.manifestProject
346
340 all = self.GetProjects(args, missing_ok=True) 347 all = self.GetProjects(args, missing_ok=True)
341 missing = [] 348 missing = []
342 for project in all: 349 for project in all:
@@ -363,10 +370,16 @@ uncommitted changes are present' % project.relpath
363 if not syncbuf.Finish(): 370 if not syncbuf.Finish():
364 sys.exit(1) 371 sys.exit(1)
365 372
366 # If there's a notice that's supposed to print at the end of the sync, print 373def _ReloadManifest(cmd):
367 # it now... 374 old = cmd.manifest
368 if self.manifest.notice: 375 new = cmd.GetManifest(reparse=True)
369 print self.manifest.notice 376
377 if old.__class__ != new.__class__:
378 print >>sys.stderr, 'NOTICE: manifest format has changed ***'
379 new.Upgrade_Local(old)
380 else:
381 if new.notice:
382 print new.notice
370 383
371def _PostRepoUpgrade(manifest): 384def _PostRepoUpgrade(manifest):
372 for project in manifest.projects.values(): 385 for project in manifest.projects.values():