##// END OF EJS Templates
rebase: fix --collapse when a file was added then removed...
Durham Goode -
r18778:1ef89df2 default
parent child Browse files
Show More
@@ -1,1165 +1,1166
1 1 # Copyright 2009-2010 Gregory P. Ward
2 2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
3 3 # Copyright 2010-2011 Fog Creek Software
4 4 # Copyright 2010-2011 Unity Technologies
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 '''Overridden Mercurial commands and functions for the largefiles extension'''
10 10
11 11 import os
12 12 import copy
13 13
14 14 from mercurial import hg, commands, util, cmdutil, scmutil, match as match_, \
15 15 node, archival, error, merge, discovery
16 16 from mercurial.i18n import _
17 17 from mercurial.node import hex
18 18 from hgext import rebase
19 19
20 20 import lfutil
21 21 import lfcommands
22 22
23 23 # -- Utility functions: commonly/repeatedly needed functionality ---------------
24 24
25 25 def installnormalfilesmatchfn(manifest):
26 26 '''overrides scmutil.match so that the matcher it returns will ignore all
27 27 largefiles'''
28 28 oldmatch = None # for the closure
29 29 def overridematch(ctx, pats=[], opts={}, globbed=False,
30 30 default='relpath'):
31 31 match = oldmatch(ctx, pats, opts, globbed, default)
32 32 m = copy.copy(match)
33 33 notlfile = lambda f: not (lfutil.isstandin(f) or lfutil.standin(f) in
34 34 manifest)
35 35 m._files = filter(notlfile, m._files)
36 36 m._fmap = set(m._files)
37 37 origmatchfn = m.matchfn
38 38 m.matchfn = lambda f: notlfile(f) and origmatchfn(f) or None
39 39 return m
40 40 oldmatch = installmatchfn(overridematch)
41 41
42 42 def installmatchfn(f):
43 43 oldmatch = scmutil.match
44 44 setattr(f, 'oldmatch', oldmatch)
45 45 scmutil.match = f
46 46 return oldmatch
47 47
48 48 def restorematchfn():
49 49 '''restores scmutil.match to what it was before installnormalfilesmatchfn
50 50 was called. no-op if scmutil.match is its original function.
51 51
52 52 Note that n calls to installnormalfilesmatchfn will require n calls to
53 53 restore matchfn to reverse'''
54 54 scmutil.match = getattr(scmutil.match, 'oldmatch', scmutil.match)
55 55
56 56 def addlargefiles(ui, repo, *pats, **opts):
57 57 large = opts.pop('large', None)
58 58 lfsize = lfutil.getminsize(
59 59 ui, lfutil.islfilesrepo(repo), opts.pop('lfsize', None))
60 60
61 61 lfmatcher = None
62 62 if lfutil.islfilesrepo(repo):
63 63 lfpats = ui.configlist(lfutil.longname, 'patterns', default=[])
64 64 if lfpats:
65 65 lfmatcher = match_.match(repo.root, '', list(lfpats))
66 66
67 67 lfnames = []
68 68 m = scmutil.match(repo[None], pats, opts)
69 69 m.bad = lambda x, y: None
70 70 wctx = repo[None]
71 71 for f in repo.walk(m):
72 72 exact = m.exact(f)
73 73 lfile = lfutil.standin(f) in wctx
74 74 nfile = f in wctx
75 75 exists = lfile or nfile
76 76
77 77 # Don't warn the user when they attempt to add a normal tracked file.
78 78 # The normal add code will do that for us.
79 79 if exact and exists:
80 80 if lfile:
81 81 ui.warn(_('%s already a largefile\n') % f)
82 82 continue
83 83
84 84 if (exact or not exists) and not lfutil.isstandin(f):
85 85 wfile = repo.wjoin(f)
86 86
87 87 # In case the file was removed previously, but not committed
88 88 # (issue3507)
89 89 if not os.path.exists(wfile):
90 90 continue
91 91
92 92 abovemin = (lfsize and
93 93 os.lstat(wfile).st_size >= lfsize * 1024 * 1024)
94 94 if large or abovemin or (lfmatcher and lfmatcher(f)):
95 95 lfnames.append(f)
96 96 if ui.verbose or not exact:
97 97 ui.status(_('adding %s as a largefile\n') % m.rel(f))
98 98
99 99 bad = []
100 100 standins = []
101 101
102 102 # Need to lock, otherwise there could be a race condition between
103 103 # when standins are created and added to the repo.
104 104 wlock = repo.wlock()
105 105 try:
106 106 if not opts.get('dry_run'):
107 107 lfdirstate = lfutil.openlfdirstate(ui, repo)
108 108 for f in lfnames:
109 109 standinname = lfutil.standin(f)
110 110 lfutil.writestandin(repo, standinname, hash='',
111 111 executable=lfutil.getexecutable(repo.wjoin(f)))
112 112 standins.append(standinname)
113 113 if lfdirstate[f] == 'r':
114 114 lfdirstate.normallookup(f)
115 115 else:
116 116 lfdirstate.add(f)
117 117 lfdirstate.write()
118 118 bad += [lfutil.splitstandin(f)
119 119 for f in repo[None].add(standins)
120 120 if f in m.files()]
121 121 finally:
122 122 wlock.release()
123 123 return bad
124 124
125 125 def removelargefiles(ui, repo, *pats, **opts):
126 126 after = opts.get('after')
127 127 if not pats and not after:
128 128 raise util.Abort(_('no files specified'))
129 129 m = scmutil.match(repo[None], pats, opts)
130 130 try:
131 131 repo.lfstatus = True
132 132 s = repo.status(match=m, clean=True)
133 133 finally:
134 134 repo.lfstatus = False
135 135 manifest = repo[None].manifest()
136 136 modified, added, deleted, clean = [[f for f in list
137 137 if lfutil.standin(f) in manifest]
138 138 for list in [s[0], s[1], s[3], s[6]]]
139 139
140 140 def warn(files, msg):
141 141 for f in files:
142 142 ui.warn(msg % m.rel(f))
143 143 return int(len(files) > 0)
144 144
145 145 result = 0
146 146
147 147 if after:
148 148 remove, forget = deleted, []
149 149 result = warn(modified + added + clean,
150 150 _('not removing %s: file still exists\n'))
151 151 else:
152 152 remove, forget = deleted + clean, []
153 153 result = warn(modified, _('not removing %s: file is modified (use -f'
154 154 ' to force removal)\n'))
155 155 result = warn(added, _('not removing %s: file has been marked for add'
156 156 ' (use forget to undo)\n')) or result
157 157
158 158 for f in sorted(remove + forget):
159 159 if ui.verbose or not m.exact(f):
160 160 ui.status(_('removing %s\n') % m.rel(f))
161 161
162 162 # Need to lock because standin files are deleted then removed from the
163 163 # repository and we could race in-between.
164 164 wlock = repo.wlock()
165 165 try:
166 166 lfdirstate = lfutil.openlfdirstate(ui, repo)
167 167 for f in remove:
168 168 if not after:
169 169 # If this is being called by addremove, notify the user that we
170 170 # are removing the file.
171 171 if getattr(repo, "_isaddremove", False):
172 172 ui.status(_('removing %s\n') % f)
173 173 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
174 174 lfdirstate.remove(f)
175 175 lfdirstate.write()
176 176 forget = [lfutil.standin(f) for f in forget]
177 177 remove = [lfutil.standin(f) for f in remove]
178 178 repo[None].forget(forget)
179 179 # If this is being called by addremove, let the original addremove
180 180 # function handle this.
181 181 if not getattr(repo, "_isaddremove", False):
182 182 for f in remove:
183 183 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
184 184 repo[None].forget(remove)
185 185 finally:
186 186 wlock.release()
187 187
188 188 return result
189 189
190 190 # For overriding mercurial.hgweb.webcommands so that largefiles will
191 191 # appear at their right place in the manifests.
192 192 def decodepath(orig, path):
193 193 return lfutil.splitstandin(path) or path
194 194
195 195 # -- Wrappers: modify existing commands --------------------------------
196 196
197 197 # Add works by going through the files that the user wanted to add and
198 198 # checking if they should be added as largefiles. Then it makes a new
199 199 # matcher which matches only the normal files and runs the original
200 200 # version of add.
201 201 def overrideadd(orig, ui, repo, *pats, **opts):
202 202 normal = opts.pop('normal')
203 203 if normal:
204 204 if opts.get('large'):
205 205 raise util.Abort(_('--normal cannot be used with --large'))
206 206 return orig(ui, repo, *pats, **opts)
207 207 bad = addlargefiles(ui, repo, *pats, **opts)
208 208 installnormalfilesmatchfn(repo[None].manifest())
209 209 result = orig(ui, repo, *pats, **opts)
210 210 restorematchfn()
211 211
212 212 return (result == 1 or bad) and 1 or 0
213 213
214 214 def overrideremove(orig, ui, repo, *pats, **opts):
215 215 installnormalfilesmatchfn(repo[None].manifest())
216 216 result = orig(ui, repo, *pats, **opts)
217 217 restorematchfn()
218 218 return removelargefiles(ui, repo, *pats, **opts) or result
219 219
220 220 def overridestatusfn(orig, repo, rev2, **opts):
221 221 try:
222 222 repo._repo.lfstatus = True
223 223 return orig(repo, rev2, **opts)
224 224 finally:
225 225 repo._repo.lfstatus = False
226 226
227 227 def overridestatus(orig, ui, repo, *pats, **opts):
228 228 try:
229 229 repo.lfstatus = True
230 230 return orig(ui, repo, *pats, **opts)
231 231 finally:
232 232 repo.lfstatus = False
233 233
234 234 def overridedirty(orig, repo, ignoreupdate=False):
235 235 try:
236 236 repo._repo.lfstatus = True
237 237 return orig(repo, ignoreupdate)
238 238 finally:
239 239 repo._repo.lfstatus = False
240 240
241 241 def overridelog(orig, ui, repo, *pats, **opts):
242 242 def overridematch(ctx, pats=[], opts={}, globbed=False,
243 243 default='relpath'):
244 244 """Matcher that merges root directory with .hglf, suitable for log.
245 245 It is still possible to match .hglf directly.
246 246 For any listed files run log on the standin too.
247 247 matchfn tries both the given filename and with .hglf stripped.
248 248 """
249 249 match = oldmatch(ctx, pats, opts, globbed, default)
250 250 m = copy.copy(match)
251 251 standins = [lfutil.standin(f) for f in m._files]
252 252 m._files.extend(standins)
253 253 m._fmap = set(m._files)
254 254 origmatchfn = m.matchfn
255 255 def lfmatchfn(f):
256 256 lf = lfutil.splitstandin(f)
257 257 if lf is not None and origmatchfn(lf):
258 258 return True
259 259 r = origmatchfn(f)
260 260 return r
261 261 m.matchfn = lfmatchfn
262 262 return m
263 263 oldmatch = installmatchfn(overridematch)
264 264 try:
265 265 repo.lfstatus = True
266 266 return orig(ui, repo, *pats, **opts)
267 267 finally:
268 268 repo.lfstatus = False
269 269 restorematchfn()
270 270
271 271 def overrideverify(orig, ui, repo, *pats, **opts):
272 272 large = opts.pop('large', False)
273 273 all = opts.pop('lfa', False)
274 274 contents = opts.pop('lfc', False)
275 275
276 276 result = orig(ui, repo, *pats, **opts)
277 277 if large or all or contents:
278 278 result = result or lfcommands.verifylfiles(ui, repo, all, contents)
279 279 return result
280 280
281 281 def overridedebugstate(orig, ui, repo, *pats, **opts):
282 282 large = opts.pop('large', False)
283 283 if large:
284 284 lfcommands.debugdirstate(ui, repo)
285 285 else:
286 286 orig(ui, repo, *pats, **opts)
287 287
288 288 # Override needs to refresh standins so that update's normal merge
289 289 # will go through properly. Then the other update hook (overriding repo.update)
290 290 # will get the new files. Filemerge is also overridden so that the merge
291 291 # will merge standins correctly.
292 292 def overrideupdate(orig, ui, repo, *pats, **opts):
293 293 lfdirstate = lfutil.openlfdirstate(ui, repo)
294 294 s = lfdirstate.status(match_.always(repo.root, repo.getcwd()), [], False,
295 295 False, False)
296 296 (unsure, modified, added, removed, missing, unknown, ignored, clean) = s
297 297
298 298 # Need to lock between the standins getting updated and their
299 299 # largefiles getting updated
300 300 wlock = repo.wlock()
301 301 try:
302 302 if opts['check']:
303 303 mod = len(modified) > 0
304 304 for lfile in unsure:
305 305 standin = lfutil.standin(lfile)
306 306 if repo['.'][standin].data().strip() != \
307 307 lfutil.hashfile(repo.wjoin(lfile)):
308 308 mod = True
309 309 else:
310 310 lfdirstate.normal(lfile)
311 311 lfdirstate.write()
312 312 if mod:
313 313 raise util.Abort(_('uncommitted local changes'))
314 314 # XXX handle removed differently
315 315 if not opts['clean']:
316 316 for lfile in unsure + modified + added:
317 317 lfutil.updatestandin(repo, lfutil.standin(lfile))
318 318 finally:
319 319 wlock.release()
320 320 return orig(ui, repo, *pats, **opts)
321 321
322 322 # Before starting the manifest merge, merge.updates will call
323 323 # _checkunknown to check if there are any files in the merged-in
324 324 # changeset that collide with unknown files in the working copy.
325 325 #
326 326 # The largefiles are seen as unknown, so this prevents us from merging
327 327 # in a file 'foo' if we already have a largefile with the same name.
328 328 #
329 329 # The overridden function filters the unknown files by removing any
330 330 # largefiles. This makes the merge proceed and we can then handle this
331 331 # case further in the overridden manifestmerge function below.
332 332 def overridecheckunknownfile(origfn, repo, wctx, mctx, f):
333 333 if lfutil.standin(f) in wctx:
334 334 return False
335 335 return origfn(repo, wctx, mctx, f)
336 336
337 337 # The manifest merge handles conflicts on the manifest level. We want
338 338 # to handle changes in largefile-ness of files at this level too.
339 339 #
340 340 # The strategy is to run the original manifestmerge and then process
341 341 # the action list it outputs. There are two cases we need to deal with:
342 342 #
343 343 # 1. Normal file in p1, largefile in p2. Here the largefile is
344 344 # detected via its standin file, which will enter the working copy
345 345 # with a "get" action. It is not "merge" since the standin is all
346 346 # Mercurial is concerned with at this level -- the link to the
347 347 # existing normal file is not relevant here.
348 348 #
349 349 # 2. Largefile in p1, normal file in p2. Here we get a "merge" action
350 350 # since the largefile will be present in the working copy and
351 351 # different from the normal file in p2. Mercurial therefore
352 352 # triggers a merge action.
353 353 #
354 354 # In both cases, we prompt the user and emit new actions to either
355 355 # remove the standin (if the normal file was kept) or to remove the
356 356 # normal file and get the standin (if the largefile was kept). The
357 357 # default prompt answer is to use the largefile version since it was
358 358 # presumably changed on purpose.
359 359 #
360 360 # Finally, the merge.applyupdates function will then take care of
361 361 # writing the files into the working copy and lfcommands.updatelfiles
362 362 # will update the largefiles.
363 363 def overridemanifestmerge(origfn, repo, p1, p2, pa, branchmerge, force,
364 partial):
364 partial, acceptremote=False):
365 365 overwrite = force and not branchmerge
366 actions = origfn(repo, p1, p2, pa, branchmerge, force, partial)
366 actions = origfn(repo, p1, p2, pa, branchmerge, force, partial,
367 acceptremote)
367 368 processed = []
368 369
369 370 for action in actions:
370 371 if overwrite:
371 372 processed.append(action)
372 373 continue
373 374 f, m, args, msg = action
374 375
375 376 choices = (_('&Largefile'), _('&Normal file'))
376 377 if m == "g" and lfutil.splitstandin(f) in p1 and f in p2:
377 378 # Case 1: normal file in the working copy, largefile in
378 379 # the second parent
379 380 lfile = lfutil.splitstandin(f)
380 381 standin = f
381 382 msg = _('%s has been turned into a largefile\n'
382 383 'use (l)argefile or keep as (n)ormal file?') % lfile
383 384 if repo.ui.promptchoice(msg, choices, 0) == 0:
384 385 processed.append((lfile, "r", None, msg))
385 386 processed.append((standin, "g", (p2.flags(standin),), msg))
386 387 else:
387 388 processed.append((standin, "r", None, msg))
388 389 elif m == "g" and lfutil.standin(f) in p1 and f in p2:
389 390 # Case 2: largefile in the working copy, normal file in
390 391 # the second parent
391 392 standin = lfutil.standin(f)
392 393 lfile = f
393 394 msg = _('%s has been turned into a normal file\n'
394 395 'keep as (l)argefile or use (n)ormal file?') % lfile
395 396 if repo.ui.promptchoice(msg, choices, 0) == 0:
396 397 processed.append((lfile, "r", None, msg))
397 398 else:
398 399 processed.append((standin, "r", None, msg))
399 400 processed.append((lfile, "g", (p2.flags(lfile),), msg))
400 401 else:
401 402 processed.append(action)
402 403
403 404 return processed
404 405
405 406 # Override filemerge to prompt the user about how they wish to merge
406 407 # largefiles. This will handle identical edits, and copy/rename +
407 408 # edit without prompting the user.
408 409 def overridefilemerge(origfn, repo, mynode, orig, fcd, fco, fca):
409 410 # Use better variable names here. Because this is a wrapper we cannot
410 411 # change the variable names in the function declaration.
411 412 fcdest, fcother, fcancestor = fcd, fco, fca
412 413 if not lfutil.isstandin(orig):
413 414 return origfn(repo, mynode, orig, fcdest, fcother, fcancestor)
414 415 else:
415 416 if not fcother.cmp(fcdest): # files identical?
416 417 return None
417 418
418 419 # backwards, use working dir parent as ancestor
419 420 if fcancestor == fcother:
420 421 fcancestor = fcdest.parents()[0]
421 422
422 423 if orig != fcother.path():
423 424 repo.ui.status(_('merging %s and %s to %s\n')
424 425 % (lfutil.splitstandin(orig),
425 426 lfutil.splitstandin(fcother.path()),
426 427 lfutil.splitstandin(fcdest.path())))
427 428 else:
428 429 repo.ui.status(_('merging %s\n')
429 430 % lfutil.splitstandin(fcdest.path()))
430 431
431 432 if fcancestor.path() != fcother.path() and fcother.data() == \
432 433 fcancestor.data():
433 434 return 0
434 435 if fcancestor.path() != fcdest.path() and fcdest.data() == \
435 436 fcancestor.data():
436 437 repo.wwrite(fcdest.path(), fcother.data(), fcother.flags())
437 438 return 0
438 439
439 440 if repo.ui.promptchoice(_('largefile %s has a merge conflict\n'
440 441 'keep (l)ocal or take (o)ther?') %
441 442 lfutil.splitstandin(orig),
442 443 (_('&Local'), _('&Other')), 0) == 0:
443 444 return 0
444 445 else:
445 446 repo.wwrite(fcdest.path(), fcother.data(), fcother.flags())
446 447 return 0
447 448
448 449 # Copy first changes the matchers to match standins instead of
449 450 # largefiles. Then it overrides util.copyfile in that function it
450 451 # checks if the destination largefile already exists. It also keeps a
451 452 # list of copied files so that the largefiles can be copied and the
452 453 # dirstate updated.
453 454 def overridecopy(orig, ui, repo, pats, opts, rename=False):
454 455 # doesn't remove largefile on rename
455 456 if len(pats) < 2:
456 457 # this isn't legal, let the original function deal with it
457 458 return orig(ui, repo, pats, opts, rename)
458 459
459 460 def makestandin(relpath):
460 461 path = scmutil.canonpath(repo.root, repo.getcwd(), relpath)
461 462 return os.path.join(repo.wjoin(lfutil.standin(path)))
462 463
463 464 fullpats = scmutil.expandpats(pats)
464 465 dest = fullpats[-1]
465 466
466 467 if os.path.isdir(dest):
467 468 if not os.path.isdir(makestandin(dest)):
468 469 os.makedirs(makestandin(dest))
469 470 # This could copy both lfiles and normal files in one command,
470 471 # but we don't want to do that. First replace their matcher to
471 472 # only match normal files and run it, then replace it to just
472 473 # match largefiles and run it again.
473 474 nonormalfiles = False
474 475 nolfiles = False
475 476 try:
476 477 try:
477 478 installnormalfilesmatchfn(repo[None].manifest())
478 479 result = orig(ui, repo, pats, opts, rename)
479 480 except util.Abort, e:
480 481 if str(e) != _('no files to copy'):
481 482 raise e
482 483 else:
483 484 nonormalfiles = True
484 485 result = 0
485 486 finally:
486 487 restorematchfn()
487 488
488 489 # The first rename can cause our current working directory to be removed.
489 490 # In that case there is nothing left to copy/rename so just quit.
490 491 try:
491 492 repo.getcwd()
492 493 except OSError:
493 494 return result
494 495
495 496 try:
496 497 try:
497 498 # When we call orig below it creates the standins but we don't add
498 499 # them to the dir state until later so lock during that time.
499 500 wlock = repo.wlock()
500 501
501 502 manifest = repo[None].manifest()
502 503 oldmatch = None # for the closure
503 504 def overridematch(ctx, pats=[], opts={}, globbed=False,
504 505 default='relpath'):
505 506 newpats = []
506 507 # The patterns were previously mangled to add the standin
507 508 # directory; we need to remove that now
508 509 for pat in pats:
509 510 if match_.patkind(pat) is None and lfutil.shortname in pat:
510 511 newpats.append(pat.replace(lfutil.shortname, ''))
511 512 else:
512 513 newpats.append(pat)
513 514 match = oldmatch(ctx, newpats, opts, globbed, default)
514 515 m = copy.copy(match)
515 516 lfile = lambda f: lfutil.standin(f) in manifest
516 517 m._files = [lfutil.standin(f) for f in m._files if lfile(f)]
517 518 m._fmap = set(m._files)
518 519 origmatchfn = m.matchfn
519 520 m.matchfn = lambda f: (lfutil.isstandin(f) and
520 521 (f in manifest) and
521 522 origmatchfn(lfutil.splitstandin(f)) or
522 523 None)
523 524 return m
524 525 oldmatch = installmatchfn(overridematch)
525 526 listpats = []
526 527 for pat in pats:
527 528 if match_.patkind(pat) is not None:
528 529 listpats.append(pat)
529 530 else:
530 531 listpats.append(makestandin(pat))
531 532
532 533 try:
533 534 origcopyfile = util.copyfile
534 535 copiedfiles = []
535 536 def overridecopyfile(src, dest):
536 537 if (lfutil.shortname in src and
537 538 dest.startswith(repo.wjoin(lfutil.shortname))):
538 539 destlfile = dest.replace(lfutil.shortname, '')
539 540 if not opts['force'] and os.path.exists(destlfile):
540 541 raise IOError('',
541 542 _('destination largefile already exists'))
542 543 copiedfiles.append((src, dest))
543 544 origcopyfile(src, dest)
544 545
545 546 util.copyfile = overridecopyfile
546 547 result += orig(ui, repo, listpats, opts, rename)
547 548 finally:
548 549 util.copyfile = origcopyfile
549 550
550 551 lfdirstate = lfutil.openlfdirstate(ui, repo)
551 552 for (src, dest) in copiedfiles:
552 553 if (lfutil.shortname in src and
553 554 dest.startswith(repo.wjoin(lfutil.shortname))):
554 555 srclfile = src.replace(repo.wjoin(lfutil.standin('')), '')
555 556 destlfile = dest.replace(repo.wjoin(lfutil.standin('')), '')
556 557 destlfiledir = os.path.dirname(repo.wjoin(destlfile)) or '.'
557 558 if not os.path.isdir(destlfiledir):
558 559 os.makedirs(destlfiledir)
559 560 if rename:
560 561 os.rename(repo.wjoin(srclfile), repo.wjoin(destlfile))
561 562 lfdirstate.remove(srclfile)
562 563 else:
563 564 util.copyfile(repo.wjoin(srclfile),
564 565 repo.wjoin(destlfile))
565 566
566 567 lfdirstate.add(destlfile)
567 568 lfdirstate.write()
568 569 except util.Abort, e:
569 570 if str(e) != _('no files to copy'):
570 571 raise e
571 572 else:
572 573 nolfiles = True
573 574 finally:
574 575 restorematchfn()
575 576 wlock.release()
576 577
577 578 if nolfiles and nonormalfiles:
578 579 raise util.Abort(_('no files to copy'))
579 580
580 581 return result
581 582
582 583 # When the user calls revert, we have to be careful to not revert any
583 584 # changes to other largefiles accidentally. This means we have to keep
584 585 # track of the largefiles that are being reverted so we only pull down
585 586 # the necessary largefiles.
586 587 #
587 588 # Standins are only updated (to match the hash of largefiles) before
588 589 # commits. Update the standins then run the original revert, changing
589 590 # the matcher to hit standins instead of largefiles. Based on the
590 591 # resulting standins update the largefiles. Then return the standins
591 592 # to their proper state
592 593 def overriderevert(orig, ui, repo, *pats, **opts):
593 594 # Because we put the standins in a bad state (by updating them)
594 595 # and then return them to a correct state we need to lock to
595 596 # prevent others from changing them in their incorrect state.
596 597 wlock = repo.wlock()
597 598 try:
598 599 lfdirstate = lfutil.openlfdirstate(ui, repo)
599 600 (modified, added, removed, missing, unknown, ignored, clean) = \
600 601 lfutil.lfdirstatestatus(lfdirstate, repo, repo['.'].rev())
601 602 lfdirstate.write()
602 603 for lfile in modified:
603 604 lfutil.updatestandin(repo, lfutil.standin(lfile))
604 605 for lfile in missing:
605 606 if (os.path.exists(repo.wjoin(lfutil.standin(lfile)))):
606 607 os.unlink(repo.wjoin(lfutil.standin(lfile)))
607 608
608 609 try:
609 610 ctx = scmutil.revsingle(repo, opts.get('rev'))
610 611 oldmatch = None # for the closure
611 612 def overridematch(ctx, pats=[], opts={}, globbed=False,
612 613 default='relpath'):
613 614 match = oldmatch(ctx, pats, opts, globbed, default)
614 615 m = copy.copy(match)
615 616 def tostandin(f):
616 617 if lfutil.standin(f) in ctx:
617 618 return lfutil.standin(f)
618 619 elif lfutil.standin(f) in repo[None]:
619 620 return None
620 621 return f
621 622 m._files = [tostandin(f) for f in m._files]
622 623 m._files = [f for f in m._files if f is not None]
623 624 m._fmap = set(m._files)
624 625 origmatchfn = m.matchfn
625 626 def matchfn(f):
626 627 if lfutil.isstandin(f):
627 628 # We need to keep track of what largefiles are being
628 629 # matched so we know which ones to update later --
629 630 # otherwise we accidentally revert changes to other
630 631 # largefiles. This is repo-specific, so duckpunch the
631 632 # repo object to keep the list of largefiles for us
632 633 # later.
633 634 if origmatchfn(lfutil.splitstandin(f)) and \
634 635 (f in repo[None] or f in ctx):
635 636 lfileslist = getattr(repo, '_lfilestoupdate', [])
636 637 lfileslist.append(lfutil.splitstandin(f))
637 638 repo._lfilestoupdate = lfileslist
638 639 return True
639 640 else:
640 641 return False
641 642 return origmatchfn(f)
642 643 m.matchfn = matchfn
643 644 return m
644 645 oldmatch = installmatchfn(overridematch)
645 646 scmutil.match
646 647 matches = overridematch(repo[None], pats, opts)
647 648 orig(ui, repo, *pats, **opts)
648 649 finally:
649 650 restorematchfn()
650 651 lfileslist = getattr(repo, '_lfilestoupdate', [])
651 652 lfcommands.updatelfiles(ui, repo, filelist=lfileslist,
652 653 printmessage=False)
653 654
654 655 # empty out the largefiles list so we start fresh next time
655 656 repo._lfilestoupdate = []
656 657 for lfile in modified:
657 658 if lfile in lfileslist:
658 659 if os.path.exists(repo.wjoin(lfutil.standin(lfile))) and lfile\
659 660 in repo['.']:
660 661 lfutil.writestandin(repo, lfutil.standin(lfile),
661 662 repo['.'][lfile].data().strip(),
662 663 'x' in repo['.'][lfile].flags())
663 664 lfdirstate = lfutil.openlfdirstate(ui, repo)
664 665 for lfile in added:
665 666 standin = lfutil.standin(lfile)
666 667 if standin not in ctx and (standin in matches or opts.get('all')):
667 668 if lfile in lfdirstate:
668 669 lfdirstate.drop(lfile)
669 670 util.unlinkpath(repo.wjoin(standin))
670 671 lfdirstate.write()
671 672 finally:
672 673 wlock.release()
673 674
674 675 def hgupdaterepo(orig, repo, node, overwrite):
675 676 if not overwrite:
676 677 # Only call updatelfiles on the standins that have changed to save time
677 678 oldstandins = lfutil.getstandinsstate(repo)
678 679
679 680 result = orig(repo, node, overwrite)
680 681
681 682 filelist = None
682 683 if not overwrite:
683 684 newstandins = lfutil.getstandinsstate(repo)
684 685 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
685 686 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist)
686 687 return result
687 688
688 689 def hgmerge(orig, repo, node, force=None, remind=True):
689 690 result = orig(repo, node, force, remind)
690 691 lfcommands.updatelfiles(repo.ui, repo)
691 692 return result
692 693
693 694 # When we rebase a repository with remotely changed largefiles, we need to
694 695 # take some extra care so that the largefiles are correctly updated in the
695 696 # working copy
696 697 def overridepull(orig, ui, repo, source=None, **opts):
697 698 revsprepull = len(repo)
698 699 if opts.get('rebase', False):
699 700 repo._isrebasing = True
700 701 try:
701 702 if opts.get('update'):
702 703 del opts['update']
703 704 ui.debug('--update and --rebase are not compatible, ignoring '
704 705 'the update flag\n')
705 706 del opts['rebase']
706 707 cmdutil.bailifchanged(repo)
707 708 origpostincoming = commands.postincoming
708 709 def _dummy(*args, **kwargs):
709 710 pass
710 711 commands.postincoming = _dummy
711 712 if not source:
712 713 source = 'default'
713 714 repo.lfpullsource = source
714 715 try:
715 716 result = commands.pull(ui, repo, source, **opts)
716 717 finally:
717 718 commands.postincoming = origpostincoming
718 719 revspostpull = len(repo)
719 720 if revspostpull > revsprepull:
720 721 result = result or rebase.rebase(ui, repo)
721 722 finally:
722 723 repo._isrebasing = False
723 724 else:
724 725 if not source:
725 726 source = 'default'
726 727 repo.lfpullsource = source
727 728 oldheads = lfutil.getcurrentheads(repo)
728 729 result = orig(ui, repo, source, **opts)
729 730 if opts.get('cache_largefiles'):
730 731 # If you are pulling from a remote location that is not your
731 732 # default location, you may want to cache largefiles for new heads
732 733 # that have been pulled, so you can easily merge or rebase with
733 734 # them later
734 735 numcached = 0
735 736 heads = lfutil.getcurrentheads(repo)
736 737 newheads = set(heads).difference(set(oldheads))
737 738 if len(newheads) > 0:
738 739 ui.status(_("caching largefiles for %s heads\n") %
739 740 len(newheads))
740 741 for head in newheads:
741 742 (cached, missing) = lfcommands.cachelfiles(ui, repo, head)
742 743 numcached += len(cached)
743 744 ui.status(_("%d largefiles cached\n") % numcached)
744 745 if opts.get('all_largefiles'):
745 746 revspostpull = len(repo)
746 747 revs = []
747 748 for rev in xrange(revsprepull, revspostpull):
748 749 revs.append(repo[rev].rev())
749 750 lfcommands.downloadlfiles(ui, repo, revs)
750 751 return result
751 752
752 753 def overrideclone(orig, ui, source, dest=None, **opts):
753 754 d = dest
754 755 if d is None:
755 756 d = hg.defaultdest(source)
756 757 if opts.get('all_largefiles') and not hg.islocal(d):
757 758 raise util.Abort(_(
758 759 '--all-largefiles is incompatible with non-local destination %s' %
759 760 d))
760 761
761 762 return orig(ui, source, dest, **opts)
762 763
763 764 def hgclone(orig, ui, opts, *args, **kwargs):
764 765 result = orig(ui, opts, *args, **kwargs)
765 766
766 767 if result is not None:
767 768 sourcerepo, destrepo = result
768 769 repo = destrepo.local()
769 770
770 771 # Caching is implicitly limited to 'rev' option, since the dest repo was
771 772 # truncated at that point. The user may expect a download count with
772 773 # this option, so attempt whether or not this is a largefile repo.
773 774 if opts.get('all_largefiles'):
774 775 success, missing = lfcommands.downloadlfiles(ui, repo, None)
775 776
776 777 if missing != 0:
777 778 return None
778 779
779 780 return result
780 781
781 782 def overriderebase(orig, ui, repo, **opts):
782 783 repo._isrebasing = True
783 784 try:
784 785 return orig(ui, repo, **opts)
785 786 finally:
786 787 repo._isrebasing = False
787 788
788 789 def overridearchive(orig, repo, dest, node, kind, decode=True, matchfn=None,
789 790 prefix=None, mtime=None, subrepos=None):
790 791 # No need to lock because we are only reading history and
791 792 # largefile caches, neither of which are modified.
792 793 lfcommands.cachelfiles(repo.ui, repo, node)
793 794
794 795 if kind not in archival.archivers:
795 796 raise util.Abort(_("unknown archive type '%s'") % kind)
796 797
797 798 ctx = repo[node]
798 799
799 800 if kind == 'files':
800 801 if prefix:
801 802 raise util.Abort(
802 803 _('cannot give prefix when archiving to files'))
803 804 else:
804 805 prefix = archival.tidyprefix(dest, kind, prefix)
805 806
806 807 def write(name, mode, islink, getdata):
807 808 if matchfn and not matchfn(name):
808 809 return
809 810 data = getdata()
810 811 if decode:
811 812 data = repo.wwritedata(name, data)
812 813 archiver.addfile(prefix + name, mode, islink, data)
813 814
814 815 archiver = archival.archivers[kind](dest, mtime or ctx.date()[0])
815 816
816 817 if repo.ui.configbool("ui", "archivemeta", True):
817 818 def metadata():
818 819 base = 'repo: %s\nnode: %s\nbranch: %s\n' % (
819 820 hex(repo.changelog.node(0)), hex(node), ctx.branch())
820 821
821 822 tags = ''.join('tag: %s\n' % t for t in ctx.tags()
822 823 if repo.tagtype(t) == 'global')
823 824 if not tags:
824 825 repo.ui.pushbuffer()
825 826 opts = {'template': '{latesttag}\n{latesttagdistance}',
826 827 'style': '', 'patch': None, 'git': None}
827 828 cmdutil.show_changeset(repo.ui, repo, opts).show(ctx)
828 829 ltags, dist = repo.ui.popbuffer().split('\n')
829 830 tags = ''.join('latesttag: %s\n' % t for t in ltags.split(':'))
830 831 tags += 'latesttagdistance: %s\n' % dist
831 832
832 833 return base + tags
833 834
834 835 write('.hg_archival.txt', 0644, False, metadata)
835 836
836 837 for f in ctx:
837 838 ff = ctx.flags(f)
838 839 getdata = ctx[f].data
839 840 if lfutil.isstandin(f):
840 841 path = lfutil.findfile(repo, getdata().strip())
841 842 if path is None:
842 843 raise util.Abort(
843 844 _('largefile %s not found in repo store or system cache')
844 845 % lfutil.splitstandin(f))
845 846 f = lfutil.splitstandin(f)
846 847
847 848 def getdatafn():
848 849 fd = None
849 850 try:
850 851 fd = open(path, 'rb')
851 852 return fd.read()
852 853 finally:
853 854 if fd:
854 855 fd.close()
855 856
856 857 getdata = getdatafn
857 858 write(f, 'x' in ff and 0755 or 0644, 'l' in ff, getdata)
858 859
859 860 if subrepos:
860 861 for subpath in sorted(ctx.substate):
861 862 sub = ctx.sub(subpath)
862 863 submatch = match_.narrowmatcher(subpath, matchfn)
863 864 sub.archive(repo.ui, archiver, prefix, submatch)
864 865
865 866 archiver.done()
866 867
867 868 def hgsubrepoarchive(orig, repo, ui, archiver, prefix, match=None):
868 869 repo._get(repo._state + ('hg',))
869 870 rev = repo._state[1]
870 871 ctx = repo._repo[rev]
871 872
872 873 lfcommands.cachelfiles(ui, repo._repo, ctx.node())
873 874
874 875 def write(name, mode, islink, getdata):
875 876 # At this point, the standin has been replaced with the largefile name,
876 877 # so the normal matcher works here without the lfutil variants.
877 878 if match and not match(f):
878 879 return
879 880 data = getdata()
880 881
881 882 archiver.addfile(prefix + repo._path + '/' + name, mode, islink, data)
882 883
883 884 for f in ctx:
884 885 ff = ctx.flags(f)
885 886 getdata = ctx[f].data
886 887 if lfutil.isstandin(f):
887 888 path = lfutil.findfile(repo._repo, getdata().strip())
888 889 if path is None:
889 890 raise util.Abort(
890 891 _('largefile %s not found in repo store or system cache')
891 892 % lfutil.splitstandin(f))
892 893 f = lfutil.splitstandin(f)
893 894
894 895 def getdatafn():
895 896 fd = None
896 897 try:
897 898 fd = open(os.path.join(prefix, path), 'rb')
898 899 return fd.read()
899 900 finally:
900 901 if fd:
901 902 fd.close()
902 903
903 904 getdata = getdatafn
904 905
905 906 write(f, 'x' in ff and 0755 or 0644, 'l' in ff, getdata)
906 907
907 908 for subpath in sorted(ctx.substate):
908 909 sub = ctx.sub(subpath)
909 910 submatch = match_.narrowmatcher(subpath, match)
910 911 sub.archive(ui, archiver, os.path.join(prefix, repo._path) + '/',
911 912 submatch)
912 913
913 914 # If a largefile is modified, the change is not reflected in its
914 915 # standin until a commit. cmdutil.bailifchanged() raises an exception
915 916 # if the repo has uncommitted changes. Wrap it to also check if
916 917 # largefiles were changed. This is used by bisect and backout.
917 918 def overridebailifchanged(orig, repo):
918 919 orig(repo)
919 920 repo.lfstatus = True
920 921 modified, added, removed, deleted = repo.status()[:4]
921 922 repo.lfstatus = False
922 923 if modified or added or removed or deleted:
923 924 raise util.Abort(_('outstanding uncommitted changes'))
924 925
925 926 # Fetch doesn't use cmdutil.bailifchanged so override it to add the check
926 927 def overridefetch(orig, ui, repo, *pats, **opts):
927 928 repo.lfstatus = True
928 929 modified, added, removed, deleted = repo.status()[:4]
929 930 repo.lfstatus = False
930 931 if modified or added or removed or deleted:
931 932 raise util.Abort(_('outstanding uncommitted changes'))
932 933 return orig(ui, repo, *pats, **opts)
933 934
934 935 def overrideforget(orig, ui, repo, *pats, **opts):
935 936 installnormalfilesmatchfn(repo[None].manifest())
936 937 result = orig(ui, repo, *pats, **opts)
937 938 restorematchfn()
938 939 m = scmutil.match(repo[None], pats, opts)
939 940
940 941 try:
941 942 repo.lfstatus = True
942 943 s = repo.status(match=m, clean=True)
943 944 finally:
944 945 repo.lfstatus = False
945 946 forget = sorted(s[0] + s[1] + s[3] + s[6])
946 947 forget = [f for f in forget if lfutil.standin(f) in repo[None].manifest()]
947 948
948 949 for f in forget:
949 950 if lfutil.standin(f) not in repo.dirstate and not \
950 951 os.path.isdir(m.rel(lfutil.standin(f))):
951 952 ui.warn(_('not removing %s: file is already untracked\n')
952 953 % m.rel(f))
953 954 result = 1
954 955
955 956 for f in forget:
956 957 if ui.verbose or not m.exact(f):
957 958 ui.status(_('removing %s\n') % m.rel(f))
958 959
959 960 # Need to lock because standin files are deleted then removed from the
960 961 # repository and we could race in-between.
961 962 wlock = repo.wlock()
962 963 try:
963 964 lfdirstate = lfutil.openlfdirstate(ui, repo)
964 965 for f in forget:
965 966 if lfdirstate[f] == 'a':
966 967 lfdirstate.drop(f)
967 968 else:
968 969 lfdirstate.remove(f)
969 970 lfdirstate.write()
970 971 standins = [lfutil.standin(f) for f in forget]
971 972 for f in standins:
972 973 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
973 974 repo[None].forget(standins)
974 975 finally:
975 976 wlock.release()
976 977
977 978 return result
978 979
979 980 def getoutgoinglfiles(ui, repo, dest=None, **opts):
980 981 dest = ui.expandpath(dest or 'default-push', dest or 'default')
981 982 dest, branches = hg.parseurl(dest, opts.get('branch'))
982 983 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
983 984 if revs:
984 985 revs = [repo.lookup(rev) for rev in scmutil.revrange(repo, revs)]
985 986
986 987 try:
987 988 remote = hg.peer(repo, opts, dest)
988 989 except error.RepoError:
989 990 return None
990 991 outgoing = discovery.findcommonoutgoing(repo, remote.peer(), force=False)
991 992 if not outgoing.missing:
992 993 return outgoing.missing
993 994 o = repo.changelog.nodesbetween(outgoing.missing, revs)[0]
994 995 if opts.get('newest_first'):
995 996 o.reverse()
996 997
997 998 toupload = set()
998 999 for n in o:
999 1000 parents = [p for p in repo.changelog.parents(n) if p != node.nullid]
1000 1001 ctx = repo[n]
1001 1002 files = set(ctx.files())
1002 1003 if len(parents) == 2:
1003 1004 mc = ctx.manifest()
1004 1005 mp1 = ctx.parents()[0].manifest()
1005 1006 mp2 = ctx.parents()[1].manifest()
1006 1007 for f in mp1:
1007 1008 if f not in mc:
1008 1009 files.add(f)
1009 1010 for f in mp2:
1010 1011 if f not in mc:
1011 1012 files.add(f)
1012 1013 for f in mc:
1013 1014 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
1014 1015 files.add(f)
1015 1016 toupload = toupload.union(
1016 1017 set([f for f in files if lfutil.isstandin(f) and f in ctx]))
1017 1018 return sorted(toupload)
1018 1019
1019 1020 def overrideoutgoing(orig, ui, repo, dest=None, **opts):
1020 1021 result = orig(ui, repo, dest, **opts)
1021 1022
1022 1023 if opts.pop('large', None):
1023 1024 toupload = getoutgoinglfiles(ui, repo, dest, **opts)
1024 1025 if toupload is None:
1025 1026 ui.status(_('largefiles: No remote repo\n'))
1026 1027 elif not toupload:
1027 1028 ui.status(_('largefiles: no files to upload\n'))
1028 1029 else:
1029 1030 ui.status(_('largefiles to upload:\n'))
1030 1031 for file in toupload:
1031 1032 ui.status(lfutil.splitstandin(file) + '\n')
1032 1033 ui.status('\n')
1033 1034
1034 1035 return result
1035 1036
1036 1037 def overridesummary(orig, ui, repo, *pats, **opts):
1037 1038 try:
1038 1039 repo.lfstatus = True
1039 1040 orig(ui, repo, *pats, **opts)
1040 1041 finally:
1041 1042 repo.lfstatus = False
1042 1043
1043 1044 if opts.pop('large', None):
1044 1045 toupload = getoutgoinglfiles(ui, repo, None, **opts)
1045 1046 if toupload is None:
1046 1047 # i18n: column positioning for "hg summary"
1047 1048 ui.status(_('largefiles: (no remote repo)\n'))
1048 1049 elif not toupload:
1049 1050 # i18n: column positioning for "hg summary"
1050 1051 ui.status(_('largefiles: (no files to upload)\n'))
1051 1052 else:
1052 1053 # i18n: column positioning for "hg summary"
1053 1054 ui.status(_('largefiles: %d to upload\n') % len(toupload))
1054 1055
1055 1056 def scmutiladdremove(orig, repo, pats=[], opts={}, dry_run=None,
1056 1057 similarity=None):
1057 1058 if not lfutil.islfilesrepo(repo):
1058 1059 return orig(repo, pats, opts, dry_run, similarity)
1059 1060 # Get the list of missing largefiles so we can remove them
1060 1061 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1061 1062 s = lfdirstate.status(match_.always(repo.root, repo.getcwd()), [], False,
1062 1063 False, False)
1063 1064 (unsure, modified, added, removed, missing, unknown, ignored, clean) = s
1064 1065
1065 1066 # Call into the normal remove code, but the removing of the standin, we want
1066 1067 # to have handled by original addremove. Monkey patching here makes sure
1067 1068 # we don't remove the standin in the largefiles code, preventing a very
1068 1069 # confused state later.
1069 1070 if missing:
1070 1071 m = [repo.wjoin(f) for f in missing]
1071 1072 repo._isaddremove = True
1072 1073 removelargefiles(repo.ui, repo, *m, **opts)
1073 1074 repo._isaddremove = False
1074 1075 # Call into the normal add code, and any files that *should* be added as
1075 1076 # largefiles will be
1076 1077 addlargefiles(repo.ui, repo, *pats, **opts)
1077 1078 # Now that we've handled largefiles, hand off to the original addremove
1078 1079 # function to take care of the rest. Make sure it doesn't do anything with
1079 1080 # largefiles by installing a matcher that will ignore them.
1080 1081 installnormalfilesmatchfn(repo[None].manifest())
1081 1082 result = orig(repo, pats, opts, dry_run, similarity)
1082 1083 restorematchfn()
1083 1084 return result
1084 1085
1085 1086 # Calling purge with --all will cause the largefiles to be deleted.
1086 1087 # Override repo.status to prevent this from happening.
1087 1088 def overridepurge(orig, ui, repo, *dirs, **opts):
1088 1089 # XXX large file status is buggy when used on repo proxy.
1089 1090 # XXX this needs to be investigate.
1090 1091 repo = repo.unfiltered()
1091 1092 oldstatus = repo.status
1092 1093 def overridestatus(node1='.', node2=None, match=None, ignored=False,
1093 1094 clean=False, unknown=False, listsubrepos=False):
1094 1095 r = oldstatus(node1, node2, match, ignored, clean, unknown,
1095 1096 listsubrepos)
1096 1097 lfdirstate = lfutil.openlfdirstate(ui, repo)
1097 1098 modified, added, removed, deleted, unknown, ignored, clean = r
1098 1099 unknown = [f for f in unknown if lfdirstate[f] == '?']
1099 1100 ignored = [f for f in ignored if lfdirstate[f] == '?']
1100 1101 return modified, added, removed, deleted, unknown, ignored, clean
1101 1102 repo.status = overridestatus
1102 1103 orig(ui, repo, *dirs, **opts)
1103 1104 repo.status = oldstatus
1104 1105
1105 1106 def overriderollback(orig, ui, repo, **opts):
1106 1107 result = orig(ui, repo, **opts)
1107 1108 merge.update(repo, node=None, branchmerge=False, force=True,
1108 1109 partial=lfutil.isstandin)
1109 1110 wlock = repo.wlock()
1110 1111 try:
1111 1112 lfdirstate = lfutil.openlfdirstate(ui, repo)
1112 1113 lfiles = lfutil.listlfiles(repo)
1113 1114 oldlfiles = lfutil.listlfiles(repo, repo[None].parents()[0].rev())
1114 1115 for file in lfiles:
1115 1116 if file in oldlfiles:
1116 1117 lfdirstate.normallookup(file)
1117 1118 else:
1118 1119 lfdirstate.add(file)
1119 1120 lfdirstate.write()
1120 1121 finally:
1121 1122 wlock.release()
1122 1123 return result
1123 1124
1124 1125 def overridetransplant(orig, ui, repo, *revs, **opts):
1125 1126 try:
1126 1127 oldstandins = lfutil.getstandinsstate(repo)
1127 1128 repo._istransplanting = True
1128 1129 result = orig(ui, repo, *revs, **opts)
1129 1130 newstandins = lfutil.getstandinsstate(repo)
1130 1131 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
1131 1132 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1132 1133 printmessage=True)
1133 1134 finally:
1134 1135 repo._istransplanting = False
1135 1136 return result
1136 1137
1137 1138 def overridecat(orig, ui, repo, file1, *pats, **opts):
1138 1139 ctx = scmutil.revsingle(repo, opts.get('rev'))
1139 1140 err = 1
1140 1141 notbad = set()
1141 1142 m = scmutil.match(ctx, (file1,) + pats, opts)
1142 1143 origmatchfn = m.matchfn
1143 1144 def lfmatchfn(f):
1144 1145 lf = lfutil.splitstandin(f)
1145 1146 if lf is None:
1146 1147 return origmatchfn(f)
1147 1148 notbad.add(lf)
1148 1149 return origmatchfn(lf)
1149 1150 m.matchfn = lfmatchfn
1150 1151 m.bad = lambda f, msg: f not in notbad
1151 1152 for f in ctx.walk(m):
1152 1153 lf = lfutil.splitstandin(f)
1153 1154 if lf is None:
1154 1155 err = orig(ui, repo, f, **opts)
1155 1156 else:
1156 1157 err = lfcommands.catlfile(repo, lf, ctx.rev(), opts.get('output'))
1157 1158 return err
1158 1159
1159 1160 def mercurialsinkbefore(orig, sink):
1160 1161 sink.repo._isconverting = True
1161 1162 orig(sink)
1162 1163
1163 1164 def mercurialsinkafter(orig, sink):
1164 1165 sink.repo._isconverting = False
1165 1166 orig(sink)
@@ -1,716 +1,724
1 1 # merge.py - directory-level update/merge handling for Mercurial
2 2 #
3 3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import nullid, nullrev, hex, bin
9 9 from i18n import _
10 10 import error, util, filemerge, copies, subrepo, worker
11 11 import errno, os, shutil
12 12
13 13 class mergestate(object):
14 14 '''track 3-way merge state of individual files'''
15 15 def __init__(self, repo):
16 16 self._repo = repo
17 17 self._dirty = False
18 18 self._read()
19 19 def reset(self, node=None):
20 20 self._state = {}
21 21 if node:
22 22 self._local = node
23 23 shutil.rmtree(self._repo.join("merge"), True)
24 24 self._dirty = False
25 25 def _read(self):
26 26 self._state = {}
27 27 try:
28 28 f = self._repo.opener("merge/state")
29 29 for i, l in enumerate(f):
30 30 if i == 0:
31 31 self._local = bin(l[:-1])
32 32 else:
33 33 bits = l[:-1].split("\0")
34 34 self._state[bits[0]] = bits[1:]
35 35 f.close()
36 36 except IOError, err:
37 37 if err.errno != errno.ENOENT:
38 38 raise
39 39 self._dirty = False
40 40 def commit(self):
41 41 if self._dirty:
42 42 f = self._repo.opener("merge/state", "w")
43 43 f.write(hex(self._local) + "\n")
44 44 for d, v in self._state.iteritems():
45 45 f.write("\0".join([d] + v) + "\n")
46 46 f.close()
47 47 self._dirty = False
48 48 def add(self, fcl, fco, fca, fd):
49 49 hash = util.sha1(fcl.path()).hexdigest()
50 50 self._repo.opener.write("merge/" + hash, fcl.data())
51 51 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
52 52 hex(fca.filenode()), fco.path(), fcl.flags()]
53 53 self._dirty = True
54 54 def __contains__(self, dfile):
55 55 return dfile in self._state
56 56 def __getitem__(self, dfile):
57 57 return self._state[dfile][0]
58 58 def __iter__(self):
59 59 l = self._state.keys()
60 60 l.sort()
61 61 for f in l:
62 62 yield f
63 63 def mark(self, dfile, state):
64 64 self._state[dfile][0] = state
65 65 self._dirty = True
66 66 def resolve(self, dfile, wctx, octx):
67 67 if self[dfile] == 'r':
68 68 return 0
69 69 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
70 70 fcd = wctx[dfile]
71 71 fco = octx[ofile]
72 72 fca = self._repo.filectx(afile, fileid=anode)
73 73 # "premerge" x flags
74 74 flo = fco.flags()
75 75 fla = fca.flags()
76 76 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
77 77 if fca.node() == nullid:
78 78 self._repo.ui.warn(_('warning: cannot merge flags for %s\n') %
79 79 afile)
80 80 elif flags == fla:
81 81 flags = flo
82 82 # restore local
83 83 f = self._repo.opener("merge/" + hash)
84 84 self._repo.wwrite(dfile, f.read(), flags)
85 85 f.close()
86 86 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
87 87 if r is None:
88 88 # no real conflict
89 89 del self._state[dfile]
90 90 elif not r:
91 91 self.mark(dfile, 'r')
92 92 return r
93 93
94 94 def _checkunknownfile(repo, wctx, mctx, f):
95 95 return (not repo.dirstate._ignore(f)
96 96 and os.path.isfile(repo.wjoin(f))
97 97 and repo.dirstate.normalize(f) not in repo.dirstate
98 98 and mctx[f].cmp(wctx[f]))
99 99
100 100 def _checkunknown(repo, wctx, mctx):
101 101 "check for collisions between unknown files and files in mctx"
102 102
103 103 error = False
104 104 for f in mctx:
105 105 if f not in wctx and _checkunknownfile(repo, wctx, mctx, f):
106 106 error = True
107 107 wctx._repo.ui.warn(_("%s: untracked file differs\n") % f)
108 108 if error:
109 109 raise util.Abort(_("untracked files in working directory differ "
110 110 "from files in requested revision"))
111 111
112 112 def _remains(f, m, ma, workingctx=False):
113 113 """check whether specified file remains after merge.
114 114
115 115 It is assumed that specified file is not contained in the manifest
116 116 of the other context.
117 117 """
118 118 if f in ma:
119 119 n = m[f]
120 120 if n != ma[f]:
121 121 return True # because it is changed locally
122 122 # even though it doesn't remain, if "remote deleted" is
123 123 # chosen in manifestmerge()
124 124 elif workingctx and n[20:] == "a":
125 125 return True # because it is added locally (linear merge specific)
126 126 else:
127 127 return False # because it is removed remotely
128 128 else:
129 129 return True # because it is added locally
130 130
131 131 def _checkcollision(mctx, extractxs):
132 132 "check for case folding collisions in the destination context"
133 133 folded = {}
134 134 for fn in mctx:
135 135 fold = util.normcase(fn)
136 136 if fold in folded:
137 137 raise util.Abort(_("case-folding collision between %s and %s")
138 138 % (fn, folded[fold]))
139 139 folded[fold] = fn
140 140
141 141 if extractxs:
142 142 wctx, actx = extractxs
143 143 # class to delay looking up copy mapping
144 144 class pathcopies(object):
145 145 @util.propertycache
146 146 def map(self):
147 147 # {dst@mctx: src@wctx} copy mapping
148 148 return copies.pathcopies(wctx, mctx)
149 149 pc = pathcopies()
150 150
151 151 for fn in wctx:
152 152 fold = util.normcase(fn)
153 153 mfn = folded.get(fold, None)
154 154 if (mfn and mfn != fn and pc.map.get(mfn) != fn and
155 155 _remains(fn, wctx.manifest(), actx.manifest(), True) and
156 156 _remains(mfn, mctx.manifest(), actx.manifest())):
157 157 raise util.Abort(_("case-folding collision between %s and %s")
158 158 % (mfn, fn))
159 159
160 160 def _forgetremoved(wctx, mctx, branchmerge):
161 161 """
162 162 Forget removed files
163 163
164 164 If we're jumping between revisions (as opposed to merging), and if
165 165 neither the working directory nor the target rev has the file,
166 166 then we need to remove it from the dirstate, to prevent the
167 167 dirstate from listing the file when it is no longer in the
168 168 manifest.
169 169
170 170 If we're merging, and the other revision has removed a file
171 171 that is not present in the working directory, we need to mark it
172 172 as removed.
173 173 """
174 174
175 175 actions = []
176 176 state = branchmerge and 'r' or 'f'
177 177 for f in wctx.deleted():
178 178 if f not in mctx:
179 179 actions.append((f, state, None, "forget deleted"))
180 180
181 181 if not branchmerge:
182 182 for f in wctx.removed():
183 183 if f not in mctx:
184 184 actions.append((f, "f", None, "forget removed"))
185 185
186 186 return actions
187 187
188 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial):
188 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial,
189 acceptremote=False):
189 190 """
190 191 Merge p1 and p2 with ancestor pa and generate merge action list
191 192
192 193 branchmerge and force are as passed in to update
193 194 partial = function to filter file lists
195 acceptremote = accept the incoming changes without prompting
194 196 """
195 197
196 198 overwrite = force and not branchmerge
197 199 actions, copy, movewithdir = [], {}, {}
198 200
199 201 followcopies = False
200 202 if overwrite:
201 203 pa = wctx
202 204 elif pa == p2: # backwards
203 205 pa = wctx.p1()
204 206 elif not branchmerge and not wctx.dirty(missing=True):
205 207 pass
206 208 elif pa and repo.ui.configbool("merge", "followcopies", True):
207 209 followcopies = True
208 210
209 211 # manifests fetched in order are going to be faster, so prime the caches
210 212 [x.manifest() for x in
211 213 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
212 214
213 215 if followcopies:
214 216 ret = copies.mergecopies(repo, wctx, p2, pa)
215 217 copy, movewithdir, diverge, renamedelete = ret
216 218 for of, fl in diverge.iteritems():
217 219 actions.append((of, "dr", (fl,), "divergent renames"))
218 220 for of, fl in renamedelete.iteritems():
219 221 actions.append((of, "rd", (fl,), "rename and delete"))
220 222
221 223 repo.ui.note(_("resolving manifests\n"))
222 224 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
223 225 % (bool(branchmerge), bool(force), bool(partial)))
224 226 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
225 227
226 228 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
227 229 copied = set(copy.values())
228 230 copied.update(movewithdir.values())
229 231
230 232 if '.hgsubstate' in m1:
231 233 # check whether sub state is modified
232 234 for s in sorted(wctx.substate):
233 235 if wctx.sub(s).dirty():
234 236 m1['.hgsubstate'] += "+"
235 237 break
236 238
237 239 aborts, prompts = [], []
238 240 # Compare manifests
239 241 for f, n in m1.iteritems():
240 242 if partial and not partial(f):
241 243 continue
242 244 if f in m2:
243 245 n2 = m2[f]
244 246 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
245 247 nol = 'l' not in fl1 + fl2 + fla
246 248 a = ma.get(f, nullid)
247 249 if n == n2 and fl1 == fl2:
248 250 pass # same - keep local
249 251 elif n2 == a and fl2 == fla:
250 252 pass # remote unchanged - keep local
251 253 elif n == a and fl1 == fla: # local unchanged - use remote
252 254 if n == n2: # optimization: keep local content
253 255 actions.append((f, "e", (fl2,), "update permissions"))
254 256 else:
255 257 actions.append((f, "g", (fl2,), "remote is newer"))
256 258 elif nol and n2 == a: # remote only changed 'x'
257 259 actions.append((f, "e", (fl2,), "update permissions"))
258 260 elif nol and n == a: # local only changed 'x'
259 261 actions.append((f, "g", (fl1,), "remote is newer"))
260 262 else: # both changed something
261 263 actions.append((f, "m", (f, f, False), "versions differ"))
262 264 elif f in copied: # files we'll deal with on m2 side
263 265 pass
264 266 elif f in movewithdir: # directory rename
265 267 f2 = movewithdir[f]
266 268 actions.append((f, "d", (None, f2, m1.flags(f)),
267 269 "remote renamed directory to " + f2))
268 270 elif f in copy:
269 271 f2 = copy[f]
270 272 actions.append((f, "m", (f2, f, False),
271 273 "local copied/moved to " + f2))
272 274 elif f in ma: # clean, a different, no remote
273 275 if n != ma[f]:
274 276 prompts.append((f, "cd")) # prompt changed/deleted
275 277 elif n[20:] == "a": # added, no remote
276 278 actions.append((f, "f", None, "remote deleted"))
277 279 else:
278 280 actions.append((f, "r", None, "other deleted"))
279 281
280 282 for f, n in m2.iteritems():
281 283 if partial and not partial(f):
282 284 continue
283 285 if f in m1 or f in copied: # files already visited
284 286 continue
285 287 if f in movewithdir:
286 288 f2 = movewithdir[f]
287 289 actions.append((None, "d", (f, f2, m2.flags(f)),
288 290 "local renamed directory to " + f2))
289 291 elif f in copy:
290 292 f2 = copy[f]
291 293 if f2 in m2:
292 294 actions.append((f2, "m", (f, f, False),
293 295 "remote copied to " + f))
294 296 else:
295 297 actions.append((f2, "m", (f, f, True),
296 298 "remote moved to " + f))
297 299 elif f not in ma:
298 300 # local unknown, remote created: the logic is described by the
299 301 # following table:
300 302 #
301 303 # force branchmerge different | action
302 304 # n * n | get
303 305 # n * y | abort
304 306 # y n * | get
305 307 # y y n | get
306 308 # y y y | merge
307 309 #
308 310 # Checking whether the files are different is expensive, so we
309 311 # don't do that when we can avoid it.
310 312 if force and not branchmerge:
311 313 actions.append((f, "g", (m2.flags(f),), "remote created"))
312 314 else:
313 315 different = _checkunknownfile(repo, wctx, p2, f)
314 316 if force and branchmerge and different:
315 317 actions.append((f, "m", (f, f, False),
316 318 "remote differs from untracked local"))
317 319 elif not force and different:
318 320 aborts.append((f, "ud"))
319 321 else:
320 322 actions.append((f, "g", (m2.flags(f),), "remote created"))
321 323 elif n != ma[f]:
322 324 prompts.append((f, "dc")) # prompt deleted/changed
323 325
324 326 for f, m in sorted(aborts):
325 327 if m == "ud":
326 328 repo.ui.warn(_("%s: untracked file differs\n") % f)
327 329 else: assert False, m
328 330 if aborts:
329 331 raise util.Abort(_("untracked files in working directory differ "
330 332 "from files in requested revision"))
331 333
332 334 for f, m in sorted(prompts):
333 335 if m == "cd":
334 if repo.ui.promptchoice(
336 if acceptremote:
337 actions.append((f, "r", None, "remote delete"))
338 elif repo.ui.promptchoice(
335 339 _("local changed %s which remote deleted\n"
336 340 "use (c)hanged version or (d)elete?") % f,
337 341 (_("&Changed"), _("&Delete")), 0):
338 342 actions.append((f, "r", None, "prompt delete"))
339 343 else:
340 344 actions.append((f, "a", None, "prompt keep"))
341 345 elif m == "dc":
342 if repo.ui.promptchoice(
346 if acceptremote:
347 actions.append((f, "g", (m2.flags(f),), "remote recreating"))
348 elif repo.ui.promptchoice(
343 349 _("remote changed %s which local deleted\n"
344 350 "use (c)hanged version or leave (d)eleted?") % f,
345 351 (_("&Changed"), _("&Deleted")), 0) == 0:
346 352 actions.append((f, "g", (m2.flags(f),), "prompt recreating"))
347 353 else: assert False, m
348 354 return actions
349 355
350 356 def actionkey(a):
351 357 return a[1] == "r" and -1 or 0, a
352 358
353 359 def getremove(repo, mctx, overwrite, args):
354 360 """apply usually-non-interactive updates to the working directory
355 361
356 362 mctx is the context to be merged into the working copy
357 363
358 364 yields tuples for progress updates
359 365 """
360 366 verbose = repo.ui.verbose
361 367 unlink = util.unlinkpath
362 368 wjoin = repo.wjoin
363 369 fctx = mctx.filectx
364 370 wwrite = repo.wwrite
365 371 audit = repo.wopener.audit
366 372 i = 0
367 373 for arg in args:
368 374 f = arg[0]
369 375 if arg[1] == 'r':
370 376 if verbose:
371 377 repo.ui.note(_("removing %s\n") % f)
372 378 audit(f)
373 379 try:
374 380 unlink(wjoin(f), ignoremissing=True)
375 381 except OSError, inst:
376 382 repo.ui.warn(_("update failed to remove %s: %s!\n") %
377 383 (f, inst.strerror))
378 384 else:
379 385 if verbose:
380 386 repo.ui.note(_("getting %s\n") % f)
381 387 wwrite(f, fctx(f).data(), arg[2][0])
382 388 if i == 100:
383 389 yield i, f
384 390 i = 0
385 391 i += 1
386 392 if i > 0:
387 393 yield i, f
388 394
389 395 def applyupdates(repo, actions, wctx, mctx, actx, overwrite):
390 396 """apply the merge action list to the working directory
391 397
392 398 wctx is the working copy context
393 399 mctx is the context to be merged into the working copy
394 400 actx is the context of the common ancestor
395 401
396 402 Return a tuple of counts (updated, merged, removed, unresolved) that
397 403 describes how many files were affected by the update.
398 404 """
399 405
400 406 updated, merged, removed, unresolved = 0, 0, 0, 0
401 407 ms = mergestate(repo)
402 408 ms.reset(wctx.p1().node())
403 409 moves = []
404 410 actions.sort(key=actionkey)
405 411
406 412 # prescan for merges
407 413 for a in actions:
408 414 f, m, args, msg = a
409 415 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
410 416 if m == "m": # merge
411 417 f2, fd, move = args
412 418 if fd == '.hgsubstate': # merged internally
413 419 continue
414 420 repo.ui.debug(" preserving %s for resolve of %s\n" % (f, fd))
415 421 fcl = wctx[f]
416 422 fco = mctx[f2]
417 423 if mctx == actx: # backwards, use working dir parent as ancestor
418 424 if fcl.parents():
419 425 fca = fcl.p1()
420 426 else:
421 427 fca = repo.filectx(f, fileid=nullrev)
422 428 else:
423 429 fca = fcl.ancestor(fco, actx)
424 430 if not fca:
425 431 fca = repo.filectx(f, fileid=nullrev)
426 432 ms.add(fcl, fco, fca, fd)
427 433 if f != fd and move:
428 434 moves.append(f)
429 435
430 436 audit = repo.wopener.audit
431 437
432 438 # remove renamed files after safely stored
433 439 for f in moves:
434 440 if os.path.lexists(repo.wjoin(f)):
435 441 repo.ui.debug("removing %s\n" % f)
436 442 audit(f)
437 443 util.unlinkpath(repo.wjoin(f))
438 444
439 445 numupdates = len(actions)
440 446 workeractions = [a for a in actions if a[1] in 'gr']
441 447 updated = len([a for a in workeractions if a[1] == 'g'])
442 448 removed = len([a for a in workeractions if a[1] == 'r'])
443 449 actions = [a for a in actions if a[1] not in 'gr']
444 450
445 451 hgsub = [a[1] for a in workeractions if a[0] == '.hgsubstate']
446 452 if hgsub and hgsub[0] == 'r':
447 453 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
448 454
449 455 z = 0
450 456 prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite),
451 457 workeractions)
452 458 for i, item in prog:
453 459 z += i
454 460 repo.ui.progress(_('updating'), z, item=item, total=numupdates,
455 461 unit=_('files'))
456 462
457 463 if hgsub and hgsub[0] == 'g':
458 464 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
459 465
460 466 _updating = _('updating')
461 467 _files = _('files')
462 468 progress = repo.ui.progress
463 469
464 470 for i, a in enumerate(actions):
465 471 f, m, args, msg = a
466 472 progress(_updating, z + i + 1, item=f, total=numupdates, unit=_files)
467 473 if m == "m": # merge
468 474 if fd == '.hgsubstate': # subrepo states need updating
469 475 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
470 476 overwrite)
471 477 continue
472 478 f2, fd, move = args
473 479 audit(fd)
474 480 r = ms.resolve(fd, wctx, mctx)
475 481 if r is not None and r > 0:
476 482 unresolved += 1
477 483 else:
478 484 if r is None:
479 485 updated += 1
480 486 else:
481 487 merged += 1
482 488 elif m == "d": # directory rename
483 489 f2, fd, flags = args
484 490 if f:
485 491 repo.ui.note(_("moving %s to %s\n") % (f, fd))
486 492 audit(f)
487 493 repo.wwrite(fd, wctx.filectx(f).data(), flags)
488 494 util.unlinkpath(repo.wjoin(f))
489 495 if f2:
490 496 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
491 497 repo.wwrite(fd, mctx.filectx(f2).data(), flags)
492 498 updated += 1
493 499 elif m == "dr": # divergent renames
494 500 fl, = args
495 501 repo.ui.warn(_("note: possible conflict - %s was renamed "
496 502 "multiple times to:\n") % f)
497 503 for nf in fl:
498 504 repo.ui.warn(" %s\n" % nf)
499 505 elif m == "rd": # rename and delete
500 506 fl, = args
501 507 repo.ui.warn(_("note: possible conflict - %s was deleted "
502 508 "and renamed to:\n") % f)
503 509 for nf in fl:
504 510 repo.ui.warn(" %s\n" % nf)
505 511 elif m == "e": # exec
506 512 flags, = args
507 513 audit(f)
508 514 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
509 515 updated += 1
510 516 ms.commit()
511 517 progress(_updating, None, total=numupdates, unit=_files)
512 518
513 519 return updated, merged, removed, unresolved
514 520
515 def calculateupdates(repo, tctx, mctx, ancestor, branchmerge, force, partial):
521 def calculateupdates(repo, tctx, mctx, ancestor, branchmerge, force, partial,
522 acceptremote=False):
516 523 "Calculate the actions needed to merge mctx into tctx"
517 524 actions = []
518 525 folding = not util.checkcase(repo.path)
519 526 if folding:
520 527 # collision check is not needed for clean update
521 528 if (not branchmerge and
522 529 (force or not tctx.dirty(missing=True, branch=False))):
523 530 _checkcollision(mctx, None)
524 531 else:
525 532 _checkcollision(mctx, (tctx, ancestor))
526 533 actions += manifestmerge(repo, tctx, mctx,
527 534 ancestor,
528 535 branchmerge, force,
529 partial)
536 partial, acceptremote)
530 537 if tctx.rev() is None:
531 538 actions += _forgetremoved(tctx, mctx, branchmerge)
532 539 return actions
533 540
534 541 def recordupdates(repo, actions, branchmerge):
535 542 "record merge actions to the dirstate"
536 543
537 544 for a in actions:
538 545 f, m, args, msg = a
539 546 if m == "r": # remove
540 547 if branchmerge:
541 548 repo.dirstate.remove(f)
542 549 else:
543 550 repo.dirstate.drop(f)
544 551 elif m == "a": # re-add
545 552 if not branchmerge:
546 553 repo.dirstate.add(f)
547 554 elif m == "f": # forget
548 555 repo.dirstate.drop(f)
549 556 elif m == "e": # exec change
550 557 repo.dirstate.normallookup(f)
551 558 elif m == "g": # get
552 559 if branchmerge:
553 560 repo.dirstate.otherparent(f)
554 561 else:
555 562 repo.dirstate.normal(f)
556 563 elif m == "m": # merge
557 564 f2, fd, move = args
558 565 if branchmerge:
559 566 # We've done a branch merge, mark this file as merged
560 567 # so that we properly record the merger later
561 568 repo.dirstate.merge(fd)
562 569 if f != f2: # copy/rename
563 570 if move:
564 571 repo.dirstate.remove(f)
565 572 if f != fd:
566 573 repo.dirstate.copy(f, fd)
567 574 else:
568 575 repo.dirstate.copy(f2, fd)
569 576 else:
570 577 # We've update-merged a locally modified file, so
571 578 # we set the dirstate to emulate a normal checkout
572 579 # of that file some time in the past. Thus our
573 580 # merge will appear as a normal local file
574 581 # modification.
575 582 if f2 == fd: # file not locally copied/moved
576 583 repo.dirstate.normallookup(fd)
577 584 if move:
578 585 repo.dirstate.drop(f)
579 586 elif m == "d": # directory rename
580 587 f2, fd, flag = args
581 588 if not f2 and f not in repo.dirstate:
582 589 # untracked file moved
583 590 continue
584 591 if branchmerge:
585 592 repo.dirstate.add(fd)
586 593 if f:
587 594 repo.dirstate.remove(f)
588 595 repo.dirstate.copy(f, fd)
589 596 if f2:
590 597 repo.dirstate.copy(f2, fd)
591 598 else:
592 599 repo.dirstate.normal(fd)
593 600 if f:
594 601 repo.dirstate.drop(f)
595 602
596 603 def update(repo, node, branchmerge, force, partial, ancestor=None,
597 604 mergeancestor=False):
598 605 """
599 606 Perform a merge between the working directory and the given node
600 607
601 608 node = the node to update to, or None if unspecified
602 609 branchmerge = whether to merge between branches
603 610 force = whether to force branch merging or file overwriting
604 611 partial = a function to filter file lists (dirstate not updated)
605 mergeancestor = if false, merging with an ancestor (fast-forward)
606 is only allowed between different named branches. This flag
607 is used by rebase extension as a temporary fix and should be
608 avoided in general.
612 mergeancestor = whether it is merging with an ancestor. If true,
613 we should accept the incoming changes for any prompts that occur.
614 If false, merging with an ancestor (fast-forward) is only allowed
615 between different named branches. This flag is used by rebase extension
616 as a temporary fix and should be avoided in general.
609 617
610 618 The table below shows all the behaviors of the update command
611 619 given the -c and -C or no options, whether the working directory
612 620 is dirty, whether a revision is specified, and the relationship of
613 621 the parent rev to the target rev (linear, on the same named
614 622 branch, or on another named branch).
615 623
616 624 This logic is tested by test-update-branches.t.
617 625
618 626 -c -C dirty rev | linear same cross
619 627 n n n n | ok (1) x
620 628 n n n y | ok ok ok
621 629 n n y * | merge (2) (2)
622 630 n y * * | --- discard ---
623 631 y n y * | --- (3) ---
624 632 y n n * | --- ok ---
625 633 y y * * | --- (4) ---
626 634
627 635 x = can't happen
628 636 * = don't-care
629 637 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
630 638 2 = abort: crosses branches (use 'hg merge' to merge or
631 639 use 'hg update -C' to discard changes)
632 640 3 = abort: uncommitted local changes
633 641 4 = incompatible options (checked in commands.py)
634 642
635 643 Return the same tuple as applyupdates().
636 644 """
637 645
638 646 onode = node
639 647 wlock = repo.wlock()
640 648 try:
641 649 wc = repo[None]
642 650 if node is None:
643 651 # tip of current branch
644 652 try:
645 653 node = repo.branchtip(wc.branch())
646 654 except error.RepoLookupError:
647 655 if wc.branch() == "default": # no default branch!
648 656 node = repo.lookup("tip") # update to tip
649 657 else:
650 658 raise util.Abort(_("branch %s not found") % wc.branch())
651 659 overwrite = force and not branchmerge
652 660 pl = wc.parents()
653 661 p1, p2 = pl[0], repo[node]
654 662 if ancestor:
655 663 pa = repo[ancestor]
656 664 else:
657 665 pa = p1.ancestor(p2)
658 666
659 667 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
660 668
661 669 ### check phase
662 670 if not overwrite and len(pl) > 1:
663 671 raise util.Abort(_("outstanding uncommitted merges"))
664 672 if branchmerge:
665 673 if pa == p2:
666 674 raise util.Abort(_("merging with a working directory ancestor"
667 675 " has no effect"))
668 676 elif pa == p1:
669 677 if not mergeancestor and p1.branch() == p2.branch():
670 678 raise util.Abort(_("nothing to merge"),
671 679 hint=_("use 'hg update' "
672 680 "or check 'hg heads'"))
673 681 if not force and (wc.files() or wc.deleted()):
674 682 raise util.Abort(_("outstanding uncommitted changes"),
675 683 hint=_("use 'hg status' to list changes"))
676 684 for s in sorted(wc.substate):
677 685 if wc.sub(s).dirty():
678 686 raise util.Abort(_("outstanding uncommitted changes in "
679 687 "subrepository '%s'") % s)
680 688
681 689 elif not overwrite:
682 690 if pa == p1 or pa == p2: # linear
683 691 pass # all good
684 692 elif wc.dirty(missing=True):
685 693 raise util.Abort(_("crosses branches (merge branches or use"
686 694 " --clean to discard changes)"))
687 695 elif onode is None:
688 696 raise util.Abort(_("crosses branches (merge branches or update"
689 697 " --check to force update)"))
690 698 else:
691 699 # Allow jumping branches if clean and specific rev given
692 700 pa = p1
693 701
694 702 ### calculate phase
695 703 actions = calculateupdates(repo, wc, p2, pa,
696 branchmerge, force, partial)
704 branchmerge, force, partial, mergeancestor)
697 705
698 706 ### apply phase
699 707 if not branchmerge: # just jump to the new rev
700 708 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
701 709 if not partial:
702 710 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
703 711
704 712 stats = applyupdates(repo, actions, wc, p2, pa, overwrite)
705 713
706 714 if not partial:
707 715 repo.setparents(fp1, fp2)
708 716 recordupdates(repo, actions, branchmerge)
709 717 if not branchmerge:
710 718 repo.dirstate.setbranch(p2.branch())
711 719 finally:
712 720 wlock.release()
713 721
714 722 if not partial:
715 723 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
716 724 return stats
@@ -1,724 +1,748
1 1 $ cat >> $HGRCPATH <<EOF
2 2 > [extensions]
3 3 > graphlog=
4 4 > rebase=
5 5 > mq=
6 6 >
7 7 > [phases]
8 8 > publish=False
9 9 >
10 10 > [alias]
11 11 > tglog = log -G --template "{rev}: '{desc}' {branches}\n"
12 12 > tglogp = log -G --template "{rev}:{phase} '{desc}' {branches}\n"
13 13 > EOF
14 14
15 15 Create repo a:
16 16
17 17 $ hg init a
18 18 $ cd a
19 19 $ hg unbundle "$TESTDIR/bundles/rebase.hg"
20 20 adding changesets
21 21 adding manifests
22 22 adding file changes
23 23 added 8 changesets with 7 changes to 7 files (+2 heads)
24 24 (run 'hg heads' to see heads, 'hg merge' to merge)
25 25 $ hg up tip
26 26 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
27 27
28 28 $ hg tglog
29 29 @ 7: 'H'
30 30 |
31 31 | o 6: 'G'
32 32 |/|
33 33 o | 5: 'F'
34 34 | |
35 35 | o 4: 'E'
36 36 |/
37 37 | o 3: 'D'
38 38 | |
39 39 | o 2: 'C'
40 40 | |
41 41 | o 1: 'B'
42 42 |/
43 43 o 0: 'A'
44 44
45 45 $ cd ..
46 46
47 47
48 48 Rebasing B onto H and collapsing changesets with different phases:
49 49
50 50
51 51 $ hg clone -q -u 3 a a1
52 52 $ cd a1
53 53
54 54 $ hg phase --force --secret 3
55 55
56 56 $ hg rebase --collapse --keepbranches
57 57 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/*-backup.hg (glob)
58 58
59 59 $ hg tglogp
60 60 @ 5:secret 'Collapsed revision
61 61 | * B
62 62 | * C
63 63 | * D'
64 64 o 4:draft 'H'
65 65 |
66 66 | o 3:draft 'G'
67 67 |/|
68 68 o | 2:draft 'F'
69 69 | |
70 70 | o 1:draft 'E'
71 71 |/
72 72 o 0:draft 'A'
73 73
74 74 $ hg manifest
75 75 A
76 76 B
77 77 C
78 78 D
79 79 F
80 80 H
81 81
82 82 $ cd ..
83 83
84 84
85 85 Rebasing E onto H:
86 86
87 87 $ hg clone -q -u . a a2
88 88 $ cd a2
89 89
90 90 $ hg phase --force --secret 6
91 91 $ hg rebase --source 4 --collapse
92 92 saved backup bundle to $TESTTMP/a2/.hg/strip-backup/*-backup.hg (glob)
93 93
94 94 $ hg tglog
95 95 @ 6: 'Collapsed revision
96 96 | * E
97 97 | * G'
98 98 o 5: 'H'
99 99 |
100 100 o 4: 'F'
101 101 |
102 102 | o 3: 'D'
103 103 | |
104 104 | o 2: 'C'
105 105 | |
106 106 | o 1: 'B'
107 107 |/
108 108 o 0: 'A'
109 109
110 110 $ hg manifest
111 111 A
112 112 E
113 113 F
114 114 H
115 115
116 116 $ cd ..
117 117
118 118 Rebasing G onto H with custom message:
119 119
120 120 $ hg clone -q -u . a a3
121 121 $ cd a3
122 122
123 123 $ hg rebase --base 6 -m 'custom message'
124 124 abort: message can only be specified with collapse
125 125 [255]
126 126
127 127 $ hg rebase --source 4 --collapse -m 'custom message'
128 128 saved backup bundle to $TESTTMP/a3/.hg/strip-backup/*-backup.hg (glob)
129 129
130 130 $ hg tglog
131 131 @ 6: 'custom message'
132 132 |
133 133 o 5: 'H'
134 134 |
135 135 o 4: 'F'
136 136 |
137 137 | o 3: 'D'
138 138 | |
139 139 | o 2: 'C'
140 140 | |
141 141 | o 1: 'B'
142 142 |/
143 143 o 0: 'A'
144 144
145 145 $ hg manifest
146 146 A
147 147 E
148 148 F
149 149 H
150 150
151 151 $ cd ..
152 152
153 153 Create repo b:
154 154
155 155 $ hg init b
156 156 $ cd b
157 157
158 158 $ echo A > A
159 159 $ hg ci -Am A
160 160 adding A
161 161 $ echo B > B
162 162 $ hg ci -Am B
163 163 adding B
164 164
165 165 $ hg up -q 0
166 166
167 167 $ echo C > C
168 168 $ hg ci -Am C
169 169 adding C
170 170 created new head
171 171
172 172 $ hg merge
173 173 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
174 174 (branch merge, don't forget to commit)
175 175
176 176 $ echo D > D
177 177 $ hg ci -Am D
178 178 adding D
179 179
180 180 $ hg up -q 1
181 181
182 182 $ echo E > E
183 183 $ hg ci -Am E
184 184 adding E
185 185 created new head
186 186
187 187 $ echo F > F
188 188 $ hg ci -Am F
189 189 adding F
190 190
191 191 $ hg merge
192 192 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
193 193 (branch merge, don't forget to commit)
194 194 $ hg ci -m G
195 195
196 196 $ hg up -q 0
197 197
198 198 $ echo H > H
199 199 $ hg ci -Am H
200 200 adding H
201 201 created new head
202 202
203 203 $ hg tglog
204 204 @ 7: 'H'
205 205 |
206 206 | o 6: 'G'
207 207 | |\
208 208 | | o 5: 'F'
209 209 | | |
210 210 | | o 4: 'E'
211 211 | | |
212 212 | o | 3: 'D'
213 213 | |\|
214 214 | o | 2: 'C'
215 215 |/ /
216 216 | o 1: 'B'
217 217 |/
218 218 o 0: 'A'
219 219
220 220 $ cd ..
221 221
222 222
223 223 Rebase and collapse - more than one external (fail):
224 224
225 225 $ hg clone -q -u . b b1
226 226 $ cd b1
227 227
228 228 $ hg rebase -s 2 --collapse
229 229 abort: unable to collapse, there is more than one external parent
230 230 [255]
231 231
232 232 Rebase and collapse - E onto H:
233 233
234 234 $ hg rebase -s 4 --collapse # root (4) is not a merge
235 235 saved backup bundle to $TESTTMP/b1/.hg/strip-backup/*-backup.hg (glob)
236 236
237 237 $ hg tglog
238 238 @ 5: 'Collapsed revision
239 239 |\ * E
240 240 | | * F
241 241 | | * G'
242 242 | o 4: 'H'
243 243 | |
244 244 o | 3: 'D'
245 245 |\ \
246 246 | o | 2: 'C'
247 247 | |/
248 248 o / 1: 'B'
249 249 |/
250 250 o 0: 'A'
251 251
252 252 $ hg manifest
253 253 A
254 254 C
255 255 D
256 256 E
257 257 F
258 258 H
259 259
260 260 $ cd ..
261 261
262 262
263 263
264 264
265 265 Test that branchheads cache is updated correctly when doing a strip in which
266 266 the parent of the ancestor node to be stripped does not become a head and also,
267 267 the parent of a node that is a child of the node stripped becomes a head (node
268 268 3). The code is now much simpler and we could just test a simpler scenario
269 269 We keep it the test this way in case new complexity is injected.
270 270
271 271 $ hg clone -q -u . b b2
272 272 $ cd b2
273 273
274 274 $ hg heads --template="{rev}:{node} {branch}\n"
275 275 7:c65502d4178782309ce0574c5ae6ee9485a9bafa default
276 276 6:c772a8b2dc17629cec88a19d09c926c4814b12c7 default
277 277
278 278 $ cat $TESTTMP/b2/.hg/cache/branchheads-served
279 279 c65502d4178782309ce0574c5ae6ee9485a9bafa 7
280 280 c772a8b2dc17629cec88a19d09c926c4814b12c7 default
281 281 c65502d4178782309ce0574c5ae6ee9485a9bafa default
282 282
283 283 $ hg strip 4
284 284 saved backup bundle to $TESTTMP/b2/.hg/strip-backup/8a5212ebc852-backup.hg (glob)
285 285
286 286 $ cat $TESTTMP/b2/.hg/cache/branchheads-served
287 287 c65502d4178782309ce0574c5ae6ee9485a9bafa 4
288 288 2870ad076e541e714f3c2bc32826b5c6a6e5b040 default
289 289 c65502d4178782309ce0574c5ae6ee9485a9bafa default
290 290
291 291 $ hg heads --template="{rev}:{node} {branch}\n"
292 292 4:c65502d4178782309ce0574c5ae6ee9485a9bafa default
293 293 3:2870ad076e541e714f3c2bc32826b5c6a6e5b040 default
294 294
295 295 $ cd ..
296 296
297 297
298 298
299 299
300 300
301 301
302 302 Create repo c:
303 303
304 304 $ hg init c
305 305 $ cd c
306 306
307 307 $ echo A > A
308 308 $ hg ci -Am A
309 309 adding A
310 310 $ echo B > B
311 311 $ hg ci -Am B
312 312 adding B
313 313
314 314 $ hg up -q 0
315 315
316 316 $ echo C > C
317 317 $ hg ci -Am C
318 318 adding C
319 319 created new head
320 320
321 321 $ hg merge
322 322 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
323 323 (branch merge, don't forget to commit)
324 324
325 325 $ echo D > D
326 326 $ hg ci -Am D
327 327 adding D
328 328
329 329 $ hg up -q 1
330 330
331 331 $ echo E > E
332 332 $ hg ci -Am E
333 333 adding E
334 334 created new head
335 335 $ echo F > E
336 336 $ hg ci -m 'F'
337 337
338 338 $ echo G > G
339 339 $ hg ci -Am G
340 340 adding G
341 341
342 342 $ hg merge
343 343 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
344 344 (branch merge, don't forget to commit)
345 345
346 346 $ hg ci -m H
347 347
348 348 $ hg up -q 0
349 349
350 350 $ echo I > I
351 351 $ hg ci -Am I
352 352 adding I
353 353 created new head
354 354
355 355 $ hg tglog
356 356 @ 8: 'I'
357 357 |
358 358 | o 7: 'H'
359 359 | |\
360 360 | | o 6: 'G'
361 361 | | |
362 362 | | o 5: 'F'
363 363 | | |
364 364 | | o 4: 'E'
365 365 | | |
366 366 | o | 3: 'D'
367 367 | |\|
368 368 | o | 2: 'C'
369 369 |/ /
370 370 | o 1: 'B'
371 371 |/
372 372 o 0: 'A'
373 373
374 374 $ cd ..
375 375
376 376
377 377 Rebase and collapse - E onto I:
378 378
379 379 $ hg clone -q -u . c c1
380 380 $ cd c1
381 381
382 382 $ hg rebase -s 4 --collapse # root (4) is not a merge
383 383 merging E
384 384 saved backup bundle to $TESTTMP/c1/.hg/strip-backup/*-backup.hg (glob)
385 385
386 386 $ hg tglog
387 387 @ 5: 'Collapsed revision
388 388 |\ * E
389 389 | | * F
390 390 | | * G
391 391 | | * H'
392 392 | o 4: 'I'
393 393 | |
394 394 o | 3: 'D'
395 395 |\ \
396 396 | o | 2: 'C'
397 397 | |/
398 398 o / 1: 'B'
399 399 |/
400 400 o 0: 'A'
401 401
402 402 $ hg manifest
403 403 A
404 404 C
405 405 D
406 406 E
407 407 G
408 408 I
409 409
410 410 $ cat E
411 411 F
412 412
413 413 $ cd ..
414 414
415 415
416 416 Create repo d:
417 417
418 418 $ hg init d
419 419 $ cd d
420 420
421 421 $ echo A > A
422 422 $ hg ci -Am A
423 423 adding A
424 424 $ echo B > B
425 425 $ hg ci -Am B
426 426 adding B
427 427 $ echo C > C
428 428 $ hg ci -Am C
429 429 adding C
430 430
431 431 $ hg up -q 1
432 432
433 433 $ echo D > D
434 434 $ hg ci -Am D
435 435 adding D
436 436 created new head
437 437 $ hg merge
438 438 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
439 439 (branch merge, don't forget to commit)
440 440
441 441 $ hg ci -m E
442 442
443 443 $ hg up -q 0
444 444
445 445 $ echo F > F
446 446 $ hg ci -Am F
447 447 adding F
448 448 created new head
449 449
450 450 $ hg tglog
451 451 @ 5: 'F'
452 452 |
453 453 | o 4: 'E'
454 454 | |\
455 455 | | o 3: 'D'
456 456 | | |
457 457 | o | 2: 'C'
458 458 | |/
459 459 | o 1: 'B'
460 460 |/
461 461 o 0: 'A'
462 462
463 463 $ cd ..
464 464
465 465
466 466 Rebase and collapse - B onto F:
467 467
468 468 $ hg clone -q -u . d d1
469 469 $ cd d1
470 470
471 471 $ hg rebase -s 1 --collapse
472 472 saved backup bundle to $TESTTMP/d1/.hg/strip-backup/*-backup.hg (glob)
473 473
474 474 $ hg tglog
475 475 @ 2: 'Collapsed revision
476 476 | * B
477 477 | * C
478 478 | * D
479 479 | * E'
480 480 o 1: 'F'
481 481 |
482 482 o 0: 'A'
483 483
484 484 $ hg manifest
485 485 A
486 486 B
487 487 C
488 488 D
489 489 F
490 490
491 491 Interactions between collapse and keepbranches
492 492 $ cd ..
493 493 $ hg init e
494 494 $ cd e
495 495 $ echo 'a' > a
496 496 $ hg ci -Am 'A'
497 497 adding a
498 498
499 499 $ hg branch 'one'
500 500 marked working directory as branch one
501 501 (branches are permanent and global, did you want a bookmark?)
502 502 $ echo 'b' > b
503 503 $ hg ci -Am 'B'
504 504 adding b
505 505
506 506 $ hg branch 'two'
507 507 marked working directory as branch two
508 508 (branches are permanent and global, did you want a bookmark?)
509 509 $ echo 'c' > c
510 510 $ hg ci -Am 'C'
511 511 adding c
512 512
513 513 $ hg up -q 0
514 514 $ echo 'd' > d
515 515 $ hg ci -Am 'D'
516 516 adding d
517 517
518 518 $ hg tglog
519 519 @ 3: 'D'
520 520 |
521 521 | o 2: 'C' two
522 522 | |
523 523 | o 1: 'B' one
524 524 |/
525 525 o 0: 'A'
526 526
527 527 $ hg rebase --keepbranches --collapse -s 1 -d 3
528 528 abort: cannot collapse multiple named branches
529 529 [255]
530 530
531 531 $ repeatchange() {
532 532 > hg checkout $1
533 533 > hg cp d z
534 534 > echo blah >> z
535 535 > hg commit -Am "$2" --user "$3"
536 536 > }
537 537 $ repeatchange 3 "E" "user1"
538 538 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
539 539 $ repeatchange 3 "E" "user2"
540 540 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
541 541 created new head
542 542 $ hg tglog
543 543 @ 5: 'E'
544 544 |
545 545 | o 4: 'E'
546 546 |/
547 547 o 3: 'D'
548 548 |
549 549 | o 2: 'C' two
550 550 | |
551 551 | o 1: 'B' one
552 552 |/
553 553 o 0: 'A'
554 554
555 555 $ hg rebase -s 5 -d 4
556 556 saved backup bundle to $TESTTMP/e/.hg/strip-backup/*-backup.hg (glob)
557 557 $ hg tglog
558 558 @ 4: 'E'
559 559 |
560 560 o 3: 'D'
561 561 |
562 562 | o 2: 'C' two
563 563 | |
564 564 | o 1: 'B' one
565 565 |/
566 566 o 0: 'A'
567 567
568 568 $ hg export tip
569 569 # HG changeset patch
570 570 # User user1
571 571 # Date 0 0
572 572 # Thu Jan 01 00:00:00 1970 +0000
573 573 # Node ID f338eb3c2c7cc5b5915676a2376ba7ac558c5213
574 574 # Parent 41acb9dca9eb976e84cd21fcb756b4afa5a35c09
575 575 E
576 576
577 577 diff -r 41acb9dca9eb -r f338eb3c2c7c z
578 578 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
579 579 +++ b/z Thu Jan 01 00:00:00 1970 +0000
580 580 @@ -0,0 +1,2 @@
581 581 +d
582 582 +blah
583 583
584 584 $ cd ..
585 585
586 586 Rebase, collapse and copies
587 587
588 588 $ hg init copies
589 589 $ cd copies
590 590 $ hg unbundle "$TESTDIR/bundles/renames.hg"
591 591 adding changesets
592 592 adding manifests
593 593 adding file changes
594 594 added 4 changesets with 11 changes to 7 files (+1 heads)
595 595 (run 'hg heads' to see heads, 'hg merge' to merge)
596 596 $ hg up -q tip
597 597 $ hg tglog
598 598 @ 3: 'move2'
599 599 |
600 600 o 2: 'move1'
601 601 |
602 602 | o 1: 'change'
603 603 |/
604 604 o 0: 'add'
605 605
606 606 $ hg rebase --collapse -d 1
607 607 merging a and d to d
608 608 merging b and e to e
609 609 merging c and f to f
610 610 merging e and g to g
611 611 merging f and c to c
612 612 saved backup bundle to $TESTTMP/copies/.hg/strip-backup/*-backup.hg (glob)
613 613 $ hg st
614 614 $ hg st --copies --change .
615 615 A d
616 616 a
617 617 A g
618 618 b
619 619 R b
620 620 $ cat c
621 621 c
622 622 c
623 623 $ cat d
624 624 a
625 625 a
626 626 $ cat g
627 627 b
628 628 b
629 629 $ hg log -r . --template "{file_copies}\n"
630 630 d (a)g (b)
631 631
632 632 Test collapsing a middle revision in-place
633 633
634 634 $ hg tglog
635 635 @ 2: 'Collapsed revision
636 636 | * move1
637 637 | * move2'
638 638 o 1: 'change'
639 639 |
640 640 o 0: 'add'
641 641
642 642 $ hg rebase --collapse -r 1 -d 0
643 643 abort: can't remove original changesets with unrebased descendants
644 644 (use --keep to keep original changesets)
645 645 [255]
646 646
647 647 Test collapsing in place
648 648
649 649 $ hg rebase --collapse -b . -d 0
650 650 saved backup bundle to $TESTTMP/copies/.hg/strip-backup/*-backup.hg (glob)
651 651 $ hg st --change . --copies
652 652 M a
653 653 M c
654 654 A d
655 655 a
656 656 A g
657 657 b
658 658 R b
659 659 $ cat a
660 660 a
661 661 a
662 662 $ cat c
663 663 c
664 664 c
665 665 $ cat d
666 666 a
667 667 a
668 668 $ cat g
669 669 b
670 670 b
671 671 $ cd ..
672 672
673 673
674 674 Test stripping a revision with another child
675 675
676 676 $ hg init f
677 677 $ cd f
678 678
679 679 $ echo A > A
680 680 $ hg ci -Am A
681 681 adding A
682 682 $ echo B > B
683 683 $ hg ci -Am B
684 684 adding B
685 685
686 686 $ hg up -q 0
687 687
688 688 $ echo C > C
689 689 $ hg ci -Am C
690 690 adding C
691 691 created new head
692 692
693 693 $ hg tglog
694 694 @ 2: 'C'
695 695 |
696 696 | o 1: 'B'
697 697 |/
698 698 o 0: 'A'
699 699
700 700
701 701
702 702 $ hg heads --template="{rev}:{node} {branch}: {desc}\n"
703 703 2:c5cefa58fd557f84b72b87f970135984337acbc5 default: C
704 704 1:27547f69f25460a52fff66ad004e58da7ad3fb56 default: B
705 705
706 706 $ hg strip 2
707 707 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
708 708 saved backup bundle to $TESTTMP/f/.hg/strip-backup/*-backup.hg (glob)
709 709
710 710 $ hg tglog
711 711 o 1: 'B'
712 712 |
713 713 @ 0: 'A'
714 714
715 715
716 716
717 717 $ hg heads --template="{rev}:{node} {branch}: {desc}\n"
718 718 1:27547f69f25460a52fff66ad004e58da7ad3fb56 default: B
719 719
720 720 $ cd ..
721 721
722 Test collapsing changes that add then remove a file
722 723
724 $ hg init collapseaddremove
725 $ cd collapseaddremove
723 726
727 $ touch base
728 $ hg commit -Am base
729 adding base
730 $ touch a
731 $ hg commit -Am a
732 adding a
733 $ hg rm a
734 $ touch b
735 $ hg commit -Am b
736 adding b
737 $ hg rebase -d 0 -r "1::2" --collapse -m collapsed
738 saved backup bundle to $TESTTMP/collapseaddremove/.hg/strip-backup/*-backup.hg (glob)
739 $ hg tglog
740 @ 1: 'collapsed'
741 |
742 o 0: 'base'
724 743
744 $ hg manifest
745 b
746 base
747
748 $ cd ..
@@ -1,400 +1,398
1 1 $ cat >> $HGRCPATH <<EOF
2 2 > [extensions]
3 3 > graphlog=
4 4 > rebase=
5 5 >
6 6 > [phases]
7 7 > publish=False
8 8 >
9 9 > [alias]
10 10 > tglog = log -G --template "{rev}: '{desc}' {branches}\n"
11 11 > EOF
12 12
13 13
14 14 $ hg init a
15 15 $ cd a
16 16 $ hg unbundle "$TESTDIR/bundles/rebase.hg"
17 17 adding changesets
18 18 adding manifests
19 19 adding file changes
20 20 added 8 changesets with 7 changes to 7 files (+2 heads)
21 21 (run 'hg heads' to see heads, 'hg merge' to merge)
22 22 $ hg up tip
23 23 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
24 24
25 25 $ cd ..
26 26
27 27
28 28 Rebasing D onto H detaching from C:
29 29
30 30 $ hg clone -q -u . a a1
31 31 $ cd a1
32 32
33 33 $ hg tglog
34 34 @ 7: 'H'
35 35 |
36 36 | o 6: 'G'
37 37 |/|
38 38 o | 5: 'F'
39 39 | |
40 40 | o 4: 'E'
41 41 |/
42 42 | o 3: 'D'
43 43 | |
44 44 | o 2: 'C'
45 45 | |
46 46 | o 1: 'B'
47 47 |/
48 48 o 0: 'A'
49 49
50 50 $ hg phase --force --secret 3
51 51 $ hg rebase -s 3 -d 7
52 52 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/*-backup.hg (glob)
53 53
54 54 $ hg log -G --template "{rev}:{phase} '{desc}' {branches}\n"
55 55 @ 7:secret 'D'
56 56 |
57 57 o 6:draft 'H'
58 58 |
59 59 | o 5:draft 'G'
60 60 |/|
61 61 o | 4:draft 'F'
62 62 | |
63 63 | o 3:draft 'E'
64 64 |/
65 65 | o 2:draft 'C'
66 66 | |
67 67 | o 1:draft 'B'
68 68 |/
69 69 o 0:draft 'A'
70 70
71 71 $ hg manifest
72 72 A
73 73 D
74 74 F
75 75 H
76 76
77 77 $ cd ..
78 78
79 79
80 80 Rebasing C onto H detaching from B:
81 81
82 82 $ hg clone -q -u . a a2
83 83 $ cd a2
84 84
85 85 $ hg tglog
86 86 @ 7: 'H'
87 87 |
88 88 | o 6: 'G'
89 89 |/|
90 90 o | 5: 'F'
91 91 | |
92 92 | o 4: 'E'
93 93 |/
94 94 | o 3: 'D'
95 95 | |
96 96 | o 2: 'C'
97 97 | |
98 98 | o 1: 'B'
99 99 |/
100 100 o 0: 'A'
101 101
102 102 $ hg rebase -s 2 -d 7
103 103 saved backup bundle to $TESTTMP/a2/.hg/strip-backup/*-backup.hg (glob)
104 104
105 105 $ hg tglog
106 106 @ 7: 'D'
107 107 |
108 108 o 6: 'C'
109 109 |
110 110 o 5: 'H'
111 111 |
112 112 | o 4: 'G'
113 113 |/|
114 114 o | 3: 'F'
115 115 | |
116 116 | o 2: 'E'
117 117 |/
118 118 | o 1: 'B'
119 119 |/
120 120 o 0: 'A'
121 121
122 122 $ hg manifest
123 123 A
124 124 C
125 125 D
126 126 F
127 127 H
128 128
129 129 $ cd ..
130 130
131 131
132 132 Rebasing B onto H using detach (same as not using it):
133 133
134 134 $ hg clone -q -u . a a3
135 135 $ cd a3
136 136
137 137 $ hg tglog
138 138 @ 7: 'H'
139 139 |
140 140 | o 6: 'G'
141 141 |/|
142 142 o | 5: 'F'
143 143 | |
144 144 | o 4: 'E'
145 145 |/
146 146 | o 3: 'D'
147 147 | |
148 148 | o 2: 'C'
149 149 | |
150 150 | o 1: 'B'
151 151 |/
152 152 o 0: 'A'
153 153
154 154 $ hg rebase -s 1 -d 7
155 155 saved backup bundle to $TESTTMP/a3/.hg/strip-backup/*-backup.hg (glob)
156 156
157 157 $ hg tglog
158 158 @ 7: 'D'
159 159 |
160 160 o 6: 'C'
161 161 |
162 162 o 5: 'B'
163 163 |
164 164 o 4: 'H'
165 165 |
166 166 | o 3: 'G'
167 167 |/|
168 168 o | 2: 'F'
169 169 | |
170 170 | o 1: 'E'
171 171 |/
172 172 o 0: 'A'
173 173
174 174 $ hg manifest
175 175 A
176 176 B
177 177 C
178 178 D
179 179 F
180 180 H
181 181
182 182 $ cd ..
183 183
184 184
185 185 Rebasing C onto H detaching from B and collapsing:
186 186
187 187 $ hg clone -q -u . a a4
188 188 $ cd a4
189 189 $ hg phase --force --secret 3
190 190
191 191 $ hg tglog
192 192 @ 7: 'H'
193 193 |
194 194 | o 6: 'G'
195 195 |/|
196 196 o | 5: 'F'
197 197 | |
198 198 | o 4: 'E'
199 199 |/
200 200 | o 3: 'D'
201 201 | |
202 202 | o 2: 'C'
203 203 | |
204 204 | o 1: 'B'
205 205 |/
206 206 o 0: 'A'
207 207
208 208 $ hg rebase --collapse -s 2 -d 7
209 209 saved backup bundle to $TESTTMP/a4/.hg/strip-backup/*-backup.hg (glob)
210 210
211 211 $ hg log -G --template "{rev}:{phase} '{desc}' {branches}\n"
212 212 @ 6:secret 'Collapsed revision
213 213 | * C
214 214 | * D'
215 215 o 5:draft 'H'
216 216 |
217 217 | o 4:draft 'G'
218 218 |/|
219 219 o | 3:draft 'F'
220 220 | |
221 221 | o 2:draft 'E'
222 222 |/
223 223 | o 1:draft 'B'
224 224 |/
225 225 o 0:draft 'A'
226 226
227 227 $ hg manifest
228 228 A
229 229 C
230 230 D
231 231 F
232 232 H
233 233
234 234 $ cd ..
235 235
236 236 Rebasing across null as ancestor
237 237 $ hg clone -q -U a a5
238 238
239 239 $ cd a5
240 240
241 241 $ echo x > x
242 242
243 243 $ hg add x
244 244
245 245 $ hg ci -m "extra branch"
246 246 created new head
247 247
248 248 $ hg tglog
249 249 @ 8: 'extra branch'
250 250
251 251 o 7: 'H'
252 252 |
253 253 | o 6: 'G'
254 254 |/|
255 255 o | 5: 'F'
256 256 | |
257 257 | o 4: 'E'
258 258 |/
259 259 | o 3: 'D'
260 260 | |
261 261 | o 2: 'C'
262 262 | |
263 263 | o 1: 'B'
264 264 |/
265 265 o 0: 'A'
266 266
267 267 $ hg rebase -s 1 -d tip
268 268 saved backup bundle to $TESTTMP/a5/.hg/strip-backup/*-backup.hg (glob)
269 269
270 270 $ hg tglog
271 271 @ 8: 'D'
272 272 |
273 273 o 7: 'C'
274 274 |
275 275 o 6: 'B'
276 276 |
277 277 o 5: 'extra branch'
278 278
279 279 o 4: 'H'
280 280 |
281 281 | o 3: 'G'
282 282 |/|
283 283 o | 2: 'F'
284 284 | |
285 285 | o 1: 'E'
286 286 |/
287 287 o 0: 'A'
288 288
289 289
290 290 $ hg rebase -d 5 -s 7
291 291 saved backup bundle to $TESTTMP/a5/.hg/strip-backup/13547172c9c0-backup.hg (glob)
292 292 $ hg tglog
293 293 @ 8: 'D'
294 294 |
295 295 o 7: 'C'
296 296 |
297 297 | o 6: 'B'
298 298 |/
299 299 o 5: 'extra branch'
300 300
301 301 o 4: 'H'
302 302 |
303 303 | o 3: 'G'
304 304 |/|
305 305 o | 2: 'F'
306 306 | |
307 307 | o 1: 'E'
308 308 |/
309 309 o 0: 'A'
310 310
311 311 $ cd ..
312 312
313 313 Verify that target is not selected as external rev (issue3085)
314 314
315 315 $ hg clone -q -U a a6
316 316 $ cd a6
317 317 $ hg up -q 6
318 318
319 319 $ echo "I" >> E
320 320 $ hg ci -m "I"
321 321 $ hg merge 7
322 322 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
323 323 (branch merge, don't forget to commit)
324 324 $ hg ci -m "Merge"
325 325 $ echo "J" >> F
326 326 $ hg ci -m "J"
327 327
328 328 $ hg rebase -s 8 -d 7 --collapse --config ui.merge=internal:other
329 remote changed E which local deleted
330 use (c)hanged version or leave (d)eleted? c
331 329 saved backup bundle to $TESTTMP/a6/.hg/strip-backup/*-backup.hg (glob)
332 330
333 331 $ hg tglog
334 332 @ 8: 'Collapsed revision
335 333 | * I
336 334 | * Merge
337 335 | * J'
338 336 o 7: 'H'
339 337 |
340 338 | o 6: 'G'
341 339 |/|
342 340 o | 5: 'F'
343 341 | |
344 342 | o 4: 'E'
345 343 |/
346 344 | o 3: 'D'
347 345 | |
348 346 | o 2: 'C'
349 347 | |
350 348 | o 1: 'B'
351 349 |/
352 350 o 0: 'A'
353 351
354 352
355 353 $ hg parents
356 354 changeset: 8:9472f4b1d736
357 355 tag: tip
358 356 user: test
359 357 date: Thu Jan 01 00:00:00 1970 +0000
360 358 summary: Collapsed revision
361 359
362 360
363 361 $ cd ..
364 362
365 363 Ensure --continue restores a correct state (issue3046) and phase:
366 364 $ hg clone -q a a7
367 365 $ cd a7
368 366 $ hg up -q 3
369 367 $ echo 'H2' > H
370 368 $ hg ci -A -m 'H2'
371 369 adding H
372 370 $ hg phase --force --secret 8
373 371 $ hg rebase -s 8 -d 7 --config ui.merge=internal:fail
374 372 merging H
375 373 warning: conflicts during merge.
376 374 merging H incomplete! (edit conflicts, then use 'hg resolve --mark')
377 375 abort: unresolved conflicts (see hg resolve, then hg rebase --continue)
378 376 [255]
379 377 $ hg resolve --all -t internal:local
380 378 $ hg rebase -c
381 379 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/6215fafa5447-backup.hg (glob)
382 380 $ hg log -G --template "{rev}:{phase} '{desc}' {branches}\n"
383 381 @ 7:draft 'H'
384 382 |
385 383 | o 6:draft 'G'
386 384 |/|
387 385 o | 5:draft 'F'
388 386 | |
389 387 | o 4:draft 'E'
390 388 |/
391 389 | o 3:draft 'D'
392 390 | |
393 391 | o 2:draft 'C'
394 392 | |
395 393 | o 1:draft 'B'
396 394 |/
397 395 o 0:draft 'A'
398 396
399 397
400 398 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now