comparison mercurial/merge.py @ 18338:384df4db6520

merge: merge file flags together with file content The 'x' flag and the 'l' flag are very different. It is usually not a problem to change the 'x' flag of a normal file independent of the content, but one does not simply change the type of a file to 'l' independent of the content. This removes the fmerge function that merged both 'x' and 'l' independent of content early in the merge process. This correctly introduces some conflicts instead of silent incorrect merges. 3-way flag merge will now be done in the resolve process, right next to file content merge. Conflicts can thus be resolved with (slightly inconvenient) resolve commands like 'resolve f --tool internal:other'. It thus brings us closer to be able to re-solve manifest merge after the merge and avoid prompts during merge. This also removes the "conflicting flags for a - (n)one, e(x)ec or sym(l)ink?" prompt that nobody could answer and that made it easy to mix symlink targets and file contents up. Instead it will give a file merge where a sufficiently clever merge tool can help resolving the issue.
author Mads Kiilerich <mads@kiilerich.com>
date Wed, 09 Jan 2013 02:02:45 +0100
parents 77973b6a7b0b
children aadefcee1f5e
comparison
equal deleted inserted replaced
18337:557c8522aec0 18338:384df4db6520
43 f.write(hex(self._local) + "\n") 43 f.write(hex(self._local) + "\n")
44 for d, v in self._state.iteritems(): 44 for d, v in self._state.iteritems():
45 f.write("\0".join([d] + v) + "\n") 45 f.write("\0".join([d] + v) + "\n")
46 f.close() 46 f.close()
47 self._dirty = False 47 self._dirty = False
48 def add(self, fcl, fco, fca, fd, flags): 48 def add(self, fcl, fco, fca, fd):
49 hash = util.sha1(fcl.path()).hexdigest() 49 hash = util.sha1(fcl.path()).hexdigest()
50 self._repo.opener.write("merge/" + hash, fcl.data()) 50 self._repo.opener.write("merge/" + hash, fcl.data())
51 self._state[fd] = ['u', hash, fcl.path(), fca.path(), 51 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
52 hex(fca.filenode()), fco.path(), flags] 52 hex(fca.filenode()), fco.path(), fcl.flags()]
53 self._dirty = True 53 self._dirty = True
54 def __contains__(self, dfile): 54 def __contains__(self, dfile):
55 return dfile in self._state 55 return dfile in self._state
56 def __getitem__(self, dfile): 56 def __getitem__(self, dfile):
57 return self._state[dfile][0] 57 return self._state[dfile][0]
65 self._dirty = True 65 self._dirty = True
66 def resolve(self, dfile, wctx, octx): 66 def resolve(self, dfile, wctx, octx):
67 if self[dfile] == 'r': 67 if self[dfile] == 'r':
68 return 0 68 return 0
69 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile] 69 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
70 fcd = wctx[dfile]
71 fco = octx[ofile]
72 fca = self._repo.filectx(afile, fileid=anode)
73 # "premerge" x flags
74 flo = fco.flags()
75 fla = fca.flags()
76 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
77 if fca.node() == nullid:
78 self._repo.ui.warn(_('warning: cannot merge flags for %s\n') %
79 afile)
80 elif flags == fla:
81 flags = flo
82 # restore local
70 f = self._repo.opener("merge/" + hash) 83 f = self._repo.opener("merge/" + hash)
71 self._repo.wwrite(dfile, f.read(), flags) 84 self._repo.wwrite(dfile, f.read(), flags)
72 f.close() 85 f.close()
73 fcd = wctx[dfile]
74 fco = octx[ofile]
75 fca = self._repo.filectx(afile, fileid=anode)
76 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca) 86 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
77 if r is None: 87 if r is None:
78 # no real conflict 88 # no real conflict
79 del self._state[dfile] 89 del self._state[dfile]
80 elif not r: 90 elif not r:
181 191
182 overwrite = whether we clobber working files 192 overwrite = whether we clobber working files
183 partial = function to filter file lists 193 partial = function to filter file lists
184 """ 194 """
185 195
186 def fmerge(f, f2, fa):
187 """merge flags"""
188 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
189 if m == n: # flags agree
190 return m # unchanged
191 if m and n and not a: # flags set, don't agree, differ from parent
192 r = repo.ui.promptchoice(
193 _(" conflicting flags for %s\n"
194 "(n)one, e(x)ec or sym(l)ink?") % f,
195 (_("&None"), _("E&xec"), _("Sym&link")), 0)
196 if r == 1:
197 return "x" # Exec
198 if r == 2:
199 return "l" # Symlink
200 return ""
201 if m and m != a: # changed from a to m
202 return m
203 if n and n != a: # changed from a to n
204 if (n == 'l' or a == 'l') and m1.get(f) != ma.get(f):
205 # can't automatically merge symlink flag when there
206 # are file-level conflicts here, let filemerge take
207 # care of it
208 return m
209 return n
210 return '' # flag was cleared
211
212 def act(msg, m, f, *args): 196 def act(msg, m, f, *args):
213 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m)) 197 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
214 actions.append((f, m) + args) 198 actions.append((f, m) + args)
215 199
216 actions, copy, movewithdir = [], {}, {} 200 actions, copy, movewithdir = [], {}, {}
246 # Compare manifests 230 # Compare manifests
247 for f, n in m1.iteritems(): 231 for f, n in m1.iteritems():
248 if partial and not partial(f): 232 if partial and not partial(f):
249 continue 233 continue
250 if f in m2: 234 if f in m2:
251 rflags = fmerge(f, f, f) 235 n2 = m2[f]
236 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
237 nol = 'l' not in fl1 + fl2 + fla
252 a = ma.get(f, nullid) 238 a = ma.get(f, nullid)
253 if n == m2[f] or m2[f] == a: # same or local newer 239 if n == n2 and fl1 == fl2:
254 # is file locally modified or flags need changing? 240 pass # same - keep local
255 # dirstate flags may need to be made current 241 elif n2 == a and fl2 == fla:
256 if m1.flags(f) != rflags or n[20:]: 242 pass # remote unchanged - keep local
257 act("update permissions", "e", f, rflags) 243 elif n == a and fl1 == fla: # local unchanged - use remote
258 elif n == a: # remote newer 244 if n == n2: # optimization: keep local content
259 act("remote is newer", "g", f, rflags) 245 act("update permissions", "e", f, fl2)
260 else: # both changed 246 else:
261 act("versions differ", "m", f, f, f, rflags, False) 247 act("remote is newer", "g", f, fl2)
248 elif nol and n2 == a: # remote only changed 'x'
249 act("update permissions", "e", f, fl2)
250 elif nol and n == a: # local only changed 'x'
251 act("remote is newer", "g", f, fl)
252 else: # both changed something
253 act("versions differ", "m", f, f, f, False)
262 elif f in copied: # files we'll deal with on m2 side 254 elif f in copied: # files we'll deal with on m2 side
263 pass 255 pass
264 elif f in movewithdir: # directory rename 256 elif f in movewithdir: # directory rename
265 f2 = movewithdir[f] 257 f2 = movewithdir[f]
266 act("remote renamed directory to " + f2, "d", f, None, f2, 258 act("remote renamed directory to " + f2, "d", f, None, f2,
267 m1.flags(f)) 259 m1.flags(f))
268 elif f in copy: # case 2 A,B/B/B or case 4,21 A/B/B 260 elif f in copy: # case 2 A,B/B/B or case 4,21 A/B/B
269 f2 = copy[f] 261 f2 = copy[f]
270 act("local copied/moved to " + f2, "m", f, f2, f, 262 act("local copied/moved to " + f2, "m", f, f2, f, False)
271 fmerge(f, f2, f2), False)
272 elif f in ma: # clean, a different, no remote 263 elif f in ma: # clean, a different, no remote
273 if n != ma[f]: 264 if n != ma[f]:
274 if repo.ui.promptchoice( 265 if repo.ui.promptchoice(
275 _(" local changed %s which remote deleted\n" 266 _(" local changed %s which remote deleted\n"
276 "use (c)hanged version or (d)elete?") % f, 267 "use (c)hanged version or (d)elete?") % f,
294 m2.flags(f)) 285 m2.flags(f))
295 elif f in copy: 286 elif f in copy:
296 f2 = copy[f] 287 f2 = copy[f]
297 if f2 in m2: # rename case 1, A/A,B/A 288 if f2 in m2: # rename case 1, A/A,B/A
298 act("remote copied to " + f, "m", 289 act("remote copied to " + f, "m",
299 f2, f, f, fmerge(f2, f, f2), False) 290 f2, f, f, False)
300 else: # case 3,20 A/B/A 291 else: # case 3,20 A/B/A
301 act("remote moved to " + f, "m", 292 act("remote moved to " + f, "m",
302 f2, f, f, fmerge(f2, f, f2), True) 293 f2, f, f, True)
303 elif f not in ma: 294 elif f not in ma:
304 if (not overwrite 295 if (not overwrite
305 and _checkunknownfile(repo, p1, p2, f)): 296 and _checkunknownfile(repo, p1, p2, f)):
306 rflags = fmerge(f, f, f)
307 act("remote differs from untracked local", 297 act("remote differs from untracked local",
308 "m", f, f, f, rflags, False) 298 "m", f, f, f, False)
309 else: 299 else:
310 act("remote created", "g", f, m2.flags(f)) 300 act("remote created", "g", f, m2.flags(f))
311 elif n != ma[f]: 301 elif n != ma[f]:
312 if repo.ui.promptchoice( 302 if repo.ui.promptchoice(
313 _("remote changed %s which local deleted\n" 303 _("remote changed %s which local deleted\n"
339 329
340 # prescan for merges 330 # prescan for merges
341 for a in actions: 331 for a in actions:
342 f, m = a[:2] 332 f, m = a[:2]
343 if m == "m": # merge 333 if m == "m": # merge
344 f2, fd, flags, move = a[2:] 334 f2, fd, move = a[2:]
345 if fd == '.hgsubstate': # merged internally 335 if fd == '.hgsubstate': # merged internally
346 continue 336 continue
347 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd)) 337 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
348 fcl = wctx[f] 338 fcl = wctx[f]
349 fco = mctx[f2] 339 fco = mctx[f2]
354 fca = repo.filectx(f, fileid=nullrev) 344 fca = repo.filectx(f, fileid=nullrev)
355 else: 345 else:
356 fca = fcl.ancestor(fco, actx) 346 fca = fcl.ancestor(fco, actx)
357 if not fca: 347 if not fca:
358 fca = repo.filectx(f, fileid=nullrev) 348 fca = repo.filectx(f, fileid=nullrev)
359 ms.add(fcl, fco, fca, fd, flags) 349 ms.add(fcl, fco, fca, fd)
360 if f != fd and move: 350 if f != fd and move:
361 moves.append(f) 351 moves.append(f)
362 352
363 audit = repo.wopener.audit 353 audit = repo.wopener.audit
364 354
388 elif m == "m": # merge 378 elif m == "m": # merge
389 if fd == '.hgsubstate': # subrepo states need updating 379 if fd == '.hgsubstate': # subrepo states need updating
390 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx), 380 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
391 overwrite) 381 overwrite)
392 continue 382 continue
393 f2, fd, flags, move = a[2:] 383 f2, fd, move = a[2:]
394 audit(fd) 384 audit(fd)
395 r = ms.resolve(fd, wctx, mctx) 385 r = ms.resolve(fd, wctx, mctx)
396 if r is not None and r > 0: 386 if r is not None and r > 0:
397 unresolved += 1 387 unresolved += 1
398 else: 388 else:
482 if branchmerge: 472 if branchmerge:
483 repo.dirstate.otherparent(f) 473 repo.dirstate.otherparent(f)
484 else: 474 else:
485 repo.dirstate.normal(f) 475 repo.dirstate.normal(f)
486 elif m == "m": # merge 476 elif m == "m": # merge
487 f2, fd, flag, move = a[2:] 477 f2, fd, move = a[2:]
488 if branchmerge: 478 if branchmerge:
489 # We've done a branch merge, mark this file as merged 479 # We've done a branch merge, mark this file as merged
490 # so that we properly record the merger later 480 # so that we properly record the merger later
491 repo.dirstate.merge(fd) 481 repo.dirstate.merge(fd)
492 if f != f2: # copy/rename 482 if f != f2: # copy/rename