##// END OF EJS Templates
merge: refactor unknown file conflict checking...
Matt Mackall -
r16093:7e30f5f2 default
parent child Browse files
Show More
@@ -1,964 +1,961 b''
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
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 override_match(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 orig_matchfn = m.matchfn
38 38 m.matchfn = lambda f: notlfile(f) and orig_matchfn(f) or None
39 39 return m
40 40 oldmatch = installmatchfn(override_match)
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 add_largefiles(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:
85 85 abovemin = (lfsize and
86 86 os.lstat(repo.wjoin(f)).st_size >= lfsize * 1024 * 1024)
87 87 if large or abovemin or (lfmatcher and lfmatcher(f)):
88 88 lfnames.append(f)
89 89 if ui.verbose or not exact:
90 90 ui.status(_('adding %s as a largefile\n') % m.rel(f))
91 91
92 92 bad = []
93 93 standins = []
94 94
95 95 # Need to lock, otherwise there could be a race condition between
96 96 # when standins are created and added to the repo.
97 97 wlock = repo.wlock()
98 98 try:
99 99 if not opts.get('dry_run'):
100 100 lfdirstate = lfutil.openlfdirstate(ui, repo)
101 101 for f in lfnames:
102 102 standinname = lfutil.standin(f)
103 103 lfutil.writestandin(repo, standinname, hash='',
104 104 executable=lfutil.getexecutable(repo.wjoin(f)))
105 105 standins.append(standinname)
106 106 if lfdirstate[f] == 'r':
107 107 lfdirstate.normallookup(f)
108 108 else:
109 109 lfdirstate.add(f)
110 110 lfdirstate.write()
111 111 bad += [lfutil.splitstandin(f)
112 112 for f in lfutil.repo_add(repo, standins)
113 113 if f in m.files()]
114 114 finally:
115 115 wlock.release()
116 116 return bad
117 117
118 118 def remove_largefiles(ui, repo, *pats, **opts):
119 119 after = opts.get('after')
120 120 if not pats and not after:
121 121 raise util.Abort(_('no files specified'))
122 122 m = scmutil.match(repo[None], pats, opts)
123 123 try:
124 124 repo.lfstatus = True
125 125 s = repo.status(match=m, clean=True)
126 126 finally:
127 127 repo.lfstatus = False
128 128 manifest = repo[None].manifest()
129 129 modified, added, deleted, clean = [[f for f in list
130 130 if lfutil.standin(f) in manifest]
131 131 for list in [s[0], s[1], s[3], s[6]]]
132 132
133 133 def warn(files, reason):
134 134 for f in files:
135 135 ui.warn(_('not removing %s: %s (use forget to undo)\n')
136 136 % (m.rel(f), reason))
137 137
138 138 if after:
139 139 remove, forget = deleted, []
140 140 warn(modified + added + clean, _('file still exists'))
141 141 else:
142 142 remove, forget = deleted + clean, []
143 143 warn(modified, _('file is modified'))
144 144 warn(added, _('file has been marked for add'))
145 145
146 146 for f in sorted(remove + forget):
147 147 if ui.verbose or not m.exact(f):
148 148 ui.status(_('removing %s\n') % m.rel(f))
149 149
150 150 # Need to lock because standin files are deleted then removed from the
151 151 # repository and we could race inbetween.
152 152 wlock = repo.wlock()
153 153 try:
154 154 lfdirstate = lfutil.openlfdirstate(ui, repo)
155 155 for f in remove:
156 156 if not after:
157 157 # If this is being called by addremove, notify the user that we
158 158 # are removing the file.
159 159 if getattr(repo, "_isaddremove", False):
160 160 ui.status(_('removing %s\n' % f))
161 161 if os.path.exists(repo.wjoin(f)):
162 162 util.unlinkpath(repo.wjoin(f))
163 163 lfdirstate.remove(f)
164 164 lfdirstate.write()
165 165 forget = [lfutil.standin(f) for f in forget]
166 166 remove = [lfutil.standin(f) for f in remove]
167 167 lfutil.repo_forget(repo, forget)
168 168 # If this is being called by addremove, let the original addremove
169 169 # function handle this.
170 170 if not getattr(repo, "_isaddremove", False):
171 171 lfutil.repo_remove(repo, remove, unlink=True)
172 172 finally:
173 173 wlock.release()
174 174
175 175 # -- Wrappers: modify existing commands --------------------------------
176 176
177 177 # Add works by going through the files that the user wanted to add and
178 178 # checking if they should be added as largefiles. Then it makes a new
179 179 # matcher which matches only the normal files and runs the original
180 180 # version of add.
181 181 def override_add(orig, ui, repo, *pats, **opts):
182 182 normal = opts.pop('normal')
183 183 if normal:
184 184 if opts.get('large'):
185 185 raise util.Abort(_('--normal cannot be used with --large'))
186 186 return orig(ui, repo, *pats, **opts)
187 187 bad = add_largefiles(ui, repo, *pats, **opts)
188 188 installnormalfilesmatchfn(repo[None].manifest())
189 189 result = orig(ui, repo, *pats, **opts)
190 190 restorematchfn()
191 191
192 192 return (result == 1 or bad) and 1 or 0
193 193
194 194 def override_remove(orig, ui, repo, *pats, **opts):
195 195 installnormalfilesmatchfn(repo[None].manifest())
196 196 orig(ui, repo, *pats, **opts)
197 197 restorematchfn()
198 198 remove_largefiles(ui, repo, *pats, **opts)
199 199
200 200 def override_status(orig, ui, repo, *pats, **opts):
201 201 try:
202 202 repo.lfstatus = True
203 203 return orig(ui, repo, *pats, **opts)
204 204 finally:
205 205 repo.lfstatus = False
206 206
207 207 def override_log(orig, ui, repo, *pats, **opts):
208 208 try:
209 209 repo.lfstatus = True
210 210 orig(ui, repo, *pats, **opts)
211 211 finally:
212 212 repo.lfstatus = False
213 213
214 214 def override_verify(orig, ui, repo, *pats, **opts):
215 215 large = opts.pop('large', False)
216 216 all = opts.pop('lfa', False)
217 217 contents = opts.pop('lfc', False)
218 218
219 219 result = orig(ui, repo, *pats, **opts)
220 220 if large:
221 221 result = result or lfcommands.verifylfiles(ui, repo, all, contents)
222 222 return result
223 223
224 224 # Override needs to refresh standins so that update's normal merge
225 225 # will go through properly. Then the other update hook (overriding repo.update)
226 226 # will get the new files. Filemerge is also overriden so that the merge
227 227 # will merge standins correctly.
228 228 def override_update(orig, ui, repo, *pats, **opts):
229 229 lfdirstate = lfutil.openlfdirstate(ui, repo)
230 230 s = lfdirstate.status(match_.always(repo.root, repo.getcwd()), [], False,
231 231 False, False)
232 232 (unsure, modified, added, removed, missing, unknown, ignored, clean) = s
233 233
234 234 # Need to lock between the standins getting updated and their
235 235 # largefiles getting updated
236 236 wlock = repo.wlock()
237 237 try:
238 238 if opts['check']:
239 239 mod = len(modified) > 0
240 240 for lfile in unsure:
241 241 standin = lfutil.standin(lfile)
242 242 if repo['.'][standin].data().strip() != \
243 243 lfutil.hashfile(repo.wjoin(lfile)):
244 244 mod = True
245 245 else:
246 246 lfdirstate.normal(lfile)
247 247 lfdirstate.write()
248 248 if mod:
249 249 raise util.Abort(_('uncommitted local changes'))
250 250 # XXX handle removed differently
251 251 if not opts['clean']:
252 252 for lfile in unsure + modified + added:
253 253 lfutil.updatestandin(repo, lfutil.standin(lfile))
254 254 finally:
255 255 wlock.release()
256 256 return orig(ui, repo, *pats, **opts)
257 257
258 258 # Before starting the manifest merge, merge.updates will call
259 259 # _checkunknown to check if there are any files in the merged-in
260 260 # changeset that collide with unknown files in the working copy.
261 261 #
262 262 # The largefiles are seen as unknown, so this prevents us from merging
263 263 # in a file 'foo' if we already have a largefile with the same name.
264 264 #
265 265 # The overridden function filters the unknown files by removing any
266 266 # largefiles. This makes the merge proceed and we can then handle this
267 267 # case further in the overridden manifestmerge function below.
268 def override_checkunknown(origfn, wctx, mctx, folding):
269 origunknown = wctx.unknown()
270 wctx._unknown = filter(lambda f: lfutil.standin(f) not in wctx, origunknown)
271 try:
272 return origfn(wctx, mctx, folding)
273 finally:
274 wctx._unknown = origunknown
268 def override_checkunknownfile(origfn, repo, wctx, mctx, f):
269 if lfutil.standin(f) in wctx:
270 return False
271 return origfn(repo, wctx, mctx, f)
275 272
276 273 # The manifest merge handles conflicts on the manifest level. We want
277 274 # to handle changes in largefile-ness of files at this level too.
278 275 #
279 276 # The strategy is to run the original manifestmerge and then process
280 277 # the action list it outputs. There are two cases we need to deal with:
281 278 #
282 279 # 1. Normal file in p1, largefile in p2. Here the largefile is
283 280 # detected via its standin file, which will enter the working copy
284 281 # with a "get" action. It is not "merge" since the standin is all
285 282 # Mercurial is concerned with at this level -- the link to the
286 283 # existing normal file is not relevant here.
287 284 #
288 285 # 2. Largefile in p1, normal file in p2. Here we get a "merge" action
289 286 # since the largefile will be present in the working copy and
290 287 # different from the normal file in p2. Mercurial therefore
291 288 # triggers a merge action.
292 289 #
293 290 # In both cases, we prompt the user and emit new actions to either
294 291 # remove the standin (if the normal file was kept) or to remove the
295 292 # normal file and get the standin (if the largefile was kept). The
296 293 # default prompt answer is to use the largefile version since it was
297 294 # presumably changed on purpose.
298 295 #
299 296 # Finally, the merge.applyupdates function will then take care of
300 297 # writing the files into the working copy and lfcommands.updatelfiles
301 298 # will update the largefiles.
302 299 def override_manifestmerge(origfn, repo, p1, p2, pa, overwrite, partial):
303 300 actions = origfn(repo, p1, p2, pa, overwrite, partial)
304 301 processed = []
305 302
306 303 for action in actions:
307 304 if overwrite:
308 305 processed.append(action)
309 306 continue
310 307 f, m = action[:2]
311 308
312 309 choices = (_('&Largefile'), _('&Normal file'))
313 310 if m == "g" and lfutil.splitstandin(f) in p1 and f in p2:
314 311 # Case 1: normal file in the working copy, largefile in
315 312 # the second parent
316 313 lfile = lfutil.splitstandin(f)
317 314 standin = f
318 315 msg = _('%s has been turned into a largefile\n'
319 316 'use (l)argefile or keep as (n)ormal file?') % lfile
320 317 if repo.ui.promptchoice(msg, choices, 0) == 0:
321 318 processed.append((lfile, "r"))
322 319 processed.append((standin, "g", p2.flags(standin)))
323 320 else:
324 321 processed.append((standin, "r"))
325 322 elif m == "m" and lfutil.standin(f) in p1 and f in p2:
326 323 # Case 2: largefile in the working copy, normal file in
327 324 # the second parent
328 325 standin = lfutil.standin(f)
329 326 lfile = f
330 327 msg = _('%s has been turned into a normal file\n'
331 328 'keep as (l)argefile or use (n)ormal file?') % lfile
332 329 if repo.ui.promptchoice(msg, choices, 0) == 0:
333 330 processed.append((lfile, "r"))
334 331 else:
335 332 processed.append((standin, "r"))
336 333 processed.append((lfile, "g", p2.flags(lfile)))
337 334 else:
338 335 processed.append(action)
339 336
340 337 return processed
341 338
342 339 # Override filemerge to prompt the user about how they wish to merge
343 340 # largefiles. This will handle identical edits, and copy/rename +
344 341 # edit without prompting the user.
345 342 def override_filemerge(origfn, repo, mynode, orig, fcd, fco, fca):
346 343 # Use better variable names here. Because this is a wrapper we cannot
347 344 # change the variable names in the function declaration.
348 345 fcdest, fcother, fcancestor = fcd, fco, fca
349 346 if not lfutil.isstandin(orig):
350 347 return origfn(repo, mynode, orig, fcdest, fcother, fcancestor)
351 348 else:
352 349 if not fcother.cmp(fcdest): # files identical?
353 350 return None
354 351
355 352 # backwards, use working dir parent as ancestor
356 353 if fcancestor == fcother:
357 354 fcancestor = fcdest.parents()[0]
358 355
359 356 if orig != fcother.path():
360 357 repo.ui.status(_('merging %s and %s to %s\n')
361 358 % (lfutil.splitstandin(orig),
362 359 lfutil.splitstandin(fcother.path()),
363 360 lfutil.splitstandin(fcdest.path())))
364 361 else:
365 362 repo.ui.status(_('merging %s\n')
366 363 % lfutil.splitstandin(fcdest.path()))
367 364
368 365 if fcancestor.path() != fcother.path() and fcother.data() == \
369 366 fcancestor.data():
370 367 return 0
371 368 if fcancestor.path() != fcdest.path() and fcdest.data() == \
372 369 fcancestor.data():
373 370 repo.wwrite(fcdest.path(), fcother.data(), fcother.flags())
374 371 return 0
375 372
376 373 if repo.ui.promptchoice(_('largefile %s has a merge conflict\n'
377 374 'keep (l)ocal or take (o)ther?') %
378 375 lfutil.splitstandin(orig),
379 376 (_('&Local'), _('&Other')), 0) == 0:
380 377 return 0
381 378 else:
382 379 repo.wwrite(fcdest.path(), fcother.data(), fcother.flags())
383 380 return 0
384 381
385 382 # Copy first changes the matchers to match standins instead of
386 383 # largefiles. Then it overrides util.copyfile in that function it
387 384 # checks if the destination largefile already exists. It also keeps a
388 385 # list of copied files so that the largefiles can be copied and the
389 386 # dirstate updated.
390 387 def override_copy(orig, ui, repo, pats, opts, rename=False):
391 388 # doesn't remove largefile on rename
392 389 if len(pats) < 2:
393 390 # this isn't legal, let the original function deal with it
394 391 return orig(ui, repo, pats, opts, rename)
395 392
396 393 def makestandin(relpath):
397 394 path = scmutil.canonpath(repo.root, repo.getcwd(), relpath)
398 395 return os.path.join(repo.wjoin(lfutil.standin(path)))
399 396
400 397 fullpats = scmutil.expandpats(pats)
401 398 dest = fullpats[-1]
402 399
403 400 if os.path.isdir(dest):
404 401 if not os.path.isdir(makestandin(dest)):
405 402 os.makedirs(makestandin(dest))
406 403 # This could copy both lfiles and normal files in one command,
407 404 # but we don't want to do that. First replace their matcher to
408 405 # only match normal files and run it, then replace it to just
409 406 # match largefiles and run it again.
410 407 nonormalfiles = False
411 408 nolfiles = False
412 409 try:
413 410 try:
414 411 installnormalfilesmatchfn(repo[None].manifest())
415 412 result = orig(ui, repo, pats, opts, rename)
416 413 except util.Abort, e:
417 414 if str(e) != 'no files to copy':
418 415 raise e
419 416 else:
420 417 nonormalfiles = True
421 418 result = 0
422 419 finally:
423 420 restorematchfn()
424 421
425 422 # The first rename can cause our current working directory to be removed.
426 423 # In that case there is nothing left to copy/rename so just quit.
427 424 try:
428 425 repo.getcwd()
429 426 except OSError:
430 427 return result
431 428
432 429 try:
433 430 try:
434 431 # When we call orig below it creates the standins but we don't add them
435 432 # to the dir state until later so lock during that time.
436 433 wlock = repo.wlock()
437 434
438 435 manifest = repo[None].manifest()
439 436 oldmatch = None # for the closure
440 437 def override_match(ctx, pats=[], opts={}, globbed=False,
441 438 default='relpath'):
442 439 newpats = []
443 440 # The patterns were previously mangled to add the standin
444 441 # directory; we need to remove that now
445 442 for pat in pats:
446 443 if match_.patkind(pat) is None and lfutil.shortname in pat:
447 444 newpats.append(pat.replace(lfutil.shortname, ''))
448 445 else:
449 446 newpats.append(pat)
450 447 match = oldmatch(ctx, newpats, opts, globbed, default)
451 448 m = copy.copy(match)
452 449 lfile = lambda f: lfutil.standin(f) in manifest
453 450 m._files = [lfutil.standin(f) for f in m._files if lfile(f)]
454 451 m._fmap = set(m._files)
455 452 orig_matchfn = m.matchfn
456 453 m.matchfn = lambda f: (lfutil.isstandin(f) and
457 454 (f in manifest) and
458 455 orig_matchfn(lfutil.splitstandin(f)) or
459 456 None)
460 457 return m
461 458 oldmatch = installmatchfn(override_match)
462 459 listpats = []
463 460 for pat in pats:
464 461 if match_.patkind(pat) is not None:
465 462 listpats.append(pat)
466 463 else:
467 464 listpats.append(makestandin(pat))
468 465
469 466 try:
470 467 origcopyfile = util.copyfile
471 468 copiedfiles = []
472 469 def override_copyfile(src, dest):
473 470 if (lfutil.shortname in src and
474 471 dest.startswith(repo.wjoin(lfutil.shortname))):
475 472 destlfile = dest.replace(lfutil.shortname, '')
476 473 if not opts['force'] and os.path.exists(destlfile):
477 474 raise IOError('',
478 475 _('destination largefile already exists'))
479 476 copiedfiles.append((src, dest))
480 477 origcopyfile(src, dest)
481 478
482 479 util.copyfile = override_copyfile
483 480 result += orig(ui, repo, listpats, opts, rename)
484 481 finally:
485 482 util.copyfile = origcopyfile
486 483
487 484 lfdirstate = lfutil.openlfdirstate(ui, repo)
488 485 for (src, dest) in copiedfiles:
489 486 if (lfutil.shortname in src and
490 487 dest.startswith(repo.wjoin(lfutil.shortname))):
491 488 srclfile = src.replace(repo.wjoin(lfutil.standin('')), '')
492 489 destlfile = dest.replace(repo.wjoin(lfutil.standin('')), '')
493 490 destlfiledir = os.path.dirname(destlfile) or '.'
494 491 if not os.path.isdir(destlfiledir):
495 492 os.makedirs(destlfiledir)
496 493 if rename:
497 494 os.rename(repo.wjoin(srclfile), repo.wjoin(destlfile))
498 495 lfdirstate.remove(srclfile)
499 496 else:
500 497 util.copyfile(srclfile, destlfile)
501 498 lfdirstate.add(destlfile)
502 499 lfdirstate.write()
503 500 except util.Abort, e:
504 501 if str(e) != 'no files to copy':
505 502 raise e
506 503 else:
507 504 nolfiles = True
508 505 finally:
509 506 restorematchfn()
510 507 wlock.release()
511 508
512 509 if nolfiles and nonormalfiles:
513 510 raise util.Abort(_('no files to copy'))
514 511
515 512 return result
516 513
517 514 # When the user calls revert, we have to be careful to not revert any
518 515 # changes to other largefiles accidentally. This means we have to keep
519 516 # track of the largefiles that are being reverted so we only pull down
520 517 # the necessary largefiles.
521 518 #
522 519 # Standins are only updated (to match the hash of largefiles) before
523 520 # commits. Update the standins then run the original revert, changing
524 521 # the matcher to hit standins instead of largefiles. Based on the
525 522 # resulting standins update the largefiles. Then return the standins
526 523 # to their proper state
527 524 def override_revert(orig, ui, repo, *pats, **opts):
528 525 # Because we put the standins in a bad state (by updating them)
529 526 # and then return them to a correct state we need to lock to
530 527 # prevent others from changing them in their incorrect state.
531 528 wlock = repo.wlock()
532 529 try:
533 530 lfdirstate = lfutil.openlfdirstate(ui, repo)
534 531 (modified, added, removed, missing, unknown, ignored, clean) = \
535 532 lfutil.lfdirstate_status(lfdirstate, repo, repo['.'].rev())
536 533 for lfile in modified:
537 534 lfutil.updatestandin(repo, lfutil.standin(lfile))
538 535 for lfile in missing:
539 536 os.unlink(repo.wjoin(lfutil.standin(lfile)))
540 537
541 538 try:
542 539 ctx = repo[opts.get('rev')]
543 540 oldmatch = None # for the closure
544 541 def override_match(ctx, pats=[], opts={}, globbed=False,
545 542 default='relpath'):
546 543 match = oldmatch(ctx, pats, opts, globbed, default)
547 544 m = copy.copy(match)
548 545 def tostandin(f):
549 546 if lfutil.standin(f) in ctx:
550 547 return lfutil.standin(f)
551 548 elif lfutil.standin(f) in repo[None]:
552 549 return None
553 550 return f
554 551 m._files = [tostandin(f) for f in m._files]
555 552 m._files = [f for f in m._files if f is not None]
556 553 m._fmap = set(m._files)
557 554 orig_matchfn = m.matchfn
558 555 def matchfn(f):
559 556 if lfutil.isstandin(f):
560 557 # We need to keep track of what largefiles are being
561 558 # matched so we know which ones to update later --
562 559 # otherwise we accidentally revert changes to other
563 560 # largefiles. This is repo-specific, so duckpunch the
564 561 # repo object to keep the list of largefiles for us
565 562 # later.
566 563 if orig_matchfn(lfutil.splitstandin(f)) and \
567 564 (f in repo[None] or f in ctx):
568 565 lfileslist = getattr(repo, '_lfilestoupdate', [])
569 566 lfileslist.append(lfutil.splitstandin(f))
570 567 repo._lfilestoupdate = lfileslist
571 568 return True
572 569 else:
573 570 return False
574 571 return orig_matchfn(f)
575 572 m.matchfn = matchfn
576 573 return m
577 574 oldmatch = installmatchfn(override_match)
578 575 scmutil.match
579 576 matches = override_match(repo[None], pats, opts)
580 577 orig(ui, repo, *pats, **opts)
581 578 finally:
582 579 restorematchfn()
583 580 lfileslist = getattr(repo, '_lfilestoupdate', [])
584 581 lfcommands.updatelfiles(ui, repo, filelist=lfileslist,
585 582 printmessage=False)
586 583
587 584 # empty out the largefiles list so we start fresh next time
588 585 repo._lfilestoupdate = []
589 586 for lfile in modified:
590 587 if lfile in lfileslist:
591 588 if os.path.exists(repo.wjoin(lfutil.standin(lfile))) and lfile\
592 589 in repo['.']:
593 590 lfutil.writestandin(repo, lfutil.standin(lfile),
594 591 repo['.'][lfile].data().strip(),
595 592 'x' in repo['.'][lfile].flags())
596 593 lfdirstate = lfutil.openlfdirstate(ui, repo)
597 594 for lfile in added:
598 595 standin = lfutil.standin(lfile)
599 596 if standin not in ctx and (standin in matches or opts.get('all')):
600 597 if lfile in lfdirstate:
601 598 lfdirstate.drop(lfile)
602 599 util.unlinkpath(repo.wjoin(standin))
603 600 lfdirstate.write()
604 601 finally:
605 602 wlock.release()
606 603
607 604 def hg_update(orig, repo, node):
608 605 result = orig(repo, node)
609 606 lfcommands.updatelfiles(repo.ui, repo)
610 607 return result
611 608
612 609 def hg_clean(orig, repo, node, show_stats=True):
613 610 result = orig(repo, node, show_stats)
614 611 lfcommands.updatelfiles(repo.ui, repo)
615 612 return result
616 613
617 614 def hg_merge(orig, repo, node, force=None, remind=True):
618 615 # Mark the repo as being in the middle of a merge, so that
619 616 # updatelfiles() will know that it needs to trust the standins in
620 617 # the working copy, not in the standins in the current node
621 618 repo._ismerging = True
622 619 try:
623 620 result = orig(repo, node, force, remind)
624 621 lfcommands.updatelfiles(repo.ui, repo)
625 622 finally:
626 623 repo._ismerging = False
627 624 return result
628 625
629 626 # When we rebase a repository with remotely changed largefiles, we need to
630 627 # take some extra care so that the largefiles are correctly updated in the
631 628 # working copy
632 629 def override_pull(orig, ui, repo, source=None, **opts):
633 630 if opts.get('rebase', False):
634 631 repo._isrebasing = True
635 632 try:
636 633 if opts.get('update'):
637 634 del opts['update']
638 635 ui.debug('--update and --rebase are not compatible, ignoring '
639 636 'the update flag\n')
640 637 del opts['rebase']
641 638 cmdutil.bailifchanged(repo)
642 639 revsprepull = len(repo)
643 640 origpostincoming = commands.postincoming
644 641 def _dummy(*args, **kwargs):
645 642 pass
646 643 commands.postincoming = _dummy
647 644 repo.lfpullsource = source
648 645 if not source:
649 646 source = 'default'
650 647 try:
651 648 result = commands.pull(ui, repo, source, **opts)
652 649 finally:
653 650 commands.postincoming = origpostincoming
654 651 revspostpull = len(repo)
655 652 if revspostpull > revsprepull:
656 653 result = result or rebase.rebase(ui, repo)
657 654 finally:
658 655 repo._isrebasing = False
659 656 else:
660 657 repo.lfpullsource = source
661 658 if not source:
662 659 source = 'default'
663 660 result = orig(ui, repo, source, **opts)
664 661 # If we do not have the new largefiles for any new heads we pulled, we
665 662 # will run into a problem later if we try to merge or rebase with one of
666 663 # these heads, so cache the largefiles now direclty into the system
667 664 # cache.
668 665 ui.status(_("caching new largefiles\n"))
669 666 numcached = 0
670 667 branches = repo.branchmap()
671 668 for branch in branches:
672 669 heads = repo.branchheads(branch)
673 670 for head in heads:
674 671 (cached, missing) = lfcommands.cachelfiles(ui, repo, head)
675 672 numcached += len(cached)
676 673 ui.status(_("%d largefiles cached\n" % numcached))
677 674 return result
678 675
679 676 def override_rebase(orig, ui, repo, **opts):
680 677 repo._isrebasing = True
681 678 try:
682 679 orig(ui, repo, **opts)
683 680 finally:
684 681 repo._isrebasing = False
685 682
686 683 def override_archive(orig, repo, dest, node, kind, decode=True, matchfn=None,
687 684 prefix=None, mtime=None, subrepos=None):
688 685 # No need to lock because we are only reading history and
689 686 # largefile caches, neither of which are modified.
690 687 lfcommands.cachelfiles(repo.ui, repo, node)
691 688
692 689 if kind not in archival.archivers:
693 690 raise util.Abort(_("unknown archive type '%s'") % kind)
694 691
695 692 ctx = repo[node]
696 693
697 694 if kind == 'files':
698 695 if prefix:
699 696 raise util.Abort(
700 697 _('cannot give prefix when archiving to files'))
701 698 else:
702 699 prefix = archival.tidyprefix(dest, kind, prefix)
703 700
704 701 def write(name, mode, islink, getdata):
705 702 if matchfn and not matchfn(name):
706 703 return
707 704 data = getdata()
708 705 if decode:
709 706 data = repo.wwritedata(name, data)
710 707 archiver.addfile(prefix + name, mode, islink, data)
711 708
712 709 archiver = archival.archivers[kind](dest, mtime or ctx.date()[0])
713 710
714 711 if repo.ui.configbool("ui", "archivemeta", True):
715 712 def metadata():
716 713 base = 'repo: %s\nnode: %s\nbranch: %s\n' % (
717 714 hex(repo.changelog.node(0)), hex(node), ctx.branch())
718 715
719 716 tags = ''.join('tag: %s\n' % t for t in ctx.tags()
720 717 if repo.tagtype(t) == 'global')
721 718 if not tags:
722 719 repo.ui.pushbuffer()
723 720 opts = {'template': '{latesttag}\n{latesttagdistance}',
724 721 'style': '', 'patch': None, 'git': None}
725 722 cmdutil.show_changeset(repo.ui, repo, opts).show(ctx)
726 723 ltags, dist = repo.ui.popbuffer().split('\n')
727 724 tags = ''.join('latesttag: %s\n' % t for t in ltags.split(':'))
728 725 tags += 'latesttagdistance: %s\n' % dist
729 726
730 727 return base + tags
731 728
732 729 write('.hg_archival.txt', 0644, False, metadata)
733 730
734 731 for f in ctx:
735 732 ff = ctx.flags(f)
736 733 getdata = ctx[f].data
737 734 if lfutil.isstandin(f):
738 735 path = lfutil.findfile(repo, getdata().strip())
739 736 if path is None:
740 737 raise util.Abort(
741 738 _('largefile %s not found in repo store or system cache')
742 739 % lfutil.splitstandin(f))
743 740 f = lfutil.splitstandin(f)
744 741
745 742 def getdatafn():
746 743 fd = None
747 744 try:
748 745 fd = open(path, 'rb')
749 746 return fd.read()
750 747 finally:
751 748 if fd:
752 749 fd.close()
753 750
754 751 getdata = getdatafn
755 752 write(f, 'x' in ff and 0755 or 0644, 'l' in ff, getdata)
756 753
757 754 if subrepos:
758 755 for subpath in ctx.substate:
759 756 sub = ctx.sub(subpath)
760 757 sub.archive(repo.ui, archiver, prefix)
761 758
762 759 archiver.done()
763 760
764 761 # If a largefile is modified, the change is not reflected in its
765 762 # standin until a commit. cmdutil.bailifchanged() raises an exception
766 763 # if the repo has uncommitted changes. Wrap it to also check if
767 764 # largefiles were changed. This is used by bisect and backout.
768 765 def override_bailifchanged(orig, repo):
769 766 orig(repo)
770 767 repo.lfstatus = True
771 768 modified, added, removed, deleted = repo.status()[:4]
772 769 repo.lfstatus = False
773 770 if modified or added or removed or deleted:
774 771 raise util.Abort(_('outstanding uncommitted changes'))
775 772
776 773 # Fetch doesn't use cmdutil.bail_if_changed so override it to add the check
777 774 def override_fetch(orig, ui, repo, *pats, **opts):
778 775 repo.lfstatus = True
779 776 modified, added, removed, deleted = repo.status()[:4]
780 777 repo.lfstatus = False
781 778 if modified or added or removed or deleted:
782 779 raise util.Abort(_('outstanding uncommitted changes'))
783 780 return orig(ui, repo, *pats, **opts)
784 781
785 782 def override_forget(orig, ui, repo, *pats, **opts):
786 783 installnormalfilesmatchfn(repo[None].manifest())
787 784 orig(ui, repo, *pats, **opts)
788 785 restorematchfn()
789 786 m = scmutil.match(repo[None], pats, opts)
790 787
791 788 try:
792 789 repo.lfstatus = True
793 790 s = repo.status(match=m, clean=True)
794 791 finally:
795 792 repo.lfstatus = False
796 793 forget = sorted(s[0] + s[1] + s[3] + s[6])
797 794 forget = [f for f in forget if lfutil.standin(f) in repo[None].manifest()]
798 795
799 796 for f in forget:
800 797 if lfutil.standin(f) not in repo.dirstate and not \
801 798 os.path.isdir(m.rel(lfutil.standin(f))):
802 799 ui.warn(_('not removing %s: file is already untracked\n')
803 800 % m.rel(f))
804 801
805 802 for f in forget:
806 803 if ui.verbose or not m.exact(f):
807 804 ui.status(_('removing %s\n') % m.rel(f))
808 805
809 806 # Need to lock because standin files are deleted then removed from the
810 807 # repository and we could race inbetween.
811 808 wlock = repo.wlock()
812 809 try:
813 810 lfdirstate = lfutil.openlfdirstate(ui, repo)
814 811 for f in forget:
815 812 if lfdirstate[f] == 'a':
816 813 lfdirstate.drop(f)
817 814 else:
818 815 lfdirstate.remove(f)
819 816 lfdirstate.write()
820 817 lfutil.repo_remove(repo, [lfutil.standin(f) for f in forget],
821 818 unlink=True)
822 819 finally:
823 820 wlock.release()
824 821
825 822 def getoutgoinglfiles(ui, repo, dest=None, **opts):
826 823 dest = ui.expandpath(dest or 'default-push', dest or 'default')
827 824 dest, branches = hg.parseurl(dest, opts.get('branch'))
828 825 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
829 826 if revs:
830 827 revs = [repo.lookup(rev) for rev in revs]
831 828
832 829 remoteui = hg.remoteui
833 830
834 831 try:
835 832 remote = hg.repository(remoteui(repo, opts), dest)
836 833 except error.RepoError:
837 834 return None
838 835 o = lfutil.findoutgoing(repo, remote, False)
839 836 if not o:
840 837 return None
841 838 o = repo.changelog.nodesbetween(o, revs)[0]
842 839 if opts.get('newest_first'):
843 840 o.reverse()
844 841
845 842 toupload = set()
846 843 for n in o:
847 844 parents = [p for p in repo.changelog.parents(n) if p != node.nullid]
848 845 ctx = repo[n]
849 846 files = set(ctx.files())
850 847 if len(parents) == 2:
851 848 mc = ctx.manifest()
852 849 mp1 = ctx.parents()[0].manifest()
853 850 mp2 = ctx.parents()[1].manifest()
854 851 for f in mp1:
855 852 if f not in mc:
856 853 files.add(f)
857 854 for f in mp2:
858 855 if f not in mc:
859 856 files.add(f)
860 857 for f in mc:
861 858 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
862 859 files.add(f)
863 860 toupload = toupload.union(
864 861 set([f for f in files if lfutil.isstandin(f) and f in ctx]))
865 862 return toupload
866 863
867 864 def override_outgoing(orig, ui, repo, dest=None, **opts):
868 865 orig(ui, repo, dest, **opts)
869 866
870 867 if opts.pop('large', None):
871 868 toupload = getoutgoinglfiles(ui, repo, dest, **opts)
872 869 if toupload is None:
873 870 ui.status(_('largefiles: No remote repo\n'))
874 871 else:
875 872 ui.status(_('largefiles to upload:\n'))
876 873 for file in toupload:
877 874 ui.status(lfutil.splitstandin(file) + '\n')
878 875 ui.status('\n')
879 876
880 877 def override_summary(orig, ui, repo, *pats, **opts):
881 878 try:
882 879 repo.lfstatus = True
883 880 orig(ui, repo, *pats, **opts)
884 881 finally:
885 882 repo.lfstatus = False
886 883
887 884 if opts.pop('large', None):
888 885 toupload = getoutgoinglfiles(ui, repo, None, **opts)
889 886 if toupload is None:
890 887 ui.status(_('largefiles: No remote repo\n'))
891 888 else:
892 889 ui.status(_('largefiles: %d to upload\n') % len(toupload))
893 890
894 891 def override_addremove(orig, ui, repo, *pats, **opts):
895 892 # Get the list of missing largefiles so we can remove them
896 893 lfdirstate = lfutil.openlfdirstate(ui, repo)
897 894 s = lfdirstate.status(match_.always(repo.root, repo.getcwd()), [], False,
898 895 False, False)
899 896 (unsure, modified, added, removed, missing, unknown, ignored, clean) = s
900 897
901 898 # Call into the normal remove code, but the removing of the standin, we want
902 899 # to have handled by original addremove. Monkey patching here makes sure
903 900 # we don't remove the standin in the largefiles code, preventing a very
904 901 # confused state later.
905 902 if missing:
906 903 repo._isaddremove = True
907 904 remove_largefiles(ui, repo, *missing, **opts)
908 905 repo._isaddremove = False
909 906 # Call into the normal add code, and any files that *should* be added as
910 907 # largefiles will be
911 908 add_largefiles(ui, repo, *pats, **opts)
912 909 # Now that we've handled largefiles, hand off to the original addremove
913 910 # function to take care of the rest. Make sure it doesn't do anything with
914 911 # largefiles by installing a matcher that will ignore them.
915 912 installnormalfilesmatchfn(repo[None].manifest())
916 913 result = orig(ui, repo, *pats, **opts)
917 914 restorematchfn()
918 915 return result
919 916
920 917 # Calling purge with --all will cause the largefiles to be deleted.
921 918 # Override repo.status to prevent this from happening.
922 919 def override_purge(orig, ui, repo, *dirs, **opts):
923 920 oldstatus = repo.status
924 921 def override_status(node1='.', node2=None, match=None, ignored=False,
925 922 clean=False, unknown=False, listsubrepos=False):
926 923 r = oldstatus(node1, node2, match, ignored, clean, unknown,
927 924 listsubrepos)
928 925 lfdirstate = lfutil.openlfdirstate(ui, repo)
929 926 modified, added, removed, deleted, unknown, ignored, clean = r
930 927 unknown = [f for f in unknown if lfdirstate[f] == '?']
931 928 ignored = [f for f in ignored if lfdirstate[f] == '?']
932 929 return modified, added, removed, deleted, unknown, ignored, clean
933 930 repo.status = override_status
934 931 orig(ui, repo, *dirs, **opts)
935 932 repo.status = oldstatus
936 933
937 934 def override_rollback(orig, ui, repo, **opts):
938 935 result = orig(ui, repo, **opts)
939 936 merge.update(repo, node=None, branchmerge=False, force=True,
940 937 partial=lfutil.isstandin)
941 938 wlock = repo.wlock()
942 939 try:
943 940 lfdirstate = lfutil.openlfdirstate(ui, repo)
944 941 lfiles = lfutil.listlfiles(repo)
945 942 oldlfiles = lfutil.listlfiles(repo, repo[None].parents()[0].rev())
946 943 for file in lfiles:
947 944 if file in oldlfiles:
948 945 lfdirstate.normallookup(file)
949 946 else:
950 947 lfdirstate.add(file)
951 948 lfdirstate.write()
952 949 finally:
953 950 wlock.release()
954 951 return result
955 952
956 953 def override_transplant(orig, ui, repo, *revs, **opts):
957 954 try:
958 955 repo._istransplanting = True
959 956 result = orig(ui, repo, *revs, **opts)
960 957 lfcommands.updatelfiles(ui, repo, filelist=None,
961 958 printmessage=False)
962 959 finally:
963 960 repo._istransplanting = False
964 961 return result
@@ -1,143 +1,143 b''
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 '''setup for largefiles extension: uisetup'''
10 10
11 11 from mercurial import archival, cmdutil, commands, extensions, filemerge, hg, \
12 12 httprepo, localrepo, merge, sshrepo, sshserver, wireproto
13 13 from mercurial.i18n import _
14 14 from mercurial.hgweb import hgweb_mod, protocol
15 15
16 16 import overrides
17 17 import proto
18 18
19 19 def uisetup(ui):
20 20 # Disable auto-status for some commands which assume that all
21 21 # files in the result are under Mercurial's control
22 22
23 23 entry = extensions.wrapcommand(commands.table, 'add',
24 24 overrides.override_add)
25 25 addopt = [('', 'large', None, _('add as largefile')),
26 26 ('', 'normal', None, _('add as normal file')),
27 27 ('', 'lfsize', '', _('add all files above this size '
28 28 '(in megabytes) as largefiles '
29 29 '(default: 10)'))]
30 30 entry[1].extend(addopt)
31 31
32 32 entry = extensions.wrapcommand(commands.table, 'addremove',
33 33 overrides.override_addremove)
34 34 entry = extensions.wrapcommand(commands.table, 'remove',
35 35 overrides.override_remove)
36 36 entry = extensions.wrapcommand(commands.table, 'forget',
37 37 overrides.override_forget)
38 38 entry = extensions.wrapcommand(commands.table, 'status',
39 39 overrides.override_status)
40 40 entry = extensions.wrapcommand(commands.table, 'log',
41 41 overrides.override_log)
42 42 entry = extensions.wrapcommand(commands.table, 'rollback',
43 43 overrides.override_rollback)
44 44 entry = extensions.wrapcommand(commands.table, 'verify',
45 45 overrides.override_verify)
46 46
47 47 verifyopt = [('', 'large', None, _('verify largefiles')),
48 48 ('', 'lfa', None,
49 49 _('verify all revisions of largefiles not just current')),
50 50 ('', 'lfc', None,
51 51 _('verify largefile contents not just existence'))]
52 52 entry[1].extend(verifyopt)
53 53
54 54 entry = extensions.wrapcommand(commands.table, 'outgoing',
55 55 overrides.override_outgoing)
56 56 outgoingopt = [('', 'large', None, _('display outgoing largefiles'))]
57 57 entry[1].extend(outgoingopt)
58 58 entry = extensions.wrapcommand(commands.table, 'summary',
59 59 overrides.override_summary)
60 60 summaryopt = [('', 'large', None, _('display outgoing largefiles'))]
61 61 entry[1].extend(summaryopt)
62 62
63 63 entry = extensions.wrapcommand(commands.table, 'update',
64 64 overrides.override_update)
65 65 entry = extensions.wrapcommand(commands.table, 'pull',
66 66 overrides.override_pull)
67 entry = extensions.wrapfunction(merge, '_checkunknown',
68 overrides.override_checkunknown)
67 entry = extensions.wrapfunction(merge, '_checkunknownfile',
68 overrides.override_checkunknownfile)
69 69 entry = extensions.wrapfunction(merge, 'manifestmerge',
70 70 overrides.override_manifestmerge)
71 71 entry = extensions.wrapfunction(filemerge, 'filemerge',
72 72 overrides.override_filemerge)
73 73 entry = extensions.wrapfunction(cmdutil, 'copy',
74 74 overrides.override_copy)
75 75
76 76 # Backout calls revert so we need to override both the command and the
77 77 # function
78 78 entry = extensions.wrapcommand(commands.table, 'revert',
79 79 overrides.override_revert)
80 80 entry = extensions.wrapfunction(commands, 'revert',
81 81 overrides.override_revert)
82 82
83 83 # clone uses hg._update instead of hg.update even though they are the
84 84 # same function... so wrap both of them)
85 85 extensions.wrapfunction(hg, 'update', overrides.hg_update)
86 86 extensions.wrapfunction(hg, '_update', overrides.hg_update)
87 87 extensions.wrapfunction(hg, 'clean', overrides.hg_clean)
88 88 extensions.wrapfunction(hg, 'merge', overrides.hg_merge)
89 89
90 90 extensions.wrapfunction(archival, 'archive', overrides.override_archive)
91 91 extensions.wrapfunction(cmdutil, 'bailifchanged',
92 92 overrides.override_bailifchanged)
93 93
94 94 # create the new wireproto commands ...
95 95 wireproto.commands['putlfile'] = (proto.putlfile, 'sha')
96 96 wireproto.commands['getlfile'] = (proto.getlfile, 'sha')
97 97 wireproto.commands['statlfile'] = (proto.statlfile, 'sha')
98 98
99 99 # ... and wrap some existing ones
100 100 wireproto.commands['capabilities'] = (proto.capabilities, '')
101 101 wireproto.commands['heads'] = (proto.heads, '')
102 102 wireproto.commands['lheads'] = (wireproto.heads, '')
103 103
104 104 # make putlfile behave the same as push and {get,stat}lfile behave
105 105 # the same as pull w.r.t. permissions checks
106 106 hgweb_mod.perms['putlfile'] = 'push'
107 107 hgweb_mod.perms['getlfile'] = 'pull'
108 108 hgweb_mod.perms['statlfile'] = 'pull'
109 109
110 110 # the hello wireproto command uses wireproto.capabilities, so it won't see
111 111 # our largefiles capability unless we replace the actual function as well.
112 112 proto.capabilities_orig = wireproto.capabilities
113 113 wireproto.capabilities = proto.capabilities
114 114
115 115 # these let us reject non-largefiles clients and make them display
116 116 # our error messages
117 117 protocol.webproto.refuseclient = proto.webproto_refuseclient
118 118 sshserver.sshserver.refuseclient = proto.sshproto_refuseclient
119 119
120 120 # can't do this in reposetup because it needs to have happened before
121 121 # wirerepo.__init__ is called
122 122 proto.ssh_oldcallstream = sshrepo.sshrepository._callstream
123 123 proto.http_oldcallstream = httprepo.httprepository._callstream
124 124 sshrepo.sshrepository._callstream = proto.sshrepo_callstream
125 125 httprepo.httprepository._callstream = proto.httprepo_callstream
126 126
127 127 # don't die on seeing a repo with the largefiles requirement
128 128 localrepo.localrepository.supported |= set(['largefiles'])
129 129
130 130 # override some extensions' stuff as well
131 131 for name, module in extensions.extensions():
132 132 if name == 'fetch':
133 133 extensions.wrapcommand(getattr(module, 'cmdtable'), 'fetch',
134 134 overrides.override_fetch)
135 135 if name == 'purge':
136 136 extensions.wrapcommand(getattr(module, 'cmdtable'), 'purge',
137 137 overrides.override_purge)
138 138 if name == 'rebase':
139 139 extensions.wrapcommand(getattr(module, 'cmdtable'), 'rebase',
140 140 overrides.override_rebase)
141 141 if name == 'transplant':
142 142 extensions.wrapcommand(getattr(module, 'cmdtable'), 'transplant',
143 143 overrides.override_transplant)
@@ -1,592 +1,589 b''
1 1 # merge.py - directory-level update/merge handling for Mercurial
2 2 #
3 3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import nullid, nullrev, hex, bin
9 9 from i18n import _
10 10 import scmutil, util, filemerge, copies, subrepo
11 11 import errno, os, shutil
12 12
13 13 class mergestate(object):
14 14 '''track 3-way merge state of individual files'''
15 15 def __init__(self, repo):
16 16 self._repo = repo
17 17 self._dirty = False
18 18 self._read()
19 19 def reset(self, node=None):
20 20 self._state = {}
21 21 if node:
22 22 self._local = node
23 23 shutil.rmtree(self._repo.join("merge"), True)
24 24 self._dirty = False
25 25 def _read(self):
26 26 self._state = {}
27 27 try:
28 28 f = self._repo.opener("merge/state")
29 29 for i, l in enumerate(f):
30 30 if i == 0:
31 31 self._local = bin(l[:-1])
32 32 else:
33 33 bits = l[:-1].split("\0")
34 34 self._state[bits[0]] = bits[1:]
35 35 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, flags):
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(), 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 f = self._repo.opener("merge/" + hash)
71 71 self._repo.wwrite(dfile, f.read(), flags)
72 72 f.close()
73 73 fcd = wctx[dfile]
74 74 fco = octx[ofile]
75 75 fca = self._repo.filectx(afile, fileid=anode)
76 76 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
77 77 if r is None:
78 78 # no real conflict
79 79 del self._state[dfile]
80 80 elif not r:
81 81 self.mark(dfile, 'r')
82 82 return r
83 83
84 def _checkunknown(wctx, mctx, folding):
84 def _checkunknownfile(repo, wctx, mctx, f):
85 return (not repo.dirstate._ignore(f)
86 and os.path.exists(repo.wjoin(f))
87 and mctx[f].cmp(wctx[f]))
88
89 def _checkunknown(repo, wctx, mctx):
85 90 "check for collisions between unknown files and files in mctx"
86 if folding:
87 foldf = util.normcase
88 else:
89 foldf = lambda fn: fn
90 folded = {}
91 for fn in mctx:
92 folded[foldf(fn)] = fn
93 91
94 92 error = False
95 for fn in wctx.unknown():
96 f = foldf(fn)
97 if f in folded and mctx[folded[f]].cmp(wctx[f]):
93 for f in mctx:
94 if f not in wctx and _checkunknownfile(repo, wctx, mctx, f):
98 95 error = True
99 wctx._repo.ui.warn(_("%s: untracked file differs\n") % fn)
96 wctx._repo.ui.warn(_("%s: untracked file differs\n") % f)
100 97 if error:
101 98 raise util.Abort(_("untracked files in working directory differ "
102 99 "from files in requested revision"))
103 100
104 101 def _checkcollision(mctx, wctx):
105 102 "check for case folding collisions in the destination context"
106 103 folded = {}
107 104 for fn in mctx:
108 105 fold = util.normcase(fn)
109 106 if fold in folded:
110 107 raise util.Abort(_("case-folding collision between %s and %s")
111 108 % (fn, folded[fold]))
112 109 folded[fold] = fn
113 110
114 111 if wctx:
115 112 for fn in wctx:
116 113 fold = util.normcase(fn)
117 114 mfn = folded.get(fold, None)
118 115 if mfn and (mfn != fn):
119 116 raise util.Abort(_("case-folding collision between %s and %s")
120 117 % (mfn, fn))
121 118
122 119 def _forgetremoved(wctx, mctx, branchmerge):
123 120 """
124 121 Forget removed files
125 122
126 123 If we're jumping between revisions (as opposed to merging), and if
127 124 neither the working directory nor the target rev has the file,
128 125 then we need to remove it from the dirstate, to prevent the
129 126 dirstate from listing the file when it is no longer in the
130 127 manifest.
131 128
132 129 If we're merging, and the other revision has removed a file
133 130 that is not present in the working directory, we need to mark it
134 131 as removed.
135 132 """
136 133
137 134 action = []
138 135 state = branchmerge and 'r' or 'f'
139 136 for f in wctx.deleted():
140 137 if f not in mctx:
141 138 action.append((f, state))
142 139
143 140 if not branchmerge:
144 141 for f in wctx.removed():
145 142 if f not in mctx:
146 143 action.append((f, "f"))
147 144
148 145 return action
149 146
150 147 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
151 148 """
152 149 Merge p1 and p2 with ancestor pa and generate merge action list
153 150
154 151 overwrite = whether we clobber working files
155 152 partial = function to filter file lists
156 153 """
157 154
158 155 def fmerge(f, f2, fa):
159 156 """merge flags"""
160 157 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
161 158 if m == n: # flags agree
162 159 return m # unchanged
163 160 if m and n and not a: # flags set, don't agree, differ from parent
164 161 r = repo.ui.promptchoice(
165 162 _(" conflicting flags for %s\n"
166 163 "(n)one, e(x)ec or sym(l)ink?") % f,
167 164 (_("&None"), _("E&xec"), _("Sym&link")), 0)
168 165 if r == 1:
169 166 return "x" # Exec
170 167 if r == 2:
171 168 return "l" # Symlink
172 169 return ""
173 170 if m and m != a: # changed from a to m
174 171 return m
175 172 if n and n != a: # changed from a to n
176 173 if n == 'l' or a == 'l':
177 174 # can't automatically merge symlink flag change here, let
178 175 # filemerge take care of it
179 176 return m
180 177 return n
181 178 return '' # flag was cleared
182 179
183 180 def act(msg, m, f, *args):
184 181 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
185 182 action.append((f, m) + args)
186 183
187 184 action, copy = [], {}
188 185
189 186 if overwrite:
190 187 pa = p1
191 188 elif pa == p2: # backwards
192 189 pa = p1.p1()
193 190 elif pa and repo.ui.configbool("merge", "followcopies", True):
194 191 dirs = repo.ui.configbool("merge", "followdirs", True)
195 192 copy, diverge = copies.mergecopies(repo, p1, p2, pa, dirs)
196 193 for of, fl in diverge.iteritems():
197 194 act("divergent renames", "dr", of, fl)
198 195
199 196 repo.ui.note(_("resolving manifests\n"))
200 197 repo.ui.debug(" overwrite: %s, partial: %s\n"
201 198 % (bool(overwrite), bool(partial)))
202 199 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, p1, p2))
203 200
204 201 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
205 202 copied = set(copy.values())
206 203
207 204 if '.hgsubstate' in m1:
208 205 # check whether sub state is modified
209 206 for s in p1.substate:
210 207 if p1.sub(s).dirty():
211 208 m1['.hgsubstate'] += "+"
212 209 break
213 210
214 211 # Compare manifests
215 212 for f, n in m1.iteritems():
216 213 if partial and not partial(f):
217 214 continue
218 215 if f in m2:
219 216 rflags = fmerge(f, f, f)
220 217 a = ma.get(f, nullid)
221 218 if n == m2[f] or m2[f] == a: # same or local newer
222 219 # is file locally modified or flags need changing?
223 220 # dirstate flags may need to be made current
224 221 if m1.flags(f) != rflags or n[20:]:
225 222 act("update permissions", "e", f, rflags)
226 223 elif n == a: # remote newer
227 224 act("remote is newer", "g", f, rflags)
228 225 else: # both changed
229 226 act("versions differ", "m", f, f, f, rflags, False)
230 227 elif f in copied: # files we'll deal with on m2 side
231 228 pass
232 229 elif f in copy:
233 230 f2 = copy[f]
234 231 if f2 not in m2: # directory rename
235 232 act("remote renamed directory to " + f2, "d",
236 233 f, None, f2, m1.flags(f))
237 234 else: # case 2 A,B/B/B or case 4,21 A/B/B
238 235 act("local copied/moved to " + f2, "m",
239 236 f, f2, f, fmerge(f, f2, f2), False)
240 237 elif f in ma: # clean, a different, no remote
241 238 if n != ma[f]:
242 239 if repo.ui.promptchoice(
243 240 _(" local changed %s which remote deleted\n"
244 241 "use (c)hanged version or (d)elete?") % f,
245 242 (_("&Changed"), _("&Delete")), 0):
246 243 act("prompt delete", "r", f)
247 244 else:
248 245 act("prompt keep", "a", f)
249 246 elif n[20:] == "a": # added, no remote
250 247 act("remote deleted", "f", f)
251 248 elif n[20:] != "u":
252 249 act("other deleted", "r", f)
253 250
254 251 for f, n in m2.iteritems():
255 252 if partial and not partial(f):
256 253 continue
257 254 if f in m1 or f in copied: # files already visited
258 255 continue
259 256 if f in copy:
260 257 f2 = copy[f]
261 258 if f2 not in m1: # directory rename
262 259 act("local renamed directory to " + f2, "d",
263 260 None, f, f2, m2.flags(f))
264 261 elif f2 in m2: # rename case 1, A/A,B/A
265 262 act("remote copied to " + f, "m",
266 263 f2, f, f, fmerge(f2, f, f2), False)
267 264 else: # case 3,20 A/B/A
268 265 act("remote moved to " + f, "m",
269 266 f2, f, f, fmerge(f2, f, f2), True)
270 267 elif f not in ma:
271 268 act("remote created", "g", f, m2.flags(f))
272 269 elif n != ma[f]:
273 270 if repo.ui.promptchoice(
274 271 _("remote changed %s which local deleted\n"
275 272 "use (c)hanged version or leave (d)eleted?") % f,
276 273 (_("&Changed"), _("&Deleted")), 0) == 0:
277 274 act("prompt recreating", "g", f, m2.flags(f))
278 275
279 276 return action
280 277
281 278 def actionkey(a):
282 279 return a[1] == 'r' and -1 or 0, a
283 280
284 281 def applyupdates(repo, action, wctx, mctx, actx, overwrite):
285 282 """apply the merge action list to the working directory
286 283
287 284 wctx is the working copy context
288 285 mctx is the context to be merged into the working copy
289 286 actx is the context of the common ancestor
290 287
291 288 Return a tuple of counts (updated, merged, removed, unresolved) that
292 289 describes how many files were affected by the update.
293 290 """
294 291
295 292 updated, merged, removed, unresolved = 0, 0, 0, 0
296 293 ms = mergestate(repo)
297 294 ms.reset(wctx.p1().node())
298 295 moves = []
299 296 action.sort(key=actionkey)
300 297
301 298 # prescan for merges
302 299 for a in action:
303 300 f, m = a[:2]
304 301 if m == 'm': # merge
305 302 f2, fd, flags, move = a[2:]
306 303 if f == '.hgsubstate': # merged internally
307 304 continue
308 305 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
309 306 fcl = wctx[f]
310 307 fco = mctx[f2]
311 308 if mctx == actx: # backwards, use working dir parent as ancestor
312 309 if fcl.parents():
313 310 fca = fcl.p1()
314 311 else:
315 312 fca = repo.filectx(f, fileid=nullrev)
316 313 else:
317 314 fca = fcl.ancestor(fco, actx)
318 315 if not fca:
319 316 fca = repo.filectx(f, fileid=nullrev)
320 317 ms.add(fcl, fco, fca, fd, flags)
321 318 if f != fd and move:
322 319 moves.append(f)
323 320
324 321 audit = scmutil.pathauditor(repo.root)
325 322
326 323 # remove renamed files after safely stored
327 324 for f in moves:
328 325 if os.path.lexists(repo.wjoin(f)):
329 326 repo.ui.debug("removing %s\n" % f)
330 327 audit(f)
331 328 os.unlink(repo.wjoin(f))
332 329
333 330 numupdates = len(action)
334 331 for i, a in enumerate(action):
335 332 f, m = a[:2]
336 333 repo.ui.progress(_('updating'), i + 1, item=f, total=numupdates,
337 334 unit=_('files'))
338 335 if f and f[0] == "/":
339 336 continue
340 337 if m == "r": # remove
341 338 repo.ui.note(_("removing %s\n") % f)
342 339 audit(f)
343 340 if f == '.hgsubstate': # subrepo states need updating
344 341 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
345 342 try:
346 343 util.unlinkpath(repo.wjoin(f))
347 344 except OSError, inst:
348 345 if inst.errno != errno.ENOENT:
349 346 repo.ui.warn(_("update failed to remove %s: %s!\n") %
350 347 (f, inst.strerror))
351 348 removed += 1
352 349 elif m == "m": # merge
353 350 if f == '.hgsubstate': # subrepo states need updating
354 351 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx), overwrite)
355 352 continue
356 353 f2, fd, flags, move = a[2:]
357 354 repo.wopener.audit(fd)
358 355 r = ms.resolve(fd, wctx, mctx)
359 356 if r is not None and r > 0:
360 357 unresolved += 1
361 358 else:
362 359 if r is None:
363 360 updated += 1
364 361 else:
365 362 merged += 1
366 363 if (move and repo.dirstate.normalize(fd) != f
367 364 and os.path.lexists(repo.wjoin(f))):
368 365 repo.ui.debug("removing %s\n" % f)
369 366 audit(f)
370 367 os.unlink(repo.wjoin(f))
371 368 elif m == "g": # get
372 369 flags = a[2]
373 370 repo.ui.note(_("getting %s\n") % f)
374 371 t = mctx.filectx(f).data()
375 372 repo.wwrite(f, t, flags)
376 373 t = None
377 374 updated += 1
378 375 if f == '.hgsubstate': # subrepo states need updating
379 376 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
380 377 elif m == "d": # directory rename
381 378 f2, fd, flags = a[2:]
382 379 if f:
383 380 repo.ui.note(_("moving %s to %s\n") % (f, fd))
384 381 audit(f)
385 382 t = wctx.filectx(f).data()
386 383 repo.wwrite(fd, t, flags)
387 384 util.unlinkpath(repo.wjoin(f))
388 385 if f2:
389 386 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
390 387 t = mctx.filectx(f2).data()
391 388 repo.wwrite(fd, t, flags)
392 389 updated += 1
393 390 elif m == "dr": # divergent renames
394 391 fl = a[2]
395 392 repo.ui.warn(_("note: possible conflict - %s was renamed "
396 393 "multiple times to:\n") % f)
397 394 for nf in fl:
398 395 repo.ui.warn(" %s\n" % nf)
399 396 elif m == "e": # exec
400 397 flags = a[2]
401 398 repo.wopener.audit(f)
402 399 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
403 400 ms.commit()
404 401 repo.ui.progress(_('updating'), None, total=numupdates, unit=_('files'))
405 402
406 403 return updated, merged, removed, unresolved
407 404
408 405 def recordupdates(repo, action, branchmerge):
409 406 "record merge actions to the dirstate"
410 407
411 408 for a in action:
412 409 f, m = a[:2]
413 410 if m == "r": # remove
414 411 if branchmerge:
415 412 repo.dirstate.remove(f)
416 413 else:
417 414 repo.dirstate.drop(f)
418 415 elif m == "a": # re-add
419 416 if not branchmerge:
420 417 repo.dirstate.add(f)
421 418 elif m == "f": # forget
422 419 repo.dirstate.drop(f)
423 420 elif m == "e": # exec change
424 421 repo.dirstate.normallookup(f)
425 422 elif m == "g": # get
426 423 if branchmerge:
427 424 repo.dirstate.otherparent(f)
428 425 else:
429 426 repo.dirstate.normal(f)
430 427 elif m == "m": # merge
431 428 f2, fd, flag, move = a[2:]
432 429 if branchmerge:
433 430 # We've done a branch merge, mark this file as merged
434 431 # so that we properly record the merger later
435 432 repo.dirstate.merge(fd)
436 433 if f != f2: # copy/rename
437 434 if move:
438 435 repo.dirstate.remove(f)
439 436 if f != fd:
440 437 repo.dirstate.copy(f, fd)
441 438 else:
442 439 repo.dirstate.copy(f2, fd)
443 440 else:
444 441 # We've update-merged a locally modified file, so
445 442 # we set the dirstate to emulate a normal checkout
446 443 # of that file some time in the past. Thus our
447 444 # merge will appear as a normal local file
448 445 # modification.
449 446 if f2 == fd: # file not locally copied/moved
450 447 repo.dirstate.normallookup(fd)
451 448 if move:
452 449 repo.dirstate.drop(f)
453 450 elif m == "d": # directory rename
454 451 f2, fd, flag = a[2:]
455 452 if not f2 and f not in repo.dirstate:
456 453 # untracked file moved
457 454 continue
458 455 if branchmerge:
459 456 repo.dirstate.add(fd)
460 457 if f:
461 458 repo.dirstate.remove(f)
462 459 repo.dirstate.copy(f, fd)
463 460 if f2:
464 461 repo.dirstate.copy(f2, fd)
465 462 else:
466 463 repo.dirstate.normal(fd)
467 464 if f:
468 465 repo.dirstate.drop(f)
469 466
470 467 def update(repo, node, branchmerge, force, partial, ancestor=None):
471 468 """
472 469 Perform a merge between the working directory and the given node
473 470
474 471 node = the node to update to, or None if unspecified
475 472 branchmerge = whether to merge between branches
476 473 force = whether to force branch merging or file overwriting
477 474 partial = a function to filter file lists (dirstate not updated)
478 475
479 476 The table below shows all the behaviors of the update command
480 477 given the -c and -C or no options, whether the working directory
481 478 is dirty, whether a revision is specified, and the relationship of
482 479 the parent rev to the target rev (linear, on the same named
483 480 branch, or on another named branch).
484 481
485 482 This logic is tested by test-update-branches.t.
486 483
487 484 -c -C dirty rev | linear same cross
488 485 n n n n | ok (1) x
489 486 n n n y | ok ok ok
490 487 n n y * | merge (2) (2)
491 488 n y * * | --- discard ---
492 489 y n y * | --- (3) ---
493 490 y n n * | --- ok ---
494 491 y y * * | --- (4) ---
495 492
496 493 x = can't happen
497 494 * = don't-care
498 495 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
499 496 2 = abort: crosses branches (use 'hg merge' to merge or
500 497 use 'hg update -C' to discard changes)
501 498 3 = abort: uncommitted local changes
502 499 4 = incompatible options (checked in commands.py)
503 500
504 501 Return the same tuple as applyupdates().
505 502 """
506 503
507 504 onode = node
508 505 wlock = repo.wlock()
509 506 try:
510 507 wc = repo[None]
511 508 if node is None:
512 509 # tip of current branch
513 510 try:
514 511 node = repo.branchtags()[wc.branch()]
515 512 except KeyError:
516 513 if wc.branch() == "default": # no default branch!
517 514 node = repo.lookup("tip") # update to tip
518 515 else:
519 516 raise util.Abort(_("branch %s not found") % wc.branch())
520 517 overwrite = force and not branchmerge
521 518 pl = wc.parents()
522 519 p1, p2 = pl[0], repo[node]
523 520 if ancestor:
524 521 pa = repo[ancestor]
525 522 else:
526 523 pa = p1.ancestor(p2)
527 524
528 525 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
529 526
530 527 ### check phase
531 528 if not overwrite and len(pl) > 1:
532 529 raise util.Abort(_("outstanding uncommitted merges"))
533 530 if branchmerge:
534 531 if pa == p2:
535 532 raise util.Abort(_("merging with a working directory ancestor"
536 533 " has no effect"))
537 534 elif pa == p1:
538 535 if p1.branch() == p2.branch():
539 536 raise util.Abort(_("nothing to merge"),
540 537 hint=_("use 'hg update' "
541 538 "or check 'hg heads'"))
542 539 if not force and (wc.files() or wc.deleted()):
543 540 raise util.Abort(_("outstanding uncommitted changes"),
544 541 hint=_("use 'hg status' to list changes"))
545 542 for s in wc.substate:
546 543 if wc.sub(s).dirty():
547 544 raise util.Abort(_("outstanding uncommitted changes in "
548 545 "subrepository '%s'") % s)
549 546
550 547 elif not overwrite:
551 548 if pa == p1 or pa == p2: # linear
552 549 pass # all good
553 550 elif wc.dirty(missing=True):
554 551 raise util.Abort(_("crosses branches (merge branches or use"
555 552 " --clean to discard changes)"))
556 553 elif onode is None:
557 554 raise util.Abort(_("crosses branches (merge branches or update"
558 555 " --check to force update)"))
559 556 else:
560 557 # Allow jumping branches if clean and specific rev given
561 558 pa = p1
562 559
563 560 ### calculate phase
564 561 action = []
565 562 wc.status(unknown=True) # prime cache
566 563 folding = not util.checkcase(repo.path)
567 564 if not force:
568 _checkunknown(wc, p2, folding)
565 _checkunknown(repo, wc, p2)
569 566 if folding:
570 567 _checkcollision(p2, branchmerge and p1)
571 568 action += _forgetremoved(wc, p2, branchmerge)
572 569 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
573 570
574 571 ### apply phase
575 572 if not branchmerge: # just jump to the new rev
576 573 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
577 574 if not partial:
578 575 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
579 576
580 577 stats = applyupdates(repo, action, wc, p2, pa, overwrite)
581 578
582 579 if not partial:
583 580 repo.dirstate.setparents(fp1, fp2)
584 581 recordupdates(repo, action, branchmerge)
585 582 if not branchmerge:
586 583 repo.dirstate.setbranch(p2.branch())
587 584 finally:
588 585 wlock.release()
589 586
590 587 if not partial:
591 588 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
592 589 return stats
General Comments 0
You need to be logged in to leave comments. Login now