##// END OF EJS Templates
subrepo: make update -C clean the working directory for svn subrepos...
Erik Zielke -
r13322:c19b9282 stable
parent child Browse files
Show More
@@ -1,544 +1,544 b''
1 1 # merge.py - directory-level update/merge handling for Mercurial
2 2 #
3 3 # Copyright 2006, 2007 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 from node import nullid, nullrev, hex, bin
9 9 from i18n import _
10 10 import util, filemerge, copies, subrepo
11 11 import errno, os, shutil
12 12
13 13 class mergestate(object):
14 14 '''track 3-way merge state of individual files'''
15 15 def __init__(self, repo):
16 16 self._repo = repo
17 17 self._dirty = False
18 18 self._read()
19 19 def reset(self, node=None):
20 20 self._state = {}
21 21 if node:
22 22 self._local = node
23 23 shutil.rmtree(self._repo.join("merge"), True)
24 24 self._dirty = False
25 25 def _read(self):
26 26 self._state = {}
27 27 try:
28 28 f = self._repo.opener("merge/state")
29 29 for i, l in enumerate(f):
30 30 if i == 0:
31 31 self._local = bin(l[:-1])
32 32 else:
33 33 bits = l[:-1].split("\0")
34 34 self._state[bits[0]] = bits[1:]
35 35 except IOError, err:
36 36 if err.errno != errno.ENOENT:
37 37 raise
38 38 self._dirty = False
39 39 def commit(self):
40 40 if self._dirty:
41 41 f = self._repo.opener("merge/state", "w")
42 42 f.write(hex(self._local) + "\n")
43 43 for d, v in self._state.iteritems():
44 44 f.write("\0".join([d] + v) + "\n")
45 45 self._dirty = False
46 46 def add(self, fcl, fco, fca, fd, flags):
47 47 hash = util.sha1(fcl.path()).hexdigest()
48 48 self._repo.opener("merge/" + hash, "w").write(fcl.data())
49 49 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
50 50 hex(fca.filenode()), fco.path(), flags]
51 51 self._dirty = True
52 52 def __contains__(self, dfile):
53 53 return dfile in self._state
54 54 def __getitem__(self, dfile):
55 55 return self._state[dfile][0]
56 56 def __iter__(self):
57 57 l = self._state.keys()
58 58 l.sort()
59 59 for f in l:
60 60 yield f
61 61 def mark(self, dfile, state):
62 62 self._state[dfile][0] = state
63 63 self._dirty = True
64 64 def resolve(self, dfile, wctx, octx):
65 65 if self[dfile] == 'r':
66 66 return 0
67 67 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
68 68 f = self._repo.opener("merge/" + hash)
69 69 self._repo.wwrite(dfile, f.read(), flags)
70 70 fcd = wctx[dfile]
71 71 fco = octx[ofile]
72 72 fca = self._repo.filectx(afile, fileid=anode)
73 73 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
74 74 if not r:
75 75 self.mark(dfile, 'r')
76 76 return r
77 77
78 78 def _checkunknown(wctx, mctx):
79 79 "check for collisions between unknown files and files in mctx"
80 80 for f in wctx.unknown():
81 81 if f in mctx and mctx[f].cmp(wctx[f]):
82 82 raise util.Abort(_("untracked file in working directory differs"
83 83 " from file in requested revision: '%s'") % f)
84 84
85 85 def _checkcollision(mctx):
86 86 "check for case folding collisions in the destination context"
87 87 folded = {}
88 88 for fn in mctx:
89 89 fold = fn.lower()
90 90 if fold in folded:
91 91 raise util.Abort(_("case-folding collision between %s and %s")
92 92 % (fn, folded[fold]))
93 93 folded[fold] = fn
94 94
95 95 def _forgetremoved(wctx, mctx, branchmerge):
96 96 """
97 97 Forget removed files
98 98
99 99 If we're jumping between revisions (as opposed to merging), and if
100 100 neither the working directory nor the target rev has the file,
101 101 then we need to remove it from the dirstate, to prevent the
102 102 dirstate from listing the file when it is no longer in the
103 103 manifest.
104 104
105 105 If we're merging, and the other revision has removed a file
106 106 that is not present in the working directory, we need to mark it
107 107 as removed.
108 108 """
109 109
110 110 action = []
111 111 state = branchmerge and 'r' or 'f'
112 112 for f in wctx.deleted():
113 113 if f not in mctx:
114 114 action.append((f, state))
115 115
116 116 if not branchmerge:
117 117 for f in wctx.removed():
118 118 if f not in mctx:
119 119 action.append((f, "f"))
120 120
121 121 return action
122 122
123 123 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
124 124 """
125 125 Merge p1 and p2 with ancestor pa and generate merge action list
126 126
127 127 overwrite = whether we clobber working files
128 128 partial = function to filter file lists
129 129 """
130 130
131 131 def fmerge(f, f2, fa):
132 132 """merge flags"""
133 133 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
134 134 if m == n: # flags agree
135 135 return m # unchanged
136 136 if m and n and not a: # flags set, don't agree, differ from parent
137 137 r = repo.ui.promptchoice(
138 138 _(" conflicting flags for %s\n"
139 139 "(n)one, e(x)ec or sym(l)ink?") % f,
140 140 (_("&None"), _("E&xec"), _("Sym&link")), 0)
141 141 if r == 1:
142 142 return "x" # Exec
143 143 if r == 2:
144 144 return "l" # Symlink
145 145 return ""
146 146 if m and m != a: # changed from a to m
147 147 return m
148 148 if n and n != a: # changed from a to n
149 149 return n
150 150 return '' # flag was cleared
151 151
152 152 def act(msg, m, f, *args):
153 153 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
154 154 action.append((f, m) + args)
155 155
156 156 action, copy = [], {}
157 157
158 158 if overwrite:
159 159 pa = p1
160 160 elif pa == p2: # backwards
161 161 pa = p1.p1()
162 162 elif pa and repo.ui.configbool("merge", "followcopies", True):
163 163 dirs = repo.ui.configbool("merge", "followdirs", True)
164 164 copy, diverge = copies.copies(repo, p1, p2, pa, dirs)
165 165 for of, fl in diverge.iteritems():
166 166 act("divergent renames", "dr", of, fl)
167 167
168 168 repo.ui.note(_("resolving manifests\n"))
169 169 repo.ui.debug(" overwrite %s partial %s\n" % (overwrite, bool(partial)))
170 170 repo.ui.debug(" ancestor %s local %s remote %s\n" % (pa, p1, p2))
171 171
172 172 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
173 173 copied = set(copy.values())
174 174
175 175 if '.hgsubstate' in m1:
176 176 # check whether sub state is modified
177 177 for s in p1.substate:
178 178 if p1.sub(s).dirty():
179 179 m1['.hgsubstate'] += "+"
180 180 break
181 181
182 182 # Compare manifests
183 183 for f, n in m1.iteritems():
184 184 if partial and not partial(f):
185 185 continue
186 186 if f in m2:
187 187 rflags = fmerge(f, f, f)
188 188 a = ma.get(f, nullid)
189 189 if n == m2[f] or m2[f] == a: # same or local newer
190 190 # is file locally modified or flags need changing?
191 191 # dirstate flags may need to be made current
192 192 if m1.flags(f) != rflags or n[20:]:
193 193 act("update permissions", "e", f, rflags)
194 194 elif n == a: # remote newer
195 195 act("remote is newer", "g", f, rflags)
196 196 else: # both changed
197 197 act("versions differ", "m", f, f, f, rflags, False)
198 198 elif f in copied: # files we'll deal with on m2 side
199 199 pass
200 200 elif f in copy:
201 201 f2 = copy[f]
202 202 if f2 not in m2: # directory rename
203 203 act("remote renamed directory to " + f2, "d",
204 204 f, None, f2, m1.flags(f))
205 205 else: # case 2 A,B/B/B or case 4,21 A/B/B
206 206 act("local copied/moved to " + f2, "m",
207 207 f, f2, f, fmerge(f, f2, f2), False)
208 208 elif f in ma: # clean, a different, no remote
209 209 if n != ma[f]:
210 210 if repo.ui.promptchoice(
211 211 _(" local changed %s which remote deleted\n"
212 212 "use (c)hanged version or (d)elete?") % f,
213 213 (_("&Changed"), _("&Delete")), 0):
214 214 act("prompt delete", "r", f)
215 215 else:
216 216 act("prompt keep", "a", f)
217 217 elif n[20:] == "a": # added, no remote
218 218 act("remote deleted", "f", f)
219 219 elif n[20:] != "u":
220 220 act("other deleted", "r", f)
221 221
222 222 for f, n in m2.iteritems():
223 223 if partial and not partial(f):
224 224 continue
225 225 if f in m1 or f in copied: # files already visited
226 226 continue
227 227 if f in copy:
228 228 f2 = copy[f]
229 229 if f2 not in m1: # directory rename
230 230 act("local renamed directory to " + f2, "d",
231 231 None, f, f2, m2.flags(f))
232 232 elif f2 in m2: # rename case 1, A/A,B/A
233 233 act("remote copied to " + f, "m",
234 234 f2, f, f, fmerge(f2, f, f2), False)
235 235 else: # case 3,20 A/B/A
236 236 act("remote moved to " + f, "m",
237 237 f2, f, f, fmerge(f2, f, f2), True)
238 238 elif f not in ma:
239 239 act("remote created", "g", f, m2.flags(f))
240 240 elif n != ma[f]:
241 241 if repo.ui.promptchoice(
242 242 _("remote changed %s which local deleted\n"
243 243 "use (c)hanged version or leave (d)eleted?") % f,
244 244 (_("&Changed"), _("&Deleted")), 0) == 0:
245 245 act("prompt recreating", "g", f, m2.flags(f))
246 246
247 247 return action
248 248
249 249 def actionkey(a):
250 250 return a[1] == 'r' and -1 or 0, a
251 251
252 def applyupdates(repo, action, wctx, mctx, actx):
252 def applyupdates(repo, action, wctx, mctx, actx, overwrite):
253 253 """apply the merge action list to the working directory
254 254
255 255 wctx is the working copy context
256 256 mctx is the context to be merged into the working copy
257 257 actx is the context of the common ancestor
258 258 """
259 259
260 260 updated, merged, removed, unresolved = 0, 0, 0, 0
261 261 ms = mergestate(repo)
262 262 ms.reset(wctx.parents()[0].node())
263 263 moves = []
264 264 action.sort(key=actionkey)
265 265 substate = wctx.substate # prime
266 266
267 267 # prescan for merges
268 268 u = repo.ui
269 269 for a in action:
270 270 f, m = a[:2]
271 271 if m == 'm': # merge
272 272 f2, fd, flags, move = a[2:]
273 273 if f == '.hgsubstate': # merged internally
274 274 continue
275 275 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
276 276 fcl = wctx[f]
277 277 fco = mctx[f2]
278 278 if mctx == actx: # backwards, use working dir parent as ancestor
279 279 if fcl.parents():
280 280 fca = fcl.parents()[0]
281 281 else:
282 282 fca = repo.filectx(f, fileid=nullrev)
283 283 else:
284 284 fca = fcl.ancestor(fco, actx)
285 285 if not fca:
286 286 fca = repo.filectx(f, fileid=nullrev)
287 287 ms.add(fcl, fco, fca, fd, flags)
288 288 if f != fd and move:
289 289 moves.append(f)
290 290
291 291 # remove renamed files after safely stored
292 292 for f in moves:
293 293 if os.path.lexists(repo.wjoin(f)):
294 294 repo.ui.debug("removing %s\n" % f)
295 295 os.unlink(repo.wjoin(f))
296 296
297 297 audit_path = util.path_auditor(repo.root)
298 298
299 299 numupdates = len(action)
300 300 for i, a in enumerate(action):
301 301 f, m = a[:2]
302 302 u.progress(_('updating'), i + 1, item=f, total=numupdates,
303 303 unit=_('files'))
304 304 if f and f[0] == "/":
305 305 continue
306 306 if m == "r": # remove
307 307 repo.ui.note(_("removing %s\n") % f)
308 308 audit_path(f)
309 309 if f == '.hgsubstate': # subrepo states need updating
310 subrepo.submerge(repo, wctx, mctx, wctx)
310 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
311 311 try:
312 312 util.unlink(repo.wjoin(f))
313 313 except OSError, inst:
314 314 if inst.errno != errno.ENOENT:
315 315 repo.ui.warn(_("update failed to remove %s: %s!\n") %
316 316 (f, inst.strerror))
317 317 removed += 1
318 318 elif m == "m": # merge
319 319 if f == '.hgsubstate': # subrepo states need updating
320 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx))
320 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx), overwrite)
321 321 continue
322 322 f2, fd, flags, move = a[2:]
323 323 r = ms.resolve(fd, wctx, mctx)
324 324 if r is not None and r > 0:
325 325 unresolved += 1
326 326 else:
327 327 if r is None:
328 328 updated += 1
329 329 else:
330 330 merged += 1
331 331 util.set_flags(repo.wjoin(fd), 'l' in flags, 'x' in flags)
332 332 if f != fd and move and os.path.lexists(repo.wjoin(f)):
333 333 repo.ui.debug("removing %s\n" % f)
334 334 os.unlink(repo.wjoin(f))
335 335 elif m == "g": # get
336 336 flags = a[2]
337 337 repo.ui.note(_("getting %s\n") % f)
338 338 t = mctx.filectx(f).data()
339 339 repo.wwrite(f, t, flags)
340 340 t = None
341 341 updated += 1
342 342 if f == '.hgsubstate': # subrepo states need updating
343 subrepo.submerge(repo, wctx, mctx, wctx)
343 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
344 344 elif m == "d": # directory rename
345 345 f2, fd, flags = a[2:]
346 346 if f:
347 347 repo.ui.note(_("moving %s to %s\n") % (f, fd))
348 348 t = wctx.filectx(f).data()
349 349 repo.wwrite(fd, t, flags)
350 350 util.unlink(repo.wjoin(f))
351 351 if f2:
352 352 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
353 353 t = mctx.filectx(f2).data()
354 354 repo.wwrite(fd, t, flags)
355 355 updated += 1
356 356 elif m == "dr": # divergent renames
357 357 fl = a[2]
358 358 repo.ui.warn(_("note: possible conflict - %s was renamed "
359 359 "multiple times to:\n") % f)
360 360 for nf in fl:
361 361 repo.ui.warn(" %s\n" % nf)
362 362 elif m == "e": # exec
363 363 flags = a[2]
364 364 util.set_flags(repo.wjoin(f), 'l' in flags, 'x' in flags)
365 365 ms.commit()
366 366 u.progress(_('updating'), None, total=numupdates, unit=_('files'))
367 367
368 368 return updated, merged, removed, unresolved
369 369
370 370 def recordupdates(repo, action, branchmerge):
371 371 "record merge actions to the dirstate"
372 372
373 373 for a in action:
374 374 f, m = a[:2]
375 375 if m == "r": # remove
376 376 if branchmerge:
377 377 repo.dirstate.remove(f)
378 378 else:
379 379 repo.dirstate.forget(f)
380 380 elif m == "a": # re-add
381 381 if not branchmerge:
382 382 repo.dirstate.add(f)
383 383 elif m == "f": # forget
384 384 repo.dirstate.forget(f)
385 385 elif m == "e": # exec change
386 386 repo.dirstate.normallookup(f)
387 387 elif m == "g": # get
388 388 if branchmerge:
389 389 repo.dirstate.otherparent(f)
390 390 else:
391 391 repo.dirstate.normal(f)
392 392 elif m == "m": # merge
393 393 f2, fd, flag, move = a[2:]
394 394 if branchmerge:
395 395 # We've done a branch merge, mark this file as merged
396 396 # so that we properly record the merger later
397 397 repo.dirstate.merge(fd)
398 398 if f != f2: # copy/rename
399 399 if move:
400 400 repo.dirstate.remove(f)
401 401 if f != fd:
402 402 repo.dirstate.copy(f, fd)
403 403 else:
404 404 repo.dirstate.copy(f2, fd)
405 405 else:
406 406 # We've update-merged a locally modified file, so
407 407 # we set the dirstate to emulate a normal checkout
408 408 # of that file some time in the past. Thus our
409 409 # merge will appear as a normal local file
410 410 # modification.
411 411 if f2 == fd: # file not locally copied/moved
412 412 repo.dirstate.normallookup(fd)
413 413 if move:
414 414 repo.dirstate.forget(f)
415 415 elif m == "d": # directory rename
416 416 f2, fd, flag = a[2:]
417 417 if not f2 and f not in repo.dirstate:
418 418 # untracked file moved
419 419 continue
420 420 if branchmerge:
421 421 repo.dirstate.add(fd)
422 422 if f:
423 423 repo.dirstate.remove(f)
424 424 repo.dirstate.copy(f, fd)
425 425 if f2:
426 426 repo.dirstate.copy(f2, fd)
427 427 else:
428 428 repo.dirstate.normal(fd)
429 429 if f:
430 430 repo.dirstate.forget(f)
431 431
432 432 def update(repo, node, branchmerge, force, partial):
433 433 """
434 434 Perform a merge between the working directory and the given node
435 435
436 436 node = the node to update to, or None if unspecified
437 437 branchmerge = whether to merge between branches
438 438 force = whether to force branch merging or file overwriting
439 439 partial = a function to filter file lists (dirstate not updated)
440 440
441 441 The table below shows all the behaviors of the update command
442 442 given the -c and -C or no options, whether the working directory
443 443 is dirty, whether a revision is specified, and the relationship of
444 444 the parent rev to the target rev (linear, on the same named
445 445 branch, or on another named branch).
446 446
447 447 This logic is tested by test-update-branches.t.
448 448
449 449 -c -C dirty rev | linear same cross
450 450 n n n n | ok (1) x
451 451 n n n y | ok ok ok
452 452 n n y * | merge (2) (2)
453 453 n y * * | --- discard ---
454 454 y n y * | --- (3) ---
455 455 y n n * | --- ok ---
456 456 y y * * | --- (4) ---
457 457
458 458 x = can't happen
459 459 * = don't-care
460 460 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
461 461 2 = abort: crosses branches (use 'hg merge' to merge or
462 462 use 'hg update -C' to discard changes)
463 463 3 = abort: uncommitted local changes
464 464 4 = incompatible options (checked in commands.py)
465 465 """
466 466
467 467 onode = node
468 468 wlock = repo.wlock()
469 469 try:
470 470 wc = repo[None]
471 471 if node is None:
472 472 # tip of current branch
473 473 try:
474 474 node = repo.branchtags()[wc.branch()]
475 475 except KeyError:
476 476 if wc.branch() == "default": # no default branch!
477 477 node = repo.lookup("tip") # update to tip
478 478 else:
479 479 raise util.Abort(_("branch %s not found") % wc.branch())
480 480 overwrite = force and not branchmerge
481 481 pl = wc.parents()
482 482 p1, p2 = pl[0], repo[node]
483 483 pa = p1.ancestor(p2)
484 484 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
485 485 fastforward = False
486 486
487 487 ### check phase
488 488 if not overwrite and len(pl) > 1:
489 489 raise util.Abort(_("outstanding uncommitted merges"))
490 490 if branchmerge:
491 491 if pa == p2:
492 492 raise util.Abort(_("merging with a working directory ancestor"
493 493 " has no effect"))
494 494 elif pa == p1:
495 495 if p1.branch() != p2.branch():
496 496 fastforward = True
497 497 else:
498 498 raise util.Abort(_("nothing to merge (use 'hg update'"
499 499 " or check 'hg heads')"))
500 500 if not force and (wc.files() or wc.deleted()):
501 501 raise util.Abort(_("outstanding uncommitted changes "
502 502 "(use 'hg status' to list changes)"))
503 503 elif not overwrite:
504 504 if pa == p1 or pa == p2: # linear
505 505 pass # all good
506 506 elif wc.files() or wc.deleted():
507 507 raise util.Abort(_("crosses branches (merge branches or use"
508 508 " --clean to discard changes)"))
509 509 elif onode is None:
510 510 raise util.Abort(_("crosses branches (merge branches or use"
511 511 " --check to force update)"))
512 512 else:
513 513 # Allow jumping branches if clean and specific rev given
514 514 overwrite = True
515 515
516 516 ### calculate phase
517 517 action = []
518 518 wc.status(unknown=True) # prime cache
519 519 if not force:
520 520 _checkunknown(wc, p2)
521 521 if not util.checkcase(repo.path):
522 522 _checkcollision(p2)
523 523 action += _forgetremoved(wc, p2, branchmerge)
524 524 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
525 525
526 526 ### apply phase
527 527 if not branchmerge: # just jump to the new rev
528 528 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
529 529 if not partial:
530 530 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
531 531
532 stats = applyupdates(repo, action, wc, p2, pa)
532 stats = applyupdates(repo, action, wc, p2, pa, overwrite)
533 533
534 534 if not partial:
535 535 repo.dirstate.setparents(fp1, fp2)
536 536 recordupdates(repo, action, branchmerge)
537 537 if not branchmerge and not fastforward:
538 538 repo.dirstate.setbranch(p2.branch())
539 539 finally:
540 540 wlock.release()
541 541
542 542 if not partial:
543 543 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
544 544 return stats
@@ -1,620 +1,622 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 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 24 try:
25 25 data = ctx[f].data()
26 26 except IOError, err:
27 27 if err.errno != errno.ENOENT:
28 28 raise
29 29 # handle missing subrepo spec files as removed
30 30 ui.warn(_("warning: subrepo spec file %s not found\n") % f)
31 31 return
32 32 p.parse(f, data, sections, remap, read)
33 33 else:
34 34 raise util.Abort(_("subrepo spec file %s not found") % f)
35 35
36 36 if '.hgsub' in ctx:
37 37 read('.hgsub')
38 38
39 39 for path, src in ui.configitems('subpaths'):
40 40 p.set('subpaths', path, src, ui.configsource('subpaths', path))
41 41
42 42 rev = {}
43 43 if '.hgsubstate' in ctx:
44 44 try:
45 45 for l in ctx['.hgsubstate'].data().splitlines():
46 46 revision, path = l.split(" ", 1)
47 47 rev[path] = revision
48 48 except IOError, err:
49 49 if err.errno != errno.ENOENT:
50 50 raise
51 51
52 52 state = {}
53 53 for path, src in p[''].items():
54 54 kind = 'hg'
55 55 if src.startswith('['):
56 56 if ']' not in src:
57 57 raise util.Abort(_('missing ] in subrepo source'))
58 58 kind, src = src.split(']', 1)
59 59 kind = kind[1:]
60 60
61 61 for pattern, repl in p.items('subpaths'):
62 62 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
63 63 # does a string decode.
64 64 repl = repl.encode('string-escape')
65 65 # However, we still want to allow back references to go
66 66 # through unharmed, so we turn r'\\1' into r'\1'. Again,
67 67 # extra escapes are needed because re.sub string decodes.
68 68 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
69 69 try:
70 70 src = re.sub(pattern, repl, src, 1)
71 71 except re.error, e:
72 72 raise util.Abort(_("bad subrepository pattern in %s: %s")
73 73 % (p.source('subpaths', pattern), e))
74 74
75 75 state[path] = (src.strip(), rev.get(path, ''), kind)
76 76
77 77 return state
78 78
79 79 def writestate(repo, state):
80 80 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
81 81 repo.wwrite('.hgsubstate',
82 82 ''.join(['%s %s\n' % (state[s][1], s)
83 83 for s in sorted(state)]), '')
84 84
85 def submerge(repo, wctx, mctx, actx):
85 def submerge(repo, wctx, mctx, actx, overwrite):
86 86 """delegated from merge.applyupdates: merging of .hgsubstate file
87 87 in working context, merging context and ancestor context"""
88 88 if mctx == actx: # backwards?
89 89 actx = wctx.p1()
90 90 s1 = wctx.substate
91 91 s2 = mctx.substate
92 92 sa = actx.substate
93 93 sm = {}
94 94
95 95 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
96 96
97 97 def debug(s, msg, r=""):
98 98 if r:
99 99 r = "%s:%s:%s" % r
100 100 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
101 101
102 102 for s, l in s1.items():
103 103 a = sa.get(s, nullstate)
104 104 ld = l # local state with possible dirty flag for compares
105 105 if wctx.sub(s).dirty():
106 106 ld = (l[0], l[1] + "+")
107 107 if wctx == actx: # overwrite
108 108 a = ld
109 109
110 110 if s in s2:
111 111 r = s2[s]
112 112 if ld == r or r == a: # no change or local is newer
113 113 sm[s] = l
114 114 continue
115 115 elif ld == a: # other side changed
116 116 debug(s, "other changed, get", r)
117 wctx.sub(s).get(r)
117 wctx.sub(s).get(r, overwrite)
118 118 sm[s] = r
119 119 elif ld[0] != r[0]: # sources differ
120 120 if repo.ui.promptchoice(
121 121 _(' subrepository sources for %s differ\n'
122 122 'use (l)ocal source (%s) or (r)emote source (%s)?')
123 123 % (s, l[0], r[0]),
124 124 (_('&Local'), _('&Remote')), 0):
125 125 debug(s, "prompt changed, get", r)
126 wctx.sub(s).get(r)
126 wctx.sub(s).get(r, overwrite)
127 127 sm[s] = r
128 128 elif ld[1] == a[1]: # local side is unchanged
129 129 debug(s, "other side changed, get", r)
130 wctx.sub(s).get(r)
130 wctx.sub(s).get(r, overwrite)
131 131 sm[s] = r
132 132 else:
133 133 debug(s, "both sides changed, merge with", r)
134 134 wctx.sub(s).merge(r)
135 135 sm[s] = l
136 136 elif ld == a: # remote removed, local unchanged
137 137 debug(s, "remote removed, remove")
138 138 wctx.sub(s).remove()
139 139 else:
140 140 if repo.ui.promptchoice(
141 141 _(' local changed subrepository %s which remote removed\n'
142 142 'use (c)hanged version or (d)elete?') % s,
143 143 (_('&Changed'), _('&Delete')), 0):
144 144 debug(s, "prompt remove")
145 145 wctx.sub(s).remove()
146 146
147 147 for s, r in s2.items():
148 148 if s in s1:
149 149 continue
150 150 elif s not in sa:
151 151 debug(s, "remote added, get", r)
152 152 mctx.sub(s).get(r)
153 153 sm[s] = r
154 154 elif r != sa[s]:
155 155 if repo.ui.promptchoice(
156 156 _(' remote changed subrepository %s which local removed\n'
157 157 'use (c)hanged version or (d)elete?') % s,
158 158 (_('&Changed'), _('&Delete')), 0) == 0:
159 159 debug(s, "prompt recreate", r)
160 160 wctx.sub(s).get(r)
161 161 sm[s] = r
162 162
163 163 # record merged .hgsubstate
164 164 writestate(repo, sm)
165 165
166 166 def reporelpath(repo):
167 167 """return path to this (sub)repo as seen from outermost repo"""
168 168 parent = repo
169 169 while hasattr(parent, '_subparent'):
170 170 parent = parent._subparent
171 171 return repo.root[len(parent.root)+1:]
172 172
173 173 def subrelpath(sub):
174 174 """return path to this subrepo as seen from outermost repo"""
175 175 if not hasattr(sub, '_repo'):
176 176 return sub._path
177 177 return reporelpath(sub._repo)
178 178
179 179 def _abssource(repo, push=False, abort=True):
180 180 """return pull/push path of repo - either based on parent repo .hgsub info
181 181 or on the top repo config. Abort or return None if no source found."""
182 182 if hasattr(repo, '_subparent'):
183 183 source = repo._subsource
184 184 if source.startswith('/') or '://' in source:
185 185 return source
186 186 parent = _abssource(repo._subparent, push, abort=False)
187 187 if parent:
188 188 if '://' in parent:
189 189 if parent[-1] == '/':
190 190 parent = parent[:-1]
191 191 r = urlparse.urlparse(parent + '/' + source)
192 192 r = urlparse.urlunparse((r[0], r[1],
193 193 posixpath.normpath(r[2]),
194 194 r[3], r[4], r[5]))
195 195 return r
196 196 else: # plain file system path
197 197 return posixpath.normpath(os.path.join(parent, repo._subsource))
198 198 else: # recursion reached top repo
199 199 if hasattr(repo, '_subtoppath'):
200 200 return repo._subtoppath
201 201 if push and repo.ui.config('paths', 'default-push'):
202 202 return repo.ui.config('paths', 'default-push')
203 203 if repo.ui.config('paths', 'default'):
204 204 return repo.ui.config('paths', 'default')
205 205 if abort:
206 206 raise util.Abort(_("default path for subrepository %s not found") %
207 207 reporelpath(repo))
208 208
209 209 def itersubrepos(ctx1, ctx2):
210 210 """find subrepos in ctx1 or ctx2"""
211 211 # Create a (subpath, ctx) mapping where we prefer subpaths from
212 212 # ctx1. The subpaths from ctx2 are important when the .hgsub file
213 213 # has been modified (in ctx2) but not yet committed (in ctx1).
214 214 subpaths = dict.fromkeys(ctx2.substate, ctx2)
215 215 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
216 216 for subpath, ctx in sorted(subpaths.iteritems()):
217 217 yield subpath, ctx.sub(subpath)
218 218
219 219 def subrepo(ctx, path):
220 220 """return instance of the right subrepo class for subrepo in path"""
221 221 # subrepo inherently violates our import layering rules
222 222 # because it wants to make repo objects from deep inside the stack
223 223 # so we manually delay the circular imports to not break
224 224 # scripts that don't use our demand-loading
225 225 global hg
226 226 import hg as h
227 227 hg = h
228 228
229 229 util.path_auditor(ctx._repo.root)(path)
230 230 state = ctx.substate.get(path, nullstate)
231 231 if state[2] not in types:
232 232 raise util.Abort(_('unknown subrepo type %s') % state[2])
233 233 return types[state[2]](ctx, path, state[:2])
234 234
235 235 # subrepo classes need to implement the following abstract class:
236 236
237 237 class abstractsubrepo(object):
238 238
239 239 def dirty(self):
240 240 """returns true if the dirstate of the subrepo does not match
241 241 current stored state
242 242 """
243 243 raise NotImplementedError
244 244
245 245 def checknested(self, path):
246 246 """check if path is a subrepository within this repository"""
247 247 return False
248 248
249 249 def commit(self, text, user, date):
250 250 """commit the current changes to the subrepo with the given
251 251 log message. Use given user and date if possible. Return the
252 252 new state of the subrepo.
253 253 """
254 254 raise NotImplementedError
255 255
256 256 def remove(self):
257 257 """remove the subrepo
258 258
259 259 (should verify the dirstate is not dirty first)
260 260 """
261 261 raise NotImplementedError
262 262
263 def get(self, state):
263 def get(self, state, overwrite=False):
264 264 """run whatever commands are needed to put the subrepo into
265 265 this state
266 266 """
267 267 raise NotImplementedError
268 268
269 def merge(self, state):
269 def merge(self, state, overwrite=False):
270 270 """merge currently-saved state with the new state."""
271 271 raise NotImplementedError
272 272
273 273 def push(self, force):
274 274 """perform whatever action is analogous to 'hg push'
275 275
276 276 This may be a no-op on some systems.
277 277 """
278 278 raise NotImplementedError
279 279
280 280 def add(self, ui, match, dryrun, prefix):
281 281 return []
282 282
283 283 def status(self, rev2, **opts):
284 284 return [], [], [], [], [], [], []
285 285
286 286 def diff(self, diffopts, node2, match, prefix, **opts):
287 287 pass
288 288
289 289 def outgoing(self, ui, dest, opts):
290 290 return 1
291 291
292 292 def incoming(self, ui, source, opts):
293 293 return 1
294 294
295 295 def files(self):
296 296 """return filename iterator"""
297 297 raise NotImplementedError
298 298
299 299 def filedata(self, name):
300 300 """return file data"""
301 301 raise NotImplementedError
302 302
303 303 def fileflags(self, name):
304 304 """return file flags"""
305 305 return ''
306 306
307 307 def archive(self, archiver, prefix):
308 308 for name in self.files():
309 309 flags = self.fileflags(name)
310 310 mode = 'x' in flags and 0755 or 0644
311 311 symlink = 'l' in flags
312 312 archiver.addfile(os.path.join(prefix, self._path, name),
313 313 mode, symlink, self.filedata(name))
314 314
315 315
316 316 class hgsubrepo(abstractsubrepo):
317 317 def __init__(self, ctx, path, state):
318 318 self._path = path
319 319 self._state = state
320 320 r = ctx._repo
321 321 root = r.wjoin(path)
322 322 create = False
323 323 if not os.path.exists(os.path.join(root, '.hg')):
324 324 create = True
325 325 util.makedirs(root)
326 326 self._repo = hg.repository(r.ui, root, create=create)
327 327 self._repo._subparent = r
328 328 self._repo._subsource = state[0]
329 329
330 330 if create:
331 331 fp = self._repo.opener("hgrc", "w", text=True)
332 332 fp.write('[paths]\n')
333 333
334 334 def addpathconfig(key, value):
335 335 if value:
336 336 fp.write('%s = %s\n' % (key, value))
337 337 self._repo.ui.setconfig('paths', key, value)
338 338
339 339 defpath = _abssource(self._repo, abort=False)
340 340 defpushpath = _abssource(self._repo, True, abort=False)
341 341 addpathconfig('default', defpath)
342 342 if defpath != defpushpath:
343 343 addpathconfig('default-push', defpushpath)
344 344 fp.close()
345 345
346 346 def add(self, ui, match, dryrun, prefix):
347 347 return cmdutil.add(ui, self._repo, match, dryrun, True,
348 348 os.path.join(prefix, self._path))
349 349
350 350 def status(self, rev2, **opts):
351 351 try:
352 352 rev1 = self._state[1]
353 353 ctx1 = self._repo[rev1]
354 354 ctx2 = self._repo[rev2]
355 355 return self._repo.status(ctx1, ctx2, **opts)
356 356 except error.RepoLookupError, inst:
357 357 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
358 358 % (inst, subrelpath(self)))
359 359 return [], [], [], [], [], [], []
360 360
361 361 def diff(self, diffopts, node2, match, prefix, **opts):
362 362 try:
363 363 node1 = node.bin(self._state[1])
364 364 # We currently expect node2 to come from substate and be
365 365 # in hex format
366 366 if node2 is not None:
367 367 node2 = node.bin(node2)
368 368 cmdutil.diffordiffstat(self._repo.ui, self._repo, diffopts,
369 369 node1, node2, match,
370 370 prefix=os.path.join(prefix, self._path),
371 371 listsubrepos=True, **opts)
372 372 except error.RepoLookupError, inst:
373 373 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
374 374 % (inst, subrelpath(self)))
375 375
376 376 def archive(self, archiver, prefix):
377 377 abstractsubrepo.archive(self, archiver, prefix)
378 378
379 379 rev = self._state[1]
380 380 ctx = self._repo[rev]
381 381 for subpath in ctx.substate:
382 382 s = subrepo(ctx, subpath)
383 383 s.archive(archiver, os.path.join(prefix, self._path))
384 384
385 385 def dirty(self):
386 386 r = self._state[1]
387 387 if r == '':
388 388 return True
389 389 w = self._repo[None]
390 390 if w.p1() != self._repo[r]: # version checked out change
391 391 return True
392 392 return w.dirty() # working directory changed
393 393
394 394 def checknested(self, path):
395 395 return self._repo._checknested(self._repo.wjoin(path))
396 396
397 397 def commit(self, text, user, date):
398 398 self._repo.ui.debug("committing subrepo %s\n" % subrelpath(self))
399 399 n = self._repo.commit(text, user, date)
400 400 if not n:
401 401 return self._repo['.'].hex() # different version checked out
402 402 return node.hex(n)
403 403
404 404 def remove(self):
405 405 # we can't fully delete the repository as it may contain
406 406 # local-only history
407 407 self._repo.ui.note(_('removing subrepo %s\n') % subrelpath(self))
408 408 hg.clean(self._repo, node.nullid, False)
409 409
410 410 def _get(self, state):
411 411 source, revision, kind = state
412 412 try:
413 413 self._repo.lookup(revision)
414 414 except error.RepoError:
415 415 self._repo._subsource = source
416 416 srcurl = _abssource(self._repo)
417 417 self._repo.ui.status(_('pulling subrepo %s from %s\n')
418 418 % (subrelpath(self), srcurl))
419 419 other = hg.repository(self._repo.ui, srcurl)
420 420 self._repo.pull(other)
421 421
422 def get(self, state):
422 def get(self, state, overwrite=False):
423 423 self._get(state)
424 424 source, revision, kind = state
425 425 self._repo.ui.debug("getting subrepo %s\n" % self._path)
426 426 hg.clean(self._repo, revision, False)
427 427
428 428 def merge(self, state):
429 429 self._get(state)
430 430 cur = self._repo['.']
431 431 dst = self._repo[state[1]]
432 432 anc = dst.ancestor(cur)
433 433 if anc == cur:
434 434 self._repo.ui.debug("updating subrepo %s\n" % subrelpath(self))
435 435 hg.update(self._repo, state[1])
436 436 elif anc == dst:
437 437 self._repo.ui.debug("skipping subrepo %s\n" % subrelpath(self))
438 438 else:
439 439 self._repo.ui.debug("merging subrepo %s\n" % subrelpath(self))
440 440 hg.merge(self._repo, state[1], remind=False)
441 441
442 442 def push(self, force):
443 443 # push subrepos depth-first for coherent ordering
444 444 c = self._repo['']
445 445 subs = c.substate # only repos that are committed
446 446 for s in sorted(subs):
447 447 if not c.sub(s).push(force):
448 448 return False
449 449
450 450 dsturl = _abssource(self._repo, True)
451 451 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
452 452 (subrelpath(self), dsturl))
453 453 other = hg.repository(self._repo.ui, dsturl)
454 454 return self._repo.push(other, force)
455 455
456 456 def outgoing(self, ui, dest, opts):
457 457 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
458 458
459 459 def incoming(self, ui, source, opts):
460 460 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
461 461
462 462 def files(self):
463 463 rev = self._state[1]
464 464 ctx = self._repo[rev]
465 465 return ctx.manifest()
466 466
467 467 def filedata(self, name):
468 468 rev = self._state[1]
469 469 return self._repo[rev][name].data()
470 470
471 471 def fileflags(self, name):
472 472 rev = self._state[1]
473 473 ctx = self._repo[rev]
474 474 return ctx.flags(name)
475 475
476 476
477 477 class svnsubrepo(abstractsubrepo):
478 478 def __init__(self, ctx, path, state):
479 479 self._path = path
480 480 self._state = state
481 481 self._ctx = ctx
482 482 self._ui = ctx._repo.ui
483 483
484 484 def _svncommand(self, commands, filename=''):
485 485 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
486 486 cmd = ['svn'] + commands + [path]
487 487 cmd = [util.shellquote(arg) for arg in cmd]
488 488 cmd = util.quotecommand(' '.join(cmd))
489 489 env = dict(os.environ)
490 490 # Avoid localized output, preserve current locale for everything else.
491 491 env['LC_MESSAGES'] = 'C'
492 492 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
493 493 close_fds=util.closefds,
494 494 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
495 495 universal_newlines=True, env=env)
496 496 stdout, stderr = p.communicate()
497 497 stderr = stderr.strip()
498 498 if stderr:
499 499 raise util.Abort(stderr)
500 500 return stdout
501 501
502 502 def _wcrevs(self):
503 503 # Get the working directory revision as well as the last
504 504 # commit revision so we can compare the subrepo state with
505 505 # both. We used to store the working directory one.
506 506 output = self._svncommand(['info', '--xml'])
507 507 doc = xml.dom.minidom.parseString(output)
508 508 entries = doc.getElementsByTagName('entry')
509 509 lastrev, rev = '0', '0'
510 510 if entries:
511 511 rev = str(entries[0].getAttribute('revision')) or '0'
512 512 commits = entries[0].getElementsByTagName('commit')
513 513 if commits:
514 514 lastrev = str(commits[0].getAttribute('revision')) or '0'
515 515 return (lastrev, rev)
516 516
517 517 def _wcrev(self):
518 518 return self._wcrevs()[0]
519 519
520 520 def _wcchanged(self):
521 521 """Return (changes, extchanges) where changes is True
522 522 if the working directory was changed, and extchanges is
523 523 True if any of these changes concern an external entry.
524 524 """
525 525 output = self._svncommand(['status', '--xml'])
526 526 externals, changes = [], []
527 527 doc = xml.dom.minidom.parseString(output)
528 528 for e in doc.getElementsByTagName('entry'):
529 529 s = e.getElementsByTagName('wc-status')
530 530 if not s:
531 531 continue
532 532 item = s[0].getAttribute('item')
533 533 props = s[0].getAttribute('props')
534 534 path = e.getAttribute('path')
535 535 if item == 'external':
536 536 externals.append(path)
537 537 if (item not in ('', 'normal', 'unversioned', 'external')
538 538 or props not in ('', 'none')):
539 539 changes.append(path)
540 540 for path in changes:
541 541 for ext in externals:
542 542 if path == ext or path.startswith(ext + os.sep):
543 543 return True, True
544 544 return bool(changes), False
545 545
546 546 def dirty(self):
547 547 if self._state[1] in self._wcrevs() and not self._wcchanged()[0]:
548 548 return False
549 549 return True
550 550
551 551 def commit(self, text, user, date):
552 552 # user and date are out of our hands since svn is centralized
553 553 changed, extchanged = self._wcchanged()
554 554 if not changed:
555 555 return self._wcrev()
556 556 if extchanged:
557 557 # Do not try to commit externals
558 558 raise util.Abort(_('cannot commit svn externals'))
559 559 commitinfo = self._svncommand(['commit', '-m', text])
560 560 self._ui.status(commitinfo)
561 561 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
562 562 if not newrev:
563 563 raise util.Abort(commitinfo.splitlines()[-1])
564 564 newrev = newrev.groups()[0]
565 565 self._ui.status(self._svncommand(['update', '-r', newrev]))
566 566 return newrev
567 567
568 568 def remove(self):
569 569 if self.dirty():
570 570 self._ui.warn(_('not removing repo %s because '
571 571 'it has changes.\n' % self._path))
572 572 return
573 573 self._ui.note(_('removing subrepo %s\n') % self._path)
574 574
575 575 def onerror(function, path, excinfo):
576 576 if function is not os.remove:
577 577 raise
578 578 # read-only files cannot be unlinked under Windows
579 579 s = os.stat(path)
580 580 if (s.st_mode & stat.S_IWRITE) != 0:
581 581 raise
582 582 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
583 583 os.remove(path)
584 584
585 585 path = self._ctx._repo.wjoin(self._path)
586 586 shutil.rmtree(path, onerror=onerror)
587 587 try:
588 588 os.removedirs(os.path.dirname(path))
589 589 except OSError:
590 590 pass
591 591
592 def get(self, state):
592 def get(self, state, overwrite=False):
593 if overwrite:
594 self._svncommand(['revert', '--recursive', self._path])
593 595 status = self._svncommand(['checkout', state[0], '--revision', state[1]])
594 596 if not re.search('Checked out revision [0-9]+.', status):
595 597 raise util.Abort(status.splitlines()[-1])
596 598 self._ui.status(status)
597 599
598 600 def merge(self, state):
599 601 old = int(self._state[1])
600 602 new = int(state[1])
601 603 if new > old:
602 604 self.get(state)
603 605
604 606 def push(self, force):
605 607 # push is a no-op for SVN
606 608 return True
607 609
608 610 def files(self):
609 611 output = self._svncommand(['list'])
610 612 # This works because svn forbids \n in filenames.
611 613 return output.splitlines()
612 614
613 615 def filedata(self, name):
614 616 return self._svncommand(['cat'], name)
615 617
616 618
617 619 types = {
618 620 'hg': hgsubrepo,
619 621 'svn': svnsubrepo,
620 622 }
@@ -1,266 +1,298 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 $ echo "subdir/s = [svn] $SVNREPO/src" >> .hgsub
68 68 $ svn co --quiet "$SVNREPO"/src s
69 69 $ mkdir subdir
70 70 $ svn co --quiet "$SVNREPO"/src subdir/s
71 71 $ hg add .hgsub
72 72 $ hg ci -m1
73 73 committing subrepository s
74 74 committing subrepository subdir/s
75 75
76 76 make sure we avoid empty commits (issue2445)
77 77
78 78 $ hg sum
79 79 parent: 1:* tip (glob)
80 80 1
81 81 branch: default
82 82 commit: (clean)
83 83 update: (current)
84 84 $ hg ci -moops
85 85 nothing changed
86 86 [1]
87 87
88 88 debugsub
89 89
90 90 $ hg debugsub
91 91 path s
92 92 source file://*/svn-repo/src (glob)
93 93 revision 2
94 94 path subdir/s
95 95 source file://*/svn-repo/src (glob)
96 96 revision 2
97 97
98 98 change file in svn and hg, commit
99 99
100 100 $ echo a >> a
101 101 $ echo alpha >> s/alpha
102 102 $ hg sum
103 103 parent: 1:* tip (glob)
104 104 1
105 105 branch: default
106 106 commit: 1 modified, 1 subrepos
107 107 update: (current)
108 108 $ hg commit -m 'Message!'
109 109 committing subrepository s
110 110 Sending*s/alpha (glob)
111 111 Transmitting file data .
112 112 Committed revision 3.
113 113
114 114 Fetching external item into '$TESTTMP/sub/t/s/externals'
115 115 External at revision 1.
116 116
117 117 At revision 3.
118 118 $ hg debugsub
119 119 path s
120 120 source file://*/svn-repo/src (glob)
121 121 revision 3
122 122 path subdir/s
123 123 source file://*/svn-repo/src (glob)
124 124 revision 2
125 125
126 126 add an unrelated revision in svn and update the subrepo to without
127 127 bringing any changes.
128 128
129 129 $ svn mkdir --parents "$SVNREPO/unrelated" -m 'create unrelated'
130 130
131 131 Committed revision 4.
132 132 $ svn up s
133 133
134 134 Fetching external item into 's/externals'
135 135 External at revision 1.
136 136
137 137 At revision 4.
138 138 $ hg sum
139 139 parent: 2:* tip (glob)
140 140 Message!
141 141 branch: default
142 142 commit: (clean)
143 143 update: (current)
144 144
145 145 $ echo a > s/a
146 146
147 147 should be empty despite change to s/a
148 148
149 149 $ hg st
150 150
151 151 add a commit from svn
152 152
153 153 $ cd "$WCROOT"/src
154 154 $ svn up
155 155 U alpha
156 156
157 157 Fetching external item into 'externals'
158 158 A externals/other
159 159 Updated external to revision 1.
160 160
161 161 Updated to revision 4.
162 162 $ echo xyz >> alpha
163 163 $ svn propset svn:mime-type 'text/xml' alpha
164 164 property 'svn:mime-type' set on 'alpha'
165 165 $ svn ci -m 'amend a from svn'
166 166 Sending src/alpha
167 167 Transmitting file data .
168 168 Committed revision 5.
169 169 $ cd ../../sub/t
170 170
171 171 this commit from hg will fail
172 172
173 173 $ echo zzz >> s/alpha
174 174 $ hg ci -m 'amend alpha from hg'
175 175 committing subrepository s
176 176 abort: svn: Commit failed (details follow):
177 177 svn: (Out of date)?.*/src/alpha.*(is out of date)? (re)
178 178 [255]
179 179 $ svn revert -q s/alpha
180 180
181 181 this commit fails because of meta changes
182 182
183 183 $ svn propset svn:mime-type 'text/html' s/alpha
184 184 property 'svn:mime-type' set on 's/alpha'
185 185 $ hg ci -m 'amend alpha from hg'
186 186 committing subrepository s
187 187 abort: svn: Commit failed (details follow):
188 188 svn: (Out of date)?.*/src/alpha.*(is out of date)? (re)
189 189 [255]
190 190 $ svn revert -q s/alpha
191 191
192 192 this commit fails because of externals changes
193 193
194 194 $ echo zzz > s/externals/other
195 195 $ hg ci -m 'amend externals from hg'
196 196 committing subrepository s
197 197 abort: cannot commit svn externals
198 198 [255]
199 199 $ hg diff --subrepos -r 1:2 | grep -v diff
200 200 --- a/.hgsubstate Thu Jan 01 00:00:00 1970 +0000
201 201 +++ b/.hgsubstate Thu Jan 01 00:00:00 1970 +0000
202 202 @@ -1,2 +1,2 @@
203 203 -2 s
204 204 +3 s
205 205 2 subdir/s
206 206 --- a/a Thu Jan 01 00:00:00 1970 +0000
207 207 +++ b/a Thu Jan 01 00:00:00 1970 +0000
208 208 @@ -1,1 +1,2 @@
209 209 a
210 210 +a
211 211 $ svn revert -q s/externals/other
212 212
213 213 this commit fails because of externals meta changes
214 214
215 215 $ svn propset svn:mime-type 'text/html' s/externals/other
216 216 property 'svn:mime-type' set on 's/externals/other'
217 217 $ hg ci -m 'amend externals from hg'
218 218 committing subrepository s
219 219 abort: cannot commit svn externals
220 220 [255]
221 221 $ svn revert -q s/externals/other
222 222
223 223 clone
224 224
225 225 $ cd ..
226 226 $ hg clone t tc | fix_path
227 227 updating to branch default
228 228 A tc/subdir/s/alpha
229 229 U tc/subdir/s
230 230
231 231 Fetching external item into 'tc/subdir/s/externals'
232 232 A tc/subdir/s/externals/other
233 233 Checked out external at revision 1.
234 234
235 235 Checked out revision 2.
236 236 A tc/s/alpha
237 237 U tc/s
238 238
239 239 Fetching external item into 'tc/s/externals'
240 240 A tc/s/externals/other
241 241 Checked out external at revision 1.
242 242
243 243 Checked out revision 3.
244 244 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
245 245 $ cd tc
246 246
247 247 debugsub in clone
248 248
249 249 $ hg debugsub
250 250 path s
251 251 source file://*/svn-repo/src (glob)
252 252 revision 3
253 253 path subdir/s
254 254 source file://*/svn-repo/src (glob)
255 255 revision 2
256 256
257 257 verify subrepo is contained within the repo directory
258 258
259 259 $ python -c "import os.path; print os.path.exists('s')"
260 260 True
261 261
262 262 update to nullrev (must delete the subrepo)
263 263
264 264 $ hg up null
265 265 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
266 266 $ ls
267
268 Check hg update --clean
269 $ cd $TESTTMP/sub/t
270 $ cd s
271 $ echo c0 > alpha
272 $ echo c1 > f1
273 $ echo c1 > f2
274 $ svn add f1 -q
275 $ svn status
276 ? a
277 X externals
278 ? f2
279 M alpha
280 A f1
281
282 Performing status on external item at 'externals'
283 $ cd ..
284 $ hg update -C
285
286 Fetching external item into '$TESTTMP/sub/t/s/externals'
287 Checked out external at revision 1.
288
289 Checked out revision 3.
290 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
291 $ cd s
292 $ svn status
293 ? a
294 X externals
295 ? f1
296 ? f2
297
298 Performing status on external item at 'externals'
@@ -1,677 +1,705 b''
1 1 $ rm -rf sub
2 2 $ mkdir sub
3 3 $ cd sub
4 4 $ hg init t
5 5 $ cd t
6 6
7 7 first revision, no sub
8 8
9 9 $ echo a > a
10 10 $ hg ci -Am0
11 11 adding a
12 12
13 13 add first sub
14 14
15 15 $ echo s = s > .hgsub
16 16 $ hg add .hgsub
17 17 $ hg init s
18 18 $ echo a > s/a
19 19
20 20 Issue2232: committing a subrepo without .hgsub
21 21
22 22 $ hg ci -mbad s
23 23 abort: can't commit subrepos without .hgsub
24 24 [255]
25 25
26 26 $ hg -R s ci -Ams0
27 27 adding a
28 28 $ hg sum
29 29 parent: 0:f7b1eb17ad24 tip
30 30 0
31 31 branch: default
32 32 commit: 1 added, 1 subrepos
33 33 update: (current)
34 34 $ hg ci -m1
35 35 committing subrepository s
36 36
37 37 Issue2022: update -C
38 38
39 39 $ echo b > s/a
40 40 $ hg sum
41 41 parent: 1:7cf8cfea66e4 tip
42 42 1
43 43 branch: default
44 44 commit: 1 subrepos
45 45 update: (current)
46 46 $ hg co -C 1
47 47 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
48 48 $ hg sum
49 49 parent: 1:7cf8cfea66e4 tip
50 50 1
51 51 branch: default
52 52 commit: (clean)
53 53 update: (current)
54 54
55 55 add sub sub
56 56
57 57 $ echo ss = ss > s/.hgsub
58 58 $ hg init s/ss
59 59 $ echo a > s/ss/a
60 60 $ hg -R s add s/.hgsub
61 61 $ hg -R s/ss add s/ss/a
62 62 $ hg sum
63 63 parent: 1:7cf8cfea66e4 tip
64 64 1
65 65 branch: default
66 66 commit: 1 subrepos
67 67 update: (current)
68 68 $ hg ci -m2
69 69 committing subrepository s
70 70 committing subrepository s/ss
71 71 $ hg sum
72 72 parent: 2:df30734270ae tip
73 73 2
74 74 branch: default
75 75 commit: (clean)
76 76 update: (current)
77 77
78 78 bump sub rev
79 79
80 80 $ echo b > s/a
81 81 $ hg -R s ci -ms1
82 82 $ hg ci -m3
83 83 committing subrepository s
84 84
85 85 leave sub dirty
86 86
87 87 $ echo c > s/a
88 88 $ hg ci -m4
89 89 committing subrepository s
90 90 $ hg tip -R s
91 91 changeset: 3:1c833a7a9e3a
92 92 tag: tip
93 93 user: test
94 94 date: Thu Jan 01 00:00:00 1970 +0000
95 95 summary: 4
96 96
97 97
98 98 check caching
99 99
100 100 $ hg co 0
101 101 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
102 102 $ hg debugsub
103 103
104 104 restore
105 105
106 106 $ hg co
107 107 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
108 108 $ hg debugsub
109 109 path s
110 110 source s
111 111 revision 1c833a7a9e3a4445c711aaf0f012379cd0d4034e
112 112
113 113 new branch for merge tests
114 114
115 115 $ hg co 1
116 116 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
117 117 $ echo t = t >> .hgsub
118 118 $ hg init t
119 119 $ echo t > t/t
120 120 $ hg -R t add t
121 121 adding t/t
122 122
123 123 5
124 124
125 125 $ hg ci -m5 # add sub
126 126 committing subrepository t
127 127 created new head
128 128 $ echo t2 > t/t
129 129
130 130 6
131 131
132 132 $ hg st -R s
133 133 $ hg ci -m6 # change sub
134 134 committing subrepository t
135 135 $ hg debugsub
136 136 path s
137 137 source s
138 138 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
139 139 path t
140 140 source t
141 141 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
142 142 $ echo t3 > t/t
143 143
144 144 7
145 145
146 146 $ hg ci -m7 # change sub again for conflict test
147 147 committing subrepository t
148 148 $ hg rm .hgsub
149 149
150 150 8
151 151
152 152 $ hg ci -m8 # remove sub
153 153
154 154 merge tests
155 155
156 156 $ hg co -C 3
157 157 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
158 158 $ hg merge 5 # test adding
159 159 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
160 160 (branch merge, don't forget to commit)
161 161 $ hg debugsub
162 162 path s
163 163 source s
164 164 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
165 165 path t
166 166 source t
167 167 revision 60ca1237c19474e7a3978b0dc1ca4e6f36d51382
168 168 $ hg ci -m9
169 169 created new head
170 170 $ hg merge 6 --debug # test change
171 171 searching for copies back to rev 2
172 172 resolving manifests
173 173 overwrite None partial False
174 174 ancestor 1f14a2e2d3ec local f0d2028bf86d+ remote 1831e14459c4
175 175 .hgsubstate: versions differ -> m
176 176 updating: .hgsubstate 1/1 files (100.00%)
177 177 subrepo merge f0d2028bf86d+ 1831e14459c4 1f14a2e2d3ec
178 178 subrepo t: other changed, get t:6747d179aa9a688023c4b0cad32e4c92bb7f34ad:hg
179 179 getting subrepo t
180 180 resolving manifests
181 181 overwrite True partial False
182 182 ancestor 60ca1237c194+ local 60ca1237c194+ remote 6747d179aa9a
183 183 t: remote is newer -> g
184 184 updating: t 1/1 files (100.00%)
185 185 getting t
186 186 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
187 187 (branch merge, don't forget to commit)
188 188 $ hg debugsub
189 189 path s
190 190 source s
191 191 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
192 192 path t
193 193 source t
194 194 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
195 195 $ echo conflict > t/t
196 196 $ hg ci -m10
197 197 committing subrepository t
198 198 $ HGMERGE=internal:merge hg merge --debug 7 # test conflict
199 199 searching for copies back to rev 2
200 200 resolving manifests
201 201 overwrite None partial False
202 202 ancestor 1831e14459c4 local e45c8b14af55+ remote f94576341bcf
203 203 .hgsubstate: versions differ -> m
204 204 updating: .hgsubstate 1/1 files (100.00%)
205 205 subrepo merge e45c8b14af55+ f94576341bcf 1831e14459c4
206 206 subrepo t: both sides changed, merge with t:7af322bc1198a32402fe903e0b7ebcfc5c9bf8f4:hg
207 207 merging subrepo t
208 208 searching for copies back to rev 2
209 209 resolving manifests
210 210 overwrite None partial False
211 211 ancestor 6747d179aa9a local 20a0db6fbf6c+ remote 7af322bc1198
212 212 t: versions differ -> m
213 213 preserving t for resolve of t
214 214 updating: t 1/1 files (100.00%)
215 215 picked tool 'internal:merge' for t (binary False symlink False)
216 216 merging t
217 217 my t@20a0db6fbf6c+ other t@7af322bc1198 ancestor t@6747d179aa9a
218 218 warning: conflicts during merge.
219 219 merging t failed!
220 220 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
221 221 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
222 222 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
223 223 (branch merge, don't forget to commit)
224 224
225 225 should conflict
226 226
227 227 $ cat t/t
228 228 <<<<<<< local
229 229 conflict
230 230 =======
231 231 t3
232 232 >>>>>>> other
233 233
234 234 clone
235 235
236 236 $ cd ..
237 237 $ hg clone t tc
238 238 updating to branch default
239 239 pulling subrepo s from $TESTTMP/sub/t/s
240 240 requesting all changes
241 241 adding changesets
242 242 adding manifests
243 243 adding file changes
244 244 added 4 changesets with 5 changes to 3 files
245 245 pulling subrepo s/ss from $TESTTMP/sub/t/s/ss
246 246 requesting all changes
247 247 adding changesets
248 248 adding manifests
249 249 adding file changes
250 250 added 1 changesets with 1 changes to 1 files
251 251 pulling subrepo t from $TESTTMP/sub/t/t
252 252 requesting all changes
253 253 adding changesets
254 254 adding manifests
255 255 adding file changes
256 256 added 4 changesets with 4 changes to 1 files (+1 heads)
257 257 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
258 258 $ cd tc
259 259 $ hg debugsub
260 260 path s
261 261 source s
262 262 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
263 263 path t
264 264 source t
265 265 revision 20a0db6fbf6c3d2836e6519a642ae929bfc67c0e
266 266
267 267 push
268 268
269 269 $ echo bah > t/t
270 270 $ hg ci -m11
271 271 committing subrepository t
272 272 $ hg push
273 273 pushing to $TESTTMP/sub/t
274 274 pushing subrepo s/ss to $TESTTMP/sub/t/s/ss
275 275 searching for changes
276 276 no changes found
277 277 pushing subrepo s to $TESTTMP/sub/t/s
278 278 searching for changes
279 279 no changes found
280 280 pushing subrepo t to $TESTTMP/sub/t/t
281 281 searching for changes
282 282 adding changesets
283 283 adding manifests
284 284 adding file changes
285 285 added 1 changesets with 1 changes to 1 files
286 286 searching for changes
287 287 adding changesets
288 288 adding manifests
289 289 adding file changes
290 290 added 1 changesets with 1 changes to 1 files
291 291
292 292 push -f
293 293
294 294 $ echo bah > s/a
295 295 $ hg ci -m12
296 296 committing subrepository s
297 297 $ hg push
298 298 pushing to $TESTTMP/sub/t
299 299 pushing subrepo s/ss to $TESTTMP/sub/t/s/ss
300 300 searching for changes
301 301 no changes found
302 302 pushing subrepo s to $TESTTMP/sub/t/s
303 303 searching for changes
304 304 abort: push creates new remote heads on branch 'default'!
305 305 (did you forget to merge? use push -f to force)
306 306 [255]
307 307 $ hg push -f
308 308 pushing to $TESTTMP/sub/t
309 309 pushing subrepo s/ss to $TESTTMP/sub/t/s/ss
310 310 searching for changes
311 311 no changes found
312 312 pushing subrepo s to $TESTTMP/sub/t/s
313 313 searching for changes
314 314 adding changesets
315 315 adding manifests
316 316 adding file changes
317 317 added 1 changesets with 1 changes to 1 files (+1 heads)
318 318 pushing subrepo t to $TESTTMP/sub/t/t
319 319 searching for changes
320 320 no changes found
321 321 searching for changes
322 322 adding changesets
323 323 adding manifests
324 324 adding file changes
325 325 added 1 changesets with 1 changes to 1 files
326 326
327 327 update
328 328
329 329 $ cd ../t
330 330 $ hg up -C # discard our earlier merge
331 331 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
332 332 $ echo blah > t/t
333 333 $ hg ci -m13
334 334 committing subrepository t
335 335
336 336 pull
337 337
338 338 $ cd ../tc
339 339 $ hg pull
340 340 pulling from $TESTTMP/sub/t
341 341 searching for changes
342 342 adding changesets
343 343 adding manifests
344 344 adding file changes
345 345 added 1 changesets with 1 changes to 1 files
346 346 (run 'hg update' to get a working copy)
347 347
348 348 should pull t
349 349
350 350 $ hg up
351 351 pulling subrepo t from $TESTTMP/sub/t/t
352 352 searching for changes
353 353 adding changesets
354 354 adding manifests
355 355 adding file changes
356 356 added 1 changesets with 1 changes to 1 files
357 357 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
358 358 $ cat t/t
359 359 blah
360 360
361 361 bogus subrepo path aborts
362 362
363 363 $ echo 'bogus=[boguspath' >> .hgsub
364 364 $ hg ci -m 'bogus subrepo path'
365 365 abort: missing ] in subrepo source
366 366 [255]
367 367
368 368 Issue1986: merge aborts when trying to merge a subrepo that
369 369 shouldn't need merging
370 370
371 371 # subrepo layout
372 372 #
373 373 # o 5 br
374 374 # /|
375 375 # o | 4 default
376 376 # | |
377 377 # | o 3 br
378 378 # |/|
379 379 # o | 2 default
380 380 # | |
381 381 # | o 1 br
382 382 # |/
383 383 # o 0 default
384 384
385 385 $ cd ..
386 386 $ rm -rf sub
387 387 $ hg init main
388 388 $ cd main
389 389 $ hg init s
390 390 $ cd s
391 391 $ echo a > a
392 392 $ hg ci -Am1
393 393 adding a
394 394 $ hg branch br
395 395 marked working directory as branch br
396 396 $ echo a >> a
397 397 $ hg ci -m1
398 398 $ hg up default
399 399 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
400 400 $ echo b > b
401 401 $ hg ci -Am1
402 402 adding b
403 403 $ hg up br
404 404 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
405 405 $ hg merge tip
406 406 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
407 407 (branch merge, don't forget to commit)
408 408 $ hg ci -m1
409 409 $ hg up 2
410 410 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
411 411 $ echo c > c
412 412 $ hg ci -Am1
413 413 adding c
414 414 $ hg up 3
415 415 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
416 416 $ hg merge 4
417 417 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
418 418 (branch merge, don't forget to commit)
419 419 $ hg ci -m1
420 420
421 421 # main repo layout:
422 422 #
423 423 # * <-- try to merge default into br again
424 424 # .`|
425 425 # . o 5 br --> substate = 5
426 426 # . |
427 427 # o | 4 default --> substate = 4
428 428 # | |
429 429 # | o 3 br --> substate = 2
430 430 # |/|
431 431 # o | 2 default --> substate = 2
432 432 # | |
433 433 # | o 1 br --> substate = 3
434 434 # |/
435 435 # o 0 default --> substate = 2
436 436
437 437 $ cd ..
438 438 $ echo 's = s' > .hgsub
439 439 $ hg -R s up 2
440 440 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
441 441 $ hg ci -Am1
442 442 adding .hgsub
443 443 committing subrepository s
444 444 $ hg branch br
445 445 marked working directory as branch br
446 446 $ echo b > b
447 447 $ hg -R s up 3
448 448 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
449 449 $ hg ci -Am1
450 450 adding b
451 451 committing subrepository s
452 452 $ hg up default
453 453 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
454 454 $ echo c > c
455 455 $ hg ci -Am1
456 456 adding c
457 457 $ hg up 1
458 458 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
459 459 $ hg merge 2
460 460 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
461 461 (branch merge, don't forget to commit)
462 462 $ hg ci -m1
463 463 $ hg up 2
464 464 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
465 465 $ hg -R s up 4
466 466 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
467 467 $ echo d > d
468 468 $ hg ci -Am1
469 469 adding d
470 470 committing subrepository s
471 471 $ hg up 3
472 472 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
473 473 $ hg -R s up 5
474 474 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
475 475 $ echo e > e
476 476 $ hg ci -Am1
477 477 adding e
478 478 committing subrepository s
479 479
480 480 $ hg up 5
481 481 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
482 482 $ hg merge 4 # try to merge default into br again
483 483 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
484 484 (branch merge, don't forget to commit)
485 485 $ cd ..
486 486
487 487 test subrepo delete from .hgsubstate
488 488
489 489 $ hg init testdelete
490 490 $ mkdir testdelete/nested testdelete/nested2
491 491 $ hg init testdelete/nested
492 492 $ hg init testdelete/nested2
493 493 $ echo test > testdelete/nested/foo
494 494 $ echo test > testdelete/nested2/foo
495 495 $ hg -R testdelete/nested add
496 496 adding testdelete/nested/foo
497 497 $ hg -R testdelete/nested2 add
498 498 adding testdelete/nested2/foo
499 499 $ hg -R testdelete/nested ci -m test
500 500 $ hg -R testdelete/nested2 ci -m test
501 501 $ echo nested = nested > testdelete/.hgsub
502 502 $ echo nested2 = nested2 >> testdelete/.hgsub
503 503 $ hg -R testdelete add
504 504 adding testdelete/.hgsub
505 505 $ hg -R testdelete ci -m "nested 1 & 2 added"
506 506 committing subrepository nested
507 507 committing subrepository nested2
508 508 $ echo nested = nested > testdelete/.hgsub
509 509 $ hg -R testdelete ci -m "nested 2 deleted"
510 510 $ cat testdelete/.hgsubstate
511 511 bdf5c9a3103743d900b12ae0db3ffdcfd7b0d878 nested
512 512 $ hg -R testdelete remove testdelete/.hgsub
513 513 $ hg -R testdelete ci -m ".hgsub deleted"
514 514 $ cat testdelete/.hgsubstate
515 515
516 516 test repository cloning
517 517
518 518 $ mkdir mercurial mercurial2
519 519 $ hg init nested_absolute
520 520 $ echo test > nested_absolute/foo
521 521 $ hg -R nested_absolute add
522 522 adding nested_absolute/foo
523 523 $ hg -R nested_absolute ci -mtest
524 524 $ cd mercurial
525 525 $ hg init nested_relative
526 526 $ echo test2 > nested_relative/foo2
527 527 $ hg -R nested_relative add
528 528 adding nested_relative/foo2
529 529 $ hg -R nested_relative ci -mtest2
530 530 $ hg init main
531 531 $ echo "nested_relative = ../nested_relative" > main/.hgsub
532 532 $ echo "nested_absolute = `pwd`/nested_absolute" >> main/.hgsub
533 533 $ hg -R main add
534 534 adding main/.hgsub
535 535 $ hg -R main ci -m "add subrepos"
536 536 committing subrepository nested_absolute
537 537 committing subrepository nested_relative
538 538 $ cd ..
539 539 $ hg clone mercurial/main mercurial2/main
540 540 updating to branch default
541 541 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
542 542 $ cat mercurial2/main/nested_absolute/.hg/hgrc \
543 543 > mercurial2/main/nested_relative/.hg/hgrc
544 544 [paths]
545 545 default = $TESTTMP/sub/mercurial/nested_absolute
546 546 [paths]
547 547 default = $TESTTMP/sub/mercurial/nested_relative
548 548 $ rm -rf mercurial mercurial2
549 549
550 550 Issue1977: multirepo push should fail if subrepo push fails
551 551
552 552 $ hg init repo
553 553 $ hg init repo/s
554 554 $ echo a > repo/s/a
555 555 $ hg -R repo/s ci -Am0
556 556 adding a
557 557 $ echo s = s > repo/.hgsub
558 558 $ hg -R repo ci -Am1
559 559 adding .hgsub
560 560 committing subrepository s
561 561 $ hg clone repo repo2
562 562 updating to branch default
563 563 pulling subrepo s from $TESTTMP/sub/repo/s
564 564 requesting all changes
565 565 adding changesets
566 566 adding manifests
567 567 adding file changes
568 568 added 1 changesets with 1 changes to 1 files
569 569 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
570 570 $ hg -q -R repo2 pull -u
571 571 $ echo 1 > repo2/s/a
572 572 $ hg -R repo2/s ci -m2
573 573 $ hg -q -R repo2/s push
574 574 $ hg -R repo2/s up -C 0
575 575 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
576 576 $ echo 2 > repo2/s/a
577 577 $ hg -R repo2/s ci -m3
578 578 created new head
579 579 $ hg -R repo2 ci -m3
580 580 committing subrepository s
581 581 $ hg -q -R repo2 push
582 582 abort: push creates new remote heads on branch 'default'!
583 583 (did you forget to merge? use push -f to force)
584 584 [255]
585 585 $ hg -R repo update
586 586 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
587 587 $ rm -rf repo2 repo
588 588
589 589
590 590 Issue1852 subrepos with relative paths always push/pull relative to default
591 591
592 592 Prepare a repo with subrepo
593 593
594 594 $ hg init issue1852a
595 595 $ cd issue1852a
596 596 $ hg init sub/repo
597 597 $ echo test > sub/repo/foo
598 598 $ hg -R sub/repo add sub/repo/foo
599 599 $ echo sub/repo = sub/repo > .hgsub
600 600 $ hg add .hgsub
601 601 $ hg ci -mtest
602 602 committing subrepository sub/repo
603 603 $ echo test >> sub/repo/foo
604 604 $ hg ci -mtest
605 605 committing subrepository sub/repo
606 606 $ cd ..
607 607
608 608 Create repo without default path, pull top repo, and see what happens on update
609 609
610 610 $ hg init issue1852b
611 611 $ hg -R issue1852b pull issue1852a
612 612 pulling from issue1852a
613 613 requesting all changes
614 614 adding changesets
615 615 adding manifests
616 616 adding file changes
617 617 added 2 changesets with 3 changes to 2 files
618 618 (run 'hg update' to get a working copy)
619 619 $ hg -R issue1852b update
620 620 abort: default path for subrepository sub/repo not found
621 621 [255]
622 622
623 623 Pull -u now doesn't help
624 624
625 625 $ hg -R issue1852b pull -u issue1852a
626 626 pulling from issue1852a
627 627 searching for changes
628 628 no changes found
629 629
630 630 Try the same, but with pull -u
631 631
632 632 $ hg init issue1852c
633 633 $ hg -R issue1852c pull -r0 -u issue1852a
634 634 pulling from issue1852a
635 635 adding changesets
636 636 adding manifests
637 637 adding file changes
638 638 added 1 changesets with 2 changes to 2 files
639 639 pulling subrepo sub/repo from issue1852a/sub/repo
640 640 requesting all changes
641 641 adding changesets
642 642 adding manifests
643 643 adding file changes
644 644 added 2 changesets with 2 changes to 1 files
645 645 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
646 646
647 647 Try to push from the other side
648 648
649 649 $ hg -R issue1852a push `pwd`/issue1852c
650 650 pushing to $TESTTMP/sub/issue1852c
651 651 pushing subrepo sub/repo to $TESTTMP/sub/issue1852c/sub/repo
652 652 searching for changes
653 653 no changes found
654 654 searching for changes
655 655 adding changesets
656 656 adding manifests
657 657 adding file changes
658 658 added 1 changesets with 1 changes to 1 files
659 659
660 660 Check status of files when none of them belong to the first
661 661 subrepository:
662 662
663 663 $ hg init subrepo-status
664 664 $ cd subrepo-status
665 665 $ hg init subrepo-1
666 666 $ hg init subrepo-2
667 667 $ cd subrepo-2
668 668 $ touch file
669 669 $ hg add file
670 670 $ cd ..
671 671 $ echo subrepo-1 = subrepo-1 > .hgsub
672 672 $ echo subrepo-2 = subrepo-2 >> .hgsub
673 673 $ hg add .hgsub
674 674 $ hg ci -m 'Added subrepos'
675 675 committing subrepository subrepo-1
676 676 committing subrepository subrepo-2
677 677 $ hg st subrepo-2/file
678
679 Check hg update --clean
680 $ cd $TESTTMP/sub/t
681 $ rm -r t/t.orig
682 $ hg status -S --all
683 C .hgsub
684 C .hgsubstate
685 C a
686 C s/.hgsub
687 C s/.hgsubstate
688 C s/a
689 C s/ss/a
690 C t/t
691 $ echo c1 > s/a
692 $ cd s
693 $ echo c1 > b
694 $ echo c1 > c
695 $ hg add b
696 $ cd ..
697 $ hg status -S
698 M s/a
699 A s/b
700 ? s/c
701 $ hg update -C
702 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
703 $ hg status -S
704 ? s/b
705 ? s/c
General Comments 0
You need to be logged in to leave comments. Login now