##// END OF EJS Templates
subrepo: correct copyright
David Soria Parra -
r10324:55d134ef default
parent child Browse files
Show More
@@ -1,360 +1,360 b''
1 1 # subrepo.py - sub-repository handling for Mercurial
2 2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2009-2010 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import errno, os, re, xml.dom.minidom, shutil
9 9 from i18n import _
10 10 import config, util, node, error
11 11 hg = None
12 12
13 13 nullstate = ('', '', 'empty')
14 14
15 15 def state(ctx):
16 16 p = config.config()
17 17 def read(f, sections=None, remap=None):
18 18 if f in ctx:
19 19 p.parse(f, ctx[f].data(), sections, remap, read)
20 20 else:
21 21 raise util.Abort(_("subrepo spec file %s not found") % f)
22 22
23 23 if '.hgsub' in ctx:
24 24 read('.hgsub')
25 25
26 26 rev = {}
27 27 if '.hgsubstate' in ctx:
28 28 try:
29 29 for l in ctx['.hgsubstate'].data().splitlines():
30 30 revision, path = l.split(" ", 1)
31 31 rev[path] = revision
32 32 except IOError, err:
33 33 if err.errno != errno.ENOENT:
34 34 raise
35 35
36 36 state = {}
37 37 for path, src in p[''].items():
38 38 kind = 'hg'
39 39 if src.startswith('['):
40 40 if ']' not in src:
41 41 raise util.Abort(_('missing ] in subrepo source'))
42 42 kind, src = src.split(']', 1)
43 43 kind = kind[1:]
44 44 state[path] = (src, rev.get(path, ''), kind)
45 45
46 46 return state
47 47
48 48 def writestate(repo, state):
49 49 repo.wwrite('.hgsubstate',
50 50 ''.join(['%s %s\n' % (state[s][1], s)
51 51 for s in sorted(state)]), '')
52 52
53 53 def submerge(repo, wctx, mctx, actx):
54 54 # working context, merging context, ancestor context
55 55 if mctx == actx: # backwards?
56 56 actx = wctx.p1()
57 57 s1 = wctx.substate
58 58 s2 = mctx.substate
59 59 sa = actx.substate
60 60 sm = {}
61 61
62 62 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
63 63
64 64 def debug(s, msg, r=""):
65 65 if r:
66 66 r = "%s:%s:%s" % r
67 67 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
68 68
69 69 for s, l in s1.items():
70 70 if wctx != actx and wctx.sub(s).dirty():
71 71 l = (l[0], l[1] + "+")
72 72 a = sa.get(s, nullstate)
73 73 if s in s2:
74 74 r = s2[s]
75 75 if l == r or r == a: # no change or local is newer
76 76 sm[s] = l
77 77 continue
78 78 elif l == a: # other side changed
79 79 debug(s, "other changed, get", r)
80 80 wctx.sub(s).get(r)
81 81 sm[s] = r
82 82 elif l[0] != r[0]: # sources differ
83 83 if repo.ui.promptchoice(
84 84 _(' subrepository sources for %s differ\n'
85 85 'use (l)ocal source (%s) or (r)emote source (%s)?')
86 86 % (s, l[0], r[0]),
87 87 (_('&Local'), _('&Remote')), 0):
88 88 debug(s, "prompt changed, get", r)
89 89 wctx.sub(s).get(r)
90 90 sm[s] = r
91 91 elif l[1] == a[1]: # local side is unchanged
92 92 debug(s, "other side changed, get", r)
93 93 wctx.sub(s).get(r)
94 94 sm[s] = r
95 95 else:
96 96 debug(s, "both sides changed, merge with", r)
97 97 wctx.sub(s).merge(r)
98 98 sm[s] = l
99 99 elif l == a: # remote removed, local unchanged
100 100 debug(s, "remote removed, remove")
101 101 wctx.sub(s).remove()
102 102 else:
103 103 if repo.ui.promptchoice(
104 104 _(' local changed subrepository %s which remote removed\n'
105 105 'use (c)hanged version or (d)elete?') % s,
106 106 (_('&Changed'), _('&Delete')), 0):
107 107 debug(s, "prompt remove")
108 108 wctx.sub(s).remove()
109 109
110 110 for s, r in s2.items():
111 111 if s in s1:
112 112 continue
113 113 elif s not in sa:
114 114 debug(s, "remote added, get", r)
115 115 mctx.sub(s).get(r)
116 116 sm[s] = r
117 117 elif r != sa[s]:
118 118 if repo.ui.promptchoice(
119 119 _(' remote changed subrepository %s which local removed\n'
120 120 'use (c)hanged version or (d)elete?') % s,
121 121 (_('&Changed'), _('&Delete')), 0) == 0:
122 122 debug(s, "prompt recreate", r)
123 123 wctx.sub(s).get(r)
124 124 sm[s] = r
125 125
126 126 # record merged .hgsubstate
127 127 writestate(repo, sm)
128 128
129 129 def _abssource(repo, push=False):
130 130 if hasattr(repo, '_subparent'):
131 131 source = repo._subsource
132 132 if source.startswith('/') or '://' in source:
133 133 return source
134 134 parent = _abssource(repo._subparent)
135 135 if '://' in parent:
136 136 if parent[-1] == '/':
137 137 parent = parent[:-1]
138 138 return parent + '/' + source
139 139 return os.path.join(parent, repo._subsource)
140 140 if push and repo.ui.config('paths', 'default-push'):
141 141 return repo.ui.config('paths', 'default-push', repo.root)
142 142 return repo.ui.config('paths', 'default', repo.root)
143 143
144 144 def subrepo(ctx, path):
145 145 # subrepo inherently violates our import layering rules
146 146 # because it wants to make repo objects from deep inside the stack
147 147 # so we manually delay the circular imports to not break
148 148 # scripts that don't use our demand-loading
149 149 global hg
150 150 import hg as h
151 151 hg = h
152 152
153 153 util.path_auditor(ctx._repo.root)(path)
154 154 state = ctx.substate.get(path, nullstate)
155 155 if state[2] not in types:
156 156 raise util.Abort(_('unknown subrepo type %s') % state[2])
157 157 return types[state[2]](ctx, path, state[:2])
158 158
159 159 # subrepo classes need to implement the following methods:
160 160 # __init__(self, ctx, path, state)
161 161 # dirty(self): returns true if the dirstate of the subrepo
162 162 # does not match current stored state
163 163 # commit(self, text, user, date): commit the current changes
164 164 # to the subrepo with the given log message. Use given
165 165 # user and date if possible. Return the new state of the subrepo.
166 166 # remove(self): remove the subrepo (should verify the dirstate
167 167 # is not dirty first)
168 168 # get(self, state): run whatever commands are needed to put the
169 169 # subrepo into this state
170 170 # merge(self, state): merge currently-saved state with the new state.
171 171 # push(self, force): perform whatever action is analagous to 'hg push'
172 172 # This may be a no-op on some systems.
173 173
174 174 class hgsubrepo(object):
175 175 def __init__(self, ctx, path, state):
176 176 self._path = path
177 177 self._state = state
178 178 r = ctx._repo
179 179 root = r.wjoin(path)
180 180 if os.path.exists(os.path.join(root, '.hg')):
181 181 self._repo = hg.repository(r.ui, root)
182 182 else:
183 183 util.makedirs(root)
184 184 self._repo = hg.repository(r.ui, root, create=True)
185 185 f = file(os.path.join(root, '.hg', 'hgrc'), 'w')
186 186 f.write('[paths]\ndefault = %s\n' % state[0])
187 187 f.close()
188 188 self._repo._subparent = r
189 189 self._repo._subsource = state[0]
190 190
191 191 def dirty(self):
192 192 r = self._state[1]
193 193 if r == '':
194 194 return True
195 195 w = self._repo[None]
196 196 if w.p1() != self._repo[r]: # version checked out changed
197 197 return True
198 198 return w.dirty() # working directory changed
199 199
200 200 def commit(self, text, user, date):
201 201 self._repo.ui.debug("committing subrepo %s\n" % self._path)
202 202 n = self._repo.commit(text, user, date)
203 203 if not n:
204 204 return self._repo['.'].hex() # different version checked out
205 205 return node.hex(n)
206 206
207 207 def remove(self):
208 208 # we can't fully delete the repository as it may contain
209 209 # local-only history
210 210 self._repo.ui.note(_('removing subrepo %s\n') % self._path)
211 211 hg.clean(self._repo, node.nullid, False)
212 212
213 213 def _get(self, state):
214 214 source, revision, kind = state
215 215 try:
216 216 self._repo.lookup(revision)
217 217 except error.RepoError:
218 218 self._repo._subsource = source
219 219 self._repo.ui.status(_('pulling subrepo %s\n') % self._path)
220 220 srcurl = _abssource(self._repo)
221 221 other = hg.repository(self._repo.ui, srcurl)
222 222 self._repo.pull(other)
223 223
224 224 def get(self, state):
225 225 self._get(state)
226 226 source, revision, kind = state
227 227 self._repo.ui.debug("getting subrepo %s\n" % self._path)
228 228 hg.clean(self._repo, revision, False)
229 229
230 230 def merge(self, state):
231 231 self._get(state)
232 232 cur = self._repo['.']
233 233 dst = self._repo[state[1]]
234 234 anc = dst.ancestor(cur)
235 235 if anc == cur:
236 236 self._repo.ui.debug("updating subrepo %s\n" % self._path)
237 237 hg.update(self._repo, state[1])
238 238 elif anc == dst:
239 239 self._repo.ui.debug("skipping subrepo %s\n" % self._path)
240 240 else:
241 241 self._repo.ui.debug("merging subrepo %s\n" % self._path)
242 242 hg.merge(self._repo, state[1], remind=False)
243 243
244 244 def push(self, force):
245 245 # push subrepos depth-first for coherent ordering
246 246 c = self._repo['']
247 247 subs = c.substate # only repos that are committed
248 248 for s in sorted(subs):
249 249 c.sub(s).push(force)
250 250
251 251 self._repo.ui.status(_('pushing subrepo %s\n') % self._path)
252 252 dsturl = _abssource(self._repo, True)
253 253 other = hg.repository(self._repo.ui, dsturl)
254 254 self._repo.push(other, force)
255 255
256 256 class svnsubrepo(object):
257 257 def __init__(self, ctx, path, state):
258 258 self._path = path
259 259 self._state = state
260 260 self._ctx = ctx
261 261 self._ui = ctx._repo.ui
262 262
263 263 def _svncommand(self, commands):
264 264 cmd = ['svn'] + commands + [self._path]
265 265 cmd = [util.shellquote(arg) for arg in cmd]
266 266 cmd = util.quotecommand(' '.join(cmd))
267 267 env = dict(os.environ)
268 268 # Avoid localized output, preserve current locale for everything else.
269 269 env['LC_MESSAGES'] = 'C'
270 270 write, read, err = util.popen3(cmd, env=env, newlines=True)
271 271 retdata = read.read()
272 272 err = err.read().strip()
273 273 if err:
274 274 raise util.Abort(err)
275 275 return retdata
276 276
277 277 def _wcrev(self):
278 278 output = self._svncommand(['info', '--xml'])
279 279 doc = xml.dom.minidom.parseString(output)
280 280 entries = doc.getElementsByTagName('entry')
281 281 if not entries:
282 282 return 0
283 283 return int(entries[0].getAttribute('revision') or 0)
284 284
285 285 def _wcchanged(self):
286 286 """Return (changes, extchanges) where changes is True
287 287 if the working directory was changed, and extchanges is
288 288 True if any of these changes concern an external entry.
289 289 """
290 290 output = self._svncommand(['status', '--xml'])
291 291 externals, changes = [], []
292 292 doc = xml.dom.minidom.parseString(output)
293 293 for e in doc.getElementsByTagName('entry'):
294 294 s = e.getElementsByTagName('wc-status')
295 295 if not s:
296 296 continue
297 297 item = s[0].getAttribute('item')
298 298 props = s[0].getAttribute('props')
299 299 path = e.getAttribute('path')
300 300 if item == 'external':
301 301 externals.append(path)
302 302 if (item not in ('', 'normal', 'unversioned', 'external')
303 303 or props not in ('', 'none')):
304 304 changes.append(path)
305 305 for path in changes:
306 306 for ext in externals:
307 307 if path == ext or path.startswith(ext + os.sep):
308 308 return True, True
309 309 return bool(changes), False
310 310
311 311 def dirty(self):
312 312 if self._wcrev() == self._state[1] and not self._wcchanged()[0]:
313 313 return False
314 314 return True
315 315
316 316 def commit(self, text, user, date):
317 317 # user and date are out of our hands since svn is centralized
318 318 changed, extchanged = self._wcchanged()
319 319 if not changed:
320 320 return self._wcrev()
321 321 if extchanged:
322 322 # Do not try to commit externals
323 323 raise util.Abort(_('cannot commit svn externals'))
324 324 commitinfo = self._svncommand(['commit', '-m', text])
325 325 self._ui.status(commitinfo)
326 326 newrev = re.search('Committed revision ([\d]+).', commitinfo)
327 327 if not newrev:
328 328 raise util.Abort(commitinfo.splitlines()[-1])
329 329 newrev = newrev.groups()[0]
330 330 self._ui.status(self._svncommand(['update', '-r', newrev]))
331 331 return newrev
332 332
333 333 def remove(self):
334 334 if self.dirty():
335 335 self._ui.warn(_('not removing repo %s because '
336 336 'it has changes.\n' % self._path))
337 337 return
338 338 self._ui.note('removing subrepo %s\n' % self._path)
339 339 shutil.rmtree(self._ctx.repo.join(self._path))
340 340
341 341 def get(self, state):
342 342 status = self._svncommand(['checkout', state[0], '--revision', state[1]])
343 343 if not re.search('Checked out revision [\d]+.', status):
344 344 raise util.Abort(status.splitlines()[-1])
345 345 self._ui.status(status)
346 346
347 347 def merge(self, state):
348 348 old = int(self._state[1])
349 349 new = int(state[1])
350 350 if new > old:
351 351 self.get(state)
352 352
353 353 def push(self, force):
354 354 # nothing for svn
355 355 pass
356 356
357 357 types = {
358 358 'hg': hgsubrepo,
359 359 'svn': svnsubrepo,
360 360 }
General Comments 0
You need to be logged in to leave comments. Login now