##// END OF EJS Templates
subrepo: use sharepath if available when locating the source repo...
Matt Harbison -
r18510:f254ab62 stable
parent child Browse files
Show More
@@ -1,1329 +1,1332
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, posixpath
9 9 import stat, subprocess, tarfile
10 10 from i18n import _
11 11 import config, scmutil, util, node, error, cmdutil, bookmarks, match as matchmod
12 12 hg = None
13 13 propertycache = util.propertycache
14 14
15 15 nullstate = ('', '', 'empty')
16 16
17 17 class SubrepoAbort(error.Abort):
18 18 """Exception class used to avoid handling a subrepo error more than once"""
19 19 def __init__(self, *args, **kw):
20 20 error.Abort.__init__(self, *args, **kw)
21 21 self.subrepo = kw.get('subrepo')
22 22
23 23 def annotatesubrepoerror(func):
24 24 def decoratedmethod(self, *args, **kargs):
25 25 try:
26 26 res = func(self, *args, **kargs)
27 27 except SubrepoAbort, ex:
28 28 # This exception has already been handled
29 29 raise ex
30 30 except error.Abort, ex:
31 31 subrepo = subrelpath(self)
32 32 errormsg = str(ex) + ' ' + _('(in subrepo %s)') % subrepo
33 33 # avoid handling this exception by raising a SubrepoAbort exception
34 34 raise SubrepoAbort(errormsg, hint=ex.hint, subrepo=subrepo)
35 35 return res
36 36 return decoratedmethod
37 37
38 38 def state(ctx, ui):
39 39 """return a state dict, mapping subrepo paths configured in .hgsub
40 40 to tuple: (source from .hgsub, revision from .hgsubstate, kind
41 41 (key in types dict))
42 42 """
43 43 p = config.config()
44 44 def read(f, sections=None, remap=None):
45 45 if f in ctx:
46 46 try:
47 47 data = ctx[f].data()
48 48 except IOError, err:
49 49 if err.errno != errno.ENOENT:
50 50 raise
51 51 # handle missing subrepo spec files as removed
52 52 ui.warn(_("warning: subrepo spec file %s not found\n") % f)
53 53 return
54 54 p.parse(f, data, sections, remap, read)
55 55 else:
56 56 raise util.Abort(_("subrepo spec file %s not found") % f)
57 57
58 58 if '.hgsub' in ctx:
59 59 read('.hgsub')
60 60
61 61 for path, src in ui.configitems('subpaths'):
62 62 p.set('subpaths', path, src, ui.configsource('subpaths', path))
63 63
64 64 rev = {}
65 65 if '.hgsubstate' in ctx:
66 66 try:
67 67 for i, l in enumerate(ctx['.hgsubstate'].data().splitlines()):
68 68 l = l.lstrip()
69 69 if not l:
70 70 continue
71 71 try:
72 72 revision, path = l.split(" ", 1)
73 73 except ValueError:
74 74 raise util.Abort(_("invalid subrepository revision "
75 75 "specifier in .hgsubstate line %d")
76 76 % (i + 1))
77 77 rev[path] = revision
78 78 except IOError, err:
79 79 if err.errno != errno.ENOENT:
80 80 raise
81 81
82 82 def remap(src):
83 83 for pattern, repl in p.items('subpaths'):
84 84 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
85 85 # does a string decode.
86 86 repl = repl.encode('string-escape')
87 87 # However, we still want to allow back references to go
88 88 # through unharmed, so we turn r'\\1' into r'\1'. Again,
89 89 # extra escapes are needed because re.sub string decodes.
90 90 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
91 91 try:
92 92 src = re.sub(pattern, repl, src, 1)
93 93 except re.error, e:
94 94 raise util.Abort(_("bad subrepository pattern in %s: %s")
95 95 % (p.source('subpaths', pattern), e))
96 96 return src
97 97
98 98 state = {}
99 99 for path, src in p[''].items():
100 100 kind = 'hg'
101 101 if src.startswith('['):
102 102 if ']' not in src:
103 103 raise util.Abort(_('missing ] in subrepo source'))
104 104 kind, src = src.split(']', 1)
105 105 kind = kind[1:]
106 106 src = src.lstrip() # strip any extra whitespace after ']'
107 107
108 108 if not util.url(src).isabs():
109 109 parent = _abssource(ctx._repo, abort=False)
110 110 if parent:
111 111 parent = util.url(parent)
112 112 parent.path = posixpath.join(parent.path or '', src)
113 113 parent.path = posixpath.normpath(parent.path)
114 114 joined = str(parent)
115 115 # Remap the full joined path and use it if it changes,
116 116 # else remap the original source.
117 117 remapped = remap(joined)
118 118 if remapped == joined:
119 119 src = remap(src)
120 120 else:
121 121 src = remapped
122 122
123 123 src = remap(src)
124 124 state[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind)
125 125
126 126 return state
127 127
128 128 def writestate(repo, state):
129 129 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
130 130 lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state)]
131 131 repo.wwrite('.hgsubstate', ''.join(lines), '')
132 132
133 133 def submerge(repo, wctx, mctx, actx, overwrite):
134 134 """delegated from merge.applyupdates: merging of .hgsubstate file
135 135 in working context, merging context and ancestor context"""
136 136 if mctx == actx: # backwards?
137 137 actx = wctx.p1()
138 138 s1 = wctx.substate
139 139 s2 = mctx.substate
140 140 sa = actx.substate
141 141 sm = {}
142 142
143 143 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
144 144
145 145 def debug(s, msg, r=""):
146 146 if r:
147 147 r = "%s:%s:%s" % r
148 148 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
149 149
150 150 for s, l in sorted(s1.iteritems()):
151 151 a = sa.get(s, nullstate)
152 152 ld = l # local state with possible dirty flag for compares
153 153 if wctx.sub(s).dirty():
154 154 ld = (l[0], l[1] + "+")
155 155 if wctx == actx: # overwrite
156 156 a = ld
157 157
158 158 if s in s2:
159 159 r = s2[s]
160 160 if ld == r or r == a: # no change or local is newer
161 161 sm[s] = l
162 162 continue
163 163 elif ld == a: # other side changed
164 164 debug(s, "other changed, get", r)
165 165 wctx.sub(s).get(r, overwrite)
166 166 sm[s] = r
167 167 elif ld[0] != r[0]: # sources differ
168 168 if repo.ui.promptchoice(
169 169 _(' subrepository sources for %s differ\n'
170 170 'use (l)ocal source (%s) or (r)emote source (%s)?')
171 171 % (s, l[0], r[0]),
172 172 (_('&Local'), _('&Remote')), 0):
173 173 debug(s, "prompt changed, get", r)
174 174 wctx.sub(s).get(r, overwrite)
175 175 sm[s] = r
176 176 elif ld[1] == a[1]: # local side is unchanged
177 177 debug(s, "other side changed, get", r)
178 178 wctx.sub(s).get(r, overwrite)
179 179 sm[s] = r
180 180 else:
181 181 debug(s, "both sides changed, merge with", r)
182 182 wctx.sub(s).merge(r)
183 183 sm[s] = l
184 184 elif ld == a: # remote removed, local unchanged
185 185 debug(s, "remote removed, remove")
186 186 wctx.sub(s).remove()
187 187 elif a == nullstate: # not present in remote or ancestor
188 188 debug(s, "local added, keep")
189 189 sm[s] = l
190 190 continue
191 191 else:
192 192 if repo.ui.promptchoice(
193 193 _(' local changed subrepository %s which remote removed\n'
194 194 'use (c)hanged version or (d)elete?') % s,
195 195 (_('&Changed'), _('&Delete')), 0):
196 196 debug(s, "prompt remove")
197 197 wctx.sub(s).remove()
198 198
199 199 for s, r in sorted(s2.items()):
200 200 if s in s1:
201 201 continue
202 202 elif s not in sa:
203 203 debug(s, "remote added, get", r)
204 204 mctx.sub(s).get(r)
205 205 sm[s] = r
206 206 elif r != sa[s]:
207 207 if repo.ui.promptchoice(
208 208 _(' remote changed subrepository %s which local removed\n'
209 209 'use (c)hanged version or (d)elete?') % s,
210 210 (_('&Changed'), _('&Delete')), 0) == 0:
211 211 debug(s, "prompt recreate", r)
212 212 wctx.sub(s).get(r)
213 213 sm[s] = r
214 214
215 215 # record merged .hgsubstate
216 216 writestate(repo, sm)
217 217
218 218 def _updateprompt(ui, sub, dirty, local, remote):
219 219 if dirty:
220 220 msg = (_(' subrepository sources for %s differ\n'
221 221 'use (l)ocal source (%s) or (r)emote source (%s)?\n')
222 222 % (subrelpath(sub), local, remote))
223 223 else:
224 224 msg = (_(' subrepository sources for %s differ (in checked out '
225 225 'version)\n'
226 226 'use (l)ocal source (%s) or (r)emote source (%s)?\n')
227 227 % (subrelpath(sub), local, remote))
228 228 return ui.promptchoice(msg, (_('&Local'), _('&Remote')), 0)
229 229
230 230 def reporelpath(repo):
231 231 """return path to this (sub)repo as seen from outermost repo"""
232 232 parent = repo
233 233 while util.safehasattr(parent, '_subparent'):
234 234 parent = parent._subparent
235 235 p = parent.root.rstrip(os.sep)
236 236 return repo.root[len(p) + 1:]
237 237
238 238 def subrelpath(sub):
239 239 """return path to this subrepo as seen from outermost repo"""
240 240 if util.safehasattr(sub, '_relpath'):
241 241 return sub._relpath
242 242 if not util.safehasattr(sub, '_repo'):
243 243 return sub._path
244 244 return reporelpath(sub._repo)
245 245
246 246 def _abssource(repo, push=False, abort=True):
247 247 """return pull/push path of repo - either based on parent repo .hgsub info
248 248 or on the top repo config. Abort or return None if no source found."""
249 249 if util.safehasattr(repo, '_subparent'):
250 250 source = util.url(repo._subsource)
251 251 if source.isabs():
252 252 return str(source)
253 253 source.path = posixpath.normpath(source.path)
254 254 parent = _abssource(repo._subparent, push, abort=False)
255 255 if parent:
256 256 parent = util.url(util.pconvert(parent))
257 257 parent.path = posixpath.join(parent.path or '', source.path)
258 258 parent.path = posixpath.normpath(parent.path)
259 259 return str(parent)
260 260 else: # recursion reached top repo
261 261 if util.safehasattr(repo, '_subtoppath'):
262 262 return repo._subtoppath
263 263 if push and repo.ui.config('paths', 'default-push'):
264 264 return repo.ui.config('paths', 'default-push')
265 265 if repo.ui.config('paths', 'default'):
266 266 return repo.ui.config('paths', 'default')
267 if repo.sharedpath != repo.path:
268 # chop off the .hg component to get the default path form
269 return os.path.dirname(repo.sharedpath)
267 270 if abort:
268 271 raise util.Abort(_("default path for subrepository not found"))
269 272
270 273 def itersubrepos(ctx1, ctx2):
271 274 """find subrepos in ctx1 or ctx2"""
272 275 # Create a (subpath, ctx) mapping where we prefer subpaths from
273 276 # ctx1. The subpaths from ctx2 are important when the .hgsub file
274 277 # has been modified (in ctx2) but not yet committed (in ctx1).
275 278 subpaths = dict.fromkeys(ctx2.substate, ctx2)
276 279 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
277 280 for subpath, ctx in sorted(subpaths.iteritems()):
278 281 yield subpath, ctx.sub(subpath)
279 282
280 283 def subrepo(ctx, path):
281 284 """return instance of the right subrepo class for subrepo in path"""
282 285 # subrepo inherently violates our import layering rules
283 286 # because it wants to make repo objects from deep inside the stack
284 287 # so we manually delay the circular imports to not break
285 288 # scripts that don't use our demand-loading
286 289 global hg
287 290 import hg as h
288 291 hg = h
289 292
290 293 scmutil.pathauditor(ctx._repo.root)(path)
291 294 state = ctx.substate[path]
292 295 if state[2] not in types:
293 296 raise util.Abort(_('unknown subrepo type %s') % state[2])
294 297 return types[state[2]](ctx, path, state[:2])
295 298
296 299 # subrepo classes need to implement the following abstract class:
297 300
298 301 class abstractsubrepo(object):
299 302
300 303 def dirty(self, ignoreupdate=False):
301 304 """returns true if the dirstate of the subrepo is dirty or does not
302 305 match current stored state. If ignoreupdate is true, only check
303 306 whether the subrepo has uncommitted changes in its dirstate.
304 307 """
305 308 raise NotImplementedError
306 309
307 310 def basestate(self):
308 311 """current working directory base state, disregarding .hgsubstate
309 312 state and working directory modifications"""
310 313 raise NotImplementedError
311 314
312 315 def checknested(self, path):
313 316 """check if path is a subrepository within this repository"""
314 317 return False
315 318
316 319 def commit(self, text, user, date):
317 320 """commit the current changes to the subrepo with the given
318 321 log message. Use given user and date if possible. Return the
319 322 new state of the subrepo.
320 323 """
321 324 raise NotImplementedError
322 325
323 326 def remove(self):
324 327 """remove the subrepo
325 328
326 329 (should verify the dirstate is not dirty first)
327 330 """
328 331 raise NotImplementedError
329 332
330 333 def get(self, state, overwrite=False):
331 334 """run whatever commands are needed to put the subrepo into
332 335 this state
333 336 """
334 337 raise NotImplementedError
335 338
336 339 def merge(self, state):
337 340 """merge currently-saved state with the new state."""
338 341 raise NotImplementedError
339 342
340 343 def push(self, opts):
341 344 """perform whatever action is analogous to 'hg push'
342 345
343 346 This may be a no-op on some systems.
344 347 """
345 348 raise NotImplementedError
346 349
347 350 def add(self, ui, match, dryrun, listsubrepos, prefix, explicitonly):
348 351 return []
349 352
350 353 def status(self, rev2, **opts):
351 354 return [], [], [], [], [], [], []
352 355
353 356 def diff(self, ui, diffopts, node2, match, prefix, **opts):
354 357 pass
355 358
356 359 def outgoing(self, ui, dest, opts):
357 360 return 1
358 361
359 362 def incoming(self, ui, source, opts):
360 363 return 1
361 364
362 365 def files(self):
363 366 """return filename iterator"""
364 367 raise NotImplementedError
365 368
366 369 def filedata(self, name):
367 370 """return file data"""
368 371 raise NotImplementedError
369 372
370 373 def fileflags(self, name):
371 374 """return file flags"""
372 375 return ''
373 376
374 377 def archive(self, ui, archiver, prefix, match=None):
375 378 if match is not None:
376 379 files = [f for f in self.files() if match(f)]
377 380 else:
378 381 files = self.files()
379 382 total = len(files)
380 383 relpath = subrelpath(self)
381 384 ui.progress(_('archiving (%s)') % relpath, 0,
382 385 unit=_('files'), total=total)
383 386 for i, name in enumerate(files):
384 387 flags = self.fileflags(name)
385 388 mode = 'x' in flags and 0755 or 0644
386 389 symlink = 'l' in flags
387 390 archiver.addfile(os.path.join(prefix, self._path, name),
388 391 mode, symlink, self.filedata(name))
389 392 ui.progress(_('archiving (%s)') % relpath, i + 1,
390 393 unit=_('files'), total=total)
391 394 ui.progress(_('archiving (%s)') % relpath, None)
392 395
393 396 def walk(self, match):
394 397 '''
395 398 walk recursively through the directory tree, finding all files
396 399 matched by the match function
397 400 '''
398 401 pass
399 402
400 403 def forget(self, ui, match, prefix):
401 404 return ([], [])
402 405
403 406 def revert(self, ui, substate, *pats, **opts):
404 407 ui.warn('%s: reverting %s subrepos is unsupported\n' \
405 408 % (substate[0], substate[2]))
406 409 return []
407 410
408 411 class hgsubrepo(abstractsubrepo):
409 412 def __init__(self, ctx, path, state):
410 413 self._path = path
411 414 self._state = state
412 415 r = ctx._repo
413 416 root = r.wjoin(path)
414 417 create = False
415 418 if not os.path.exists(os.path.join(root, '.hg')):
416 419 create = True
417 420 util.makedirs(root)
418 421 self._repo = hg.repository(r.baseui, root, create=create)
419 422 for s, k in [('ui', 'commitsubrepos')]:
420 423 v = r.ui.config(s, k)
421 424 if v:
422 425 self._repo.ui.setconfig(s, k, v)
423 426 self._initrepo(r, state[0], create)
424 427
425 428 @annotatesubrepoerror
426 429 def _initrepo(self, parentrepo, source, create):
427 430 self._repo._subparent = parentrepo
428 431 self._repo._subsource = source
429 432
430 433 if create:
431 434 fp = self._repo.opener("hgrc", "w", text=True)
432 435 fp.write('[paths]\n')
433 436
434 437 def addpathconfig(key, value):
435 438 if value:
436 439 fp.write('%s = %s\n' % (key, value))
437 440 self._repo.ui.setconfig('paths', key, value)
438 441
439 442 defpath = _abssource(self._repo, abort=False)
440 443 defpushpath = _abssource(self._repo, True, abort=False)
441 444 addpathconfig('default', defpath)
442 445 if defpath != defpushpath:
443 446 addpathconfig('default-push', defpushpath)
444 447 fp.close()
445 448
446 449 @annotatesubrepoerror
447 450 def add(self, ui, match, dryrun, listsubrepos, prefix, explicitonly):
448 451 return cmdutil.add(ui, self._repo, match, dryrun, listsubrepos,
449 452 os.path.join(prefix, self._path), explicitonly)
450 453
451 454 @annotatesubrepoerror
452 455 def status(self, rev2, **opts):
453 456 try:
454 457 rev1 = self._state[1]
455 458 ctx1 = self._repo[rev1]
456 459 ctx2 = self._repo[rev2]
457 460 return self._repo.status(ctx1, ctx2, **opts)
458 461 except error.RepoLookupError, inst:
459 462 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
460 463 % (inst, subrelpath(self)))
461 464 return [], [], [], [], [], [], []
462 465
463 466 @annotatesubrepoerror
464 467 def diff(self, ui, diffopts, node2, match, prefix, **opts):
465 468 try:
466 469 node1 = node.bin(self._state[1])
467 470 # We currently expect node2 to come from substate and be
468 471 # in hex format
469 472 if node2 is not None:
470 473 node2 = node.bin(node2)
471 474 cmdutil.diffordiffstat(ui, self._repo, diffopts,
472 475 node1, node2, match,
473 476 prefix=posixpath.join(prefix, self._path),
474 477 listsubrepos=True, **opts)
475 478 except error.RepoLookupError, inst:
476 479 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
477 480 % (inst, subrelpath(self)))
478 481
479 482 @annotatesubrepoerror
480 483 def archive(self, ui, archiver, prefix, match=None):
481 484 self._get(self._state + ('hg',))
482 485 abstractsubrepo.archive(self, ui, archiver, prefix, match)
483 486
484 487 rev = self._state[1]
485 488 ctx = self._repo[rev]
486 489 for subpath in ctx.substate:
487 490 s = subrepo(ctx, subpath)
488 491 submatch = matchmod.narrowmatcher(subpath, match)
489 492 s.archive(ui, archiver, os.path.join(prefix, self._path), submatch)
490 493
491 494 @annotatesubrepoerror
492 495 def dirty(self, ignoreupdate=False):
493 496 r = self._state[1]
494 497 if r == '' and not ignoreupdate: # no state recorded
495 498 return True
496 499 w = self._repo[None]
497 500 if r != w.p1().hex() and not ignoreupdate:
498 501 # different version checked out
499 502 return True
500 503 return w.dirty() # working directory changed
501 504
502 505 def basestate(self):
503 506 return self._repo['.'].hex()
504 507
505 508 def checknested(self, path):
506 509 return self._repo._checknested(self._repo.wjoin(path))
507 510
508 511 @annotatesubrepoerror
509 512 def commit(self, text, user, date):
510 513 # don't bother committing in the subrepo if it's only been
511 514 # updated
512 515 if not self.dirty(True):
513 516 return self._repo['.'].hex()
514 517 self._repo.ui.debug("committing subrepo %s\n" % subrelpath(self))
515 518 n = self._repo.commit(text, user, date)
516 519 if not n:
517 520 return self._repo['.'].hex() # different version checked out
518 521 return node.hex(n)
519 522
520 523 @annotatesubrepoerror
521 524 def remove(self):
522 525 # we can't fully delete the repository as it may contain
523 526 # local-only history
524 527 self._repo.ui.note(_('removing subrepo %s\n') % subrelpath(self))
525 528 hg.clean(self._repo, node.nullid, False)
526 529
527 530 def _get(self, state):
528 531 source, revision, kind = state
529 532 if revision not in self._repo:
530 533 self._repo._subsource = source
531 534 srcurl = _abssource(self._repo)
532 535 other = hg.peer(self._repo, {}, srcurl)
533 536 if len(self._repo) == 0:
534 537 self._repo.ui.status(_('cloning subrepo %s from %s\n')
535 538 % (subrelpath(self), srcurl))
536 539 parentrepo = self._repo._subparent
537 540 shutil.rmtree(self._repo.path)
538 541 other, cloned = hg.clone(self._repo._subparent.baseui, {},
539 542 other, self._repo.root,
540 543 update=False)
541 544 self._repo = cloned.local()
542 545 self._initrepo(parentrepo, source, create=True)
543 546 else:
544 547 self._repo.ui.status(_('pulling subrepo %s from %s\n')
545 548 % (subrelpath(self), srcurl))
546 549 self._repo.pull(other)
547 550 bookmarks.updatefromremote(self._repo.ui, self._repo, other,
548 551 srcurl)
549 552
550 553 @annotatesubrepoerror
551 554 def get(self, state, overwrite=False):
552 555 self._get(state)
553 556 source, revision, kind = state
554 557 self._repo.ui.debug("getting subrepo %s\n" % self._path)
555 558 hg.updaterepo(self._repo, revision, overwrite)
556 559
557 560 @annotatesubrepoerror
558 561 def merge(self, state):
559 562 self._get(state)
560 563 cur = self._repo['.']
561 564 dst = self._repo[state[1]]
562 565 anc = dst.ancestor(cur)
563 566
564 567 def mergefunc():
565 568 if anc == cur and dst.branch() == cur.branch():
566 569 self._repo.ui.debug("updating subrepo %s\n" % subrelpath(self))
567 570 hg.update(self._repo, state[1])
568 571 elif anc == dst:
569 572 self._repo.ui.debug("skipping subrepo %s\n" % subrelpath(self))
570 573 else:
571 574 self._repo.ui.debug("merging subrepo %s\n" % subrelpath(self))
572 575 hg.merge(self._repo, state[1], remind=False)
573 576
574 577 wctx = self._repo[None]
575 578 if self.dirty():
576 579 if anc != dst:
577 580 if _updateprompt(self._repo.ui, self, wctx.dirty(), cur, dst):
578 581 mergefunc()
579 582 else:
580 583 mergefunc()
581 584 else:
582 585 mergefunc()
583 586
584 587 @annotatesubrepoerror
585 588 def push(self, opts):
586 589 force = opts.get('force')
587 590 newbranch = opts.get('new_branch')
588 591 ssh = opts.get('ssh')
589 592
590 593 # push subrepos depth-first for coherent ordering
591 594 c = self._repo['']
592 595 subs = c.substate # only repos that are committed
593 596 for s in sorted(subs):
594 597 if c.sub(s).push(opts) == 0:
595 598 return False
596 599
597 600 dsturl = _abssource(self._repo, True)
598 601 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
599 602 (subrelpath(self), dsturl))
600 603 other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
601 604 return self._repo.push(other, force, newbranch=newbranch)
602 605
603 606 @annotatesubrepoerror
604 607 def outgoing(self, ui, dest, opts):
605 608 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
606 609
607 610 @annotatesubrepoerror
608 611 def incoming(self, ui, source, opts):
609 612 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
610 613
611 614 @annotatesubrepoerror
612 615 def files(self):
613 616 rev = self._state[1]
614 617 ctx = self._repo[rev]
615 618 return ctx.manifest()
616 619
617 620 def filedata(self, name):
618 621 rev = self._state[1]
619 622 return self._repo[rev][name].data()
620 623
621 624 def fileflags(self, name):
622 625 rev = self._state[1]
623 626 ctx = self._repo[rev]
624 627 return ctx.flags(name)
625 628
626 629 def walk(self, match):
627 630 ctx = self._repo[None]
628 631 return ctx.walk(match)
629 632
630 633 @annotatesubrepoerror
631 634 def forget(self, ui, match, prefix):
632 635 return cmdutil.forget(ui, self._repo, match,
633 636 os.path.join(prefix, self._path), True)
634 637
635 638 @annotatesubrepoerror
636 639 def revert(self, ui, substate, *pats, **opts):
637 640 # reverting a subrepo is a 2 step process:
638 641 # 1. if the no_backup is not set, revert all modified
639 642 # files inside the subrepo
640 643 # 2. update the subrepo to the revision specified in
641 644 # the corresponding substate dictionary
642 645 ui.status(_('reverting subrepo %s\n') % substate[0])
643 646 if not opts.get('no_backup'):
644 647 # Revert all files on the subrepo, creating backups
645 648 # Note that this will not recursively revert subrepos
646 649 # We could do it if there was a set:subrepos() predicate
647 650 opts = opts.copy()
648 651 opts['date'] = None
649 652 opts['rev'] = substate[1]
650 653
651 654 pats = []
652 655 if not opts['all']:
653 656 pats = ['set:modified()']
654 657 self.filerevert(ui, *pats, **opts)
655 658
656 659 # Update the repo to the revision specified in the given substate
657 660 self.get(substate, overwrite=True)
658 661
659 662 def filerevert(self, ui, *pats, **opts):
660 663 ctx = self._repo[opts['rev']]
661 664 parents = self._repo.dirstate.parents()
662 665 if opts['all']:
663 666 pats = ['set:modified()']
664 667 else:
665 668 pats = []
666 669 cmdutil.revert(ui, self._repo, ctx, parents, *pats, **opts)
667 670
668 671 class svnsubrepo(abstractsubrepo):
669 672 def __init__(self, ctx, path, state):
670 673 self._path = path
671 674 self._state = state
672 675 self._ctx = ctx
673 676 self._ui = ctx._repo.ui
674 677 self._exe = util.findexe('svn')
675 678 if not self._exe:
676 679 raise util.Abort(_("'svn' executable not found for subrepo '%s'")
677 680 % self._path)
678 681
679 682 def _svncommand(self, commands, filename='', failok=False):
680 683 cmd = [self._exe]
681 684 extrakw = {}
682 685 if not self._ui.interactive():
683 686 # Making stdin be a pipe should prevent svn from behaving
684 687 # interactively even if we can't pass --non-interactive.
685 688 extrakw['stdin'] = subprocess.PIPE
686 689 # Starting in svn 1.5 --non-interactive is a global flag
687 690 # instead of being per-command, but we need to support 1.4 so
688 691 # we have to be intelligent about what commands take
689 692 # --non-interactive.
690 693 if commands[0] in ('update', 'checkout', 'commit'):
691 694 cmd.append('--non-interactive')
692 695 cmd.extend(commands)
693 696 if filename is not None:
694 697 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
695 698 cmd.append(path)
696 699 env = dict(os.environ)
697 700 # Avoid localized output, preserve current locale for everything else.
698 701 lc_all = env.get('LC_ALL')
699 702 if lc_all:
700 703 env['LANG'] = lc_all
701 704 del env['LC_ALL']
702 705 env['LC_MESSAGES'] = 'C'
703 706 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
704 707 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
705 708 universal_newlines=True, env=env, **extrakw)
706 709 stdout, stderr = p.communicate()
707 710 stderr = stderr.strip()
708 711 if not failok:
709 712 if p.returncode:
710 713 raise util.Abort(stderr or 'exited with code %d' % p.returncode)
711 714 if stderr:
712 715 self._ui.warn(stderr + '\n')
713 716 return stdout, stderr
714 717
715 718 @propertycache
716 719 def _svnversion(self):
717 720 output, err = self._svncommand(['--version', '--quiet'], filename=None)
718 721 m = re.search(r'^(\d+)\.(\d+)', output)
719 722 if not m:
720 723 raise util.Abort(_('cannot retrieve svn tool version'))
721 724 return (int(m.group(1)), int(m.group(2)))
722 725
723 726 def _wcrevs(self):
724 727 # Get the working directory revision as well as the last
725 728 # commit revision so we can compare the subrepo state with
726 729 # both. We used to store the working directory one.
727 730 output, err = self._svncommand(['info', '--xml'])
728 731 doc = xml.dom.minidom.parseString(output)
729 732 entries = doc.getElementsByTagName('entry')
730 733 lastrev, rev = '0', '0'
731 734 if entries:
732 735 rev = str(entries[0].getAttribute('revision')) or '0'
733 736 commits = entries[0].getElementsByTagName('commit')
734 737 if commits:
735 738 lastrev = str(commits[0].getAttribute('revision')) or '0'
736 739 return (lastrev, rev)
737 740
738 741 def _wcrev(self):
739 742 return self._wcrevs()[0]
740 743
741 744 def _wcchanged(self):
742 745 """Return (changes, extchanges, missing) where changes is True
743 746 if the working directory was changed, extchanges is
744 747 True if any of these changes concern an external entry and missing
745 748 is True if any change is a missing entry.
746 749 """
747 750 output, err = self._svncommand(['status', '--xml'])
748 751 externals, changes, missing = [], [], []
749 752 doc = xml.dom.minidom.parseString(output)
750 753 for e in doc.getElementsByTagName('entry'):
751 754 s = e.getElementsByTagName('wc-status')
752 755 if not s:
753 756 continue
754 757 item = s[0].getAttribute('item')
755 758 props = s[0].getAttribute('props')
756 759 path = e.getAttribute('path')
757 760 if item == 'external':
758 761 externals.append(path)
759 762 elif item == 'missing':
760 763 missing.append(path)
761 764 if (item not in ('', 'normal', 'unversioned', 'external')
762 765 or props not in ('', 'none', 'normal')):
763 766 changes.append(path)
764 767 for path in changes:
765 768 for ext in externals:
766 769 if path == ext or path.startswith(ext + os.sep):
767 770 return True, True, bool(missing)
768 771 return bool(changes), False, bool(missing)
769 772
770 773 def dirty(self, ignoreupdate=False):
771 774 if not self._wcchanged()[0]:
772 775 if self._state[1] in self._wcrevs() or ignoreupdate:
773 776 return False
774 777 return True
775 778
776 779 def basestate(self):
777 780 lastrev, rev = self._wcrevs()
778 781 if lastrev != rev:
779 782 # Last committed rev is not the same than rev. We would
780 783 # like to take lastrev but we do not know if the subrepo
781 784 # URL exists at lastrev. Test it and fallback to rev it
782 785 # is not there.
783 786 try:
784 787 self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)])
785 788 return lastrev
786 789 except error.Abort:
787 790 pass
788 791 return rev
789 792
790 793 @annotatesubrepoerror
791 794 def commit(self, text, user, date):
792 795 # user and date are out of our hands since svn is centralized
793 796 changed, extchanged, missing = self._wcchanged()
794 797 if not changed:
795 798 return self.basestate()
796 799 if extchanged:
797 800 # Do not try to commit externals
798 801 raise util.Abort(_('cannot commit svn externals'))
799 802 if missing:
800 803 # svn can commit with missing entries but aborting like hg
801 804 # seems a better approach.
802 805 raise util.Abort(_('cannot commit missing svn entries'))
803 806 commitinfo, err = self._svncommand(['commit', '-m', text])
804 807 self._ui.status(commitinfo)
805 808 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
806 809 if not newrev:
807 810 if not commitinfo.strip():
808 811 # Sometimes, our definition of "changed" differs from
809 812 # svn one. For instance, svn ignores missing files
810 813 # when committing. If there are only missing files, no
811 814 # commit is made, no output and no error code.
812 815 raise util.Abort(_('failed to commit svn changes'))
813 816 raise util.Abort(commitinfo.splitlines()[-1])
814 817 newrev = newrev.groups()[0]
815 818 self._ui.status(self._svncommand(['update', '-r', newrev])[0])
816 819 return newrev
817 820
818 821 @annotatesubrepoerror
819 822 def remove(self):
820 823 if self.dirty():
821 824 self._ui.warn(_('not removing repo %s because '
822 825 'it has changes.\n' % self._path))
823 826 return
824 827 self._ui.note(_('removing subrepo %s\n') % self._path)
825 828
826 829 def onerror(function, path, excinfo):
827 830 if function is not os.remove:
828 831 raise
829 832 # read-only files cannot be unlinked under Windows
830 833 s = os.stat(path)
831 834 if (s.st_mode & stat.S_IWRITE) != 0:
832 835 raise
833 836 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
834 837 os.remove(path)
835 838
836 839 path = self._ctx._repo.wjoin(self._path)
837 840 shutil.rmtree(path, onerror=onerror)
838 841 try:
839 842 os.removedirs(os.path.dirname(path))
840 843 except OSError:
841 844 pass
842 845
843 846 @annotatesubrepoerror
844 847 def get(self, state, overwrite=False):
845 848 if overwrite:
846 849 self._svncommand(['revert', '--recursive'])
847 850 args = ['checkout']
848 851 if self._svnversion >= (1, 5):
849 852 args.append('--force')
850 853 # The revision must be specified at the end of the URL to properly
851 854 # update to a directory which has since been deleted and recreated.
852 855 args.append('%s@%s' % (state[0], state[1]))
853 856 status, err = self._svncommand(args, failok=True)
854 857 if not re.search('Checked out revision [0-9]+.', status):
855 858 if ('is already a working copy for a different URL' in err
856 859 and (self._wcchanged()[:2] == (False, False))):
857 860 # obstructed but clean working copy, so just blow it away.
858 861 self.remove()
859 862 self.get(state, overwrite=False)
860 863 return
861 864 raise util.Abort((status or err).splitlines()[-1])
862 865 self._ui.status(status)
863 866
864 867 @annotatesubrepoerror
865 868 def merge(self, state):
866 869 old = self._state[1]
867 870 new = state[1]
868 871 wcrev = self._wcrev()
869 872 if new != wcrev:
870 873 dirty = old == wcrev or self._wcchanged()[0]
871 874 if _updateprompt(self._ui, self, dirty, wcrev, new):
872 875 self.get(state, False)
873 876
874 877 def push(self, opts):
875 878 # push is a no-op for SVN
876 879 return True
877 880
878 881 @annotatesubrepoerror
879 882 def files(self):
880 883 output = self._svncommand(['list', '--recursive', '--xml'])[0]
881 884 doc = xml.dom.minidom.parseString(output)
882 885 paths = []
883 886 for e in doc.getElementsByTagName('entry'):
884 887 kind = str(e.getAttribute('kind'))
885 888 if kind != 'file':
886 889 continue
887 890 name = ''.join(c.data for c
888 891 in e.getElementsByTagName('name')[0].childNodes
889 892 if c.nodeType == c.TEXT_NODE)
890 893 paths.append(name.encode('utf-8'))
891 894 return paths
892 895
893 896 def filedata(self, name):
894 897 return self._svncommand(['cat'], name)[0]
895 898
896 899
897 900 class gitsubrepo(abstractsubrepo):
898 901 def __init__(self, ctx, path, state):
899 902 self._state = state
900 903 self._ctx = ctx
901 904 self._path = path
902 905 self._relpath = os.path.join(reporelpath(ctx._repo), path)
903 906 self._abspath = ctx._repo.wjoin(path)
904 907 self._subparent = ctx._repo
905 908 self._ui = ctx._repo.ui
906 909 self._ensuregit()
907 910
908 911 def _ensuregit(self):
909 912 try:
910 913 self._gitexecutable = 'git'
911 914 out, err = self._gitnodir(['--version'])
912 915 except OSError, e:
913 916 if e.errno != 2 or os.name != 'nt':
914 917 raise
915 918 self._gitexecutable = 'git.cmd'
916 919 out, err = self._gitnodir(['--version'])
917 920 m = re.search(r'^git version (\d+)\.(\d+)\.(\d+)', out)
918 921 if not m:
919 922 self._ui.warn(_('cannot retrieve git version'))
920 923 return
921 924 version = (int(m.group(1)), m.group(2), m.group(3))
922 925 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
923 926 # despite the docstring comment. For now, error on 1.4.0, warn on
924 927 # 1.5.0 but attempt to continue.
925 928 if version < (1, 5, 0):
926 929 raise util.Abort(_('git subrepo requires at least 1.6.0 or later'))
927 930 elif version < (1, 6, 0):
928 931 self._ui.warn(_('git subrepo requires at least 1.6.0 or later'))
929 932
930 933 def _gitcommand(self, commands, env=None, stream=False):
931 934 return self._gitdir(commands, env=env, stream=stream)[0]
932 935
933 936 def _gitdir(self, commands, env=None, stream=False):
934 937 return self._gitnodir(commands, env=env, stream=stream,
935 938 cwd=self._abspath)
936 939
937 940 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
938 941 """Calls the git command
939 942
940 943 The methods tries to call the git command. versions prior to 1.6.0
941 944 are not supported and very probably fail.
942 945 """
943 946 self._ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
944 947 # unless ui.quiet is set, print git's stderr,
945 948 # which is mostly progress and useful info
946 949 errpipe = None
947 950 if self._ui.quiet:
948 951 errpipe = open(os.devnull, 'w')
949 952 p = subprocess.Popen([self._gitexecutable] + commands, bufsize=-1,
950 953 cwd=cwd, env=env, close_fds=util.closefds,
951 954 stdout=subprocess.PIPE, stderr=errpipe)
952 955 if stream:
953 956 return p.stdout, None
954 957
955 958 retdata = p.stdout.read().strip()
956 959 # wait for the child to exit to avoid race condition.
957 960 p.wait()
958 961
959 962 if p.returncode != 0 and p.returncode != 1:
960 963 # there are certain error codes that are ok
961 964 command = commands[0]
962 965 if command in ('cat-file', 'symbolic-ref'):
963 966 return retdata, p.returncode
964 967 # for all others, abort
965 968 raise util.Abort('git %s error %d in %s' %
966 969 (command, p.returncode, self._relpath))
967 970
968 971 return retdata, p.returncode
969 972
970 973 def _gitmissing(self):
971 974 return not os.path.exists(os.path.join(self._abspath, '.git'))
972 975
973 976 def _gitstate(self):
974 977 return self._gitcommand(['rev-parse', 'HEAD'])
975 978
976 979 def _gitcurrentbranch(self):
977 980 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
978 981 if err:
979 982 current = None
980 983 return current
981 984
982 985 def _gitremote(self, remote):
983 986 out = self._gitcommand(['remote', 'show', '-n', remote])
984 987 line = out.split('\n')[1]
985 988 i = line.index('URL: ') + len('URL: ')
986 989 return line[i:]
987 990
988 991 def _githavelocally(self, revision):
989 992 out, code = self._gitdir(['cat-file', '-e', revision])
990 993 return code == 0
991 994
992 995 def _gitisancestor(self, r1, r2):
993 996 base = self._gitcommand(['merge-base', r1, r2])
994 997 return base == r1
995 998
996 999 def _gitisbare(self):
997 1000 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
998 1001
999 1002 def _gitupdatestat(self):
1000 1003 """This must be run before git diff-index.
1001 1004 diff-index only looks at changes to file stat;
1002 1005 this command looks at file contents and updates the stat."""
1003 1006 self._gitcommand(['update-index', '-q', '--refresh'])
1004 1007
1005 1008 def _gitbranchmap(self):
1006 1009 '''returns 2 things:
1007 1010 a map from git branch to revision
1008 1011 a map from revision to branches'''
1009 1012 branch2rev = {}
1010 1013 rev2branch = {}
1011 1014
1012 1015 out = self._gitcommand(['for-each-ref', '--format',
1013 1016 '%(objectname) %(refname)'])
1014 1017 for line in out.split('\n'):
1015 1018 revision, ref = line.split(' ')
1016 1019 if (not ref.startswith('refs/heads/') and
1017 1020 not ref.startswith('refs/remotes/')):
1018 1021 continue
1019 1022 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
1020 1023 continue # ignore remote/HEAD redirects
1021 1024 branch2rev[ref] = revision
1022 1025 rev2branch.setdefault(revision, []).append(ref)
1023 1026 return branch2rev, rev2branch
1024 1027
1025 1028 def _gittracking(self, branches):
1026 1029 'return map of remote branch to local tracking branch'
1027 1030 # assumes no more than one local tracking branch for each remote
1028 1031 tracking = {}
1029 1032 for b in branches:
1030 1033 if b.startswith('refs/remotes/'):
1031 1034 continue
1032 1035 bname = b.split('/', 2)[2]
1033 1036 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
1034 1037 if remote:
1035 1038 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
1036 1039 tracking['refs/remotes/%s/%s' %
1037 1040 (remote, ref.split('/', 2)[2])] = b
1038 1041 return tracking
1039 1042
1040 1043 def _abssource(self, source):
1041 1044 if '://' not in source:
1042 1045 # recognize the scp syntax as an absolute source
1043 1046 colon = source.find(':')
1044 1047 if colon != -1 and '/' not in source[:colon]:
1045 1048 return source
1046 1049 self._subsource = source
1047 1050 return _abssource(self)
1048 1051
1049 1052 def _fetch(self, source, revision):
1050 1053 if self._gitmissing():
1051 1054 source = self._abssource(source)
1052 1055 self._ui.status(_('cloning subrepo %s from %s\n') %
1053 1056 (self._relpath, source))
1054 1057 self._gitnodir(['clone', source, self._abspath])
1055 1058 if self._githavelocally(revision):
1056 1059 return
1057 1060 self._ui.status(_('pulling subrepo %s from %s\n') %
1058 1061 (self._relpath, self._gitremote('origin')))
1059 1062 # try only origin: the originally cloned repo
1060 1063 self._gitcommand(['fetch'])
1061 1064 if not self._githavelocally(revision):
1062 1065 raise util.Abort(_("revision %s does not exist in subrepo %s\n") %
1063 1066 (revision, self._relpath))
1064 1067
1065 1068 @annotatesubrepoerror
1066 1069 def dirty(self, ignoreupdate=False):
1067 1070 if self._gitmissing():
1068 1071 return self._state[1] != ''
1069 1072 if self._gitisbare():
1070 1073 return True
1071 1074 if not ignoreupdate and self._state[1] != self._gitstate():
1072 1075 # different version checked out
1073 1076 return True
1074 1077 # check for staged changes or modified files; ignore untracked files
1075 1078 self._gitupdatestat()
1076 1079 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1077 1080 return code == 1
1078 1081
1079 1082 def basestate(self):
1080 1083 return self._gitstate()
1081 1084
1082 1085 @annotatesubrepoerror
1083 1086 def get(self, state, overwrite=False):
1084 1087 source, revision, kind = state
1085 1088 if not revision:
1086 1089 self.remove()
1087 1090 return
1088 1091 self._fetch(source, revision)
1089 1092 # if the repo was set to be bare, unbare it
1090 1093 if self._gitisbare():
1091 1094 self._gitcommand(['config', 'core.bare', 'false'])
1092 1095 if self._gitstate() == revision:
1093 1096 self._gitcommand(['reset', '--hard', 'HEAD'])
1094 1097 return
1095 1098 elif self._gitstate() == revision:
1096 1099 if overwrite:
1097 1100 # first reset the index to unmark new files for commit, because
1098 1101 # reset --hard will otherwise throw away files added for commit,
1099 1102 # not just unmark them.
1100 1103 self._gitcommand(['reset', 'HEAD'])
1101 1104 self._gitcommand(['reset', '--hard', 'HEAD'])
1102 1105 return
1103 1106 branch2rev, rev2branch = self._gitbranchmap()
1104 1107
1105 1108 def checkout(args):
1106 1109 cmd = ['checkout']
1107 1110 if overwrite:
1108 1111 # first reset the index to unmark new files for commit, because
1109 1112 # the -f option will otherwise throw away files added for
1110 1113 # commit, not just unmark them.
1111 1114 self._gitcommand(['reset', 'HEAD'])
1112 1115 cmd.append('-f')
1113 1116 self._gitcommand(cmd + args)
1114 1117
1115 1118 def rawcheckout():
1116 1119 # no branch to checkout, check it out with no branch
1117 1120 self._ui.warn(_('checking out detached HEAD in subrepo %s\n') %
1118 1121 self._relpath)
1119 1122 self._ui.warn(_('check out a git branch if you intend '
1120 1123 'to make changes\n'))
1121 1124 checkout(['-q', revision])
1122 1125
1123 1126 if revision not in rev2branch:
1124 1127 rawcheckout()
1125 1128 return
1126 1129 branches = rev2branch[revision]
1127 1130 firstlocalbranch = None
1128 1131 for b in branches:
1129 1132 if b == 'refs/heads/master':
1130 1133 # master trumps all other branches
1131 1134 checkout(['refs/heads/master'])
1132 1135 return
1133 1136 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1134 1137 firstlocalbranch = b
1135 1138 if firstlocalbranch:
1136 1139 checkout([firstlocalbranch])
1137 1140 return
1138 1141
1139 1142 tracking = self._gittracking(branch2rev.keys())
1140 1143 # choose a remote branch already tracked if possible
1141 1144 remote = branches[0]
1142 1145 if remote not in tracking:
1143 1146 for b in branches:
1144 1147 if b in tracking:
1145 1148 remote = b
1146 1149 break
1147 1150
1148 1151 if remote not in tracking:
1149 1152 # create a new local tracking branch
1150 1153 local = remote.split('/', 2)[2]
1151 1154 checkout(['-b', local, remote])
1152 1155 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1153 1156 # When updating to a tracked remote branch,
1154 1157 # if the local tracking branch is downstream of it,
1155 1158 # a normal `git pull` would have performed a "fast-forward merge"
1156 1159 # which is equivalent to updating the local branch to the remote.
1157 1160 # Since we are only looking at branching at update, we need to
1158 1161 # detect this situation and perform this action lazily.
1159 1162 if tracking[remote] != self._gitcurrentbranch():
1160 1163 checkout([tracking[remote]])
1161 1164 self._gitcommand(['merge', '--ff', remote])
1162 1165 else:
1163 1166 # a real merge would be required, just checkout the revision
1164 1167 rawcheckout()
1165 1168
1166 1169 @annotatesubrepoerror
1167 1170 def commit(self, text, user, date):
1168 1171 if self._gitmissing():
1169 1172 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1170 1173 cmd = ['commit', '-a', '-m', text]
1171 1174 env = os.environ.copy()
1172 1175 if user:
1173 1176 cmd += ['--author', user]
1174 1177 if date:
1175 1178 # git's date parser silently ignores when seconds < 1e9
1176 1179 # convert to ISO8601
1177 1180 env['GIT_AUTHOR_DATE'] = util.datestr(date,
1178 1181 '%Y-%m-%dT%H:%M:%S %1%2')
1179 1182 self._gitcommand(cmd, env=env)
1180 1183 # make sure commit works otherwise HEAD might not exist under certain
1181 1184 # circumstances
1182 1185 return self._gitstate()
1183 1186
1184 1187 @annotatesubrepoerror
1185 1188 def merge(self, state):
1186 1189 source, revision, kind = state
1187 1190 self._fetch(source, revision)
1188 1191 base = self._gitcommand(['merge-base', revision, self._state[1]])
1189 1192 self._gitupdatestat()
1190 1193 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1191 1194
1192 1195 def mergefunc():
1193 1196 if base == revision:
1194 1197 self.get(state) # fast forward merge
1195 1198 elif base != self._state[1]:
1196 1199 self._gitcommand(['merge', '--no-commit', revision])
1197 1200
1198 1201 if self.dirty():
1199 1202 if self._gitstate() != revision:
1200 1203 dirty = self._gitstate() == self._state[1] or code != 0
1201 1204 if _updateprompt(self._ui, self, dirty,
1202 1205 self._state[1][:7], revision[:7]):
1203 1206 mergefunc()
1204 1207 else:
1205 1208 mergefunc()
1206 1209
1207 1210 @annotatesubrepoerror
1208 1211 def push(self, opts):
1209 1212 force = opts.get('force')
1210 1213
1211 1214 if not self._state[1]:
1212 1215 return True
1213 1216 if self._gitmissing():
1214 1217 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1215 1218 # if a branch in origin contains the revision, nothing to do
1216 1219 branch2rev, rev2branch = self._gitbranchmap()
1217 1220 if self._state[1] in rev2branch:
1218 1221 for b in rev2branch[self._state[1]]:
1219 1222 if b.startswith('refs/remotes/origin/'):
1220 1223 return True
1221 1224 for b, revision in branch2rev.iteritems():
1222 1225 if b.startswith('refs/remotes/origin/'):
1223 1226 if self._gitisancestor(self._state[1], revision):
1224 1227 return True
1225 1228 # otherwise, try to push the currently checked out branch
1226 1229 cmd = ['push']
1227 1230 if force:
1228 1231 cmd.append('--force')
1229 1232
1230 1233 current = self._gitcurrentbranch()
1231 1234 if current:
1232 1235 # determine if the current branch is even useful
1233 1236 if not self._gitisancestor(self._state[1], current):
1234 1237 self._ui.warn(_('unrelated git branch checked out '
1235 1238 'in subrepo %s\n') % self._relpath)
1236 1239 return False
1237 1240 self._ui.status(_('pushing branch %s of subrepo %s\n') %
1238 1241 (current.split('/', 2)[2], self._relpath))
1239 1242 self._gitcommand(cmd + ['origin', current])
1240 1243 return True
1241 1244 else:
1242 1245 self._ui.warn(_('no branch checked out in subrepo %s\n'
1243 1246 'cannot push revision %s\n') %
1244 1247 (self._relpath, self._state[1]))
1245 1248 return False
1246 1249
1247 1250 @annotatesubrepoerror
1248 1251 def remove(self):
1249 1252 if self._gitmissing():
1250 1253 return
1251 1254 if self.dirty():
1252 1255 self._ui.warn(_('not removing repo %s because '
1253 1256 'it has changes.\n') % self._relpath)
1254 1257 return
1255 1258 # we can't fully delete the repository as it may contain
1256 1259 # local-only history
1257 1260 self._ui.note(_('removing subrepo %s\n') % self._relpath)
1258 1261 self._gitcommand(['config', 'core.bare', 'true'])
1259 1262 for f in os.listdir(self._abspath):
1260 1263 if f == '.git':
1261 1264 continue
1262 1265 path = os.path.join(self._abspath, f)
1263 1266 if os.path.isdir(path) and not os.path.islink(path):
1264 1267 shutil.rmtree(path)
1265 1268 else:
1266 1269 os.remove(path)
1267 1270
1268 1271 def archive(self, ui, archiver, prefix, match=None):
1269 1272 source, revision = self._state
1270 1273 if not revision:
1271 1274 return
1272 1275 self._fetch(source, revision)
1273 1276
1274 1277 # Parse git's native archive command.
1275 1278 # This should be much faster than manually traversing the trees
1276 1279 # and objects with many subprocess calls.
1277 1280 tarstream = self._gitcommand(['archive', revision], stream=True)
1278 1281 tar = tarfile.open(fileobj=tarstream, mode='r|')
1279 1282 relpath = subrelpath(self)
1280 1283 ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1281 1284 for i, info in enumerate(tar):
1282 1285 if info.isdir():
1283 1286 continue
1284 1287 if match and not match(info.name):
1285 1288 continue
1286 1289 if info.issym():
1287 1290 data = info.linkname
1288 1291 else:
1289 1292 data = tar.extractfile(info).read()
1290 1293 archiver.addfile(os.path.join(prefix, self._path, info.name),
1291 1294 info.mode, info.issym(), data)
1292 1295 ui.progress(_('archiving (%s)') % relpath, i + 1,
1293 1296 unit=_('files'))
1294 1297 ui.progress(_('archiving (%s)') % relpath, None)
1295 1298
1296 1299
1297 1300 @annotatesubrepoerror
1298 1301 def status(self, rev2, **opts):
1299 1302 rev1 = self._state[1]
1300 1303 if self._gitmissing() or not rev1:
1301 1304 # if the repo is missing, return no results
1302 1305 return [], [], [], [], [], [], []
1303 1306 modified, added, removed = [], [], []
1304 1307 self._gitupdatestat()
1305 1308 if rev2:
1306 1309 command = ['diff-tree', rev1, rev2]
1307 1310 else:
1308 1311 command = ['diff-index', rev1]
1309 1312 out = self._gitcommand(command)
1310 1313 for line in out.split('\n'):
1311 1314 tab = line.find('\t')
1312 1315 if tab == -1:
1313 1316 continue
1314 1317 status, f = line[tab - 1], line[tab + 1:]
1315 1318 if status == 'M':
1316 1319 modified.append(f)
1317 1320 elif status == 'A':
1318 1321 added.append(f)
1319 1322 elif status == 'D':
1320 1323 removed.append(f)
1321 1324
1322 1325 deleted = unknown = ignored = clean = []
1323 1326 return modified, added, removed, deleted, unknown, ignored, clean
1324 1327
1325 1328 types = {
1326 1329 'hg': hgsubrepo,
1327 1330 'svn': svnsubrepo,
1328 1331 'git': gitsubrepo,
1329 1332 }
@@ -1,1053 +1,1066
1 1 Let commit recurse into subrepos by default to match pre-2.0 behavior:
2 2
3 3 $ echo "[ui]" >> $HGRCPATH
4 4 $ echo "commitsubrepos = Yes" >> $HGRCPATH
5 5
6 6 $ hg init t
7 7 $ cd t
8 8
9 9 first revision, no sub
10 10
11 11 $ echo a > a
12 12 $ hg ci -Am0
13 13 adding a
14 14
15 15 add first sub
16 16
17 17 $ echo s = s > .hgsub
18 18 $ hg add .hgsub
19 19 $ hg init s
20 20 $ echo a > s/a
21 21
22 22 Issue2232: committing a subrepo without .hgsub
23 23
24 24 $ hg ci -mbad s
25 25 abort: can't commit subrepos without .hgsub
26 26 [255]
27 27
28 28 $ hg -R s ci -Ams0
29 29 adding a
30 30 $ hg sum
31 31 parent: 0:f7b1eb17ad24 tip
32 32 0
33 33 branch: default
34 34 commit: 1 added, 1 subrepos
35 35 update: (current)
36 36 $ hg ci -m1
37 37
38 38 Revert subrepo and test subrepo fileset keyword:
39 39
40 40 $ echo b > s/a
41 41 $ hg revert "set:subrepo('glob:s*')"
42 42 reverting subrepo s
43 43 reverting s/a (glob)
44 44 $ rm s/a.orig
45 45
46 46 Revert subrepo with no backup. The "reverting s/a" line is gone since
47 47 we're really running 'hg update' in the subrepo:
48 48
49 49 $ echo b > s/a
50 50 $ hg revert --no-backup s
51 51 reverting subrepo s
52 52
53 53 Issue2022: update -C
54 54
55 55 $ echo b > s/a
56 56 $ hg sum
57 57 parent: 1:7cf8cfea66e4 tip
58 58 1
59 59 branch: default
60 60 commit: 1 subrepos
61 61 update: (current)
62 62 $ hg co -C 1
63 63 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
64 64 $ hg sum
65 65 parent: 1:7cf8cfea66e4 tip
66 66 1
67 67 branch: default
68 68 commit: (clean)
69 69 update: (current)
70 70
71 71 commands that require a clean repo should respect subrepos
72 72
73 73 $ echo b >> s/a
74 74 $ hg backout tip
75 75 abort: uncommitted changes in subrepo s
76 76 [255]
77 77 $ hg revert -C -R s s/a
78 78
79 79 add sub sub
80 80
81 81 $ echo ss = ss > s/.hgsub
82 82 $ hg init s/ss
83 83 $ echo a > s/ss/a
84 84 $ hg -R s add s/.hgsub
85 85 $ hg -R s/ss add s/ss/a
86 86 $ hg sum
87 87 parent: 1:7cf8cfea66e4 tip
88 88 1
89 89 branch: default
90 90 commit: 1 subrepos
91 91 update: (current)
92 92 $ hg ci -m2
93 93 committing subrepository s
94 94 committing subrepository s/ss (glob)
95 95 $ hg sum
96 96 parent: 2:df30734270ae tip
97 97 2
98 98 branch: default
99 99 commit: (clean)
100 100 update: (current)
101 101
102 102 bump sub rev (and check it is ignored by ui.commitsubrepos)
103 103
104 104 $ echo b > s/a
105 105 $ hg -R s ci -ms1
106 106 $ hg --config ui.commitsubrepos=no ci -m3
107 107
108 108 leave sub dirty (and check ui.commitsubrepos=no aborts the commit)
109 109
110 110 $ echo c > s/a
111 111 $ hg --config ui.commitsubrepos=no ci -m4
112 112 abort: uncommitted changes in subrepo s
113 113 (use --subrepos for recursive commit)
114 114 [255]
115 115 $ hg id
116 116 f6affe3fbfaa+ tip
117 117 $ hg -R s ci -mc
118 118 $ hg id
119 119 f6affe3fbfaa+ tip
120 120 $ echo d > s/a
121 121 $ hg ci -m4
122 122 committing subrepository s
123 123 $ hg tip -R s
124 124 changeset: 4:02dcf1d70411
125 125 tag: tip
126 126 user: test
127 127 date: Thu Jan 01 00:00:00 1970 +0000
128 128 summary: 4
129 129
130 130
131 131 check caching
132 132
133 133 $ hg co 0
134 134 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
135 135 $ hg debugsub
136 136
137 137 restore
138 138
139 139 $ hg co
140 140 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
141 141 $ hg debugsub
142 142 path s
143 143 source s
144 144 revision 02dcf1d704118aee3ee306ccfa1910850d5b05ef
145 145
146 146 new branch for merge tests
147 147
148 148 $ hg co 1
149 149 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
150 150 $ echo t = t >> .hgsub
151 151 $ hg init t
152 152 $ echo t > t/t
153 153 $ hg -R t add t
154 154 adding t/t (glob)
155 155
156 156 5
157 157
158 158 $ hg ci -m5 # add sub
159 159 committing subrepository t
160 160 created new head
161 161 $ echo t2 > t/t
162 162
163 163 6
164 164
165 165 $ hg st -R s
166 166 $ hg ci -m6 # change sub
167 167 committing subrepository t
168 168 $ hg debugsub
169 169 path s
170 170 source s
171 171 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
172 172 path t
173 173 source t
174 174 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
175 175 $ echo t3 > t/t
176 176
177 177 7
178 178
179 179 $ hg ci -m7 # change sub again for conflict test
180 180 committing subrepository t
181 181 $ hg rm .hgsub
182 182
183 183 8
184 184
185 185 $ hg ci -m8 # remove sub
186 186
187 187 merge tests
188 188
189 189 $ hg co -C 3
190 190 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
191 191 $ hg merge 5 # test adding
192 192 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
193 193 (branch merge, don't forget to commit)
194 194 $ hg debugsub
195 195 path s
196 196 source s
197 197 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
198 198 path t
199 199 source t
200 200 revision 60ca1237c19474e7a3978b0dc1ca4e6f36d51382
201 201 $ hg ci -m9
202 202 created new head
203 203 $ hg merge 6 --debug # test change
204 204 searching for copies back to rev 2
205 205 resolving manifests
206 206 overwrite: False, partial: False
207 207 ancestor: 1f14a2e2d3ec, local: f0d2028bf86d+, remote: 1831e14459c4
208 208 .hgsubstate: versions differ -> m
209 209 updating: .hgsubstate 1/1 files (100.00%)
210 210 subrepo merge f0d2028bf86d+ 1831e14459c4 1f14a2e2d3ec
211 211 subrepo t: other changed, get t:6747d179aa9a688023c4b0cad32e4c92bb7f34ad:hg
212 212 getting subrepo t
213 213 searching for copies back to rev 1
214 214 resolving manifests
215 215 overwrite: False, partial: False
216 216 ancestor: 60ca1237c194, local: 60ca1237c194+, remote: 6747d179aa9a
217 217 t: remote is newer -> g
218 218 updating: t 1/1 files (100.00%)
219 219 getting t
220 220 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
221 221 (branch merge, don't forget to commit)
222 222 $ hg debugsub
223 223 path s
224 224 source s
225 225 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
226 226 path t
227 227 source t
228 228 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
229 229 $ echo conflict > t/t
230 230 $ hg ci -m10
231 231 committing subrepository t
232 232 $ HGMERGE=internal:merge hg merge --debug 7 # test conflict
233 233 searching for copies back to rev 2
234 234 resolving manifests
235 235 overwrite: False, partial: False
236 236 ancestor: 1831e14459c4, local: e45c8b14af55+, remote: f94576341bcf
237 237 .hgsubstate: versions differ -> m
238 238 updating: .hgsubstate 1/1 files (100.00%)
239 239 subrepo merge e45c8b14af55+ f94576341bcf 1831e14459c4
240 240 subrepo t: both sides changed, merge with t:7af322bc1198a32402fe903e0b7ebcfc5c9bf8f4:hg
241 241 merging subrepo t
242 242 searching for copies back to rev 2
243 243 resolving manifests
244 244 overwrite: False, partial: False
245 245 ancestor: 6747d179aa9a, local: 20a0db6fbf6c+, remote: 7af322bc1198
246 246 t: versions differ -> m
247 247 preserving t for resolve of t
248 248 updating: t 1/1 files (100.00%)
249 249 picked tool 'internal:merge' for t (binary False symlink False)
250 250 merging t
251 251 my t@20a0db6fbf6c+ other t@7af322bc1198 ancestor t@6747d179aa9a
252 252 warning: conflicts during merge.
253 253 merging t incomplete! (edit conflicts, then use 'hg resolve --mark')
254 254 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
255 255 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
256 256 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
257 257 (branch merge, don't forget to commit)
258 258
259 259 should conflict
260 260
261 261 $ cat t/t
262 262 <<<<<<< local
263 263 conflict
264 264 =======
265 265 t3
266 266 >>>>>>> other
267 267
268 268 clone
269 269
270 270 $ cd ..
271 271 $ hg clone t tc
272 272 updating to branch default
273 273 cloning subrepo s from $TESTTMP/t/s (glob)
274 274 cloning subrepo s/ss from $TESTTMP/t/s/ss (glob)
275 275 cloning subrepo t from $TESTTMP/t/t (glob)
276 276 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
277 277 $ cd tc
278 278 $ hg debugsub
279 279 path s
280 280 source s
281 281 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
282 282 path t
283 283 source t
284 284 revision 20a0db6fbf6c3d2836e6519a642ae929bfc67c0e
285 285
286 286 push
287 287
288 288 $ echo bah > t/t
289 289 $ hg ci -m11
290 290 committing subrepository t
291 291 $ hg push
292 292 pushing to $TESTTMP/t (glob)
293 293 pushing subrepo s/ss to $TESTTMP/t/s/ss (glob)
294 294 searching for changes
295 295 no changes found
296 296 pushing subrepo s to $TESTTMP/t/s (glob)
297 297 searching for changes
298 298 no changes found
299 299 pushing subrepo t to $TESTTMP/t/t (glob)
300 300 searching for changes
301 301 adding changesets
302 302 adding manifests
303 303 adding file changes
304 304 added 1 changesets with 1 changes to 1 files
305 305 searching for changes
306 306 adding changesets
307 307 adding manifests
308 308 adding file changes
309 309 added 1 changesets with 1 changes to 1 files
310 310
311 311 push -f
312 312
313 313 $ echo bah > s/a
314 314 $ hg ci -m12
315 315 committing subrepository s
316 316 $ hg push
317 317 pushing to $TESTTMP/t (glob)
318 318 pushing subrepo s/ss to $TESTTMP/t/s/ss (glob)
319 319 searching for changes
320 320 no changes found
321 321 pushing subrepo s to $TESTTMP/t/s (glob)
322 322 searching for changes
323 323 abort: push creates new remote head 12a213df6fa9! (in subrepo s)
324 324 (did you forget to merge? use push -f to force)
325 325 [255]
326 326 $ hg push -f
327 327 pushing to $TESTTMP/t (glob)
328 328 pushing subrepo s/ss to $TESTTMP/t/s/ss (glob)
329 329 searching for changes
330 330 no changes found
331 331 pushing subrepo s to $TESTTMP/t/s (glob)
332 332 searching for changes
333 333 adding changesets
334 334 adding manifests
335 335 adding file changes
336 336 added 1 changesets with 1 changes to 1 files (+1 heads)
337 337 pushing subrepo t to $TESTTMP/t/t (glob)
338 338 searching for changes
339 339 no changes found
340 340 searching for changes
341 341 adding changesets
342 342 adding manifests
343 343 adding file changes
344 344 added 1 changesets with 1 changes to 1 files
345 345
346 346 update
347 347
348 348 $ cd ../t
349 349 $ hg up -C # discard our earlier merge
350 350 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
351 351 $ echo blah > t/t
352 352 $ hg ci -m13
353 353 committing subrepository t
354 354
355 355 pull
356 356
357 357 $ cd ../tc
358 358 $ hg pull
359 359 pulling from $TESTTMP/t (glob)
360 360 searching for changes
361 361 adding changesets
362 362 adding manifests
363 363 adding file changes
364 364 added 1 changesets with 1 changes to 1 files
365 365 (run 'hg update' to get a working copy)
366 366
367 367 should pull t
368 368
369 369 $ hg up
370 370 pulling subrepo t from $TESTTMP/t/t (glob)
371 371 searching for changes
372 372 adding changesets
373 373 adding manifests
374 374 adding file changes
375 375 added 1 changesets with 1 changes to 1 files
376 376 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
377 377 $ cat t/t
378 378 blah
379 379
380 380 bogus subrepo path aborts
381 381
382 382 $ echo 'bogus=[boguspath' >> .hgsub
383 383 $ hg ci -m 'bogus subrepo path'
384 384 abort: missing ] in subrepo source
385 385 [255]
386 386
387 387 Issue1986: merge aborts when trying to merge a subrepo that
388 388 shouldn't need merging
389 389
390 390 # subrepo layout
391 391 #
392 392 # o 5 br
393 393 # /|
394 394 # o | 4 default
395 395 # | |
396 396 # | o 3 br
397 397 # |/|
398 398 # o | 2 default
399 399 # | |
400 400 # | o 1 br
401 401 # |/
402 402 # o 0 default
403 403
404 404 $ cd ..
405 405 $ rm -rf sub
406 406 $ hg init main
407 407 $ cd main
408 408 $ hg init s
409 409 $ cd s
410 410 $ echo a > a
411 411 $ hg ci -Am1
412 412 adding a
413 413 $ hg branch br
414 414 marked working directory as branch br
415 415 (branches are permanent and global, did you want a bookmark?)
416 416 $ echo a >> a
417 417 $ hg ci -m1
418 418 $ hg up default
419 419 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
420 420 $ echo b > b
421 421 $ hg ci -Am1
422 422 adding b
423 423 $ hg up br
424 424 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
425 425 $ hg merge tip
426 426 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
427 427 (branch merge, don't forget to commit)
428 428 $ hg ci -m1
429 429 $ hg up 2
430 430 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
431 431 $ echo c > c
432 432 $ hg ci -Am1
433 433 adding c
434 434 $ hg up 3
435 435 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
436 436 $ hg merge 4
437 437 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
438 438 (branch merge, don't forget to commit)
439 439 $ hg ci -m1
440 440
441 441 # main repo layout:
442 442 #
443 443 # * <-- try to merge default into br again
444 444 # .`|
445 445 # . o 5 br --> substate = 5
446 446 # . |
447 447 # o | 4 default --> substate = 4
448 448 # | |
449 449 # | o 3 br --> substate = 2
450 450 # |/|
451 451 # o | 2 default --> substate = 2
452 452 # | |
453 453 # | o 1 br --> substate = 3
454 454 # |/
455 455 # o 0 default --> substate = 2
456 456
457 457 $ cd ..
458 458 $ echo 's = s' > .hgsub
459 459 $ hg -R s up 2
460 460 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
461 461 $ hg ci -Am1
462 462 adding .hgsub
463 463 $ hg branch br
464 464 marked working directory as branch br
465 465 (branches are permanent and global, did you want a bookmark?)
466 466 $ echo b > b
467 467 $ hg -R s up 3
468 468 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
469 469 $ hg ci -Am1
470 470 adding b
471 471 $ hg up default
472 472 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
473 473 $ echo c > c
474 474 $ hg ci -Am1
475 475 adding c
476 476 $ hg up 1
477 477 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
478 478 $ hg merge 2
479 479 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
480 480 (branch merge, don't forget to commit)
481 481 $ hg ci -m1
482 482 $ hg up 2
483 483 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
484 484 $ hg -R s up 4
485 485 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
486 486 $ echo d > d
487 487 $ hg ci -Am1
488 488 adding d
489 489 $ hg up 3
490 490 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
491 491 $ hg -R s up 5
492 492 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
493 493 $ echo e > e
494 494 $ hg ci -Am1
495 495 adding e
496 496
497 497 $ hg up 5
498 498 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
499 499 $ hg merge 4 # try to merge default into br again
500 500 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
501 501 (branch merge, don't forget to commit)
502 502 $ cd ..
503 503
504 504 test subrepo delete from .hgsubstate
505 505
506 506 $ hg init testdelete
507 507 $ mkdir testdelete/nested testdelete/nested2
508 508 $ hg init testdelete/nested
509 509 $ hg init testdelete/nested2
510 510 $ echo test > testdelete/nested/foo
511 511 $ echo test > testdelete/nested2/foo
512 512 $ hg -R testdelete/nested add
513 513 adding testdelete/nested/foo (glob)
514 514 $ hg -R testdelete/nested2 add
515 515 adding testdelete/nested2/foo (glob)
516 516 $ hg -R testdelete/nested ci -m test
517 517 $ hg -R testdelete/nested2 ci -m test
518 518 $ echo nested = nested > testdelete/.hgsub
519 519 $ echo nested2 = nested2 >> testdelete/.hgsub
520 520 $ hg -R testdelete add
521 521 adding testdelete/.hgsub (glob)
522 522 $ hg -R testdelete ci -m "nested 1 & 2 added"
523 523 $ echo nested = nested > testdelete/.hgsub
524 524 $ hg -R testdelete ci -m "nested 2 deleted"
525 525 $ cat testdelete/.hgsubstate
526 526 bdf5c9a3103743d900b12ae0db3ffdcfd7b0d878 nested
527 527 $ hg -R testdelete remove testdelete/.hgsub
528 528 $ hg -R testdelete ci -m ".hgsub deleted"
529 529 $ cat testdelete/.hgsubstate
530 530 bdf5c9a3103743d900b12ae0db3ffdcfd7b0d878 nested
531 531
532 532 test repository cloning
533 533
534 534 $ mkdir mercurial mercurial2
535 535 $ hg init nested_absolute
536 536 $ echo test > nested_absolute/foo
537 537 $ hg -R nested_absolute add
538 538 adding nested_absolute/foo (glob)
539 539 $ hg -R nested_absolute ci -mtest
540 540 $ cd mercurial
541 541 $ hg init nested_relative
542 542 $ echo test2 > nested_relative/foo2
543 543 $ hg -R nested_relative add
544 544 adding nested_relative/foo2 (glob)
545 545 $ hg -R nested_relative ci -mtest2
546 546 $ hg init main
547 547 $ echo "nested_relative = ../nested_relative" > main/.hgsub
548 548 $ echo "nested_absolute = `pwd`/nested_absolute" >> main/.hgsub
549 549 $ hg -R main add
550 550 adding main/.hgsub (glob)
551 551 $ hg -R main ci -m "add subrepos"
552 552 $ cd ..
553 553 $ hg clone mercurial/main mercurial2/main
554 554 updating to branch default
555 555 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
556 556 $ cat mercurial2/main/nested_absolute/.hg/hgrc \
557 557 > mercurial2/main/nested_relative/.hg/hgrc
558 558 [paths]
559 559 default = $TESTTMP/mercurial/nested_absolute
560 560 [paths]
561 561 default = $TESTTMP/mercurial/nested_relative
562 562 $ rm -rf mercurial mercurial2
563 563
564 564 Issue1977: multirepo push should fail if subrepo push fails
565 565
566 566 $ hg init repo
567 567 $ hg init repo/s
568 568 $ echo a > repo/s/a
569 569 $ hg -R repo/s ci -Am0
570 570 adding a
571 571 $ echo s = s > repo/.hgsub
572 572 $ hg -R repo ci -Am1
573 573 adding .hgsub
574 574 $ hg clone repo repo2
575 575 updating to branch default
576 576 cloning subrepo s from $TESTTMP/repo/s (glob)
577 577 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
578 578 $ hg -q -R repo2 pull -u
579 579 $ echo 1 > repo2/s/a
580 580 $ hg -R repo2/s ci -m2
581 581 $ hg -q -R repo2/s push
582 582 $ hg -R repo2/s up -C 0
583 583 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
584 584 $ echo 2 > repo2/s/b
585 585 $ hg -R repo2/s ci -m3 -A
586 586 adding b
587 587 created new head
588 588 $ hg -R repo2 ci -m3
589 589 $ hg -q -R repo2 push
590 590 abort: push creates new remote head cc505f09a8b2! (in subrepo s)
591 591 (did you forget to merge? use push -f to force)
592 592 [255]
593 593 $ hg -R repo update
594 594 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
595 595
596 596 test if untracked file is not overwritten
597 597
598 598 $ echo issue3276_ok > repo/s/b
599 599 $ hg -R repo2 push -f -q
600 600 $ hg -R repo update
601 601 b: untracked file differs
602 602 abort: untracked files in working directory differ from files in requested revision (in subrepo s)
603 603 [255]
604 604
605 605 $ cat repo/s/b
606 606 issue3276_ok
607 607 $ rm repo/s/b
608 608 $ hg -R repo revert --all
609 609 reverting repo/.hgsubstate (glob)
610 610 reverting subrepo s
611 611 $ hg -R repo update
612 612 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
613 613 $ cat repo/s/b
614 614 2
615 615 $ rm -rf repo2 repo
616 616
617 617
618 618 Issue1852 subrepos with relative paths always push/pull relative to default
619 619
620 620 Prepare a repo with subrepo
621 621
622 622 $ hg init issue1852a
623 623 $ cd issue1852a
624 624 $ hg init sub/repo
625 625 $ echo test > sub/repo/foo
626 626 $ hg -R sub/repo add sub/repo/foo
627 627 $ echo sub/repo = sub/repo > .hgsub
628 628 $ hg add .hgsub
629 629 $ hg ci -mtest
630 630 committing subrepository sub/repo (glob)
631 631 $ echo test >> sub/repo/foo
632 632 $ hg ci -mtest
633 633 committing subrepository sub/repo (glob)
634 634 $ cd ..
635 635
636 636 Create repo without default path, pull top repo, and see what happens on update
637 637
638 638 $ hg init issue1852b
639 639 $ hg -R issue1852b pull issue1852a
640 640 pulling from issue1852a
641 641 requesting all changes
642 642 adding changesets
643 643 adding manifests
644 644 adding file changes
645 645 added 2 changesets with 3 changes to 2 files
646 646 (run 'hg update' to get a working copy)
647 647 $ hg -R issue1852b update
648 648 abort: default path for subrepository not found (in subrepo sub/repo) (glob)
649 649 [255]
650 650
651 651 Pull -u now doesn't help
652 652
653 653 $ hg -R issue1852b pull -u issue1852a
654 654 pulling from issue1852a
655 655 searching for changes
656 656 no changes found
657 657
658 658 Try the same, but with pull -u
659 659
660 660 $ hg init issue1852c
661 661 $ hg -R issue1852c pull -r0 -u issue1852a
662 662 pulling from issue1852a
663 663 adding changesets
664 664 adding manifests
665 665 adding file changes
666 666 added 1 changesets with 2 changes to 2 files
667 667 cloning subrepo sub/repo from issue1852a/sub/repo (glob)
668 668 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
669 669
670 670 Try to push from the other side
671 671
672 672 $ hg -R issue1852a push `pwd`/issue1852c
673 673 pushing to $TESTTMP/issue1852c
674 674 pushing subrepo sub/repo to $TESTTMP/issue1852c/sub/repo (glob)
675 675 searching for changes
676 676 no changes found
677 677 searching for changes
678 678 adding changesets
679 679 adding manifests
680 680 adding file changes
681 681 added 1 changesets with 1 changes to 1 files
682 682
683 683 Incoming and outgoing should not use the default path:
684 684
685 685 $ hg clone -q issue1852a issue1852d
686 686 $ hg -R issue1852d outgoing --subrepos issue1852c
687 687 comparing with issue1852c
688 688 searching for changes
689 689 no changes found
690 690 comparing with issue1852c/sub/repo
691 691 searching for changes
692 692 no changes found
693 693 [1]
694 694 $ hg -R issue1852d incoming --subrepos issue1852c
695 695 comparing with issue1852c
696 696 searching for changes
697 697 no changes found
698 698 comparing with issue1852c/sub/repo
699 699 searching for changes
700 700 no changes found
701 701 [1]
702 702
703 703 Check status of files when none of them belong to the first
704 704 subrepository:
705 705
706 706 $ hg init subrepo-status
707 707 $ cd subrepo-status
708 708 $ hg init subrepo-1
709 709 $ hg init subrepo-2
710 710 $ cd subrepo-2
711 711 $ touch file
712 712 $ hg add file
713 713 $ cd ..
714 714 $ echo subrepo-1 = subrepo-1 > .hgsub
715 715 $ echo subrepo-2 = subrepo-2 >> .hgsub
716 716 $ hg add .hgsub
717 717 $ hg ci -m 'Added subrepos'
718 718 committing subrepository subrepo-2
719 719 $ hg st subrepo-2/file
720 720
721 721 Check that share works with subrepo
722 722 $ hg --config extensions.share= share . ../shared
723 723 updating working directory
724 724 cloning subrepo subrepo-2 from $TESTTMP/subrepo-status/subrepo-2
725 725 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
726 726 $ test -f ../shared/subrepo-1/.hg/sharedpath
727 727 [1]
728 $ hg -R ../shared in
729 comparing with $TESTTMP/subrepo-status (glob)
730 searching for changes
731 no changes found
732 [1]
733 $ hg -R ../shared/subrepo-2 showconfig paths
734 paths.default=$TESTTMP/subrepo-status/subrepo-2
735 $ hg -R ../shared/subrepo-1 sum --remote
736 parent: -1:000000000000 tip (empty repository)
737 branch: default
738 commit: (clean)
739 update: (current)
740 remote: (synced)
728 741
729 742 Check hg update --clean
730 743 $ cd $TESTTMP/t
731 744 $ rm -r t/t.orig
732 745 $ hg status -S --all
733 746 C .hgsub
734 747 C .hgsubstate
735 748 C a
736 749 C s/.hgsub
737 750 C s/.hgsubstate
738 751 C s/a
739 752 C s/ss/a
740 753 C t/t
741 754 $ echo c1 > s/a
742 755 $ cd s
743 756 $ echo c1 > b
744 757 $ echo c1 > c
745 758 $ hg add b
746 759 $ cd ..
747 760 $ hg status -S
748 761 M s/a
749 762 A s/b
750 763 ? s/c
751 764 $ hg update -C
752 765 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
753 766 $ hg status -S
754 767 ? s/b
755 768 ? s/c
756 769
757 770 Sticky subrepositories, no changes
758 771 $ cd $TESTTMP/t
759 772 $ hg id
760 773 925c17564ef8 tip
761 774 $ hg -R s id
762 775 12a213df6fa9 tip
763 776 $ hg -R t id
764 777 52c0adc0515a tip
765 778 $ hg update 11
766 779 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
767 780 $ hg id
768 781 365661e5936a
769 782 $ hg -R s id
770 783 fc627a69481f
771 784 $ hg -R t id
772 785 e95bcfa18a35
773 786
774 787 Sticky subrepositorys, file changes
775 788 $ touch s/f1
776 789 $ touch t/f1
777 790 $ hg add -S s/f1
778 791 $ hg add -S t/f1
779 792 $ hg id
780 793 365661e5936a+
781 794 $ hg -R s id
782 795 fc627a69481f+
783 796 $ hg -R t id
784 797 e95bcfa18a35+
785 798 $ hg update tip
786 799 subrepository sources for s differ
787 800 use (l)ocal source (fc627a69481f) or (r)emote source (12a213df6fa9)?
788 801 l
789 802 subrepository sources for t differ
790 803 use (l)ocal source (e95bcfa18a35) or (r)emote source (52c0adc0515a)?
791 804 l
792 805 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
793 806 $ hg id
794 807 925c17564ef8+ tip
795 808 $ hg -R s id
796 809 fc627a69481f+
797 810 $ hg -R t id
798 811 e95bcfa18a35+
799 812 $ hg update --clean tip
800 813 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
801 814
802 815 Sticky subrepository, revision updates
803 816 $ hg id
804 817 925c17564ef8 tip
805 818 $ hg -R s id
806 819 12a213df6fa9 tip
807 820 $ hg -R t id
808 821 52c0adc0515a tip
809 822 $ cd s
810 823 $ hg update -r -2
811 824 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
812 825 $ cd ../t
813 826 $ hg update -r 2
814 827 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
815 828 $ cd ..
816 829 $ hg update 10
817 830 subrepository sources for t differ (in checked out version)
818 831 use (l)ocal source (7af322bc1198) or (r)emote source (20a0db6fbf6c)?
819 832 l
820 833 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
821 834 $ hg id
822 835 e45c8b14af55+
823 836 $ hg -R s id
824 837 02dcf1d70411
825 838 $ hg -R t id
826 839 7af322bc1198
827 840
828 841 Sticky subrepository, file changes and revision updates
829 842 $ touch s/f1
830 843 $ touch t/f1
831 844 $ hg add -S s/f1
832 845 $ hg add -S t/f1
833 846 $ hg id
834 847 e45c8b14af55+
835 848 $ hg -R s id
836 849 02dcf1d70411+
837 850 $ hg -R t id
838 851 7af322bc1198+
839 852 $ hg update tip
840 853 subrepository sources for s differ
841 854 use (l)ocal source (02dcf1d70411) or (r)emote source (12a213df6fa9)?
842 855 l
843 856 subrepository sources for t differ
844 857 use (l)ocal source (7af322bc1198) or (r)emote source (52c0adc0515a)?
845 858 l
846 859 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
847 860 $ hg id
848 861 925c17564ef8+ tip
849 862 $ hg -R s id
850 863 02dcf1d70411+
851 864 $ hg -R t id
852 865 7af322bc1198+
853 866
854 867 Sticky repository, update --clean
855 868 $ hg update --clean tip
856 869 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
857 870 $ hg id
858 871 925c17564ef8 tip
859 872 $ hg -R s id
860 873 12a213df6fa9 tip
861 874 $ hg -R t id
862 875 52c0adc0515a tip
863 876
864 877 Test subrepo already at intended revision:
865 878 $ cd s
866 879 $ hg update fc627a69481f
867 880 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
868 881 $ cd ..
869 882 $ hg update 11
870 883 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
871 884 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
872 885 $ hg id -n
873 886 11+
874 887 $ hg -R s id
875 888 fc627a69481f
876 889 $ hg -R t id
877 890 e95bcfa18a35
878 891
879 892 Test that removing .hgsubstate doesn't break anything:
880 893
881 894 $ hg rm -f .hgsubstate
882 895 $ hg ci -mrm
883 896 nothing changed
884 897 [1]
885 898 $ hg log -vr tip
886 899 changeset: 13:925c17564ef8
887 900 tag: tip
888 901 user: test
889 902 date: Thu Jan 01 00:00:00 1970 +0000
890 903 files: .hgsubstate
891 904 description:
892 905 13
893 906
894 907
895 908
896 909 Test that removing .hgsub removes .hgsubstate:
897 910
898 911 $ hg rm .hgsub
899 912 $ hg ci -mrm2
900 913 created new head
901 914 $ hg log -vr tip
902 915 changeset: 14:2400bccd50af
903 916 tag: tip
904 917 parent: 11:365661e5936a
905 918 user: test
906 919 date: Thu Jan 01 00:00:00 1970 +0000
907 920 files: .hgsub .hgsubstate
908 921 description:
909 922 rm2
910 923
911 924
912 925 Test issue3153: diff -S with deleted subrepos
913 926
914 927 $ hg diff --nodates -S -c .
915 928 diff -r 365661e5936a -r 2400bccd50af .hgsub
916 929 --- a/.hgsub
917 930 +++ /dev/null
918 931 @@ -1,2 +0,0 @@
919 932 -s = s
920 933 -t = t
921 934 diff -r 365661e5936a -r 2400bccd50af .hgsubstate
922 935 --- a/.hgsubstate
923 936 +++ /dev/null
924 937 @@ -1,2 +0,0 @@
925 938 -fc627a69481fcbe5f1135069e8a3881c023e4cf5 s
926 939 -e95bcfa18a358dc4936da981ebf4147b4cad1362 t
927 940
928 941 Test behavior of add for explicit path in subrepo:
929 942 $ cd ..
930 943 $ hg init explicit
931 944 $ cd explicit
932 945 $ echo s = s > .hgsub
933 946 $ hg add .hgsub
934 947 $ hg init s
935 948 $ hg ci -m0
936 949 Adding with an explicit path in a subrepo adds the file
937 950 $ echo c1 > f1
938 951 $ echo c2 > s/f2
939 952 $ hg st -S
940 953 ? f1
941 954 ? s/f2
942 955 $ hg add s/f2
943 956 $ hg st -S
944 957 A s/f2
945 958 ? f1
946 959 $ hg ci -R s -m0
947 960 $ hg ci -Am1
948 961 adding f1
949 962 Adding with an explicit path in a subrepo with -S has the same behavior
950 963 $ echo c3 > f3
951 964 $ echo c4 > s/f4
952 965 $ hg st -S
953 966 ? f3
954 967 ? s/f4
955 968 $ hg add -S s/f4
956 969 $ hg st -S
957 970 A s/f4
958 971 ? f3
959 972 $ hg ci -R s -m1
960 973 $ hg ci -Ama2
961 974 adding f3
962 975 Adding without a path or pattern silently ignores subrepos
963 976 $ echo c5 > f5
964 977 $ echo c6 > s/f6
965 978 $ echo c7 > s/f7
966 979 $ hg st -S
967 980 ? f5
968 981 ? s/f6
969 982 ? s/f7
970 983 $ hg add
971 984 adding f5
972 985 $ hg st -S
973 986 A f5
974 987 ? s/f6
975 988 ? s/f7
976 989 $ hg ci -R s -Am2
977 990 adding f6
978 991 adding f7
979 992 $ hg ci -m3
980 993 Adding without a path or pattern with -S also adds files in subrepos
981 994 $ echo c8 > f8
982 995 $ echo c9 > s/f9
983 996 $ echo c10 > s/f10
984 997 $ hg st -S
985 998 ? f8
986 999 ? s/f10
987 1000 ? s/f9
988 1001 $ hg add -S
989 1002 adding f8
990 1003 adding s/f10 (glob)
991 1004 adding s/f9 (glob)
992 1005 $ hg st -S
993 1006 A f8
994 1007 A s/f10
995 1008 A s/f9
996 1009 $ hg ci -R s -m3
997 1010 $ hg ci -m4
998 1011 Adding with a pattern silently ignores subrepos
999 1012 $ echo c11 > fm11
1000 1013 $ echo c12 > fn12
1001 1014 $ echo c13 > s/fm13
1002 1015 $ echo c14 > s/fn14
1003 1016 $ hg st -S
1004 1017 ? fm11
1005 1018 ? fn12
1006 1019 ? s/fm13
1007 1020 ? s/fn14
1008 1021 $ hg add 'glob:**fm*'
1009 1022 adding fm11
1010 1023 $ hg st -S
1011 1024 A fm11
1012 1025 ? fn12
1013 1026 ? s/fm13
1014 1027 ? s/fn14
1015 1028 $ hg ci -R s -Am4
1016 1029 adding fm13
1017 1030 adding fn14
1018 1031 $ hg ci -Am5
1019 1032 adding fn12
1020 1033 Adding with a pattern with -S also adds matches in subrepos
1021 1034 $ echo c15 > fm15
1022 1035 $ echo c16 > fn16
1023 1036 $ echo c17 > s/fm17
1024 1037 $ echo c18 > s/fn18
1025 1038 $ hg st -S
1026 1039 ? fm15
1027 1040 ? fn16
1028 1041 ? s/fm17
1029 1042 ? s/fn18
1030 1043 $ hg add -S 'glob:**fm*'
1031 1044 adding fm15
1032 1045 adding s/fm17 (glob)
1033 1046 $ hg st -S
1034 1047 A fm15
1035 1048 A s/fm17
1036 1049 ? fn16
1037 1050 ? s/fn18
1038 1051 $ hg ci -R s -Am5
1039 1052 adding fn18
1040 1053 $ hg ci -Am6
1041 1054 adding fn16
1042 1055
1043 1056 Test behavior of forget for explicit path in subrepo:
1044 1057 Forgetting an explicit path in a subrepo untracks the file
1045 1058 $ echo c19 > s/f19
1046 1059 $ hg add s/f19
1047 1060 $ hg st -S
1048 1061 A s/f19
1049 1062 $ hg forget s/f19
1050 1063 $ hg st -S
1051 1064 ? s/f19
1052 1065 $ rm s/f19
1053 1066 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now