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