##// END OF EJS Templates
subrepo: don't abort in add when non-hg subrepos are present (issue4513)...
Matt Harbison -
r23963:8f02682f stable
parent child Browse files
Show More
@@ -1,1686 +1,1686 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 copy
9 9 import errno, os, re, shutil, posixpath, sys
10 10 import xml.dom.minidom
11 11 import stat, subprocess, tarfile
12 12 from i18n import _
13 13 import config, util, node, error, cmdutil, scmutil, match as matchmod
14 14 import phases
15 15 import pathutil
16 16 import exchange
17 17 hg = None
18 18 propertycache = util.propertycache
19 19
20 20 nullstate = ('', '', 'empty')
21 21
22 22 def _expandedabspath(path):
23 23 '''
24 24 get a path or url and if it is a path expand it and return an absolute path
25 25 '''
26 26 expandedpath = util.urllocalpath(util.expandpath(path))
27 27 u = util.url(expandedpath)
28 28 if not u.scheme:
29 29 path = util.normpath(os.path.abspath(u.path))
30 30 return path
31 31
32 32 def _getstorehashcachename(remotepath):
33 33 '''get a unique filename for the store hash cache of a remote repository'''
34 34 return util.sha1(_expandedabspath(remotepath)).hexdigest()[0:12]
35 35
36 36 class SubrepoAbort(error.Abort):
37 37 """Exception class used to avoid handling a subrepo error more than once"""
38 38 def __init__(self, *args, **kw):
39 39 error.Abort.__init__(self, *args, **kw)
40 40 self.subrepo = kw.get('subrepo')
41 41 self.cause = kw.get('cause')
42 42
43 43 def annotatesubrepoerror(func):
44 44 def decoratedmethod(self, *args, **kargs):
45 45 try:
46 46 res = func(self, *args, **kargs)
47 47 except SubrepoAbort, ex:
48 48 # This exception has already been handled
49 49 raise ex
50 50 except error.Abort, ex:
51 51 subrepo = subrelpath(self)
52 52 errormsg = str(ex) + ' ' + _('(in subrepo %s)') % subrepo
53 53 # avoid handling this exception by raising a SubrepoAbort exception
54 54 raise SubrepoAbort(errormsg, hint=ex.hint, subrepo=subrepo,
55 55 cause=sys.exc_info())
56 56 return res
57 57 return decoratedmethod
58 58
59 59 def state(ctx, ui):
60 60 """return a state dict, mapping subrepo paths configured in .hgsub
61 61 to tuple: (source from .hgsub, revision from .hgsubstate, kind
62 62 (key in types dict))
63 63 """
64 64 p = config.config()
65 65 def read(f, sections=None, remap=None):
66 66 if f in ctx:
67 67 try:
68 68 data = ctx[f].data()
69 69 except IOError, err:
70 70 if err.errno != errno.ENOENT:
71 71 raise
72 72 # handle missing subrepo spec files as removed
73 73 ui.warn(_("warning: subrepo spec file %s not found\n") % f)
74 74 return
75 75 p.parse(f, data, sections, remap, read)
76 76 else:
77 77 raise util.Abort(_("subrepo spec file %s not found") % f)
78 78
79 79 if '.hgsub' in ctx:
80 80 read('.hgsub')
81 81
82 82 for path, src in ui.configitems('subpaths'):
83 83 p.set('subpaths', path, src, ui.configsource('subpaths', path))
84 84
85 85 rev = {}
86 86 if '.hgsubstate' in ctx:
87 87 try:
88 88 for i, l in enumerate(ctx['.hgsubstate'].data().splitlines()):
89 89 l = l.lstrip()
90 90 if not l:
91 91 continue
92 92 try:
93 93 revision, path = l.split(" ", 1)
94 94 except ValueError:
95 95 raise util.Abort(_("invalid subrepository revision "
96 96 "specifier in .hgsubstate line %d")
97 97 % (i + 1))
98 98 rev[path] = revision
99 99 except IOError, err:
100 100 if err.errno != errno.ENOENT:
101 101 raise
102 102
103 103 def remap(src):
104 104 for pattern, repl in p.items('subpaths'):
105 105 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
106 106 # does a string decode.
107 107 repl = repl.encode('string-escape')
108 108 # However, we still want to allow back references to go
109 109 # through unharmed, so we turn r'\\1' into r'\1'. Again,
110 110 # extra escapes are needed because re.sub string decodes.
111 111 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
112 112 try:
113 113 src = re.sub(pattern, repl, src, 1)
114 114 except re.error, e:
115 115 raise util.Abort(_("bad subrepository pattern in %s: %s")
116 116 % (p.source('subpaths', pattern), e))
117 117 return src
118 118
119 119 state = {}
120 120 for path, src in p[''].items():
121 121 kind = 'hg'
122 122 if src.startswith('['):
123 123 if ']' not in src:
124 124 raise util.Abort(_('missing ] in subrepo source'))
125 125 kind, src = src.split(']', 1)
126 126 kind = kind[1:]
127 127 src = src.lstrip() # strip any extra whitespace after ']'
128 128
129 129 if not util.url(src).isabs():
130 130 parent = _abssource(ctx._repo, abort=False)
131 131 if parent:
132 132 parent = util.url(parent)
133 133 parent.path = posixpath.join(parent.path or '', src)
134 134 parent.path = posixpath.normpath(parent.path)
135 135 joined = str(parent)
136 136 # Remap the full joined path and use it if it changes,
137 137 # else remap the original source.
138 138 remapped = remap(joined)
139 139 if remapped == joined:
140 140 src = remap(src)
141 141 else:
142 142 src = remapped
143 143
144 144 src = remap(src)
145 145 state[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind)
146 146
147 147 return state
148 148
149 149 def writestate(repo, state):
150 150 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
151 151 lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state)]
152 152 repo.wwrite('.hgsubstate', ''.join(lines), '')
153 153
154 154 def submerge(repo, wctx, mctx, actx, overwrite):
155 155 """delegated from merge.applyupdates: merging of .hgsubstate file
156 156 in working context, merging context and ancestor context"""
157 157 if mctx == actx: # backwards?
158 158 actx = wctx.p1()
159 159 s1 = wctx.substate
160 160 s2 = mctx.substate
161 161 sa = actx.substate
162 162 sm = {}
163 163
164 164 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
165 165
166 166 def debug(s, msg, r=""):
167 167 if r:
168 168 r = "%s:%s:%s" % r
169 169 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
170 170
171 171 for s, l in sorted(s1.iteritems()):
172 172 a = sa.get(s, nullstate)
173 173 ld = l # local state with possible dirty flag for compares
174 174 if wctx.sub(s).dirty():
175 175 ld = (l[0], l[1] + "+")
176 176 if wctx == actx: # overwrite
177 177 a = ld
178 178
179 179 if s in s2:
180 180 r = s2[s]
181 181 if ld == r or r == a: # no change or local is newer
182 182 sm[s] = l
183 183 continue
184 184 elif ld == a: # other side changed
185 185 debug(s, "other changed, get", r)
186 186 wctx.sub(s).get(r, overwrite)
187 187 sm[s] = r
188 188 elif ld[0] != r[0]: # sources differ
189 189 if repo.ui.promptchoice(
190 190 _(' subrepository sources for %s differ\n'
191 191 'use (l)ocal source (%s) or (r)emote source (%s)?'
192 192 '$$ &Local $$ &Remote') % (s, l[0], r[0]), 0):
193 193 debug(s, "prompt changed, get", r)
194 194 wctx.sub(s).get(r, overwrite)
195 195 sm[s] = r
196 196 elif ld[1] == a[1]: # local side is unchanged
197 197 debug(s, "other side changed, get", r)
198 198 wctx.sub(s).get(r, overwrite)
199 199 sm[s] = r
200 200 else:
201 201 debug(s, "both sides changed")
202 202 srepo = wctx.sub(s)
203 203 option = repo.ui.promptchoice(
204 204 _(' subrepository %s diverged (local revision: %s, '
205 205 'remote revision: %s)\n'
206 206 '(M)erge, keep (l)ocal or keep (r)emote?'
207 207 '$$ &Merge $$ &Local $$ &Remote')
208 208 % (s, srepo.shortid(l[1]), srepo.shortid(r[1])), 0)
209 209 if option == 0:
210 210 wctx.sub(s).merge(r)
211 211 sm[s] = l
212 212 debug(s, "merge with", r)
213 213 elif option == 1:
214 214 sm[s] = l
215 215 debug(s, "keep local subrepo revision", l)
216 216 else:
217 217 wctx.sub(s).get(r, overwrite)
218 218 sm[s] = r
219 219 debug(s, "get remote subrepo revision", r)
220 220 elif ld == a: # remote removed, local unchanged
221 221 debug(s, "remote removed, remove")
222 222 wctx.sub(s).remove()
223 223 elif a == nullstate: # not present in remote or ancestor
224 224 debug(s, "local added, keep")
225 225 sm[s] = l
226 226 continue
227 227 else:
228 228 if repo.ui.promptchoice(
229 229 _(' local changed subrepository %s which remote removed\n'
230 230 'use (c)hanged version or (d)elete?'
231 231 '$$ &Changed $$ &Delete') % s, 0):
232 232 debug(s, "prompt remove")
233 233 wctx.sub(s).remove()
234 234
235 235 for s, r in sorted(s2.items()):
236 236 if s in s1:
237 237 continue
238 238 elif s not in sa:
239 239 debug(s, "remote added, get", r)
240 240 mctx.sub(s).get(r)
241 241 sm[s] = r
242 242 elif r != sa[s]:
243 243 if repo.ui.promptchoice(
244 244 _(' remote changed subrepository %s which local removed\n'
245 245 'use (c)hanged version or (d)elete?'
246 246 '$$ &Changed $$ &Delete') % s, 0) == 0:
247 247 debug(s, "prompt recreate", r)
248 248 wctx.sub(s).get(r)
249 249 sm[s] = r
250 250
251 251 # record merged .hgsubstate
252 252 writestate(repo, sm)
253 253 return sm
254 254
255 255 def _updateprompt(ui, sub, dirty, local, remote):
256 256 if dirty:
257 257 msg = (_(' subrepository sources for %s differ\n'
258 258 'use (l)ocal source (%s) or (r)emote source (%s)?'
259 259 '$$ &Local $$ &Remote')
260 260 % (subrelpath(sub), local, remote))
261 261 else:
262 262 msg = (_(' subrepository sources for %s differ (in checked out '
263 263 'version)\n'
264 264 'use (l)ocal source (%s) or (r)emote source (%s)?'
265 265 '$$ &Local $$ &Remote')
266 266 % (subrelpath(sub), local, remote))
267 267 return ui.promptchoice(msg, 0)
268 268
269 269 def reporelpath(repo):
270 270 """return path to this (sub)repo as seen from outermost repo"""
271 271 parent = repo
272 272 while util.safehasattr(parent, '_subparent'):
273 273 parent = parent._subparent
274 274 return repo.root[len(pathutil.normasprefix(parent.root)):]
275 275
276 276 def subrelpath(sub):
277 277 """return path to this subrepo as seen from outermost repo"""
278 278 if util.safehasattr(sub, '_relpath'):
279 279 return sub._relpath
280 280 if not util.safehasattr(sub, '_repo'):
281 281 return sub._path
282 282 return reporelpath(sub._repo)
283 283
284 284 def _abssource(repo, push=False, abort=True):
285 285 """return pull/push path of repo - either based on parent repo .hgsub info
286 286 or on the top repo config. Abort or return None if no source found."""
287 287 if util.safehasattr(repo, '_subparent'):
288 288 source = util.url(repo._subsource)
289 289 if source.isabs():
290 290 return str(source)
291 291 source.path = posixpath.normpath(source.path)
292 292 parent = _abssource(repo._subparent, push, abort=False)
293 293 if parent:
294 294 parent = util.url(util.pconvert(parent))
295 295 parent.path = posixpath.join(parent.path or '', source.path)
296 296 parent.path = posixpath.normpath(parent.path)
297 297 return str(parent)
298 298 else: # recursion reached top repo
299 299 if util.safehasattr(repo, '_subtoppath'):
300 300 return repo._subtoppath
301 301 if push and repo.ui.config('paths', 'default-push'):
302 302 return repo.ui.config('paths', 'default-push')
303 303 if repo.ui.config('paths', 'default'):
304 304 return repo.ui.config('paths', 'default')
305 305 if repo.shared():
306 306 # chop off the .hg component to get the default path form
307 307 return os.path.dirname(repo.sharedpath)
308 308 if abort:
309 309 raise util.Abort(_("default path for subrepository not found"))
310 310
311 311 def _sanitize(ui, path, ignore):
312 312 for dirname, dirs, names in os.walk(path):
313 313 for i, d in enumerate(dirs):
314 314 if d.lower() == ignore:
315 315 del dirs[i]
316 316 break
317 317 if os.path.basename(dirname).lower() != '.hg':
318 318 continue
319 319 for f in names:
320 320 if f.lower() == 'hgrc':
321 321 ui.warn(_("warning: removing potentially hostile 'hgrc' "
322 322 "in '%s'\n") % dirname)
323 323 os.unlink(os.path.join(dirname, f))
324 324
325 325 def subrepo(ctx, path):
326 326 """return instance of the right subrepo class for subrepo in path"""
327 327 # subrepo inherently violates our import layering rules
328 328 # because it wants to make repo objects from deep inside the stack
329 329 # so we manually delay the circular imports to not break
330 330 # scripts that don't use our demand-loading
331 331 global hg
332 332 import hg as h
333 333 hg = h
334 334
335 335 pathutil.pathauditor(ctx._repo.root)(path)
336 336 state = ctx.substate[path]
337 337 if state[2] not in types:
338 338 raise util.Abort(_('unknown subrepo type %s') % state[2])
339 339 return types[state[2]](ctx, path, state[:2])
340 340
341 341 def newcommitphase(ui, ctx):
342 342 commitphase = phases.newcommitphase(ui)
343 343 substate = getattr(ctx, "substate", None)
344 344 if not substate:
345 345 return commitphase
346 346 check = ui.config('phases', 'checksubrepos', 'follow')
347 347 if check not in ('ignore', 'follow', 'abort'):
348 348 raise util.Abort(_('invalid phases.checksubrepos configuration: %s')
349 349 % (check))
350 350 if check == 'ignore':
351 351 return commitphase
352 352 maxphase = phases.public
353 353 maxsub = None
354 354 for s in sorted(substate):
355 355 sub = ctx.sub(s)
356 356 subphase = sub.phase(substate[s][1])
357 357 if maxphase < subphase:
358 358 maxphase = subphase
359 359 maxsub = s
360 360 if commitphase < maxphase:
361 361 if check == 'abort':
362 362 raise util.Abort(_("can't commit in %s phase"
363 363 " conflicting %s from subrepository %s") %
364 364 (phases.phasenames[commitphase],
365 365 phases.phasenames[maxphase], maxsub))
366 366 ui.warn(_("warning: changes are committed in"
367 367 " %s phase from subrepository %s\n") %
368 368 (phases.phasenames[maxphase], maxsub))
369 369 return maxphase
370 370 return commitphase
371 371
372 372 # subrepo classes need to implement the following abstract class:
373 373
374 374 class abstractsubrepo(object):
375 375
376 376 def __init__(self, ui):
377 377 self.ui = ui
378 378
379 379 def storeclean(self, path):
380 380 """
381 381 returns true if the repository has not changed since it was last
382 382 cloned from or pushed to a given repository.
383 383 """
384 384 return False
385 385
386 386 def dirty(self, ignoreupdate=False):
387 387 """returns true if the dirstate of the subrepo is dirty or does not
388 388 match current stored state. If ignoreupdate is true, only check
389 389 whether the subrepo has uncommitted changes in its dirstate.
390 390 """
391 391 raise NotImplementedError
392 392
393 393 def basestate(self):
394 394 """current working directory base state, disregarding .hgsubstate
395 395 state and working directory modifications"""
396 396 raise NotImplementedError
397 397
398 398 def checknested(self, path):
399 399 """check if path is a subrepository within this repository"""
400 400 return False
401 401
402 402 def commit(self, text, user, date):
403 403 """commit the current changes to the subrepo with the given
404 404 log message. Use given user and date if possible. Return the
405 405 new state of the subrepo.
406 406 """
407 407 raise NotImplementedError
408 408
409 409 def phase(self, state):
410 410 """returns phase of specified state in the subrepository.
411 411 """
412 412 return phases.public
413 413
414 414 def remove(self):
415 415 """remove the subrepo
416 416
417 417 (should verify the dirstate is not dirty first)
418 418 """
419 419 raise NotImplementedError
420 420
421 421 def get(self, state, overwrite=False):
422 422 """run whatever commands are needed to put the subrepo into
423 423 this state
424 424 """
425 425 raise NotImplementedError
426 426
427 427 def merge(self, state):
428 428 """merge currently-saved state with the new state."""
429 429 raise NotImplementedError
430 430
431 431 def push(self, opts):
432 432 """perform whatever action is analogous to 'hg push'
433 433
434 434 This may be a no-op on some systems.
435 435 """
436 436 raise NotImplementedError
437 437
438 def add(self, ui, match, dryrun, listsubrepos, prefix, explicitonly):
438 def add(self, ui, match, prefix, explicitonly, **opts):
439 439 return []
440 440
441 441 def addremove(self, matcher, prefix, opts, dry_run, similarity):
442 442 self.ui.warn("%s: %s" % (prefix, _("addremove is not supported")))
443 443 return 1
444 444
445 445 def cat(self, match, prefix, **opts):
446 446 return 1
447 447
448 448 def status(self, rev2, **opts):
449 449 return scmutil.status([], [], [], [], [], [], [])
450 450
451 451 def diff(self, ui, diffopts, node2, match, prefix, **opts):
452 452 pass
453 453
454 454 def outgoing(self, ui, dest, opts):
455 455 return 1
456 456
457 457 def incoming(self, ui, source, opts):
458 458 return 1
459 459
460 460 def files(self):
461 461 """return filename iterator"""
462 462 raise NotImplementedError
463 463
464 464 def filedata(self, name):
465 465 """return file data"""
466 466 raise NotImplementedError
467 467
468 468 def fileflags(self, name):
469 469 """return file flags"""
470 470 return ''
471 471
472 472 def archive(self, archiver, prefix, match=None):
473 473 if match is not None:
474 474 files = [f for f in self.files() if match(f)]
475 475 else:
476 476 files = self.files()
477 477 total = len(files)
478 478 relpath = subrelpath(self)
479 479 self.ui.progress(_('archiving (%s)') % relpath, 0,
480 480 unit=_('files'), total=total)
481 481 for i, name in enumerate(files):
482 482 flags = self.fileflags(name)
483 483 mode = 'x' in flags and 0755 or 0644
484 484 symlink = 'l' in flags
485 485 archiver.addfile(os.path.join(prefix, self._path, name),
486 486 mode, symlink, self.filedata(name))
487 487 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
488 488 unit=_('files'), total=total)
489 489 self.ui.progress(_('archiving (%s)') % relpath, None)
490 490 return total
491 491
492 492 def walk(self, match):
493 493 '''
494 494 walk recursively through the directory tree, finding all files
495 495 matched by the match function
496 496 '''
497 497 pass
498 498
499 499 def forget(self, match, prefix):
500 500 return ([], [])
501 501
502 502 def removefiles(self, matcher, prefix, after, force, subrepos):
503 503 """remove the matched files from the subrepository and the filesystem,
504 504 possibly by force and/or after the file has been removed from the
505 505 filesystem. Return 0 on success, 1 on any warning.
506 506 """
507 507 return 1
508 508
509 509 def revert(self, substate, *pats, **opts):
510 510 self.ui.warn('%s: reverting %s subrepos is unsupported\n' \
511 511 % (substate[0], substate[2]))
512 512 return []
513 513
514 514 def shortid(self, revid):
515 515 return revid
516 516
517 517 class hgsubrepo(abstractsubrepo):
518 518 def __init__(self, ctx, path, state):
519 519 super(hgsubrepo, self).__init__(ctx._repo.ui)
520 520 self._path = path
521 521 self._state = state
522 522 r = ctx._repo
523 523 root = r.wjoin(path)
524 524 create = not r.wvfs.exists('%s/.hg' % path)
525 525 self._repo = hg.repository(r.baseui, root, create=create)
526 526 self.ui = self._repo.ui
527 527 for s, k in [('ui', 'commitsubrepos')]:
528 528 v = r.ui.config(s, k)
529 529 if v:
530 530 self.ui.setconfig(s, k, v, 'subrepo')
531 531 self.ui.setconfig('ui', '_usedassubrepo', 'True', 'subrepo')
532 532 self._initrepo(r, state[0], create)
533 533
534 534 def storeclean(self, path):
535 535 lock = self._repo.lock()
536 536 try:
537 537 return self._storeclean(path)
538 538 finally:
539 539 lock.release()
540 540
541 541 def _storeclean(self, path):
542 542 clean = True
543 543 itercache = self._calcstorehash(path)
544 544 try:
545 545 for filehash in self._readstorehashcache(path):
546 546 if filehash != itercache.next():
547 547 clean = False
548 548 break
549 549 except StopIteration:
550 550 # the cached and current pull states have a different size
551 551 clean = False
552 552 if clean:
553 553 try:
554 554 itercache.next()
555 555 # the cached and current pull states have a different size
556 556 clean = False
557 557 except StopIteration:
558 558 pass
559 559 return clean
560 560
561 561 def _calcstorehash(self, remotepath):
562 562 '''calculate a unique "store hash"
563 563
564 564 This method is used to to detect when there are changes that may
565 565 require a push to a given remote path.'''
566 566 # sort the files that will be hashed in increasing (likely) file size
567 567 filelist = ('bookmarks', 'store/phaseroots', 'store/00changelog.i')
568 568 yield '# %s\n' % _expandedabspath(remotepath)
569 569 vfs = self._repo.vfs
570 570 for relname in filelist:
571 571 filehash = util.sha1(vfs.tryread(relname)).hexdigest()
572 572 yield '%s = %s\n' % (relname, filehash)
573 573
574 574 @propertycache
575 575 def _cachestorehashvfs(self):
576 576 return scmutil.vfs(self._repo.join('cache/storehash'))
577 577
578 578 def _readstorehashcache(self, remotepath):
579 579 '''read the store hash cache for a given remote repository'''
580 580 cachefile = _getstorehashcachename(remotepath)
581 581 return self._cachestorehashvfs.tryreadlines(cachefile, 'r')
582 582
583 583 def _cachestorehash(self, remotepath):
584 584 '''cache the current store hash
585 585
586 586 Each remote repo requires its own store hash cache, because a subrepo
587 587 store may be "clean" versus a given remote repo, but not versus another
588 588 '''
589 589 cachefile = _getstorehashcachename(remotepath)
590 590 lock = self._repo.lock()
591 591 try:
592 592 storehash = list(self._calcstorehash(remotepath))
593 593 vfs = self._cachestorehashvfs
594 594 vfs.writelines(cachefile, storehash, mode='w', notindexed=True)
595 595 finally:
596 596 lock.release()
597 597
598 598 @annotatesubrepoerror
599 599 def _initrepo(self, parentrepo, source, create):
600 600 self._repo._subparent = parentrepo
601 601 self._repo._subsource = source
602 602
603 603 if create:
604 604 lines = ['[paths]\n']
605 605
606 606 def addpathconfig(key, value):
607 607 if value:
608 608 lines.append('%s = %s\n' % (key, value))
609 609 self.ui.setconfig('paths', key, value, 'subrepo')
610 610
611 611 defpath = _abssource(self._repo, abort=False)
612 612 defpushpath = _abssource(self._repo, True, abort=False)
613 613 addpathconfig('default', defpath)
614 614 if defpath != defpushpath:
615 615 addpathconfig('default-push', defpushpath)
616 616
617 617 fp = self._repo.vfs("hgrc", "w", text=True)
618 618 try:
619 619 fp.write(''.join(lines))
620 620 finally:
621 621 fp.close()
622 622
623 623 @annotatesubrepoerror
624 624 def add(self, ui, match, prefix, explicitonly, **opts):
625 625 return cmdutil.add(ui, self._repo, match,
626 626 os.path.join(prefix, self._path), explicitonly,
627 627 **opts)
628 628
629 629 def addremove(self, m, prefix, opts, dry_run, similarity):
630 630 # In the same way as sub directories are processed, once in a subrepo,
631 631 # always entry any of its subrepos. Don't corrupt the options that will
632 632 # be used to process sibling subrepos however.
633 633 opts = copy.copy(opts)
634 634 opts['subrepos'] = True
635 635 return scmutil.addremove(self._repo, m,
636 636 os.path.join(prefix, self._path), opts,
637 637 dry_run, similarity)
638 638
639 639 @annotatesubrepoerror
640 640 def cat(self, match, prefix, **opts):
641 641 rev = self._state[1]
642 642 ctx = self._repo[rev]
643 643 return cmdutil.cat(self.ui, self._repo, ctx, match, prefix, **opts)
644 644
645 645 @annotatesubrepoerror
646 646 def status(self, rev2, **opts):
647 647 try:
648 648 rev1 = self._state[1]
649 649 ctx1 = self._repo[rev1]
650 650 ctx2 = self._repo[rev2]
651 651 return self._repo.status(ctx1, ctx2, **opts)
652 652 except error.RepoLookupError, inst:
653 653 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
654 654 % (inst, subrelpath(self)))
655 655 return scmutil.status([], [], [], [], [], [], [])
656 656
657 657 @annotatesubrepoerror
658 658 def diff(self, ui, diffopts, node2, match, prefix, **opts):
659 659 try:
660 660 node1 = node.bin(self._state[1])
661 661 # We currently expect node2 to come from substate and be
662 662 # in hex format
663 663 if node2 is not None:
664 664 node2 = node.bin(node2)
665 665 cmdutil.diffordiffstat(ui, self._repo, diffopts,
666 666 node1, node2, match,
667 667 prefix=posixpath.join(prefix, self._path),
668 668 listsubrepos=True, **opts)
669 669 except error.RepoLookupError, inst:
670 670 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
671 671 % (inst, subrelpath(self)))
672 672
673 673 @annotatesubrepoerror
674 674 def archive(self, archiver, prefix, match=None):
675 675 self._get(self._state + ('hg',))
676 676 total = abstractsubrepo.archive(self, archiver, prefix, match)
677 677 rev = self._state[1]
678 678 ctx = self._repo[rev]
679 679 for subpath in ctx.substate:
680 680 s = subrepo(ctx, subpath)
681 681 submatch = matchmod.narrowmatcher(subpath, match)
682 682 total += s.archive(
683 683 archiver, os.path.join(prefix, self._path), submatch)
684 684 return total
685 685
686 686 @annotatesubrepoerror
687 687 def dirty(self, ignoreupdate=False):
688 688 r = self._state[1]
689 689 if r == '' and not ignoreupdate: # no state recorded
690 690 return True
691 691 w = self._repo[None]
692 692 if r != w.p1().hex() and not ignoreupdate:
693 693 # different version checked out
694 694 return True
695 695 return w.dirty() # working directory changed
696 696
697 697 def basestate(self):
698 698 return self._repo['.'].hex()
699 699
700 700 def checknested(self, path):
701 701 return self._repo._checknested(self._repo.wjoin(path))
702 702
703 703 @annotatesubrepoerror
704 704 def commit(self, text, user, date):
705 705 # don't bother committing in the subrepo if it's only been
706 706 # updated
707 707 if not self.dirty(True):
708 708 return self._repo['.'].hex()
709 709 self.ui.debug("committing subrepo %s\n" % subrelpath(self))
710 710 n = self._repo.commit(text, user, date)
711 711 if not n:
712 712 return self._repo['.'].hex() # different version checked out
713 713 return node.hex(n)
714 714
715 715 @annotatesubrepoerror
716 716 def phase(self, state):
717 717 return self._repo[state].phase()
718 718
719 719 @annotatesubrepoerror
720 720 def remove(self):
721 721 # we can't fully delete the repository as it may contain
722 722 # local-only history
723 723 self.ui.note(_('removing subrepo %s\n') % subrelpath(self))
724 724 hg.clean(self._repo, node.nullid, False)
725 725
726 726 def _get(self, state):
727 727 source, revision, kind = state
728 728 if revision in self._repo.unfiltered():
729 729 return True
730 730 self._repo._subsource = source
731 731 srcurl = _abssource(self._repo)
732 732 other = hg.peer(self._repo, {}, srcurl)
733 733 if len(self._repo) == 0:
734 734 self.ui.status(_('cloning subrepo %s from %s\n')
735 735 % (subrelpath(self), srcurl))
736 736 parentrepo = self._repo._subparent
737 737 shutil.rmtree(self._repo.path)
738 738 other, cloned = hg.clone(self._repo._subparent.baseui, {},
739 739 other, self._repo.root,
740 740 update=False)
741 741 self._repo = cloned.local()
742 742 self._initrepo(parentrepo, source, create=True)
743 743 self._cachestorehash(srcurl)
744 744 else:
745 745 self.ui.status(_('pulling subrepo %s from %s\n')
746 746 % (subrelpath(self), srcurl))
747 747 cleansub = self.storeclean(srcurl)
748 748 exchange.pull(self._repo, other)
749 749 if cleansub:
750 750 # keep the repo clean after pull
751 751 self._cachestorehash(srcurl)
752 752 return False
753 753
754 754 @annotatesubrepoerror
755 755 def get(self, state, overwrite=False):
756 756 inrepo = self._get(state)
757 757 source, revision, kind = state
758 758 repo = self._repo
759 759 repo.ui.debug("getting subrepo %s\n" % self._path)
760 760 if inrepo:
761 761 urepo = repo.unfiltered()
762 762 ctx = urepo[revision]
763 763 if ctx.hidden():
764 764 urepo.ui.warn(
765 765 _('revision %s in subrepo %s is hidden\n') \
766 766 % (revision[0:12], self._path))
767 767 repo = urepo
768 768 hg.updaterepo(repo, revision, overwrite)
769 769
770 770 @annotatesubrepoerror
771 771 def merge(self, state):
772 772 self._get(state)
773 773 cur = self._repo['.']
774 774 dst = self._repo[state[1]]
775 775 anc = dst.ancestor(cur)
776 776
777 777 def mergefunc():
778 778 if anc == cur and dst.branch() == cur.branch():
779 779 self.ui.debug("updating subrepo %s\n" % subrelpath(self))
780 780 hg.update(self._repo, state[1])
781 781 elif anc == dst:
782 782 self.ui.debug("skipping subrepo %s\n" % subrelpath(self))
783 783 else:
784 784 self.ui.debug("merging subrepo %s\n" % subrelpath(self))
785 785 hg.merge(self._repo, state[1], remind=False)
786 786
787 787 wctx = self._repo[None]
788 788 if self.dirty():
789 789 if anc != dst:
790 790 if _updateprompt(self.ui, self, wctx.dirty(), cur, dst):
791 791 mergefunc()
792 792 else:
793 793 mergefunc()
794 794 else:
795 795 mergefunc()
796 796
797 797 @annotatesubrepoerror
798 798 def push(self, opts):
799 799 force = opts.get('force')
800 800 newbranch = opts.get('new_branch')
801 801 ssh = opts.get('ssh')
802 802
803 803 # push subrepos depth-first for coherent ordering
804 804 c = self._repo['']
805 805 subs = c.substate # only repos that are committed
806 806 for s in sorted(subs):
807 807 if c.sub(s).push(opts) == 0:
808 808 return False
809 809
810 810 dsturl = _abssource(self._repo, True)
811 811 if not force:
812 812 if self.storeclean(dsturl):
813 813 self.ui.status(
814 814 _('no changes made to subrepo %s since last push to %s\n')
815 815 % (subrelpath(self), dsturl))
816 816 return None
817 817 self.ui.status(_('pushing subrepo %s to %s\n') %
818 818 (subrelpath(self), dsturl))
819 819 other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
820 820 res = exchange.push(self._repo, other, force, newbranch=newbranch)
821 821
822 822 # the repo is now clean
823 823 self._cachestorehash(dsturl)
824 824 return res.cgresult
825 825
826 826 @annotatesubrepoerror
827 827 def outgoing(self, ui, dest, opts):
828 828 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
829 829
830 830 @annotatesubrepoerror
831 831 def incoming(self, ui, source, opts):
832 832 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
833 833
834 834 @annotatesubrepoerror
835 835 def files(self):
836 836 rev = self._state[1]
837 837 ctx = self._repo[rev]
838 838 return ctx.manifest()
839 839
840 840 def filedata(self, name):
841 841 rev = self._state[1]
842 842 return self._repo[rev][name].data()
843 843
844 844 def fileflags(self, name):
845 845 rev = self._state[1]
846 846 ctx = self._repo[rev]
847 847 return ctx.flags(name)
848 848
849 849 def walk(self, match):
850 850 ctx = self._repo[None]
851 851 return ctx.walk(match)
852 852
853 853 @annotatesubrepoerror
854 854 def forget(self, match, prefix):
855 855 return cmdutil.forget(self.ui, self._repo, match,
856 856 os.path.join(prefix, self._path), True)
857 857
858 858 @annotatesubrepoerror
859 859 def removefiles(self, matcher, prefix, after, force, subrepos):
860 860 return cmdutil.remove(self.ui, self._repo, matcher,
861 861 os.path.join(prefix, self._path), after, force,
862 862 subrepos)
863 863
864 864 @annotatesubrepoerror
865 865 def revert(self, substate, *pats, **opts):
866 866 # reverting a subrepo is a 2 step process:
867 867 # 1. if the no_backup is not set, revert all modified
868 868 # files inside the subrepo
869 869 # 2. update the subrepo to the revision specified in
870 870 # the corresponding substate dictionary
871 871 self.ui.status(_('reverting subrepo %s\n') % substate[0])
872 872 if not opts.get('no_backup'):
873 873 # Revert all files on the subrepo, creating backups
874 874 # Note that this will not recursively revert subrepos
875 875 # We could do it if there was a set:subrepos() predicate
876 876 opts = opts.copy()
877 877 opts['date'] = None
878 878 opts['rev'] = substate[1]
879 879
880 880 pats = []
881 881 if not opts.get('all'):
882 882 pats = ['set:modified()']
883 883 self.filerevert(*pats, **opts)
884 884
885 885 # Update the repo to the revision specified in the given substate
886 886 self.get(substate, overwrite=True)
887 887
888 888 def filerevert(self, *pats, **opts):
889 889 ctx = self._repo[opts['rev']]
890 890 parents = self._repo.dirstate.parents()
891 891 if opts.get('all'):
892 892 pats = ['set:modified()']
893 893 else:
894 894 pats = []
895 895 cmdutil.revert(self.ui, self._repo, ctx, parents, *pats, **opts)
896 896
897 897 def shortid(self, revid):
898 898 return revid[:12]
899 899
900 900 class svnsubrepo(abstractsubrepo):
901 901 def __init__(self, ctx, path, state):
902 902 super(svnsubrepo, self).__init__(ctx._repo.ui)
903 903 self._path = path
904 904 self._state = state
905 905 self._ctx = ctx
906 906 self._exe = util.findexe('svn')
907 907 if not self._exe:
908 908 raise util.Abort(_("'svn' executable not found for subrepo '%s'")
909 909 % self._path)
910 910
911 911 def _svncommand(self, commands, filename='', failok=False):
912 912 cmd = [self._exe]
913 913 extrakw = {}
914 914 if not self.ui.interactive():
915 915 # Making stdin be a pipe should prevent svn from behaving
916 916 # interactively even if we can't pass --non-interactive.
917 917 extrakw['stdin'] = subprocess.PIPE
918 918 # Starting in svn 1.5 --non-interactive is a global flag
919 919 # instead of being per-command, but we need to support 1.4 so
920 920 # we have to be intelligent about what commands take
921 921 # --non-interactive.
922 922 if commands[0] in ('update', 'checkout', 'commit'):
923 923 cmd.append('--non-interactive')
924 924 cmd.extend(commands)
925 925 if filename is not None:
926 926 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
927 927 cmd.append(path)
928 928 env = dict(os.environ)
929 929 # Avoid localized output, preserve current locale for everything else.
930 930 lc_all = env.get('LC_ALL')
931 931 if lc_all:
932 932 env['LANG'] = lc_all
933 933 del env['LC_ALL']
934 934 env['LC_MESSAGES'] = 'C'
935 935 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
936 936 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
937 937 universal_newlines=True, env=env, **extrakw)
938 938 stdout, stderr = p.communicate()
939 939 stderr = stderr.strip()
940 940 if not failok:
941 941 if p.returncode:
942 942 raise util.Abort(stderr or 'exited with code %d' % p.returncode)
943 943 if stderr:
944 944 self.ui.warn(stderr + '\n')
945 945 return stdout, stderr
946 946
947 947 @propertycache
948 948 def _svnversion(self):
949 949 output, err = self._svncommand(['--version', '--quiet'], filename=None)
950 950 m = re.search(r'^(\d+)\.(\d+)', output)
951 951 if not m:
952 952 raise util.Abort(_('cannot retrieve svn tool version'))
953 953 return (int(m.group(1)), int(m.group(2)))
954 954
955 955 def _wcrevs(self):
956 956 # Get the working directory revision as well as the last
957 957 # commit revision so we can compare the subrepo state with
958 958 # both. We used to store the working directory one.
959 959 output, err = self._svncommand(['info', '--xml'])
960 960 doc = xml.dom.minidom.parseString(output)
961 961 entries = doc.getElementsByTagName('entry')
962 962 lastrev, rev = '0', '0'
963 963 if entries:
964 964 rev = str(entries[0].getAttribute('revision')) or '0'
965 965 commits = entries[0].getElementsByTagName('commit')
966 966 if commits:
967 967 lastrev = str(commits[0].getAttribute('revision')) or '0'
968 968 return (lastrev, rev)
969 969
970 970 def _wcrev(self):
971 971 return self._wcrevs()[0]
972 972
973 973 def _wcchanged(self):
974 974 """Return (changes, extchanges, missing) where changes is True
975 975 if the working directory was changed, extchanges is
976 976 True if any of these changes concern an external entry and missing
977 977 is True if any change is a missing entry.
978 978 """
979 979 output, err = self._svncommand(['status', '--xml'])
980 980 externals, changes, missing = [], [], []
981 981 doc = xml.dom.minidom.parseString(output)
982 982 for e in doc.getElementsByTagName('entry'):
983 983 s = e.getElementsByTagName('wc-status')
984 984 if not s:
985 985 continue
986 986 item = s[0].getAttribute('item')
987 987 props = s[0].getAttribute('props')
988 988 path = e.getAttribute('path')
989 989 if item == 'external':
990 990 externals.append(path)
991 991 elif item == 'missing':
992 992 missing.append(path)
993 993 if (item not in ('', 'normal', 'unversioned', 'external')
994 994 or props not in ('', 'none', 'normal')):
995 995 changes.append(path)
996 996 for path in changes:
997 997 for ext in externals:
998 998 if path == ext or path.startswith(ext + os.sep):
999 999 return True, True, bool(missing)
1000 1000 return bool(changes), False, bool(missing)
1001 1001
1002 1002 def dirty(self, ignoreupdate=False):
1003 1003 if not self._wcchanged()[0]:
1004 1004 if self._state[1] in self._wcrevs() or ignoreupdate:
1005 1005 return False
1006 1006 return True
1007 1007
1008 1008 def basestate(self):
1009 1009 lastrev, rev = self._wcrevs()
1010 1010 if lastrev != rev:
1011 1011 # Last committed rev is not the same than rev. We would
1012 1012 # like to take lastrev but we do not know if the subrepo
1013 1013 # URL exists at lastrev. Test it and fallback to rev it
1014 1014 # is not there.
1015 1015 try:
1016 1016 self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)])
1017 1017 return lastrev
1018 1018 except error.Abort:
1019 1019 pass
1020 1020 return rev
1021 1021
1022 1022 @annotatesubrepoerror
1023 1023 def commit(self, text, user, date):
1024 1024 # user and date are out of our hands since svn is centralized
1025 1025 changed, extchanged, missing = self._wcchanged()
1026 1026 if not changed:
1027 1027 return self.basestate()
1028 1028 if extchanged:
1029 1029 # Do not try to commit externals
1030 1030 raise util.Abort(_('cannot commit svn externals'))
1031 1031 if missing:
1032 1032 # svn can commit with missing entries but aborting like hg
1033 1033 # seems a better approach.
1034 1034 raise util.Abort(_('cannot commit missing svn entries'))
1035 1035 commitinfo, err = self._svncommand(['commit', '-m', text])
1036 1036 self.ui.status(commitinfo)
1037 1037 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
1038 1038 if not newrev:
1039 1039 if not commitinfo.strip():
1040 1040 # Sometimes, our definition of "changed" differs from
1041 1041 # svn one. For instance, svn ignores missing files
1042 1042 # when committing. If there are only missing files, no
1043 1043 # commit is made, no output and no error code.
1044 1044 raise util.Abort(_('failed to commit svn changes'))
1045 1045 raise util.Abort(commitinfo.splitlines()[-1])
1046 1046 newrev = newrev.groups()[0]
1047 1047 self.ui.status(self._svncommand(['update', '-r', newrev])[0])
1048 1048 return newrev
1049 1049
1050 1050 @annotatesubrepoerror
1051 1051 def remove(self):
1052 1052 if self.dirty():
1053 1053 self.ui.warn(_('not removing repo %s because '
1054 1054 'it has changes.\n') % self._path)
1055 1055 return
1056 1056 self.ui.note(_('removing subrepo %s\n') % self._path)
1057 1057
1058 1058 def onerror(function, path, excinfo):
1059 1059 if function is not os.remove:
1060 1060 raise
1061 1061 # read-only files cannot be unlinked under Windows
1062 1062 s = os.stat(path)
1063 1063 if (s.st_mode & stat.S_IWRITE) != 0:
1064 1064 raise
1065 1065 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
1066 1066 os.remove(path)
1067 1067
1068 1068 path = self._ctx._repo.wjoin(self._path)
1069 1069 shutil.rmtree(path, onerror=onerror)
1070 1070 try:
1071 1071 os.removedirs(os.path.dirname(path))
1072 1072 except OSError:
1073 1073 pass
1074 1074
1075 1075 @annotatesubrepoerror
1076 1076 def get(self, state, overwrite=False):
1077 1077 if overwrite:
1078 1078 self._svncommand(['revert', '--recursive'])
1079 1079 args = ['checkout']
1080 1080 if self._svnversion >= (1, 5):
1081 1081 args.append('--force')
1082 1082 # The revision must be specified at the end of the URL to properly
1083 1083 # update to a directory which has since been deleted and recreated.
1084 1084 args.append('%s@%s' % (state[0], state[1]))
1085 1085 status, err = self._svncommand(args, failok=True)
1086 1086 _sanitize(self.ui, self._ctx._repo.wjoin(self._path), '.svn')
1087 1087 if not re.search('Checked out revision [0-9]+.', status):
1088 1088 if ('is already a working copy for a different URL' in err
1089 1089 and (self._wcchanged()[:2] == (False, False))):
1090 1090 # obstructed but clean working copy, so just blow it away.
1091 1091 self.remove()
1092 1092 self.get(state, overwrite=False)
1093 1093 return
1094 1094 raise util.Abort((status or err).splitlines()[-1])
1095 1095 self.ui.status(status)
1096 1096
1097 1097 @annotatesubrepoerror
1098 1098 def merge(self, state):
1099 1099 old = self._state[1]
1100 1100 new = state[1]
1101 1101 wcrev = self._wcrev()
1102 1102 if new != wcrev:
1103 1103 dirty = old == wcrev or self._wcchanged()[0]
1104 1104 if _updateprompt(self.ui, self, dirty, wcrev, new):
1105 1105 self.get(state, False)
1106 1106
1107 1107 def push(self, opts):
1108 1108 # push is a no-op for SVN
1109 1109 return True
1110 1110
1111 1111 @annotatesubrepoerror
1112 1112 def files(self):
1113 1113 output = self._svncommand(['list', '--recursive', '--xml'])[0]
1114 1114 doc = xml.dom.minidom.parseString(output)
1115 1115 paths = []
1116 1116 for e in doc.getElementsByTagName('entry'):
1117 1117 kind = str(e.getAttribute('kind'))
1118 1118 if kind != 'file':
1119 1119 continue
1120 1120 name = ''.join(c.data for c
1121 1121 in e.getElementsByTagName('name')[0].childNodes
1122 1122 if c.nodeType == c.TEXT_NODE)
1123 1123 paths.append(name.encode('utf-8'))
1124 1124 return paths
1125 1125
1126 1126 def filedata(self, name):
1127 1127 return self._svncommand(['cat'], name)[0]
1128 1128
1129 1129
1130 1130 class gitsubrepo(abstractsubrepo):
1131 1131 def __init__(self, ctx, path, state):
1132 1132 super(gitsubrepo, self).__init__(ctx._repo.ui)
1133 1133 self._state = state
1134 1134 self._ctx = ctx
1135 1135 self._path = path
1136 1136 self._relpath = os.path.join(reporelpath(ctx._repo), path)
1137 1137 self._abspath = ctx._repo.wjoin(path)
1138 1138 self._subparent = ctx._repo
1139 1139 self._ensuregit()
1140 1140
1141 1141 def _ensuregit(self):
1142 1142 try:
1143 1143 self._gitexecutable = 'git'
1144 1144 out, err = self._gitnodir(['--version'])
1145 1145 except OSError, e:
1146 1146 if e.errno != 2 or os.name != 'nt':
1147 1147 raise
1148 1148 self._gitexecutable = 'git.cmd'
1149 1149 out, err = self._gitnodir(['--version'])
1150 1150 versionstatus = self._checkversion(out)
1151 1151 if versionstatus == 'unknown':
1152 1152 self.ui.warn(_('cannot retrieve git version\n'))
1153 1153 elif versionstatus == 'abort':
1154 1154 raise util.Abort(_('git subrepo requires at least 1.6.0 or later'))
1155 1155 elif versionstatus == 'warning':
1156 1156 self.ui.warn(_('git subrepo requires at least 1.6.0 or later\n'))
1157 1157
1158 1158 @staticmethod
1159 1159 def _gitversion(out):
1160 1160 m = re.search(r'^git version (\d+)\.(\d+)\.(\d+)', out)
1161 1161 if m:
1162 1162 return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
1163 1163
1164 1164 m = re.search(r'^git version (\d+)\.(\d+)', out)
1165 1165 if m:
1166 1166 return (int(m.group(1)), int(m.group(2)), 0)
1167 1167
1168 1168 return -1
1169 1169
1170 1170 @staticmethod
1171 1171 def _checkversion(out):
1172 1172 '''ensure git version is new enough
1173 1173
1174 1174 >>> _checkversion = gitsubrepo._checkversion
1175 1175 >>> _checkversion('git version 1.6.0')
1176 1176 'ok'
1177 1177 >>> _checkversion('git version 1.8.5')
1178 1178 'ok'
1179 1179 >>> _checkversion('git version 1.4.0')
1180 1180 'abort'
1181 1181 >>> _checkversion('git version 1.5.0')
1182 1182 'warning'
1183 1183 >>> _checkversion('git version 1.9-rc0')
1184 1184 'ok'
1185 1185 >>> _checkversion('git version 1.9.0.265.g81cdec2')
1186 1186 'ok'
1187 1187 >>> _checkversion('git version 1.9.0.GIT')
1188 1188 'ok'
1189 1189 >>> _checkversion('git version 12345')
1190 1190 'unknown'
1191 1191 >>> _checkversion('no')
1192 1192 'unknown'
1193 1193 '''
1194 1194 version = gitsubrepo._gitversion(out)
1195 1195 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
1196 1196 # despite the docstring comment. For now, error on 1.4.0, warn on
1197 1197 # 1.5.0 but attempt to continue.
1198 1198 if version == -1:
1199 1199 return 'unknown'
1200 1200 if version < (1, 5, 0):
1201 1201 return 'abort'
1202 1202 elif version < (1, 6, 0):
1203 1203 return 'warning'
1204 1204 return 'ok'
1205 1205
1206 1206 def _gitcommand(self, commands, env=None, stream=False):
1207 1207 return self._gitdir(commands, env=env, stream=stream)[0]
1208 1208
1209 1209 def _gitdir(self, commands, env=None, stream=False):
1210 1210 return self._gitnodir(commands, env=env, stream=stream,
1211 1211 cwd=self._abspath)
1212 1212
1213 1213 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
1214 1214 """Calls the git command
1215 1215
1216 1216 The methods tries to call the git command. versions prior to 1.6.0
1217 1217 are not supported and very probably fail.
1218 1218 """
1219 1219 self.ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
1220 1220 # unless ui.quiet is set, print git's stderr,
1221 1221 # which is mostly progress and useful info
1222 1222 errpipe = None
1223 1223 if self.ui.quiet:
1224 1224 errpipe = open(os.devnull, 'w')
1225 1225 p = subprocess.Popen([self._gitexecutable] + commands, bufsize=-1,
1226 1226 cwd=cwd, env=env, close_fds=util.closefds,
1227 1227 stdout=subprocess.PIPE, stderr=errpipe)
1228 1228 if stream:
1229 1229 return p.stdout, None
1230 1230
1231 1231 retdata = p.stdout.read().strip()
1232 1232 # wait for the child to exit to avoid race condition.
1233 1233 p.wait()
1234 1234
1235 1235 if p.returncode != 0 and p.returncode != 1:
1236 1236 # there are certain error codes that are ok
1237 1237 command = commands[0]
1238 1238 if command in ('cat-file', 'symbolic-ref'):
1239 1239 return retdata, p.returncode
1240 1240 # for all others, abort
1241 1241 raise util.Abort('git %s error %d in %s' %
1242 1242 (command, p.returncode, self._relpath))
1243 1243
1244 1244 return retdata, p.returncode
1245 1245
1246 1246 def _gitmissing(self):
1247 1247 return not os.path.exists(os.path.join(self._abspath, '.git'))
1248 1248
1249 1249 def _gitstate(self):
1250 1250 return self._gitcommand(['rev-parse', 'HEAD'])
1251 1251
1252 1252 def _gitcurrentbranch(self):
1253 1253 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
1254 1254 if err:
1255 1255 current = None
1256 1256 return current
1257 1257
1258 1258 def _gitremote(self, remote):
1259 1259 out = self._gitcommand(['remote', 'show', '-n', remote])
1260 1260 line = out.split('\n')[1]
1261 1261 i = line.index('URL: ') + len('URL: ')
1262 1262 return line[i:]
1263 1263
1264 1264 def _githavelocally(self, revision):
1265 1265 out, code = self._gitdir(['cat-file', '-e', revision])
1266 1266 return code == 0
1267 1267
1268 1268 def _gitisancestor(self, r1, r2):
1269 1269 base = self._gitcommand(['merge-base', r1, r2])
1270 1270 return base == r1
1271 1271
1272 1272 def _gitisbare(self):
1273 1273 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
1274 1274
1275 1275 def _gitupdatestat(self):
1276 1276 """This must be run before git diff-index.
1277 1277 diff-index only looks at changes to file stat;
1278 1278 this command looks at file contents and updates the stat."""
1279 1279 self._gitcommand(['update-index', '-q', '--refresh'])
1280 1280
1281 1281 def _gitbranchmap(self):
1282 1282 '''returns 2 things:
1283 1283 a map from git branch to revision
1284 1284 a map from revision to branches'''
1285 1285 branch2rev = {}
1286 1286 rev2branch = {}
1287 1287
1288 1288 out = self._gitcommand(['for-each-ref', '--format',
1289 1289 '%(objectname) %(refname)'])
1290 1290 for line in out.split('\n'):
1291 1291 revision, ref = line.split(' ')
1292 1292 if (not ref.startswith('refs/heads/') and
1293 1293 not ref.startswith('refs/remotes/')):
1294 1294 continue
1295 1295 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
1296 1296 continue # ignore remote/HEAD redirects
1297 1297 branch2rev[ref] = revision
1298 1298 rev2branch.setdefault(revision, []).append(ref)
1299 1299 return branch2rev, rev2branch
1300 1300
1301 1301 def _gittracking(self, branches):
1302 1302 'return map of remote branch to local tracking branch'
1303 1303 # assumes no more than one local tracking branch for each remote
1304 1304 tracking = {}
1305 1305 for b in branches:
1306 1306 if b.startswith('refs/remotes/'):
1307 1307 continue
1308 1308 bname = b.split('/', 2)[2]
1309 1309 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
1310 1310 if remote:
1311 1311 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
1312 1312 tracking['refs/remotes/%s/%s' %
1313 1313 (remote, ref.split('/', 2)[2])] = b
1314 1314 return tracking
1315 1315
1316 1316 def _abssource(self, source):
1317 1317 if '://' not in source:
1318 1318 # recognize the scp syntax as an absolute source
1319 1319 colon = source.find(':')
1320 1320 if colon != -1 and '/' not in source[:colon]:
1321 1321 return source
1322 1322 self._subsource = source
1323 1323 return _abssource(self)
1324 1324
1325 1325 def _fetch(self, source, revision):
1326 1326 if self._gitmissing():
1327 1327 source = self._abssource(source)
1328 1328 self.ui.status(_('cloning subrepo %s from %s\n') %
1329 1329 (self._relpath, source))
1330 1330 self._gitnodir(['clone', source, self._abspath])
1331 1331 if self._githavelocally(revision):
1332 1332 return
1333 1333 self.ui.status(_('pulling subrepo %s from %s\n') %
1334 1334 (self._relpath, self._gitremote('origin')))
1335 1335 # try only origin: the originally cloned repo
1336 1336 self._gitcommand(['fetch'])
1337 1337 if not self._githavelocally(revision):
1338 1338 raise util.Abort(_("revision %s does not exist in subrepo %s\n") %
1339 1339 (revision, self._relpath))
1340 1340
1341 1341 @annotatesubrepoerror
1342 1342 def dirty(self, ignoreupdate=False):
1343 1343 if self._gitmissing():
1344 1344 return self._state[1] != ''
1345 1345 if self._gitisbare():
1346 1346 return True
1347 1347 if not ignoreupdate and self._state[1] != self._gitstate():
1348 1348 # different version checked out
1349 1349 return True
1350 1350 # check for staged changes or modified files; ignore untracked files
1351 1351 self._gitupdatestat()
1352 1352 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1353 1353 return code == 1
1354 1354
1355 1355 def basestate(self):
1356 1356 return self._gitstate()
1357 1357
1358 1358 @annotatesubrepoerror
1359 1359 def get(self, state, overwrite=False):
1360 1360 source, revision, kind = state
1361 1361 if not revision:
1362 1362 self.remove()
1363 1363 return
1364 1364 self._fetch(source, revision)
1365 1365 # if the repo was set to be bare, unbare it
1366 1366 if self._gitisbare():
1367 1367 self._gitcommand(['config', 'core.bare', 'false'])
1368 1368 if self._gitstate() == revision:
1369 1369 self._gitcommand(['reset', '--hard', 'HEAD'])
1370 1370 return
1371 1371 elif self._gitstate() == revision:
1372 1372 if overwrite:
1373 1373 # first reset the index to unmark new files for commit, because
1374 1374 # reset --hard will otherwise throw away files added for commit,
1375 1375 # not just unmark them.
1376 1376 self._gitcommand(['reset', 'HEAD'])
1377 1377 self._gitcommand(['reset', '--hard', 'HEAD'])
1378 1378 return
1379 1379 branch2rev, rev2branch = self._gitbranchmap()
1380 1380
1381 1381 def checkout(args):
1382 1382 cmd = ['checkout']
1383 1383 if overwrite:
1384 1384 # first reset the index to unmark new files for commit, because
1385 1385 # the -f option will otherwise throw away files added for
1386 1386 # commit, not just unmark them.
1387 1387 self._gitcommand(['reset', 'HEAD'])
1388 1388 cmd.append('-f')
1389 1389 self._gitcommand(cmd + args)
1390 1390 _sanitize(self.ui, self._abspath, '.git')
1391 1391
1392 1392 def rawcheckout():
1393 1393 # no branch to checkout, check it out with no branch
1394 1394 self.ui.warn(_('checking out detached HEAD in subrepo %s\n') %
1395 1395 self._relpath)
1396 1396 self.ui.warn(_('check out a git branch if you intend '
1397 1397 'to make changes\n'))
1398 1398 checkout(['-q', revision])
1399 1399
1400 1400 if revision not in rev2branch:
1401 1401 rawcheckout()
1402 1402 return
1403 1403 branches = rev2branch[revision]
1404 1404 firstlocalbranch = None
1405 1405 for b in branches:
1406 1406 if b == 'refs/heads/master':
1407 1407 # master trumps all other branches
1408 1408 checkout(['refs/heads/master'])
1409 1409 return
1410 1410 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1411 1411 firstlocalbranch = b
1412 1412 if firstlocalbranch:
1413 1413 checkout([firstlocalbranch])
1414 1414 return
1415 1415
1416 1416 tracking = self._gittracking(branch2rev.keys())
1417 1417 # choose a remote branch already tracked if possible
1418 1418 remote = branches[0]
1419 1419 if remote not in tracking:
1420 1420 for b in branches:
1421 1421 if b in tracking:
1422 1422 remote = b
1423 1423 break
1424 1424
1425 1425 if remote not in tracking:
1426 1426 # create a new local tracking branch
1427 1427 local = remote.split('/', 3)[3]
1428 1428 checkout(['-b', local, remote])
1429 1429 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1430 1430 # When updating to a tracked remote branch,
1431 1431 # if the local tracking branch is downstream of it,
1432 1432 # a normal `git pull` would have performed a "fast-forward merge"
1433 1433 # which is equivalent to updating the local branch to the remote.
1434 1434 # Since we are only looking at branching at update, we need to
1435 1435 # detect this situation and perform this action lazily.
1436 1436 if tracking[remote] != self._gitcurrentbranch():
1437 1437 checkout([tracking[remote]])
1438 1438 self._gitcommand(['merge', '--ff', remote])
1439 1439 _sanitize(self.ui, self._abspath, '.git')
1440 1440 else:
1441 1441 # a real merge would be required, just checkout the revision
1442 1442 rawcheckout()
1443 1443
1444 1444 @annotatesubrepoerror
1445 1445 def commit(self, text, user, date):
1446 1446 if self._gitmissing():
1447 1447 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1448 1448 cmd = ['commit', '-a', '-m', text]
1449 1449 env = os.environ.copy()
1450 1450 if user:
1451 1451 cmd += ['--author', user]
1452 1452 if date:
1453 1453 # git's date parser silently ignores when seconds < 1e9
1454 1454 # convert to ISO8601
1455 1455 env['GIT_AUTHOR_DATE'] = util.datestr(date,
1456 1456 '%Y-%m-%dT%H:%M:%S %1%2')
1457 1457 self._gitcommand(cmd, env=env)
1458 1458 # make sure commit works otherwise HEAD might not exist under certain
1459 1459 # circumstances
1460 1460 return self._gitstate()
1461 1461
1462 1462 @annotatesubrepoerror
1463 1463 def merge(self, state):
1464 1464 source, revision, kind = state
1465 1465 self._fetch(source, revision)
1466 1466 base = self._gitcommand(['merge-base', revision, self._state[1]])
1467 1467 self._gitupdatestat()
1468 1468 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1469 1469
1470 1470 def mergefunc():
1471 1471 if base == revision:
1472 1472 self.get(state) # fast forward merge
1473 1473 elif base != self._state[1]:
1474 1474 self._gitcommand(['merge', '--no-commit', revision])
1475 1475 _sanitize(self.ui, self._abspath, '.git')
1476 1476
1477 1477 if self.dirty():
1478 1478 if self._gitstate() != revision:
1479 1479 dirty = self._gitstate() == self._state[1] or code != 0
1480 1480 if _updateprompt(self.ui, self, dirty,
1481 1481 self._state[1][:7], revision[:7]):
1482 1482 mergefunc()
1483 1483 else:
1484 1484 mergefunc()
1485 1485
1486 1486 @annotatesubrepoerror
1487 1487 def push(self, opts):
1488 1488 force = opts.get('force')
1489 1489
1490 1490 if not self._state[1]:
1491 1491 return True
1492 1492 if self._gitmissing():
1493 1493 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1494 1494 # if a branch in origin contains the revision, nothing to do
1495 1495 branch2rev, rev2branch = self._gitbranchmap()
1496 1496 if self._state[1] in rev2branch:
1497 1497 for b in rev2branch[self._state[1]]:
1498 1498 if b.startswith('refs/remotes/origin/'):
1499 1499 return True
1500 1500 for b, revision in branch2rev.iteritems():
1501 1501 if b.startswith('refs/remotes/origin/'):
1502 1502 if self._gitisancestor(self._state[1], revision):
1503 1503 return True
1504 1504 # otherwise, try to push the currently checked out branch
1505 1505 cmd = ['push']
1506 1506 if force:
1507 1507 cmd.append('--force')
1508 1508
1509 1509 current = self._gitcurrentbranch()
1510 1510 if current:
1511 1511 # determine if the current branch is even useful
1512 1512 if not self._gitisancestor(self._state[1], current):
1513 1513 self.ui.warn(_('unrelated git branch checked out '
1514 1514 'in subrepo %s\n') % self._relpath)
1515 1515 return False
1516 1516 self.ui.status(_('pushing branch %s of subrepo %s\n') %
1517 1517 (current.split('/', 2)[2], self._relpath))
1518 1518 ret = self._gitdir(cmd + ['origin', current])
1519 1519 return ret[1] == 0
1520 1520 else:
1521 1521 self.ui.warn(_('no branch checked out in subrepo %s\n'
1522 1522 'cannot push revision %s\n') %
1523 1523 (self._relpath, self._state[1]))
1524 1524 return False
1525 1525
1526 1526 @annotatesubrepoerror
1527 1527 def remove(self):
1528 1528 if self._gitmissing():
1529 1529 return
1530 1530 if self.dirty():
1531 1531 self.ui.warn(_('not removing repo %s because '
1532 1532 'it has changes.\n') % self._relpath)
1533 1533 return
1534 1534 # we can't fully delete the repository as it may contain
1535 1535 # local-only history
1536 1536 self.ui.note(_('removing subrepo %s\n') % self._relpath)
1537 1537 self._gitcommand(['config', 'core.bare', 'true'])
1538 1538 for f in os.listdir(self._abspath):
1539 1539 if f == '.git':
1540 1540 continue
1541 1541 path = os.path.join(self._abspath, f)
1542 1542 if os.path.isdir(path) and not os.path.islink(path):
1543 1543 shutil.rmtree(path)
1544 1544 else:
1545 1545 os.remove(path)
1546 1546
1547 1547 def archive(self, archiver, prefix, match=None):
1548 1548 total = 0
1549 1549 source, revision = self._state
1550 1550 if not revision:
1551 1551 return total
1552 1552 self._fetch(source, revision)
1553 1553
1554 1554 # Parse git's native archive command.
1555 1555 # This should be much faster than manually traversing the trees
1556 1556 # and objects with many subprocess calls.
1557 1557 tarstream = self._gitcommand(['archive', revision], stream=True)
1558 1558 tar = tarfile.open(fileobj=tarstream, mode='r|')
1559 1559 relpath = subrelpath(self)
1560 1560 self.ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1561 1561 for i, info in enumerate(tar):
1562 1562 if info.isdir():
1563 1563 continue
1564 1564 if match and not match(info.name):
1565 1565 continue
1566 1566 if info.issym():
1567 1567 data = info.linkname
1568 1568 else:
1569 1569 data = tar.extractfile(info).read()
1570 1570 archiver.addfile(os.path.join(prefix, self._path, info.name),
1571 1571 info.mode, info.issym(), data)
1572 1572 total += 1
1573 1573 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
1574 1574 unit=_('files'))
1575 1575 self.ui.progress(_('archiving (%s)') % relpath, None)
1576 1576 return total
1577 1577
1578 1578
1579 1579 @annotatesubrepoerror
1580 1580 def status(self, rev2, **opts):
1581 1581 rev1 = self._state[1]
1582 1582 if self._gitmissing() or not rev1:
1583 1583 # if the repo is missing, return no results
1584 1584 return [], [], [], [], [], [], []
1585 1585 modified, added, removed = [], [], []
1586 1586 self._gitupdatestat()
1587 1587 if rev2:
1588 1588 command = ['diff-tree', rev1, rev2]
1589 1589 else:
1590 1590 command = ['diff-index', rev1]
1591 1591 out = self._gitcommand(command)
1592 1592 for line in out.split('\n'):
1593 1593 tab = line.find('\t')
1594 1594 if tab == -1:
1595 1595 continue
1596 1596 status, f = line[tab - 1], line[tab + 1:]
1597 1597 if status == 'M':
1598 1598 modified.append(f)
1599 1599 elif status == 'A':
1600 1600 added.append(f)
1601 1601 elif status == 'D':
1602 1602 removed.append(f)
1603 1603
1604 1604 deleted, unknown, ignored, clean = [], [], [], []
1605 1605
1606 1606 if not rev2:
1607 1607 command = ['ls-files', '--others', '--exclude-standard']
1608 1608 out = self._gitcommand(command)
1609 1609 for line in out.split('\n'):
1610 1610 if len(line) == 0:
1611 1611 continue
1612 1612 unknown.append(line)
1613 1613
1614 1614 return scmutil.status(modified, added, removed, deleted,
1615 1615 unknown, ignored, clean)
1616 1616
1617 1617 @annotatesubrepoerror
1618 1618 def diff(self, ui, diffopts, node2, match, prefix, **opts):
1619 1619 node1 = self._state[1]
1620 1620 cmd = ['diff']
1621 1621 if opts['stat']:
1622 1622 cmd.append('--stat')
1623 1623 else:
1624 1624 # for Git, this also implies '-p'
1625 1625 cmd.append('-U%d' % diffopts.context)
1626 1626
1627 1627 gitprefix = os.path.join(prefix, self._path)
1628 1628
1629 1629 if diffopts.noprefix:
1630 1630 cmd.extend(['--src-prefix=%s/' % gitprefix,
1631 1631 '--dst-prefix=%s/' % gitprefix])
1632 1632 else:
1633 1633 cmd.extend(['--src-prefix=a/%s/' % gitprefix,
1634 1634 '--dst-prefix=b/%s/' % gitprefix])
1635 1635
1636 1636 if diffopts.ignorews:
1637 1637 cmd.append('--ignore-all-space')
1638 1638 if diffopts.ignorewsamount:
1639 1639 cmd.append('--ignore-space-change')
1640 1640 if self._gitversion(self._gitcommand(['--version'])) >= (1, 8, 4) \
1641 1641 and diffopts.ignoreblanklines:
1642 1642 cmd.append('--ignore-blank-lines')
1643 1643
1644 1644 cmd.append(node1)
1645 1645 if node2:
1646 1646 cmd.append(node2)
1647 1647
1648 1648 if match.anypats():
1649 1649 return #No support for include/exclude yet
1650 1650
1651 1651 output = ""
1652 1652 if match.always():
1653 1653 output += self._gitcommand(cmd) + '\n'
1654 1654 elif match.files():
1655 1655 for f in match.files():
1656 1656 output += self._gitcommand(cmd + [f]) + '\n'
1657 1657 elif match(gitprefix): #Subrepo is matched
1658 1658 output += self._gitcommand(cmd) + '\n'
1659 1659
1660 1660 if output.strip():
1661 1661 ui.write(output)
1662 1662
1663 1663 @annotatesubrepoerror
1664 1664 def revert(self, substate, *pats, **opts):
1665 1665 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1666 1666 if not opts.get('no_backup'):
1667 1667 status = self.status(None)
1668 1668 names = status.modified
1669 1669 for name in names:
1670 1670 bakname = "%s.orig" % name
1671 1671 self.ui.note(_('saving current version of %s as %s\n') %
1672 1672 (name, bakname))
1673 1673 util.rename(os.path.join(self._abspath, name),
1674 1674 os.path.join(self._abspath, bakname))
1675 1675
1676 1676 self.get(substate, overwrite=True)
1677 1677 return []
1678 1678
1679 1679 def shortid(self, revid):
1680 1680 return revid[:7]
1681 1681
1682 1682 types = {
1683 1683 'hg': hgsubrepo,
1684 1684 'svn': svnsubrepo,
1685 1685 'git': gitsubrepo,
1686 1686 }
@@ -1,804 +1,805 b''
1 1 #require git
2 2
3 3 make git commits repeatable
4 4
5 5 $ echo "[core]" >> $HOME/.gitconfig
6 6 $ echo "autocrlf = false" >> $HOME/.gitconfig
7 7 $ GIT_AUTHOR_NAME='test'; export GIT_AUTHOR_NAME
8 8 $ GIT_AUTHOR_EMAIL='test@example.org'; export GIT_AUTHOR_EMAIL
9 9 $ GIT_AUTHOR_DATE='1234567891 +0000'; export GIT_AUTHOR_DATE
10 10 $ GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"; export GIT_COMMITTER_NAME
11 11 $ GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"; export GIT_COMMITTER_EMAIL
12 12 $ GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"; export GIT_COMMITTER_DATE
13 13 $ GIT_CONFIG_NOSYSTEM=1; export GIT_CONFIG_NOSYSTEM
14 14
15 15 root hg repo
16 16
17 17 $ hg init t
18 18 $ cd t
19 19 $ echo a > a
20 20 $ hg add a
21 21 $ hg commit -m a
22 22 $ cd ..
23 23
24 24 new external git repo
25 25
26 26 $ mkdir gitroot
27 27 $ cd gitroot
28 28 $ git init -q
29 29 $ echo g > g
30 30 $ git add g
31 31 $ git commit -q -m g
32 32
33 33 add subrepo clone
34 34
35 35 $ cd ../t
36 36 $ echo 's = [git]../gitroot' > .hgsub
37 37 $ git clone -q ../gitroot s
38 38 $ hg add .hgsub
39 39 $ hg commit -m 'new git subrepo'
40 40 $ hg debugsub
41 41 path s
42 42 source ../gitroot
43 43 revision da5f5b1d8ffcf62fb8327bcd3c89a4367a6018e7
44 44
45 45 record a new commit from upstream from a different branch
46 46
47 47 $ cd ../gitroot
48 48 $ git checkout -q -b testing
49 49 $ echo gg >> g
50 50 $ git commit -q -a -m gg
51 51
52 52 $ cd ../t/s
53 53 $ git pull -q >/dev/null 2>/dev/null
54 54 $ git checkout -q -b testing origin/testing >/dev/null
55 55
56 56 $ cd ..
57 57 $ hg status --subrepos
58 58 M s/g
59 59 $ hg commit -m 'update git subrepo'
60 60 $ hg debugsub
61 61 path s
62 62 source ../gitroot
63 63 revision 126f2a14290cd5ce061fdedc430170e8d39e1c5a
64 64
65 65 make $GITROOT pushable, by replacing it with a clone with nothing checked out
66 66
67 67 $ cd ..
68 68 $ git clone gitroot gitrootbare --bare -q
69 69 $ rm -rf gitroot
70 70 $ mv gitrootbare gitroot
71 71
72 72 clone root
73 73
74 74 $ cd t
75 75 $ hg clone . ../tc 2> /dev/null
76 76 updating to branch default
77 77 cloning subrepo s from $TESTTMP/gitroot
78 78 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
79 79 $ cd ../tc
80 80 $ hg debugsub
81 81 path s
82 82 source ../gitroot
83 83 revision 126f2a14290cd5ce061fdedc430170e8d39e1c5a
84 84
85 85 update to previous substate
86 86
87 87 $ hg update 1 -q
88 88 $ cat s/g
89 89 g
90 90 $ hg debugsub
91 91 path s
92 92 source ../gitroot
93 93 revision da5f5b1d8ffcf62fb8327bcd3c89a4367a6018e7
94 94
95 95 clone root, make local change
96 96
97 97 $ cd ../t
98 98 $ hg clone . ../ta 2> /dev/null
99 99 updating to branch default
100 100 cloning subrepo s from $TESTTMP/gitroot
101 101 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
102 102
103 103 $ cd ../ta
104 104 $ echo ggg >> s/g
105 105 $ hg status --subrepos
106 106 M s/g
107 107 $ hg diff --subrepos
108 108 diff --git a/s/g b/s/g
109 109 index 089258f..85341ee 100644
110 110 --- a/s/g
111 111 +++ b/s/g
112 112 @@ -1,2 +1,3 @@
113 113 g
114 114 gg
115 115 +ggg
116 116 $ hg commit --subrepos -m ggg
117 117 committing subrepository s
118 118 $ hg debugsub
119 119 path s
120 120 source ../gitroot
121 121 revision 79695940086840c99328513acbe35f90fcd55e57
122 122
123 123 clone root separately, make different local change
124 124
125 125 $ cd ../t
126 126 $ hg clone . ../tb 2> /dev/null
127 127 updating to branch default
128 128 cloning subrepo s from $TESTTMP/gitroot
129 129 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
130 130
131 131 $ cd ../tb/s
132 132 $ hg status --subrepos
133 133 $ echo f > f
134 134 $ hg status --subrepos
135 135 ? s/f
136 $ hg add .
136 137 $ git add f
137 138 $ cd ..
138 139
139 140 $ hg status --subrepos
140 141 A s/f
141 142 $ hg commit --subrepos -m f
142 143 committing subrepository s
143 144 $ hg debugsub
144 145 path s
145 146 source ../gitroot
146 147 revision aa84837ccfbdfedcdcdeeedc309d73e6eb069edc
147 148
148 149 user b push changes
149 150
150 151 $ hg push 2>/dev/null
151 152 pushing to $TESTTMP/t (glob)
152 153 pushing branch testing of subrepo s
153 154 searching for changes
154 155 adding changesets
155 156 adding manifests
156 157 adding file changes
157 158 added 1 changesets with 1 changes to 1 files
158 159
159 160 user a pulls, merges, commits
160 161
161 162 $ cd ../ta
162 163 $ hg pull
163 164 pulling from $TESTTMP/t (glob)
164 165 searching for changes
165 166 adding changesets
166 167 adding manifests
167 168 adding file changes
168 169 added 1 changesets with 1 changes to 1 files (+1 heads)
169 170 (run 'hg heads' to see heads, 'hg merge' to merge)
170 171 $ hg merge 2>/dev/null
171 172 subrepository s diverged (local revision: 7969594, remote revision: aa84837)
172 173 (M)erge, keep (l)ocal or keep (r)emote? m
173 174 pulling subrepo s from $TESTTMP/gitroot
174 175 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
175 176 (branch merge, don't forget to commit)
176 177 $ cat s/f
177 178 f
178 179 $ cat s/g
179 180 g
180 181 gg
181 182 ggg
182 183 $ hg commit --subrepos -m 'merge'
183 184 committing subrepository s
184 185 $ hg status --subrepos --rev 1:5
185 186 M .hgsubstate
186 187 M s/g
187 188 A s/f
188 189 $ hg debugsub
189 190 path s
190 191 source ../gitroot
191 192 revision f47b465e1bce645dbf37232a00574aa1546ca8d3
192 193 $ hg push 2>/dev/null
193 194 pushing to $TESTTMP/t (glob)
194 195 pushing branch testing of subrepo s
195 196 searching for changes
196 197 adding changesets
197 198 adding manifests
198 199 adding file changes
199 200 added 2 changesets with 2 changes to 1 files
200 201
201 202 make upstream git changes
202 203
203 204 $ cd ..
204 205 $ git clone -q gitroot gitclone
205 206 $ cd gitclone
206 207 $ echo ff >> f
207 208 $ git commit -q -a -m ff
208 209 $ echo fff >> f
209 210 $ git commit -q -a -m fff
210 211 $ git push origin testing 2>/dev/null
211 212
212 213 make and push changes to hg without updating the subrepo
213 214
214 215 $ cd ../t
215 216 $ hg clone . ../td 2>&1 | egrep -v '^Cloning into|^done\.'
216 217 updating to branch default
217 218 cloning subrepo s from $TESTTMP/gitroot
218 219 checking out detached HEAD in subrepo s
219 220 check out a git branch if you intend to make changes
220 221 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
221 222 $ cd ../td
222 223 $ echo aa >> a
223 224 $ hg commit -m aa
224 225 $ hg push
225 226 pushing to $TESTTMP/t (glob)
226 227 searching for changes
227 228 adding changesets
228 229 adding manifests
229 230 adding file changes
230 231 added 1 changesets with 1 changes to 1 files
231 232
232 233 sync to upstream git, distribute changes
233 234
234 235 $ cd ../ta
235 236 $ hg pull -u -q
236 237 $ cd s
237 238 $ git pull -q >/dev/null 2>/dev/null
238 239 $ cd ..
239 240 $ hg commit -m 'git upstream sync'
240 241 $ hg debugsub
241 242 path s
242 243 source ../gitroot
243 244 revision 32a343883b74769118bb1d3b4b1fbf9156f4dddc
244 245 $ hg push -q
245 246
246 247 $ cd ../tb
247 248 $ hg pull -q
248 249 $ hg update 2>/dev/null
249 250 pulling subrepo s from $TESTTMP/gitroot
250 251 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
251 252 $ hg debugsub
252 253 path s
253 254 source ../gitroot
254 255 revision 32a343883b74769118bb1d3b4b1fbf9156f4dddc
255 256
256 257 create a new git branch
257 258
258 259 $ cd s
259 260 $ git checkout -b b2
260 261 Switched to a new branch 'b2'
261 262 $ echo a>a
262 263 $ git add a
263 264 $ git commit -qm 'add a'
264 265 $ cd ..
265 266 $ hg commit -m 'add branch in s'
266 267
267 268 pulling new git branch should not create tracking branch named 'origin/b2'
268 269 (issue3870)
269 270 $ cd ../td/s
270 271 $ git remote set-url origin $TESTTMP/tb/s
271 272 $ git branch --no-track oldtesting
272 273 $ cd ..
273 274 $ hg pull -q ../tb
274 275 $ hg up
275 276 From $TESTTMP/tb/s
276 277 * [new branch] b2 -> origin/b2
277 278 Previous HEAD position was f47b465... merge
278 279 Switched to a new branch 'b2'
279 280 pulling subrepo s from $TESTTMP/tb/s
280 281 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
281 282
282 283 update to a revision without the subrepo, keeping the local git repository
283 284
284 285 $ cd ../t
285 286 $ hg up 0
286 287 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
287 288 $ ls -a s
288 289 .
289 290 ..
290 291 .git
291 292
292 293 $ hg up 2
293 294 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
294 295 $ ls -a s
295 296 .
296 297 ..
297 298 .git
298 299 g
299 300
300 301 archive subrepos
301 302
302 303 $ cd ../tc
303 304 $ hg pull -q
304 305 $ hg archive --subrepos -r 5 ../archive 2>/dev/null
305 306 pulling subrepo s from $TESTTMP/gitroot
306 307 $ cd ../archive
307 308 $ cat s/f
308 309 f
309 310 $ cat s/g
310 311 g
311 312 gg
312 313 ggg
313 314
314 315 $ hg -R ../tc archive --subrepo -r 5 -X ../tc/**f ../archive_x 2>/dev/null
315 316 $ find ../archive_x | sort | grep -v pax_global_header
316 317 ../archive_x
317 318 ../archive_x/.hg_archival.txt
318 319 ../archive_x/.hgsub
319 320 ../archive_x/.hgsubstate
320 321 ../archive_x/a
321 322 ../archive_x/s
322 323 ../archive_x/s/g
323 324
324 325 create nested repo
325 326
326 327 $ cd ..
327 328 $ hg init outer
328 329 $ cd outer
329 330 $ echo b>b
330 331 $ hg add b
331 332 $ hg commit -m b
332 333
333 334 $ hg clone ../t inner 2> /dev/null
334 335 updating to branch default
335 336 cloning subrepo s from $TESTTMP/gitroot
336 337 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
337 338 $ echo inner = inner > .hgsub
338 339 $ hg add .hgsub
339 340 $ hg commit -m 'nested sub'
340 341
341 342 nested commit
342 343
343 344 $ echo ffff >> inner/s/f
344 345 $ hg status --subrepos
345 346 M inner/s/f
346 347 $ hg commit --subrepos -m nested
347 348 committing subrepository inner
348 349 committing subrepository inner/s (glob)
349 350
350 351 nested archive
351 352
352 353 $ hg archive --subrepos ../narchive
353 354 $ ls ../narchive/inner/s | grep -v pax_global_header
354 355 f
355 356 g
356 357
357 358 relative source expansion
358 359
359 360 $ cd ..
360 361 $ mkdir d
361 362 $ hg clone t d/t 2> /dev/null
362 363 updating to branch default
363 364 cloning subrepo s from $TESTTMP/gitroot
364 365 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
365 366
366 367 Don't crash if the subrepo is missing
367 368
368 369 $ hg clone t missing -q
369 370 $ cd missing
370 371 $ rm -rf s
371 372 $ hg status -S
372 373 $ hg sum | grep commit
373 374 commit: 1 subrepos
374 375 $ hg push -q
375 376 abort: subrepo s is missing (in subrepo s)
376 377 [255]
377 378 $ hg commit --subrepos -qm missing
378 379 abort: subrepo s is missing (in subrepo s)
379 380 [255]
380 381 $ hg update -C 2> /dev/null
381 382 cloning subrepo s from $TESTTMP/gitroot
382 383 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
383 384 $ hg sum | grep commit
384 385 commit: (clean)
385 386
386 387 Don't crash if the .hgsubstate entry is missing
387 388
388 389 $ hg update 1 -q
389 390 $ hg rm .hgsubstate
390 391 $ hg commit .hgsubstate -m 'no substate'
391 392 nothing changed
392 393 [1]
393 394 $ hg tag -l nosubstate
394 395 $ hg manifest
395 396 .hgsub
396 397 .hgsubstate
397 398 a
398 399
399 400 $ hg status -S
400 401 R .hgsubstate
401 402 $ hg sum | grep commit
402 403 commit: 1 removed, 1 subrepos (new branch head)
403 404
404 405 $ hg commit -m 'restore substate'
405 406 nothing changed
406 407 [1]
407 408 $ hg manifest
408 409 .hgsub
409 410 .hgsubstate
410 411 a
411 412 $ hg sum | grep commit
412 413 commit: 1 removed, 1 subrepos (new branch head)
413 414
414 415 $ hg update -qC nosubstate
415 416 $ ls s
416 417 g
417 418
418 419 issue3109: false positives in git diff-index
419 420
420 421 $ hg update -q
421 422 $ touch -t 200001010000 s/g
422 423 $ hg status --subrepos
423 424 $ touch -t 200001010000 s/g
424 425 $ hg sum | grep commit
425 426 commit: (clean)
426 427
427 428 Check hg update --clean
428 429 $ cd $TESTTMP/ta
429 430 $ echo > s/g
430 431 $ cd s
431 432 $ echo c1 > f1
432 433 $ echo c1 > f2
433 434 $ git add f1
434 435 $ cd ..
435 436 $ hg status -S
436 437 M s/g
437 438 A s/f1
438 439 ? s/f2
439 440 $ ls s
440 441 f
441 442 f1
442 443 f2
443 444 g
444 445 $ hg update --clean
445 446 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
446 447 $ hg status -S
447 448 ? s/f1
448 449 ? s/f2
449 450 $ ls s
450 451 f
451 452 f1
452 453 f2
453 454 g
454 455
455 456 Sticky subrepositories, no changes
456 457 $ cd $TESTTMP/ta
457 458 $ hg id -n
458 459 7
459 460 $ cd s
460 461 $ git rev-parse HEAD
461 462 32a343883b74769118bb1d3b4b1fbf9156f4dddc
462 463 $ cd ..
463 464 $ hg update 1 > /dev/null 2>&1
464 465 $ hg id -n
465 466 1
466 467 $ cd s
467 468 $ git rev-parse HEAD
468 469 da5f5b1d8ffcf62fb8327bcd3c89a4367a6018e7
469 470 $ cd ..
470 471
471 472 Sticky subrepositories, file changes
472 473 $ touch s/f1
473 474 $ cd s
474 475 $ git add f1
475 476 $ cd ..
476 477 $ hg id -n
477 478 1+
478 479 $ cd s
479 480 $ git rev-parse HEAD
480 481 da5f5b1d8ffcf62fb8327bcd3c89a4367a6018e7
481 482 $ cd ..
482 483 $ hg update 4
483 484 subrepository s diverged (local revision: da5f5b1, remote revision: aa84837)
484 485 (M)erge, keep (l)ocal or keep (r)emote? m
485 486 subrepository sources for s differ
486 487 use (l)ocal source (da5f5b1) or (r)emote source (aa84837)? l
487 488 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
488 489 $ hg id -n
489 490 4+
490 491 $ cd s
491 492 $ git rev-parse HEAD
492 493 da5f5b1d8ffcf62fb8327bcd3c89a4367a6018e7
493 494 $ cd ..
494 495 $ hg update --clean tip > /dev/null 2>&1
495 496
496 497 Sticky subrepository, revision updates
497 498 $ hg id -n
498 499 7
499 500 $ cd s
500 501 $ git rev-parse HEAD
501 502 32a343883b74769118bb1d3b4b1fbf9156f4dddc
502 503 $ cd ..
503 504 $ cd s
504 505 $ git checkout aa84837ccfbdfedcdcdeeedc309d73e6eb069edc
505 506 Previous HEAD position was 32a3438... fff
506 507 HEAD is now at aa84837... f
507 508 $ cd ..
508 509 $ hg update 1
509 510 subrepository s diverged (local revision: 32a3438, remote revision: da5f5b1)
510 511 (M)erge, keep (l)ocal or keep (r)emote? m
511 512 subrepository sources for s differ (in checked out version)
512 513 use (l)ocal source (32a3438) or (r)emote source (da5f5b1)? l
513 514 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
514 515 $ hg id -n
515 516 1+
516 517 $ cd s
517 518 $ git rev-parse HEAD
518 519 aa84837ccfbdfedcdcdeeedc309d73e6eb069edc
519 520 $ cd ..
520 521
521 522 Sticky subrepository, file changes and revision updates
522 523 $ touch s/f1
523 524 $ cd s
524 525 $ git add f1
525 526 $ git rev-parse HEAD
526 527 aa84837ccfbdfedcdcdeeedc309d73e6eb069edc
527 528 $ cd ..
528 529 $ hg id -n
529 530 1+
530 531 $ hg update 7
531 532 subrepository s diverged (local revision: 32a3438, remote revision: 32a3438)
532 533 (M)erge, keep (l)ocal or keep (r)emote? m
533 534 subrepository sources for s differ
534 535 use (l)ocal source (32a3438) or (r)emote source (32a3438)? l
535 536 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
536 537 $ hg id -n
537 538 7+
538 539 $ cd s
539 540 $ git rev-parse HEAD
540 541 aa84837ccfbdfedcdcdeeedc309d73e6eb069edc
541 542 $ cd ..
542 543
543 544 Sticky repository, update --clean
544 545 $ hg update --clean tip 2>/dev/null
545 546 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
546 547 $ hg id -n
547 548 7
548 549 $ cd s
549 550 $ git rev-parse HEAD
550 551 32a343883b74769118bb1d3b4b1fbf9156f4dddc
551 552 $ cd ..
552 553
553 554 Test subrepo already at intended revision:
554 555 $ cd s
555 556 $ git checkout 32a343883b74769118bb1d3b4b1fbf9156f4dddc
556 557 HEAD is now at 32a3438... fff
557 558 $ cd ..
558 559 $ hg update 1
559 560 Previous HEAD position was 32a3438... fff
560 561 HEAD is now at da5f5b1... g
561 562 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
562 563 $ hg id -n
563 564 1
564 565 $ cd s
565 566 $ git rev-parse HEAD
566 567 da5f5b1d8ffcf62fb8327bcd3c89a4367a6018e7
567 568 $ cd ..
568 569
569 570 Test forgetting files, not implemented in git subrepo, used to
570 571 traceback
571 572 #if no-windows
572 573 $ hg forget 'notafile*'
573 574 notafile*: No such file or directory
574 575 [1]
575 576 #else
576 577 $ hg forget 'notafile'
577 578 notafile: * (glob)
578 579 [1]
579 580 #endif
580 581
581 582 $ cd ..
582 583
583 584 Test sanitizing ".hg/hgrc" in subrepo
584 585
585 586 $ cd t
586 587 $ hg tip -q
587 588 7:af6d2edbb0d3
588 589 $ hg update -q -C af6d2edbb0d3
589 590 $ cd s
590 591 $ git checkout -q -b sanitize-test
591 592 $ mkdir .hg
592 593 $ echo '.hg/hgrc in git repo' > .hg/hgrc
593 594 $ mkdir -p sub/.hg
594 595 $ echo 'sub/.hg/hgrc in git repo' > sub/.hg/hgrc
595 596 $ git add .hg sub
596 597 $ git commit -qm 'add .hg/hgrc to be sanitized at hg update'
597 598 $ git push -q origin sanitize-test
598 599 $ cd ..
599 600 $ grep ' s$' .hgsubstate
600 601 32a343883b74769118bb1d3b4b1fbf9156f4dddc s
601 602 $ hg commit -qm 'commit with git revision including .hg/hgrc'
602 603 $ hg parents -q
603 604 8:3473d20bddcf
604 605 $ grep ' s$' .hgsubstate
605 606 c4069473b459cf27fd4d7c2f50c4346b4e936599 s
606 607 $ cd ..
607 608
608 609 $ hg -R tc pull -q
609 610 $ hg -R tc update -q -C 3473d20bddcf 2>&1 | sort
610 611 warning: removing potentially hostile 'hgrc' in '$TESTTMP/tc/s/.hg' (glob)
611 612 warning: removing potentially hostile 'hgrc' in '$TESTTMP/tc/s/sub/.hg' (glob)
612 613 $ cd tc
613 614 $ hg parents -q
614 615 8:3473d20bddcf
615 616 $ grep ' s$' .hgsubstate
616 617 c4069473b459cf27fd4d7c2f50c4346b4e936599 s
617 618 $ test -f s/.hg/hgrc
618 619 [1]
619 620 $ test -f s/sub/.hg/hgrc
620 621 [1]
621 622 $ cd ..
622 623
623 624 additional test for "git merge --ff" route:
624 625
625 626 $ cd t
626 627 $ hg tip -q
627 628 8:3473d20bddcf
628 629 $ hg update -q -C af6d2edbb0d3
629 630 $ cd s
630 631 $ git checkout -q testing
631 632 $ mkdir .hg
632 633 $ echo '.hg/hgrc in git repo' > .hg/hgrc
633 634 $ mkdir -p sub/.hg
634 635 $ echo 'sub/.hg/hgrc in git repo' > sub/.hg/hgrc
635 636 $ git add .hg sub
636 637 $ git commit -qm 'add .hg/hgrc to be sanitized at hg update (git merge --ff)'
637 638 $ git push -q origin testing
638 639 $ cd ..
639 640 $ grep ' s$' .hgsubstate
640 641 32a343883b74769118bb1d3b4b1fbf9156f4dddc s
641 642 $ hg commit -qm 'commit with git revision including .hg/hgrc'
642 643 $ hg parents -q
643 644 9:ed23f7fe024e
644 645 $ grep ' s$' .hgsubstate
645 646 f262643c1077219fbd3858d54e78ef050ef84fbf s
646 647 $ cd ..
647 648
648 649 $ cd tc
649 650 $ hg update -q -C af6d2edbb0d3
650 651 $ test -f s/.hg/hgrc
651 652 [1]
652 653 $ test -f s/sub/.hg/hgrc
653 654 [1]
654 655 $ cd ..
655 656 $ hg -R tc pull -q
656 657 $ hg -R tc update -q -C ed23f7fe024e 2>&1 | sort
657 658 warning: removing potentially hostile 'hgrc' in '$TESTTMP/tc/s/.hg' (glob)
658 659 warning: removing potentially hostile 'hgrc' in '$TESTTMP/tc/s/sub/.hg' (glob)
659 660 $ cd tc
660 661 $ hg parents -q
661 662 9:ed23f7fe024e
662 663 $ grep ' s$' .hgsubstate
663 664 f262643c1077219fbd3858d54e78ef050ef84fbf s
664 665 $ test -f s/.hg/hgrc
665 666 [1]
666 667 $ test -f s/sub/.hg/hgrc
667 668 [1]
668 669
669 670 Test that sanitizing is omitted in meta data area:
670 671
671 672 $ mkdir s/.git/.hg
672 673 $ echo '.hg/hgrc in git metadata area' > s/.git/.hg/hgrc
673 674 $ hg update -q -C af6d2edbb0d3
674 675 checking out detached HEAD in subrepo s
675 676 check out a git branch if you intend to make changes
676 677
677 678 check differences made by most recent change
678 679 $ cd s
679 680 $ cat > foobar << EOF
680 681 > woopwoop
681 682 >
682 683 > foo
683 684 > bar
684 685 > EOF
685 686 $ git add foobar
686 687 $ cd ..
687 688
688 689 $ hg diff --subrepos
689 690 diff --git a/s/foobar b/s/foobar
690 691 new file mode 100644
691 692 index 0000000..8a5a5e2
692 693 --- /dev/null
693 694 +++ b/s/foobar
694 695 @@ -0,0 +1,4 @@
695 696 +woopwoop
696 697 +
697 698 +foo
698 699 +bar
699 700
700 701 $ hg commit --subrepos -m "Added foobar"
701 702 committing subrepository s
702 703 created new head
703 704
704 705 $ hg diff -c . --subrepos --nodates
705 706 diff -r af6d2edbb0d3 -r 255ee8cf690e .hgsubstate
706 707 --- a/.hgsubstate
707 708 +++ b/.hgsubstate
708 709 @@ -1,1 +1,1 @@
709 710 -32a343883b74769118bb1d3b4b1fbf9156f4dddc s
710 711 +fd4dbf828a5b2fcd36b2bcf21ea773820970d129 s
711 712 diff --git a/s/foobar b/s/foobar
712 713 new file mode 100644
713 714 index 0000000..8a5a5e2
714 715 --- /dev/null
715 716 +++ b/s/foobar
716 717 @@ -0,0 +1,4 @@
717 718 +woopwoop
718 719 +
719 720 +foo
720 721 +bar
721 722
722 723 check output when only diffing the subrepository
723 724 $ hg diff -c . --subrepos s
724 725 diff --git a/s/foobar b/s/foobar
725 726 new file mode 100644
726 727 index 0000000..8a5a5e2
727 728 --- /dev/null
728 729 +++ b/s/foobar
729 730 @@ -0,0 +1,4 @@
730 731 +woopwoop
731 732 +
732 733 +foo
733 734 +bar
734 735
735 736 check output when diffing something else
736 737 $ hg diff -c . --subrepos .hgsubstate --nodates
737 738 diff -r af6d2edbb0d3 -r 255ee8cf690e .hgsubstate
738 739 --- a/.hgsubstate
739 740 +++ b/.hgsubstate
740 741 @@ -1,1 +1,1 @@
741 742 -32a343883b74769118bb1d3b4b1fbf9156f4dddc s
742 743 +fd4dbf828a5b2fcd36b2bcf21ea773820970d129 s
743 744
744 745 add new changes, including whitespace
745 746 $ cd s
746 747 $ cat > foobar << EOF
747 748 > woop woop
748 749 >
749 750 > foo
750 751 > bar
751 752 > EOF
752 753 $ echo foo > barfoo
753 754 $ git add barfoo
754 755 $ cd ..
755 756
756 757 $ hg diff --subrepos --ignore-all-space
757 758 diff --git a/s/barfoo b/s/barfoo
758 759 new file mode 100644
759 760 index 0000000..257cc56
760 761 --- /dev/null
761 762 +++ b/s/barfoo
762 763 @@ -0,0 +1 @@
763 764 +foo
764 765 $ hg diff --subrepos s/foobar
765 766 diff --git a/s/foobar b/s/foobar
766 767 index 8a5a5e2..bd5812a 100644
767 768 --- a/s/foobar
768 769 +++ b/s/foobar
769 770 @@ -1,4 +1,4 @@
770 771 -woopwoop
771 772 +woop woop
772 773
773 774 foo
774 775 bar
775 776
776 777 execute a diffstat
777 778 the output contains a regex, because git 1.7.10 and 1.7.11
778 779 change the amount of whitespace
779 780 $ hg diff --subrepos --stat
780 781 \s*barfoo |\s*1 + (re)
781 782 \s*foobar |\s*2 +- (re)
782 783 2 files changed, 2 insertions\(\+\), 1 deletions?\(-\) (re)
783 784
784 785 ensure adding include/exclude ignores the subrepo
785 786 $ hg diff --subrepos -I s/foobar
786 787 $ hg diff --subrepos -X s/foobar
787 788
788 789 revert the subrepository
789 790 $ hg revert --all
790 791 reverting subrepo ../gitroot
791 792
792 793 $ hg status --subrepos
793 794 ? s/barfoo
794 795 ? s/foobar.orig
795 796
796 797 $ mv s/foobar.orig s/foobar
797 798
798 799 $ hg revert --no-backup s
799 800 reverting subrepo ../gitroot
800 801
801 802 $ hg status --subrepos
802 803 ? s/barfoo
803 804
804 805 $ cd ..
@@ -1,683 +1,684 b''
1 1 #require svn15
2 2
3 3 $ SVNREPOPATH=`pwd`/svn-repo
4 4 #if windows
5 5 $ SVNREPOURL=file:///`$PYTHON -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
6 6 #else
7 7 $ SVNREPOURL=file://`$PYTHON -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
8 8 #endif
9 9
10 10 create subversion repo
11 11
12 12 $ WCROOT="`pwd`/svn-wc"
13 13 $ svnadmin create svn-repo
14 14 $ svn co "$SVNREPOURL" svn-wc
15 15 Checked out revision 0.
16 16 $ cd svn-wc
17 17 $ mkdir src
18 18 $ echo alpha > src/alpha
19 19 $ svn add src
20 20 A src
21 21 A src/alpha (glob)
22 22 $ mkdir externals
23 23 $ echo other > externals/other
24 24 $ svn add externals
25 25 A externals
26 26 A externals/other (glob)
27 27 $ svn ci -m 'Add alpha'
28 28 Adding externals
29 29 Adding externals/other (glob)
30 30 Adding src
31 31 Adding src/alpha (glob)
32 32 Transmitting file data ..
33 33 Committed revision 1.
34 34 $ svn up -q
35 35 $ echo "externals -r1 $SVNREPOURL/externals" > extdef
36 36 $ svn propset -F extdef svn:externals src
37 37 property 'svn:externals' set on 'src'
38 38 $ svn ci -m 'Setting externals'
39 39 Sending src
40 40
41 41 Committed revision 2.
42 42 $ cd ..
43 43
44 44 create hg repo
45 45
46 46 $ mkdir sub
47 47 $ cd sub
48 48 $ hg init t
49 49 $ cd t
50 50
51 51 first revision, no sub
52 52
53 53 $ echo a > a
54 54 $ hg ci -Am0
55 55 adding a
56 56
57 57 add first svn sub with leading whitespaces
58 58
59 59 $ echo "s = [svn] $SVNREPOURL/src" >> .hgsub
60 60 $ echo "subdir/s = [svn] $SVNREPOURL/src" >> .hgsub
61 61 $ svn co --quiet "$SVNREPOURL"/src s
62 62 $ mkdir subdir
63 63 $ svn co --quiet "$SVNREPOURL"/src subdir/s
64 64 $ hg add .hgsub
65 65 $ hg ci -m1
66 66
67 67 make sure we avoid empty commits (issue2445)
68 68
69 69 $ hg sum
70 70 parent: 1:* tip (glob)
71 71 1
72 72 branch: default
73 73 commit: (clean)
74 74 update: (current)
75 75 $ hg ci -moops
76 76 nothing changed
77 77 [1]
78 78
79 79 debugsub
80 80
81 81 $ hg debugsub
82 82 path s
83 83 source file://*/svn-repo/src (glob)
84 84 revision 2
85 85 path subdir/s
86 86 source file://*/svn-repo/src (glob)
87 87 revision 2
88 88
89 89 change file in svn and hg, commit
90 90
91 91 $ echo a >> a
92 92 $ echo alpha >> s/alpha
93 93 $ hg sum
94 94 parent: 1:* tip (glob)
95 95 1
96 96 branch: default
97 97 commit: 1 modified, 1 subrepos
98 98 update: (current)
99 99 $ hg commit --subrepos -m 'Message!' | grep -v Updating
100 100 committing subrepository s
101 101 Sending*s/alpha (glob)
102 102 Transmitting file data .
103 103 Committed revision 3.
104 104
105 105 Fetching external item into '*s/externals'* (glob)
106 106 External at revision 1.
107 107
108 108 At revision 3.
109 109 $ hg debugsub
110 110 path s
111 111 source file://*/svn-repo/src (glob)
112 112 revision 3
113 113 path subdir/s
114 114 source file://*/svn-repo/src (glob)
115 115 revision 2
116 116
117 117 missing svn file, commit should fail
118 118
119 119 $ rm s/alpha
120 120 $ hg commit --subrepos -m 'abort on missing file'
121 121 committing subrepository s
122 122 abort: cannot commit missing svn entries (in subrepo s)
123 123 [255]
124 124 $ svn revert s/alpha > /dev/null
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 "$SVNREPOURL/unrelated" -m 'create unrelated'
130 130
131 131 Committed revision 4.
132 132 $ svn up -q s
133 133 $ hg sum
134 134 parent: 2:* tip (glob)
135 135 Message!
136 136 branch: default
137 137 commit: (clean)
138 138 update: (current)
139 139
140 140 $ echo a > s/a
141 141
142 142 should be empty despite change to s/a
143 143
144 144 $ hg st
145 145
146 146 add a commit from svn
147 147
148 148 $ cd "$WCROOT/src"
149 149 $ svn up -q
150 150 $ echo xyz >> alpha
151 151 $ svn propset svn:mime-type 'text/xml' alpha
152 152 property 'svn:mime-type' set on 'alpha'
153 153 $ svn ci -m 'amend a from svn'
154 154 Sending *alpha (glob)
155 155 Transmitting file data .
156 156 Committed revision 5.
157 157 $ cd ../../sub/t
158 158
159 159 this commit from hg will fail
160 160
161 161 $ echo zzz >> s/alpha
162 162 $ (hg ci --subrepos -m 'amend alpha from hg' 2>&1; echo "[$?]") | grep -vi 'out of date'
163 163 committing subrepository s
164 164 abort: svn:*Commit failed (details follow): (glob)
165 165 [255]
166 166 $ svn revert -q s/alpha
167 167
168 168 this commit fails because of meta changes
169 169
170 170 $ svn propset svn:mime-type 'text/html' s/alpha
171 171 property 'svn:mime-type' set on 's/alpha' (glob)
172 172 $ (hg ci --subrepos -m 'amend alpha from hg' 2>&1; echo "[$?]") | grep -vi 'out of date'
173 173 committing subrepository s
174 174 abort: svn:*Commit failed (details follow): (glob)
175 175 [255]
176 176 $ svn revert -q s/alpha
177 177
178 178 this commit fails because of externals changes
179 179
180 180 $ echo zzz > s/externals/other
181 181 $ hg ci --subrepos -m 'amend externals from hg'
182 182 committing subrepository s
183 183 abort: cannot commit svn externals (in subrepo s)
184 184 [255]
185 185 $ hg diff --subrepos -r 1:2 | grep -v diff
186 186 --- a/.hgsubstate Thu Jan 01 00:00:00 1970 +0000
187 187 +++ b/.hgsubstate Thu Jan 01 00:00:00 1970 +0000
188 188 @@ -1,2 +1,2 @@
189 189 -2 s
190 190 +3 s
191 191 2 subdir/s
192 192 --- a/a Thu Jan 01 00:00:00 1970 +0000
193 193 +++ b/a Thu Jan 01 00:00:00 1970 +0000
194 194 @@ -1,1 +1,2 @@
195 195 a
196 196 +a
197 197 $ svn revert -q s/externals/other
198 198
199 199 this commit fails because of externals meta changes
200 200
201 201 $ svn propset svn:mime-type 'text/html' s/externals/other
202 202 property 'svn:mime-type' set on 's/externals/other' (glob)
203 203 $ hg ci --subrepos -m 'amend externals from hg'
204 204 committing subrepository s
205 205 abort: cannot commit svn externals (in subrepo s)
206 206 [255]
207 207 $ svn revert -q s/externals/other
208 208
209 209 clone
210 210
211 211 $ cd ..
212 212 $ hg clone t tc
213 213 updating to branch default
214 214 A tc/s/alpha (glob)
215 215 U tc/s (glob)
216 216
217 217 Fetching external item into 'tc/s/externals'* (glob)
218 218 A tc/s/externals/other (glob)
219 219 Checked out external at revision 1.
220 220
221 221 Checked out revision 3.
222 222 A tc/subdir/s/alpha (glob)
223 223 U tc/subdir/s (glob)
224 224
225 225 Fetching external item into 'tc/subdir/s/externals'* (glob)
226 226 A tc/subdir/s/externals/other (glob)
227 227 Checked out external at revision 1.
228 228
229 229 Checked out revision 2.
230 230 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
231 231 $ cd tc
232 232
233 233 debugsub in clone
234 234
235 235 $ hg debugsub
236 236 path s
237 237 source file://*/svn-repo/src (glob)
238 238 revision 3
239 239 path subdir/s
240 240 source file://*/svn-repo/src (glob)
241 241 revision 2
242 242
243 243 verify subrepo is contained within the repo directory
244 244
245 245 $ $PYTHON -c "import os.path; print os.path.exists('s')"
246 246 True
247 247
248 248 update to nullrev (must delete the subrepo)
249 249
250 250 $ hg up null
251 251 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
252 252 $ ls
253 253
254 254 Check hg update --clean
255 255 $ cd "$TESTTMP/sub/t"
256 256 $ cd s
257 257 $ echo c0 > alpha
258 258 $ echo c1 > f1
259 259 $ echo c1 > f2
260 260 $ svn add f1 -q
261 261 $ svn status | sort
262 262
263 263 ? * a (glob)
264 264 ? * f2 (glob)
265 265 A * f1 (glob)
266 266 M * alpha (glob)
267 267 Performing status on external item at 'externals'* (glob)
268 268 X * externals (glob)
269 269 $ cd ../..
270 270 $ hg -R t update -C
271 271
272 272 Fetching external item into 't/s/externals'* (glob)
273 273 Checked out external at revision 1.
274 274
275 275 Checked out revision 3.
276 276 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
277 277 $ cd t/s
278 278 $ svn status | sort
279 279
280 280 ? * a (glob)
281 281 ? * f1 (glob)
282 282 ? * f2 (glob)
283 283 Performing status on external item at 'externals'* (glob)
284 284 X * externals (glob)
285 285
286 286 Sticky subrepositories, no changes
287 287 $ cd "$TESTTMP/sub/t"
288 288 $ hg id -n
289 289 2
290 290 $ cd s
291 291 $ svnversion
292 292 3
293 293 $ cd ..
294 294 $ hg update 1
295 295 U *s/alpha (glob)
296 296
297 297 Fetching external item into '*s/externals'* (glob)
298 298 Checked out external at revision 1.
299 299
300 300 Checked out revision 2.
301 301 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
302 302 $ hg id -n
303 303 1
304 304 $ cd s
305 305 $ svnversion
306 306 2
307 307 $ cd ..
308 308
309 309 Sticky subrepositories, file changes
310 310 $ touch s/f1
311 311 $ cd s
312 312 $ svn add f1
313 313 A f1
314 314 $ cd ..
315 315 $ hg id -n
316 316 1+
317 317 $ cd s
318 318 $ svnversion
319 319 2M
320 320 $ cd ..
321 321 $ hg update tip
322 322 subrepository s diverged (local revision: 2, remote revision: 3)
323 323 (M)erge, keep (l)ocal or keep (r)emote? m
324 324 subrepository sources for s differ
325 325 use (l)ocal source (2) or (r)emote source (3)? l
326 326 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
327 327 $ hg id -n
328 328 2+
329 329 $ cd s
330 330 $ svnversion
331 331 2M
332 332 $ cd ..
333 333 $ hg update --clean tip
334 334 U *s/alpha (glob)
335 335
336 336 Fetching external item into '*s/externals'* (glob)
337 337 Checked out external at revision 1.
338 338
339 339 Checked out revision 3.
340 340 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
341 341
342 342 Sticky subrepository, revision updates
343 343 $ hg id -n
344 344 2
345 345 $ cd s
346 346 $ svnversion
347 347 3
348 348 $ cd ..
349 349 $ cd s
350 350 $ svn update -qr 1
351 351 $ cd ..
352 352 $ hg update 1
353 353 subrepository s diverged (local revision: 3, remote revision: 2)
354 354 (M)erge, keep (l)ocal or keep (r)emote? m
355 355 subrepository sources for s differ (in checked out version)
356 356 use (l)ocal source (1) or (r)emote source (2)? l
357 357 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
358 358 $ hg id -n
359 359 1+
360 360 $ cd s
361 361 $ svnversion
362 362 1
363 363 $ cd ..
364 364
365 365 Sticky subrepository, file changes and revision updates
366 366 $ touch s/f1
367 367 $ cd s
368 368 $ svn add f1
369 369 A f1
370 370 $ svnversion
371 371 1M
372 372 $ cd ..
373 373 $ hg id -n
374 374 1+
375 375 $ hg update tip
376 376 subrepository s diverged (local revision: 3, remote revision: 3)
377 377 (M)erge, keep (l)ocal or keep (r)emote? m
378 378 subrepository sources for s differ
379 379 use (l)ocal source (1) or (r)emote source (3)? l
380 380 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
381 381 $ hg id -n
382 382 2+
383 383 $ cd s
384 384 $ svnversion
385 385 1M
386 386 $ cd ..
387 387
388 388 Sticky repository, update --clean
389 389 $ hg update --clean tip | grep -v 's[/\]externals[/\]other'
390 390 U *s/alpha (glob)
391 391 U *s (glob)
392 392
393 393 Fetching external item into '*s/externals'* (glob)
394 394 Checked out external at revision 1.
395 395
396 396 Checked out revision 3.
397 397 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
398 398 $ hg id -n
399 399 2
400 400 $ cd s
401 401 $ svnversion
402 402 3
403 403 $ cd ..
404 404
405 405 Test subrepo already at intended revision:
406 406 $ cd s
407 407 $ svn update -qr 2
408 408 $ cd ..
409 409 $ hg update 1
410 410 subrepository s diverged (local revision: 3, remote revision: 2)
411 411 (M)erge, keep (l)ocal or keep (r)emote? m
412 412 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
413 413 $ hg id -n
414 414 1+
415 415 $ cd s
416 416 $ svnversion
417 417 2
418 418 $ cd ..
419 419
420 420 Test case where subversion would fail to update the subrepo because there
421 421 are unknown directories being replaced by tracked ones (happens with rebase).
422 422
423 423 $ cd "$WCROOT/src"
424 424 $ mkdir dir
425 425 $ echo epsilon.py > dir/epsilon.py
426 426 $ svn add dir
427 427 A dir
428 428 A dir/epsilon.py (glob)
429 429 $ svn ci -m 'Add dir/epsilon.py'
430 430 Adding *dir (glob)
431 431 Adding *dir/epsilon.py (glob)
432 432 Transmitting file data .
433 433 Committed revision 6.
434 434 $ cd ../..
435 435 $ hg init rebaserepo
436 436 $ cd rebaserepo
437 437 $ svn co -r5 --quiet "$SVNREPOURL"/src s
438 438 $ echo "s = [svn] $SVNREPOURL/src" >> .hgsub
439 439 $ hg add .hgsub
440 440 $ hg ci -m addsub
441 441 $ echo a > a
442 $ hg ci -Am adda
442 $ hg add .
443 443 adding a
444 $ hg ci -m adda
444 445 $ hg up 0
445 446 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
446 447 $ svn up -qr6 s
447 448 $ hg ci -m updatesub
448 449 created new head
449 450 $ echo pyc > s/dir/epsilon.pyc
450 451 $ hg up 1
451 452 D *s/dir (glob)
452 453
453 454 Fetching external item into '*s/externals'* (glob)
454 455 Checked out external at revision 1.
455 456
456 457 Checked out revision 5.
457 458 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
458 459 $ hg up -q 2
459 460
460 461 Modify one of the externals to point to a different path so we can
461 462 test having obstructions when switching branches on checkout:
462 463 $ hg checkout tip
463 464 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
464 465 $ echo "obstruct = [svn] $SVNREPOURL/externals" >> .hgsub
465 466 $ svn co -r5 --quiet "$SVNREPOURL"/externals obstruct
466 467 $ hg commit -m 'Start making obstructed working copy'
467 468 $ hg book other
468 469 $ hg co -r 'p1(tip)'
469 470 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
470 471 (leaving bookmark other)
471 472 $ echo "obstruct = [svn] $SVNREPOURL/src" >> .hgsub
472 473 $ svn co -r5 --quiet "$SVNREPOURL"/src obstruct
473 474 $ hg commit -m 'Other branch which will be obstructed'
474 475 created new head
475 476
476 477 Switching back to the head where we have another path mapped to the
477 478 same subrepo should work if the subrepo is clean.
478 479 $ hg co other
479 480 A *obstruct/other (glob)
480 481 Checked out revision 1.
481 482 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
482 483 (activating bookmark other)
483 484
484 485 This is surprising, but is also correct based on the current code:
485 486 $ echo "updating should (maybe) fail" > obstruct/other
486 487 $ hg co tip
487 488 abort: uncommitted changes
488 489 (commit or update --clean to discard changes)
489 490 [255]
490 491
491 492 Point to a Subversion branch which has since been deleted and recreated
492 493 First, create that condition in the repository.
493 494
494 495 $ hg ci --subrepos -m cleanup | grep -v Updating
495 496 committing subrepository obstruct
496 497 Sending obstruct/other (glob)
497 498 Transmitting file data .
498 499 Committed revision 7.
499 500 At revision 7.
500 501 $ svn mkdir -m "baseline" $SVNREPOURL/trunk
501 502
502 503 Committed revision 8.
503 504 $ svn copy -m "initial branch" $SVNREPOURL/trunk $SVNREPOURL/branch
504 505
505 506 Committed revision 9.
506 507 $ svn co --quiet "$SVNREPOURL"/branch tempwc
507 508 $ cd tempwc
508 509 $ echo "something old" > somethingold
509 510 $ svn add somethingold
510 511 A somethingold
511 512 $ svn ci -m 'Something old'
512 513 Adding somethingold
513 514 Transmitting file data .
514 515 Committed revision 10.
515 516 $ svn rm -m "remove branch" $SVNREPOURL/branch
516 517
517 518 Committed revision 11.
518 519 $ svn copy -m "recreate branch" $SVNREPOURL/trunk $SVNREPOURL/branch
519 520
520 521 Committed revision 12.
521 522 $ svn up -q
522 523 $ echo "something new" > somethingnew
523 524 $ svn add somethingnew
524 525 A somethingnew
525 526 $ svn ci -m 'Something new'
526 527 Adding somethingnew
527 528 Transmitting file data .
528 529 Committed revision 13.
529 530 $ cd ..
530 531 $ rm -rf tempwc
531 532 $ svn co "$SVNREPOURL/branch"@10 recreated
532 533 A recreated/somethingold (glob)
533 534 Checked out revision 10.
534 535 $ echo "recreated = [svn] $SVNREPOURL/branch" >> .hgsub
535 536 $ hg ci -m addsub
536 537 $ cd recreated
537 538 $ svn up -q
538 539 $ cd ..
539 540 $ hg ci -m updatesub
540 541 $ hg up -r-2
541 542 D *recreated/somethingnew (glob)
542 543 A *recreated/somethingold (glob)
543 544 Checked out revision 10.
544 545 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
545 546 (leaving bookmark other)
546 547 $ test -f recreated/somethingold
547 548
548 549 Test archive
549 550
550 551 $ hg archive -S ../archive-all --debug
551 552 archiving: 0/2 files (0.00%)
552 553 archiving: .hgsub 1/2 files (50.00%)
553 554 archiving: .hgsubstate 2/2 files (100.00%)
554 555 archiving (obstruct): 0/1 files (0.00%)
555 556 archiving (obstruct): 1/1 files (100.00%)
556 557 archiving (recreated): 0/1 files (0.00%)
557 558 archiving (recreated): 1/1 files (100.00%)
558 559 archiving (s): 0/2 files (0.00%)
559 560 archiving (s): 1/2 files (50.00%)
560 561 archiving (s): 2/2 files (100.00%)
561 562
562 563 $ hg archive -S ../archive-exclude --debug -X **old
563 564 archiving: 0/2 files (0.00%)
564 565 archiving: .hgsub 1/2 files (50.00%)
565 566 archiving: .hgsubstate 2/2 files (100.00%)
566 567 archiving (obstruct): 0/1 files (0.00%)
567 568 archiving (obstruct): 1/1 files (100.00%)
568 569 archiving (recreated): 0 files
569 570 archiving (s): 0/2 files (0.00%)
570 571 archiving (s): 1/2 files (50.00%)
571 572 archiving (s): 2/2 files (100.00%)
572 573 $ find ../archive-exclude | sort
573 574 ../archive-exclude
574 575 ../archive-exclude/.hg_archival.txt
575 576 ../archive-exclude/.hgsub
576 577 ../archive-exclude/.hgsubstate
577 578 ../archive-exclude/obstruct
578 579 ../archive-exclude/obstruct/other
579 580 ../archive-exclude/s
580 581 ../archive-exclude/s/alpha
581 582 ../archive-exclude/s/dir
582 583 ../archive-exclude/s/dir/epsilon.py
583 584
584 585 Test forgetting files, not implemented in svn subrepo, used to
585 586 traceback
586 587
587 588 #if no-windows
588 589 $ hg forget 'notafile*'
589 590 notafile*: No such file or directory
590 591 [1]
591 592 #else
592 593 $ hg forget 'notafile'
593 594 notafile: * (glob)
594 595 [1]
595 596 #endif
596 597
597 598 Test a subrepo referencing a just moved svn path. Last commit rev will
598 599 be different from the revision, and the path will be different as
599 600 well.
600 601
601 602 $ cd "$WCROOT"
602 603 $ svn up > /dev/null
603 604 $ mkdir trunk/subdir branches
604 605 $ echo a > trunk/subdir/a
605 606 $ svn add trunk/subdir branches
606 607 A trunk/subdir (glob)
607 608 A trunk/subdir/a (glob)
608 609 A branches
609 610 $ svn ci -m addsubdir
610 611 Adding branches
611 612 Adding trunk/subdir (glob)
612 613 Adding trunk/subdir/a (glob)
613 614 Transmitting file data .
614 615 Committed revision 14.
615 616 $ svn cp -m branchtrunk $SVNREPOURL/trunk $SVNREPOURL/branches/somebranch
616 617
617 618 Committed revision 15.
618 619 $ cd ..
619 620
620 621 $ hg init repo2
621 622 $ cd repo2
622 623 $ svn co $SVNREPOURL/branches/somebranch/subdir
623 624 A subdir/a (glob)
624 625 Checked out revision 15.
625 626 $ echo "subdir = [svn] $SVNREPOURL/branches/somebranch/subdir" > .hgsub
626 627 $ hg add .hgsub
627 628 $ hg ci -m addsub
628 629 $ hg up null
629 630 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
630 631 $ hg up
631 632 A *subdir/a (glob)
632 633 Checked out revision 15.
633 634 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
634 635 $ cd ..
635 636
636 637 Test sanitizing ".hg/hgrc" in subrepo
637 638
638 639 $ cd sub/t
639 640 $ hg update -q -C tip
640 641 $ cd s
641 642 $ mkdir .hg
642 643 $ echo '.hg/hgrc in svn repo' > .hg/hgrc
643 644 $ mkdir -p sub/.hg
644 645 $ echo 'sub/.hg/hgrc in svn repo' > sub/.hg/hgrc
645 646 $ svn add .hg sub
646 647 A .hg
647 648 A .hg/hgrc (glob)
648 649 A sub
649 650 A sub/.hg (glob)
650 651 A sub/.hg/hgrc (glob)
651 652 $ svn ci -m 'add .hg/hgrc to be sanitized at hg update'
652 653 Adding .hg
653 654 Adding .hg/hgrc (glob)
654 655 Adding sub
655 656 Adding sub/.hg (glob)
656 657 Adding sub/.hg/hgrc (glob)
657 658 Transmitting file data ..
658 659 Committed revision 16.
659 660 $ svn up -q
660 661 $ cd ..
661 662 $ hg commit -S -m 'commit with svn revision including .hg/hgrc'
662 663 $ grep ' s$' .hgsubstate
663 664 16 s
664 665 $ cd ..
665 666
666 667 $ hg -R tc pull -u -q 2>&1 | sort
667 668 warning: removing potentially hostile 'hgrc' in '$TESTTMP/sub/tc/s/.hg' (glob)
668 669 warning: removing potentially hostile 'hgrc' in '$TESTTMP/sub/tc/s/sub/.hg' (glob)
669 670 $ cd tc
670 671 $ grep ' s$' .hgsubstate
671 672 16 s
672 673 $ test -f s/.hg/hgrc
673 674 [1]
674 675 $ test -f s/sub/.hg/hgrc
675 676 [1]
676 677
677 678 Test that sanitizing is omitted in meta data area:
678 679
679 680 $ mkdir s/.svn/.hg
680 681 $ echo '.hg/hgrc in svn metadata area' > s/.svn/.hg/hgrc
681 682 $ hg update -q -C '.^1'
682 683
683 684 $ cd ../..
General Comments 0
You need to be logged in to leave comments. Login now