##// END OF EJS Templates
subrepo: handle missing subrepo spec file as removed...
Patrick Mezard -
r13017:d0e21c5f stable
parent child Browse files
Show More
@@ -0,0 +1,51
1 $ hg init repo
2 $ cd repo
3 $ hg init subrepo
4 $ echo a > subrepo/a
5 $ hg -R subrepo ci -Am adda
6 adding a
7 $ echo 'subrepo = subrepo' > .hgsub
8 $ hg ci -Am addsubrepo
9 adding .hgsub
10 committing subrepository subrepo
11 $ echo b > subrepo/b
12 $ hg -R subrepo ci -Am addb
13 adding b
14 $ hg ci -m updatedsub
15 committing subrepository subrepo
16
17 delete .hgsub and revert it
18
19 $ rm .hgsub
20 $ hg revert .hgsub
21 warning: subrepo spec file .hgsub not found
22
23 delete .hgsubstate and revert it
24
25 $ rm .hgsubstate
26 $ hg revert .hgsubstate
27
28 delete .hgsub and update
29
30 $ rm .hgsub
31 $ hg up 0
32 warning: subrepo spec file .hgsub not found
33 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
34 $ hg st
35 ! .hgsub
36 $ ls subrepo
37 a
38
39 delete .hgsubstate and update
40
41 $ hg up -C
42 warning: subrepo spec file .hgsub not found
43 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
44 $ rm .hgsubstate
45 $ hg up 0
46 remote changed .hgsubstate which local deleted
47 use (c)hanged version or leave (d)eleted? c
48 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
49 $ hg st
50 $ ls subrepo
51 a
@@ -1,602 +1,610
1 1 # subrepo.py - sub-repository handling for Mercurial
2 2 #
3 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, urlparse, posixpath
9 9 import stat, subprocess
10 10 from i18n import _
11 11 import config, util, node, error, cmdutil
12 12 hg = None
13 13
14 14 nullstate = ('', '', 'empty')
15 15
16 16 def state(ctx, ui):
17 17 """return a state dict, mapping subrepo paths configured in .hgsub
18 18 to tuple: (source from .hgsub, revision from .hgsubstate, kind
19 19 (key in types dict))
20 20 """
21 21 p = config.config()
22 22 def read(f, sections=None, remap=None):
23 23 if f in ctx:
24 p.parse(f, ctx[f].data(), sections, remap, read)
24 try:
25 data = ctx[f].data()
26 except IOError, err:
27 if err.errno != errno.ENOENT:
28 raise
29 # handle missing subrepo spec files as removed
30 ui.warn(_("warning: subrepo spec file %s not found\n") % f)
31 return
32 p.parse(f, data, sections, remap, read)
25 33 else:
26 34 raise util.Abort(_("subrepo spec file %s not found") % f)
27 35
28 36 if '.hgsub' in ctx:
29 37 read('.hgsub')
30 38
31 39 for path, src in ui.configitems('subpaths'):
32 40 p.set('subpaths', path, src, ui.configsource('subpaths', path))
33 41
34 42 rev = {}
35 43 if '.hgsubstate' in ctx:
36 44 try:
37 45 for l in ctx['.hgsubstate'].data().splitlines():
38 46 revision, path = l.split(" ", 1)
39 47 rev[path] = revision
40 48 except IOError, err:
41 49 if err.errno != errno.ENOENT:
42 50 raise
43 51
44 52 state = {}
45 53 for path, src in p[''].items():
46 54 kind = 'hg'
47 55 if src.startswith('['):
48 56 if ']' not in src:
49 57 raise util.Abort(_('missing ] in subrepo source'))
50 58 kind, src = src.split(']', 1)
51 59 kind = kind[1:]
52 60
53 61 for pattern, repl in p.items('subpaths'):
54 62 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
55 63 # does a string decode.
56 64 repl = repl.encode('string-escape')
57 65 # However, we still want to allow back references to go
58 66 # through unharmed, so we turn r'\\1' into r'\1'. Again,
59 67 # extra escapes are needed because re.sub string decodes.
60 68 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
61 69 try:
62 70 src = re.sub(pattern, repl, src, 1)
63 71 except re.error, e:
64 72 raise util.Abort(_("bad subrepository pattern in %s: %s")
65 73 % (p.source('subpaths', pattern), e))
66 74
67 75 state[path] = (src.strip(), rev.get(path, ''), kind)
68 76
69 77 return state
70 78
71 79 def writestate(repo, state):
72 80 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
73 81 repo.wwrite('.hgsubstate',
74 82 ''.join(['%s %s\n' % (state[s][1], s)
75 83 for s in sorted(state)]), '')
76 84
77 85 def submerge(repo, wctx, mctx, actx):
78 86 """delegated from merge.applyupdates: merging of .hgsubstate file
79 87 in working context, merging context and ancestor context"""
80 88 if mctx == actx: # backwards?
81 89 actx = wctx.p1()
82 90 s1 = wctx.substate
83 91 s2 = mctx.substate
84 92 sa = actx.substate
85 93 sm = {}
86 94
87 95 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
88 96
89 97 def debug(s, msg, r=""):
90 98 if r:
91 99 r = "%s:%s:%s" % r
92 100 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
93 101
94 102 for s, l in s1.items():
95 103 a = sa.get(s, nullstate)
96 104 ld = l # local state with possible dirty flag for compares
97 105 if wctx.sub(s).dirty():
98 106 ld = (l[0], l[1] + "+")
99 107 if wctx == actx: # overwrite
100 108 a = ld
101 109
102 110 if s in s2:
103 111 r = s2[s]
104 112 if ld == r or r == a: # no change or local is newer
105 113 sm[s] = l
106 114 continue
107 115 elif ld == a: # other side changed
108 116 debug(s, "other changed, get", r)
109 117 wctx.sub(s).get(r)
110 118 sm[s] = r
111 119 elif ld[0] != r[0]: # sources differ
112 120 if repo.ui.promptchoice(
113 121 _(' subrepository sources for %s differ\n'
114 122 'use (l)ocal source (%s) or (r)emote source (%s)?')
115 123 % (s, l[0], r[0]),
116 124 (_('&Local'), _('&Remote')), 0):
117 125 debug(s, "prompt changed, get", r)
118 126 wctx.sub(s).get(r)
119 127 sm[s] = r
120 128 elif ld[1] == a[1]: # local side is unchanged
121 129 debug(s, "other side changed, get", r)
122 130 wctx.sub(s).get(r)
123 131 sm[s] = r
124 132 else:
125 133 debug(s, "both sides changed, merge with", r)
126 134 wctx.sub(s).merge(r)
127 135 sm[s] = l
128 136 elif ld == a: # remote removed, local unchanged
129 137 debug(s, "remote removed, remove")
130 138 wctx.sub(s).remove()
131 139 else:
132 140 if repo.ui.promptchoice(
133 141 _(' local changed subrepository %s which remote removed\n'
134 142 'use (c)hanged version or (d)elete?') % s,
135 143 (_('&Changed'), _('&Delete')), 0):
136 144 debug(s, "prompt remove")
137 145 wctx.sub(s).remove()
138 146
139 147 for s, r in s2.items():
140 148 if s in s1:
141 149 continue
142 150 elif s not in sa:
143 151 debug(s, "remote added, get", r)
144 152 mctx.sub(s).get(r)
145 153 sm[s] = r
146 154 elif r != sa[s]:
147 155 if repo.ui.promptchoice(
148 156 _(' remote changed subrepository %s which local removed\n'
149 157 'use (c)hanged version or (d)elete?') % s,
150 158 (_('&Changed'), _('&Delete')), 0) == 0:
151 159 debug(s, "prompt recreate", r)
152 160 wctx.sub(s).get(r)
153 161 sm[s] = r
154 162
155 163 # record merged .hgsubstate
156 164 writestate(repo, sm)
157 165
158 166 def reporelpath(repo):
159 167 """return path to this (sub)repo as seen from outermost repo"""
160 168 parent = repo
161 169 while hasattr(parent, '_subparent'):
162 170 parent = parent._subparent
163 171 return repo.root[len(parent.root)+1:]
164 172
165 173 def subrelpath(sub):
166 174 """return path to this subrepo as seen from outermost repo"""
167 175 if not hasattr(sub, '_repo'):
168 176 return sub._path
169 177 return reporelpath(sub._repo)
170 178
171 179 def _abssource(repo, push=False, abort=True):
172 180 """return pull/push path of repo - either based on parent repo .hgsub info
173 181 or on the top repo config. Abort or return None if no source found."""
174 182 if hasattr(repo, '_subparent'):
175 183 source = repo._subsource
176 184 if source.startswith('/') or '://' in source:
177 185 return source
178 186 parent = _abssource(repo._subparent, push, abort=False)
179 187 if parent:
180 188 if '://' in parent:
181 189 if parent[-1] == '/':
182 190 parent = parent[:-1]
183 191 r = urlparse.urlparse(parent + '/' + source)
184 192 r = urlparse.urlunparse((r[0], r[1],
185 193 posixpath.normpath(r[2]),
186 194 r[3], r[4], r[5]))
187 195 return r
188 196 else: # plain file system path
189 197 return posixpath.normpath(os.path.join(parent, repo._subsource))
190 198 else: # recursion reached top repo
191 199 if hasattr(repo, '_subtoppath'):
192 200 return repo._subtoppath
193 201 if push and repo.ui.config('paths', 'default-push'):
194 202 return repo.ui.config('paths', 'default-push')
195 203 if repo.ui.config('paths', 'default'):
196 204 return repo.ui.config('paths', 'default')
197 205 if abort:
198 206 raise util.Abort(_("default path for subrepository %s not found") %
199 207 reporelpath(repo))
200 208
201 209 def itersubrepos(ctx1, ctx2):
202 210 """find subrepos in ctx1 or ctx2"""
203 211 # Create a (subpath, ctx) mapping where we prefer subpaths from
204 212 # ctx1. The subpaths from ctx2 are important when the .hgsub file
205 213 # has been modified (in ctx2) but not yet committed (in ctx1).
206 214 subpaths = dict.fromkeys(ctx2.substate, ctx2)
207 215 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
208 216 for subpath, ctx in sorted(subpaths.iteritems()):
209 217 yield subpath, ctx.sub(subpath)
210 218
211 219 def subrepo(ctx, path):
212 220 """return instance of the right subrepo class for subrepo in path"""
213 221 # subrepo inherently violates our import layering rules
214 222 # because it wants to make repo objects from deep inside the stack
215 223 # so we manually delay the circular imports to not break
216 224 # scripts that don't use our demand-loading
217 225 global hg
218 226 import hg as h
219 227 hg = h
220 228
221 229 util.path_auditor(ctx._repo.root)(path)
222 230 state = ctx.substate.get(path, nullstate)
223 231 if state[2] not in types:
224 232 raise util.Abort(_('unknown subrepo type %s') % state[2])
225 233 return types[state[2]](ctx, path, state[:2])
226 234
227 235 # subrepo classes need to implement the following abstract class:
228 236
229 237 class abstractsubrepo(object):
230 238
231 239 def dirty(self):
232 240 """returns true if the dirstate of the subrepo does not match
233 241 current stored state
234 242 """
235 243 raise NotImplementedError
236 244
237 245 def checknested(self, path):
238 246 """check if path is a subrepository within this repository"""
239 247 return False
240 248
241 249 def commit(self, text, user, date):
242 250 """commit the current changes to the subrepo with the given
243 251 log message. Use given user and date if possible. Return the
244 252 new state of the subrepo.
245 253 """
246 254 raise NotImplementedError
247 255
248 256 def remove(self):
249 257 """remove the subrepo
250 258
251 259 (should verify the dirstate is not dirty first)
252 260 """
253 261 raise NotImplementedError
254 262
255 263 def get(self, state):
256 264 """run whatever commands are needed to put the subrepo into
257 265 this state
258 266 """
259 267 raise NotImplementedError
260 268
261 269 def merge(self, state):
262 270 """merge currently-saved state with the new state."""
263 271 raise NotImplementedError
264 272
265 273 def push(self, force):
266 274 """perform whatever action is analogous to 'hg push'
267 275
268 276 This may be a no-op on some systems.
269 277 """
270 278 raise NotImplementedError
271 279
272 280 def add(self, ui, match, dryrun, prefix):
273 281 return []
274 282
275 283 def status(self, rev2, **opts):
276 284 return [], [], [], [], [], [], []
277 285
278 286 def diff(self, diffopts, node2, match, prefix, **opts):
279 287 pass
280 288
281 289 def outgoing(self, ui, dest, opts):
282 290 return 1
283 291
284 292 def incoming(self, ui, source, opts):
285 293 return 1
286 294
287 295 def files(self):
288 296 """return filename iterator"""
289 297 raise NotImplementedError
290 298
291 299 def filedata(self, name):
292 300 """return file data"""
293 301 raise NotImplementedError
294 302
295 303 def fileflags(self, name):
296 304 """return file flags"""
297 305 return ''
298 306
299 307 def archive(self, archiver, prefix):
300 308 for name in self.files():
301 309 flags = self.fileflags(name)
302 310 mode = 'x' in flags and 0755 or 0644
303 311 symlink = 'l' in flags
304 312 archiver.addfile(os.path.join(prefix, self._path, name),
305 313 mode, symlink, self.filedata(name))
306 314
307 315
308 316 class hgsubrepo(abstractsubrepo):
309 317 def __init__(self, ctx, path, state):
310 318 self._path = path
311 319 self._state = state
312 320 r = ctx._repo
313 321 root = r.wjoin(path)
314 322 create = False
315 323 if not os.path.exists(os.path.join(root, '.hg')):
316 324 create = True
317 325 util.makedirs(root)
318 326 self._repo = hg.repository(r.ui, root, create=create)
319 327 self._repo._subparent = r
320 328 self._repo._subsource = state[0]
321 329
322 330 if create:
323 331 fp = self._repo.opener("hgrc", "w", text=True)
324 332 fp.write('[paths]\n')
325 333
326 334 def addpathconfig(key, value):
327 335 if value:
328 336 fp.write('%s = %s\n' % (key, value))
329 337 self._repo.ui.setconfig('paths', key, value)
330 338
331 339 defpath = _abssource(self._repo, abort=False)
332 340 defpushpath = _abssource(self._repo, True, abort=False)
333 341 addpathconfig('default', defpath)
334 342 if defpath != defpushpath:
335 343 addpathconfig('default-push', defpushpath)
336 344 fp.close()
337 345
338 346 def add(self, ui, match, dryrun, prefix):
339 347 return cmdutil.add(ui, self._repo, match, dryrun, True,
340 348 os.path.join(prefix, self._path))
341 349
342 350 def status(self, rev2, **opts):
343 351 try:
344 352 rev1 = self._state[1]
345 353 ctx1 = self._repo[rev1]
346 354 ctx2 = self._repo[rev2]
347 355 return self._repo.status(ctx1, ctx2, **opts)
348 356 except error.RepoLookupError, inst:
349 357 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
350 358 % (inst, subrelpath(self)))
351 359 return [], [], [], [], [], [], []
352 360
353 361 def diff(self, diffopts, node2, match, prefix, **opts):
354 362 try:
355 363 node1 = node.bin(self._state[1])
356 364 # We currently expect node2 to come from substate and be
357 365 # in hex format
358 366 if node2 is not None:
359 367 node2 = node.bin(node2)
360 368 cmdutil.diffordiffstat(self._repo.ui, self._repo, diffopts,
361 369 node1, node2, match,
362 370 prefix=os.path.join(prefix, self._path),
363 371 listsubrepos=True, **opts)
364 372 except error.RepoLookupError, inst:
365 373 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
366 374 % (inst, subrelpath(self)))
367 375
368 376 def archive(self, archiver, prefix):
369 377 abstractsubrepo.archive(self, archiver, prefix)
370 378
371 379 rev = self._state[1]
372 380 ctx = self._repo[rev]
373 381 for subpath in ctx.substate:
374 382 s = subrepo(ctx, subpath)
375 383 s.archive(archiver, os.path.join(prefix, self._path))
376 384
377 385 def dirty(self):
378 386 r = self._state[1]
379 387 if r == '':
380 388 return True
381 389 w = self._repo[None]
382 390 if w.p1() != self._repo[r]: # version checked out change
383 391 return True
384 392 return w.dirty() # working directory changed
385 393
386 394 def checknested(self, path):
387 395 return self._repo._checknested(self._repo.wjoin(path))
388 396
389 397 def commit(self, text, user, date):
390 398 self._repo.ui.debug("committing subrepo %s\n" % subrelpath(self))
391 399 n = self._repo.commit(text, user, date)
392 400 if not n:
393 401 return self._repo['.'].hex() # different version checked out
394 402 return node.hex(n)
395 403
396 404 def remove(self):
397 405 # we can't fully delete the repository as it may contain
398 406 # local-only history
399 407 self._repo.ui.note(_('removing subrepo %s\n') % subrelpath(self))
400 408 hg.clean(self._repo, node.nullid, False)
401 409
402 410 def _get(self, state):
403 411 source, revision, kind = state
404 412 try:
405 413 self._repo.lookup(revision)
406 414 except error.RepoError:
407 415 self._repo._subsource = source
408 416 srcurl = _abssource(self._repo)
409 417 self._repo.ui.status(_('pulling subrepo %s from %s\n')
410 418 % (subrelpath(self), srcurl))
411 419 other = hg.repository(self._repo.ui, srcurl)
412 420 self._repo.pull(other)
413 421
414 422 def get(self, state):
415 423 self._get(state)
416 424 source, revision, kind = state
417 425 self._repo.ui.debug("getting subrepo %s\n" % self._path)
418 426 hg.clean(self._repo, revision, False)
419 427
420 428 def merge(self, state):
421 429 self._get(state)
422 430 cur = self._repo['.']
423 431 dst = self._repo[state[1]]
424 432 anc = dst.ancestor(cur)
425 433 if anc == cur:
426 434 self._repo.ui.debug("updating subrepo %s\n" % subrelpath(self))
427 435 hg.update(self._repo, state[1])
428 436 elif anc == dst:
429 437 self._repo.ui.debug("skipping subrepo %s\n" % subrelpath(self))
430 438 else:
431 439 self._repo.ui.debug("merging subrepo %s\n" % subrelpath(self))
432 440 hg.merge(self._repo, state[1], remind=False)
433 441
434 442 def push(self, force):
435 443 # push subrepos depth-first for coherent ordering
436 444 c = self._repo['']
437 445 subs = c.substate # only repos that are committed
438 446 for s in sorted(subs):
439 447 if not c.sub(s).push(force):
440 448 return False
441 449
442 450 dsturl = _abssource(self._repo, True)
443 451 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
444 452 (subrelpath(self), dsturl))
445 453 other = hg.repository(self._repo.ui, dsturl)
446 454 return self._repo.push(other, force)
447 455
448 456 def outgoing(self, ui, dest, opts):
449 457 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
450 458
451 459 def incoming(self, ui, source, opts):
452 460 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
453 461
454 462 def files(self):
455 463 rev = self._state[1]
456 464 ctx = self._repo[rev]
457 465 return ctx.manifest()
458 466
459 467 def filedata(self, name):
460 468 rev = self._state[1]
461 469 return self._repo[rev][name].data()
462 470
463 471 def fileflags(self, name):
464 472 rev = self._state[1]
465 473 ctx = self._repo[rev]
466 474 return ctx.flags(name)
467 475
468 476
469 477 class svnsubrepo(abstractsubrepo):
470 478 def __init__(self, ctx, path, state):
471 479 self._path = path
472 480 self._state = state
473 481 self._ctx = ctx
474 482 self._ui = ctx._repo.ui
475 483
476 484 def _svncommand(self, commands, filename=''):
477 485 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
478 486 cmd = ['svn'] + commands + [path]
479 487 cmd = [util.shellquote(arg) for arg in cmd]
480 488 cmd = util.quotecommand(' '.join(cmd))
481 489 env = dict(os.environ)
482 490 # Avoid localized output, preserve current locale for everything else.
483 491 env['LC_MESSAGES'] = 'C'
484 492 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
485 493 close_fds=util.closefds,
486 494 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
487 495 universal_newlines=True, env=env)
488 496 stdout, stderr = p.communicate()
489 497 stderr = stderr.strip()
490 498 if stderr:
491 499 raise util.Abort(stderr)
492 500 return stdout
493 501
494 502 def _wcrev(self):
495 503 output = self._svncommand(['info', '--xml'])
496 504 doc = xml.dom.minidom.parseString(output)
497 505 entries = doc.getElementsByTagName('entry')
498 506 if not entries:
499 507 return '0'
500 508 return str(entries[0].getAttribute('revision')) or '0'
501 509
502 510 def _wcchanged(self):
503 511 """Return (changes, extchanges) where changes is True
504 512 if the working directory was changed, and extchanges is
505 513 True if any of these changes concern an external entry.
506 514 """
507 515 output = self._svncommand(['status', '--xml'])
508 516 externals, changes = [], []
509 517 doc = xml.dom.minidom.parseString(output)
510 518 for e in doc.getElementsByTagName('entry'):
511 519 s = e.getElementsByTagName('wc-status')
512 520 if not s:
513 521 continue
514 522 item = s[0].getAttribute('item')
515 523 props = s[0].getAttribute('props')
516 524 path = e.getAttribute('path')
517 525 if item == 'external':
518 526 externals.append(path)
519 527 if (item not in ('', 'normal', 'unversioned', 'external')
520 528 or props not in ('', 'none')):
521 529 changes.append(path)
522 530 for path in changes:
523 531 for ext in externals:
524 532 if path == ext or path.startswith(ext + os.sep):
525 533 return True, True
526 534 return bool(changes), False
527 535
528 536 def dirty(self):
529 537 if self._wcrev() == self._state[1] and not self._wcchanged()[0]:
530 538 return False
531 539 return True
532 540
533 541 def commit(self, text, user, date):
534 542 # user and date are out of our hands since svn is centralized
535 543 changed, extchanged = self._wcchanged()
536 544 if not changed:
537 545 return self._wcrev()
538 546 if extchanged:
539 547 # Do not try to commit externals
540 548 raise util.Abort(_('cannot commit svn externals'))
541 549 commitinfo = self._svncommand(['commit', '-m', text])
542 550 self._ui.status(commitinfo)
543 551 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
544 552 if not newrev:
545 553 raise util.Abort(commitinfo.splitlines()[-1])
546 554 newrev = newrev.groups()[0]
547 555 self._ui.status(self._svncommand(['update', '-r', newrev]))
548 556 return newrev
549 557
550 558 def remove(self):
551 559 if self.dirty():
552 560 self._ui.warn(_('not removing repo %s because '
553 561 'it has changes.\n' % self._path))
554 562 return
555 563 self._ui.note(_('removing subrepo %s\n') % self._path)
556 564
557 565 def onerror(function, path, excinfo):
558 566 if function is not os.remove:
559 567 raise
560 568 # read-only files cannot be unlinked under Windows
561 569 s = os.stat(path)
562 570 if (s.st_mode & stat.S_IWRITE) != 0:
563 571 raise
564 572 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
565 573 os.remove(path)
566 574
567 575 path = self._ctx._repo.wjoin(self._path)
568 576 shutil.rmtree(path, onerror=onerror)
569 577 try:
570 578 os.removedirs(os.path.dirname(path))
571 579 except OSError:
572 580 pass
573 581
574 582 def get(self, state):
575 583 status = self._svncommand(['checkout', state[0], '--revision', state[1]])
576 584 if not re.search('Checked out revision [0-9]+.', status):
577 585 raise util.Abort(status.splitlines()[-1])
578 586 self._ui.status(status)
579 587
580 588 def merge(self, state):
581 589 old = int(self._state[1])
582 590 new = int(state[1])
583 591 if new > old:
584 592 self.get(state)
585 593
586 594 def push(self, force):
587 595 # push is a no-op for SVN
588 596 return True
589 597
590 598 def files(self):
591 599 output = self._svncommand(['list'])
592 600 # This works because svn forbids \n in filenames.
593 601 return output.splitlines()
594 602
595 603 def filedata(self, name):
596 604 return self._svncommand(['cat'], name)
597 605
598 606
599 607 types = {
600 608 'hg': hgsubrepo,
601 609 'svn': svnsubrepo,
602 610 }
General Comments 0
You need to be logged in to leave comments. Login now