##// END OF EJS Templates
merge: check for untracked files more precisely (issue3400)...
Matt Mackall -
r16534:11212bab stable
parent child Browse files
Show More
@@ -1,608 +1,608
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 84 def _checkunknownfile(repo, wctx, mctx, f):
85 85 return (not repo.dirstate._ignore(f)
86 and os.path.exists(repo.wjoin(f))
86 and os.path.isfile(repo.wjoin(f))
87 87 and repo.dirstate.normalize(f) not in repo.dirstate
88 88 and mctx[f].cmp(wctx[f]))
89 89
90 90 def _checkunknown(repo, wctx, mctx):
91 91 "check for collisions between unknown files and files in mctx"
92 92
93 93 error = False
94 94 for f in mctx:
95 95 if f not in wctx and _checkunknownfile(repo, wctx, mctx, f):
96 96 error = True
97 97 wctx._repo.ui.warn(_("%s: untracked file differs\n") % f)
98 98 if error:
99 99 raise util.Abort(_("untracked files in working directory differ "
100 100 "from files in requested revision"))
101 101
102 102 def _checkcollision(mctx, wctx):
103 103 "check for case folding collisions in the destination context"
104 104 folded = {}
105 105 for fn in mctx:
106 106 fold = util.normcase(fn)
107 107 if fold in folded:
108 108 raise util.Abort(_("case-folding collision between %s and %s")
109 109 % (fn, folded[fold]))
110 110 folded[fold] = fn
111 111
112 112 if wctx:
113 113 # class to delay looking up copy mapping
114 114 class pathcopies(object):
115 115 @util.propertycache
116 116 def map(self):
117 117 # {dst@mctx: src@wctx} copy mapping
118 118 return copies.pathcopies(wctx, mctx)
119 119 pc = pathcopies()
120 120
121 121 for fn in wctx:
122 122 fold = util.normcase(fn)
123 123 mfn = folded.get(fold, None)
124 124 if mfn and mfn != fn and pc.map.get(mfn) != fn:
125 125 raise util.Abort(_("case-folding collision between %s and %s")
126 126 % (mfn, fn))
127 127
128 128 def _forgetremoved(wctx, mctx, branchmerge):
129 129 """
130 130 Forget removed files
131 131
132 132 If we're jumping between revisions (as opposed to merging), and if
133 133 neither the working directory nor the target rev has the file,
134 134 then we need to remove it from the dirstate, to prevent the
135 135 dirstate from listing the file when it is no longer in the
136 136 manifest.
137 137
138 138 If we're merging, and the other revision has removed a file
139 139 that is not present in the working directory, we need to mark it
140 140 as removed.
141 141 """
142 142
143 143 action = []
144 144 state = branchmerge and 'r' or 'f'
145 145 for f in wctx.deleted():
146 146 if f not in mctx:
147 147 action.append((f, state))
148 148
149 149 if not branchmerge:
150 150 for f in wctx.removed():
151 151 if f not in mctx:
152 152 action.append((f, "f"))
153 153
154 154 return action
155 155
156 156 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
157 157 """
158 158 Merge p1 and p2 with ancestor pa and generate merge action list
159 159
160 160 overwrite = whether we clobber working files
161 161 partial = function to filter file lists
162 162 """
163 163
164 164 def fmerge(f, f2, fa):
165 165 """merge flags"""
166 166 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
167 167 if m == n: # flags agree
168 168 return m # unchanged
169 169 if m and n and not a: # flags set, don't agree, differ from parent
170 170 r = repo.ui.promptchoice(
171 171 _(" conflicting flags for %s\n"
172 172 "(n)one, e(x)ec or sym(l)ink?") % f,
173 173 (_("&None"), _("E&xec"), _("Sym&link")), 0)
174 174 if r == 1:
175 175 return "x" # Exec
176 176 if r == 2:
177 177 return "l" # Symlink
178 178 return ""
179 179 if m and m != a: # changed from a to m
180 180 return m
181 181 if n and n != a: # changed from a to n
182 182 if (n == 'l' or a == 'l') and m1.get(f) != ma.get(f):
183 183 # can't automatically merge symlink flag when there
184 184 # are file-level conflicts here, let filemerge take
185 185 # care of it
186 186 return m
187 187 return n
188 188 return '' # flag was cleared
189 189
190 190 def act(msg, m, f, *args):
191 191 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
192 192 action.append((f, m) + args)
193 193
194 194 action, copy = [], {}
195 195
196 196 if overwrite:
197 197 pa = p1
198 198 elif pa == p2: # backwards
199 199 pa = p1.p1()
200 200 elif pa and repo.ui.configbool("merge", "followcopies", True):
201 201 copy, diverge = copies.mergecopies(repo, p1, p2, pa)
202 202 for of, fl in diverge.iteritems():
203 203 act("divergent renames", "dr", of, fl)
204 204
205 205 repo.ui.note(_("resolving manifests\n"))
206 206 repo.ui.debug(" overwrite: %s, partial: %s\n"
207 207 % (bool(overwrite), bool(partial)))
208 208 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, p1, p2))
209 209
210 210 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
211 211 copied = set(copy.values())
212 212
213 213 if '.hgsubstate' in m1:
214 214 # check whether sub state is modified
215 215 for s in p1.substate:
216 216 if p1.sub(s).dirty():
217 217 m1['.hgsubstate'] += "+"
218 218 break
219 219
220 220 # Compare manifests
221 221 for f, n in m1.iteritems():
222 222 if partial and not partial(f):
223 223 continue
224 224 if f in m2:
225 225 rflags = fmerge(f, f, f)
226 226 a = ma.get(f, nullid)
227 227 if n == m2[f] or m2[f] == a: # same or local newer
228 228 # is file locally modified or flags need changing?
229 229 # dirstate flags may need to be made current
230 230 if m1.flags(f) != rflags or n[20:]:
231 231 act("update permissions", "e", f, rflags)
232 232 elif n == a: # remote newer
233 233 act("remote is newer", "g", f, rflags)
234 234 else: # both changed
235 235 act("versions differ", "m", f, f, f, rflags, False)
236 236 elif f in copied: # files we'll deal with on m2 side
237 237 pass
238 238 elif f in copy:
239 239 f2 = copy[f]
240 240 if f2 not in m2: # directory rename
241 241 act("remote renamed directory to " + f2, "d",
242 242 f, None, f2, m1.flags(f))
243 243 else: # case 2 A,B/B/B or case 4,21 A/B/B
244 244 act("local copied/moved to " + f2, "m",
245 245 f, f2, f, fmerge(f, f2, f2), False)
246 246 elif f in ma: # clean, a different, no remote
247 247 if n != ma[f]:
248 248 if repo.ui.promptchoice(
249 249 _(" local changed %s which remote deleted\n"
250 250 "use (c)hanged version or (d)elete?") % f,
251 251 (_("&Changed"), _("&Delete")), 0):
252 252 act("prompt delete", "r", f)
253 253 else:
254 254 act("prompt keep", "a", f)
255 255 elif n[20:] == "a": # added, no remote
256 256 act("remote deleted", "f", f)
257 257 else:
258 258 act("other deleted", "r", f)
259 259
260 260 for f, n in m2.iteritems():
261 261 if partial and not partial(f):
262 262 continue
263 263 if f in m1 or f in copied: # files already visited
264 264 continue
265 265 if f in copy:
266 266 f2 = copy[f]
267 267 if f2 not in m1: # directory rename
268 268 act("local renamed directory to " + f2, "d",
269 269 None, f, f2, m2.flags(f))
270 270 elif f2 in m2: # rename case 1, A/A,B/A
271 271 act("remote copied to " + f, "m",
272 272 f2, f, f, fmerge(f2, f, f2), False)
273 273 else: # case 3,20 A/B/A
274 274 act("remote moved to " + f, "m",
275 275 f2, f, f, fmerge(f2, f, f2), True)
276 276 elif f not in ma:
277 277 if (not overwrite
278 278 and _checkunknownfile(repo, p1, p2, f)):
279 279 rflags = fmerge(f, f, f)
280 280 act("remote differs from untracked local",
281 281 "m", f, f, f, rflags, False)
282 282 else:
283 283 act("remote created", "g", f, m2.flags(f))
284 284 elif n != ma[f]:
285 285 if repo.ui.promptchoice(
286 286 _("remote changed %s which local deleted\n"
287 287 "use (c)hanged version or leave (d)eleted?") % f,
288 288 (_("&Changed"), _("&Deleted")), 0) == 0:
289 289 act("prompt recreating", "g", f, m2.flags(f))
290 290
291 291 return action
292 292
293 293 def actionkey(a):
294 294 return a[1] == 'r' and -1 or 0, a
295 295
296 296 def applyupdates(repo, action, wctx, mctx, actx, overwrite):
297 297 """apply the merge action list to the working directory
298 298
299 299 wctx is the working copy context
300 300 mctx is the context to be merged into the working copy
301 301 actx is the context of the common ancestor
302 302
303 303 Return a tuple of counts (updated, merged, removed, unresolved) that
304 304 describes how many files were affected by the update.
305 305 """
306 306
307 307 updated, merged, removed, unresolved = 0, 0, 0, 0
308 308 ms = mergestate(repo)
309 309 ms.reset(wctx.p1().node())
310 310 moves = []
311 311 action.sort(key=actionkey)
312 312
313 313 # prescan for merges
314 314 for a in action:
315 315 f, m = a[:2]
316 316 if m == 'm': # merge
317 317 f2, fd, flags, move = a[2:]
318 318 if f == '.hgsubstate': # merged internally
319 319 continue
320 320 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
321 321 fcl = wctx[f]
322 322 fco = mctx[f2]
323 323 if mctx == actx: # backwards, use working dir parent as ancestor
324 324 if fcl.parents():
325 325 fca = fcl.p1()
326 326 else:
327 327 fca = repo.filectx(f, fileid=nullrev)
328 328 else:
329 329 fca = fcl.ancestor(fco, actx)
330 330 if not fca:
331 331 fca = repo.filectx(f, fileid=nullrev)
332 332 ms.add(fcl, fco, fca, fd, flags)
333 333 if f != fd and move:
334 334 moves.append(f)
335 335
336 336 audit = scmutil.pathauditor(repo.root)
337 337
338 338 # remove renamed files after safely stored
339 339 for f in moves:
340 340 if os.path.lexists(repo.wjoin(f)):
341 341 repo.ui.debug("removing %s\n" % f)
342 342 audit(f)
343 343 os.unlink(repo.wjoin(f))
344 344
345 345 numupdates = len(action)
346 346 for i, a in enumerate(action):
347 347 f, m = a[:2]
348 348 repo.ui.progress(_('updating'), i + 1, item=f, total=numupdates,
349 349 unit=_('files'))
350 350 if f and f[0] == "/":
351 351 continue
352 352 if m == "r": # remove
353 353 repo.ui.note(_("removing %s\n") % f)
354 354 audit(f)
355 355 if f == '.hgsubstate': # subrepo states need updating
356 356 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
357 357 try:
358 358 util.unlinkpath(repo.wjoin(f))
359 359 except OSError, inst:
360 360 if inst.errno != errno.ENOENT:
361 361 repo.ui.warn(_("update failed to remove %s: %s!\n") %
362 362 (f, inst.strerror))
363 363 removed += 1
364 364 elif m == "m": # merge
365 365 if f == '.hgsubstate': # subrepo states need updating
366 366 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx), overwrite)
367 367 continue
368 368 f2, fd, flags, move = a[2:]
369 369 repo.wopener.audit(fd)
370 370 r = ms.resolve(fd, wctx, mctx)
371 371 if r is not None and r > 0:
372 372 unresolved += 1
373 373 else:
374 374 if r is None:
375 375 updated += 1
376 376 else:
377 377 merged += 1
378 378 if (move and repo.dirstate.normalize(fd) != f
379 379 and os.path.lexists(repo.wjoin(f))):
380 380 repo.ui.debug("removing %s\n" % f)
381 381 audit(f)
382 382 os.unlink(repo.wjoin(f))
383 383 elif m == "g": # get
384 384 flags = a[2]
385 385 repo.ui.note(_("getting %s\n") % f)
386 386 t = mctx.filectx(f).data()
387 387 repo.wwrite(f, t, flags)
388 388 t = None
389 389 updated += 1
390 390 if f == '.hgsubstate': # subrepo states need updating
391 391 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
392 392 elif m == "d": # directory rename
393 393 f2, fd, flags = a[2:]
394 394 if f:
395 395 repo.ui.note(_("moving %s to %s\n") % (f, fd))
396 396 audit(f)
397 397 t = wctx.filectx(f).data()
398 398 repo.wwrite(fd, t, flags)
399 399 util.unlinkpath(repo.wjoin(f))
400 400 if f2:
401 401 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
402 402 t = mctx.filectx(f2).data()
403 403 repo.wwrite(fd, t, flags)
404 404 updated += 1
405 405 elif m == "dr": # divergent renames
406 406 fl = a[2]
407 407 repo.ui.warn(_("note: possible conflict - %s was renamed "
408 408 "multiple times to:\n") % f)
409 409 for nf in fl:
410 410 repo.ui.warn(" %s\n" % nf)
411 411 elif m == "e": # exec
412 412 flags = a[2]
413 413 repo.wopener.audit(f)
414 414 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
415 415 ms.commit()
416 416 repo.ui.progress(_('updating'), None, total=numupdates, unit=_('files'))
417 417
418 418 return updated, merged, removed, unresolved
419 419
420 420 def recordupdates(repo, action, branchmerge):
421 421 "record merge actions to the dirstate"
422 422
423 423 for a in action:
424 424 f, m = a[:2]
425 425 if m == "r": # remove
426 426 if branchmerge:
427 427 repo.dirstate.remove(f)
428 428 else:
429 429 repo.dirstate.drop(f)
430 430 elif m == "a": # re-add
431 431 if not branchmerge:
432 432 repo.dirstate.add(f)
433 433 elif m == "f": # forget
434 434 repo.dirstate.drop(f)
435 435 elif m == "e": # exec change
436 436 repo.dirstate.normallookup(f)
437 437 elif m == "g": # get
438 438 if branchmerge:
439 439 repo.dirstate.otherparent(f)
440 440 else:
441 441 repo.dirstate.normal(f)
442 442 elif m == "m": # merge
443 443 f2, fd, flag, move = a[2:]
444 444 if branchmerge:
445 445 # We've done a branch merge, mark this file as merged
446 446 # so that we properly record the merger later
447 447 repo.dirstate.merge(fd)
448 448 if f != f2: # copy/rename
449 449 if move:
450 450 repo.dirstate.remove(f)
451 451 if f != fd:
452 452 repo.dirstate.copy(f, fd)
453 453 else:
454 454 repo.dirstate.copy(f2, fd)
455 455 else:
456 456 # We've update-merged a locally modified file, so
457 457 # we set the dirstate to emulate a normal checkout
458 458 # of that file some time in the past. Thus our
459 459 # merge will appear as a normal local file
460 460 # modification.
461 461 if f2 == fd: # file not locally copied/moved
462 462 repo.dirstate.normallookup(fd)
463 463 if move:
464 464 repo.dirstate.drop(f)
465 465 elif m == "d": # directory rename
466 466 f2, fd, flag = a[2:]
467 467 if not f2 and f not in repo.dirstate:
468 468 # untracked file moved
469 469 continue
470 470 if branchmerge:
471 471 repo.dirstate.add(fd)
472 472 if f:
473 473 repo.dirstate.remove(f)
474 474 repo.dirstate.copy(f, fd)
475 475 if f2:
476 476 repo.dirstate.copy(f2, fd)
477 477 else:
478 478 repo.dirstate.normal(fd)
479 479 if f:
480 480 repo.dirstate.drop(f)
481 481
482 482 def update(repo, node, branchmerge, force, partial, ancestor=None):
483 483 """
484 484 Perform a merge between the working directory and the given node
485 485
486 486 node = the node to update to, or None if unspecified
487 487 branchmerge = whether to merge between branches
488 488 force = whether to force branch merging or file overwriting
489 489 partial = a function to filter file lists (dirstate not updated)
490 490
491 491 The table below shows all the behaviors of the update command
492 492 given the -c and -C or no options, whether the working directory
493 493 is dirty, whether a revision is specified, and the relationship of
494 494 the parent rev to the target rev (linear, on the same named
495 495 branch, or on another named branch).
496 496
497 497 This logic is tested by test-update-branches.t.
498 498
499 499 -c -C dirty rev | linear same cross
500 500 n n n n | ok (1) x
501 501 n n n y | ok ok ok
502 502 n n y * | merge (2) (2)
503 503 n y * * | --- discard ---
504 504 y n y * | --- (3) ---
505 505 y n n * | --- ok ---
506 506 y y * * | --- (4) ---
507 507
508 508 x = can't happen
509 509 * = don't-care
510 510 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
511 511 2 = abort: crosses branches (use 'hg merge' to merge or
512 512 use 'hg update -C' to discard changes)
513 513 3 = abort: uncommitted local changes
514 514 4 = incompatible options (checked in commands.py)
515 515
516 516 Return the same tuple as applyupdates().
517 517 """
518 518
519 519 onode = node
520 520 wlock = repo.wlock()
521 521 try:
522 522 wc = repo[None]
523 523 if node is None:
524 524 # tip of current branch
525 525 try:
526 526 node = repo.branchtags()[wc.branch()]
527 527 except KeyError:
528 528 if wc.branch() == "default": # no default branch!
529 529 node = repo.lookup("tip") # update to tip
530 530 else:
531 531 raise util.Abort(_("branch %s not found") % wc.branch())
532 532 overwrite = force and not branchmerge
533 533 pl = wc.parents()
534 534 p1, p2 = pl[0], repo[node]
535 535 if ancestor:
536 536 pa = repo[ancestor]
537 537 else:
538 538 pa = p1.ancestor(p2)
539 539
540 540 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
541 541
542 542 ### check phase
543 543 if not overwrite and len(pl) > 1:
544 544 raise util.Abort(_("outstanding uncommitted merges"))
545 545 if branchmerge:
546 546 if pa == p2:
547 547 raise util.Abort(_("merging with a working directory ancestor"
548 548 " has no effect"))
549 549 elif pa == p1:
550 550 if p1.branch() == p2.branch():
551 551 raise util.Abort(_("nothing to merge"),
552 552 hint=_("use 'hg update' "
553 553 "or check 'hg heads'"))
554 554 if not force and (wc.files() or wc.deleted()):
555 555 raise util.Abort(_("outstanding uncommitted changes"),
556 556 hint=_("use 'hg status' to list changes"))
557 557 for s in wc.substate:
558 558 if wc.sub(s).dirty():
559 559 raise util.Abort(_("outstanding uncommitted changes in "
560 560 "subrepository '%s'") % s)
561 561
562 562 elif not overwrite:
563 563 if pa == p1 or pa == p2: # linear
564 564 pass # all good
565 565 elif wc.dirty(missing=True):
566 566 raise util.Abort(_("crosses branches (merge branches or use"
567 567 " --clean to discard changes)"))
568 568 elif onode is None:
569 569 raise util.Abort(_("crosses branches (merge branches or update"
570 570 " --check to force update)"))
571 571 else:
572 572 # Allow jumping branches if clean and specific rev given
573 573 pa = p1
574 574
575 575 ### calculate phase
576 576 action = []
577 577 folding = not util.checkcase(repo.path)
578 578 if folding:
579 579 # collision check is not needed for clean update
580 580 if (not branchmerge and
581 581 (force or not wc.dirty(missing=True, branch=False))):
582 582 _checkcollision(p2, None)
583 583 else:
584 584 _checkcollision(p2, wc)
585 585 if not force:
586 586 _checkunknown(repo, wc, p2)
587 587 action += _forgetremoved(wc, p2, branchmerge)
588 588 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
589 589
590 590 ### apply phase
591 591 if not branchmerge: # just jump to the new rev
592 592 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
593 593 if not partial:
594 594 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
595 595
596 596 stats = applyupdates(repo, action, wc, p2, pa, overwrite)
597 597
598 598 if not partial:
599 599 repo.dirstate.setparents(fp1, fp2)
600 600 recordupdates(repo, action, branchmerge)
601 601 if not branchmerge:
602 602 repo.dirstate.setbranch(p2.branch())
603 603 finally:
604 604 wlock.release()
605 605
606 606 if not partial:
607 607 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
608 608 return stats
General Comments 0
You need to be logged in to leave comments. Login now