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