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