Mercurial > public > mercurial-scm > hg-stable
comparison mercurial/merge.py @ 19105:c60a7f5a741f stable
icasefs: rewrite case-folding collision detection (issue3452)
Before this patch, case-folding collision detection uses
"copies.pathcopies()" before "manifestmerge()", and is not aware of
renaming in some cases.
For example, in the case of issue3452, "copies.pathcopies()" can't
detect renaming, if the file is renamed at the revision before common
ancestor of merging. So, "hg merge" is aborted unexpectedly on case
insensitive filesystem.
This patch fully rewrites case-folding collision detection, and
relocate it into "manifestmerge()".
New implementation uses list of actions held in "actions" and
"prompts" to build provisional merged manifest up.
Provisional merged manifest should be correct, if actions required to
build merge result up in working directory are listed up in "actions"
and "prompts" correctly.
This patch checks case-folding collision still before prompting for
merge, to avoid aborting after some interactions with users. So, this
assumes that user would choose not "deleted" but "changed".
This patch also changes existing abort message, because sorting before
collision detection changes order of checked files.
author | FUJIWARA Katsunori <foozy@lares.dti.ne.jp> |
---|---|
date | Tue, 30 Apr 2013 05:01:32 +0900 |
parents | 5cc71484ee9c |
children | 113681bbef9e |
comparison
equal
deleted
inserted
replaced
19104:370d9ea027b1 | 19105:c60a7f5a741f |
---|---|
108 wctx._repo.ui.warn(_("%s: untracked file differs\n") % f) | 108 wctx._repo.ui.warn(_("%s: untracked file differs\n") % f) |
109 if error: | 109 if error: |
110 raise util.Abort(_("untracked files in working directory differ " | 110 raise util.Abort(_("untracked files in working directory differ " |
111 "from files in requested revision")) | 111 "from files in requested revision")) |
112 | 112 |
113 def _remains(f, m, ma, workingctx=False): | |
114 """check whether specified file remains after merge. | |
115 | |
116 It is assumed that specified file is not contained in the manifest | |
117 of the other context. | |
118 """ | |
119 if f in ma: | |
120 n = m[f] | |
121 if n != ma[f]: | |
122 return True # because it is changed locally | |
123 # even though it doesn't remain, if "remote deleted" is | |
124 # chosen in manifestmerge() | |
125 elif workingctx and n[20:] == "a": | |
126 return True # because it is added locally (linear merge specific) | |
127 else: | |
128 return False # because it is removed remotely | |
129 else: | |
130 return True # because it is added locally | |
131 | |
132 def _checkcollision(mctx, extractxs): | |
133 "check for case folding collisions in the destination context" | |
134 folded = {} | |
135 for fn in mctx: | |
136 fold = util.normcase(fn) | |
137 if fold in folded: | |
138 raise util.Abort(_("case-folding collision between %s and %s") | |
139 % (fn, folded[fold])) | |
140 folded[fold] = fn | |
141 | |
142 if extractxs: | |
143 wctx, actx = extractxs | |
144 # class to delay looking up copy mapping | |
145 class pathcopies(object): | |
146 @util.propertycache | |
147 def map(self): | |
148 # {dst@mctx: src@wctx} copy mapping | |
149 return copies.pathcopies(wctx, mctx) | |
150 pc = pathcopies() | |
151 | |
152 for fn in wctx: | |
153 fold = util.normcase(fn) | |
154 mfn = folded.get(fold, None) | |
155 if (mfn and mfn != fn and pc.map.get(mfn) != fn and | |
156 _remains(fn, wctx.manifest(), actx.manifest(), True) and | |
157 _remains(mfn, mctx.manifest(), actx.manifest())): | |
158 raise util.Abort(_("case-folding collision between %s and %s") | |
159 % (mfn, fn)) | |
160 | |
161 def _forgetremoved(wctx, mctx, branchmerge): | 113 def _forgetremoved(wctx, mctx, branchmerge): |
162 """ | 114 """ |
163 Forget removed files | 115 Forget removed files |
164 | 116 |
165 If we're jumping between revisions (as opposed to merging), and if | 117 If we're jumping between revisions (as opposed to merging), and if |
183 for f in wctx.removed(): | 135 for f in wctx.removed(): |
184 if f not in mctx: | 136 if f not in mctx: |
185 actions.append((f, "f", None, "forget removed")) | 137 actions.append((f, "f", None, "forget removed")) |
186 | 138 |
187 return actions | 139 return actions |
140 | |
141 def _checkcollision(repo, wmf, actions, prompts): | |
142 # build provisional merged manifest up | |
143 pmmf = set(wmf) | |
144 | |
145 def addop(f, args): | |
146 pmmf.add(f) | |
147 def removeop(f, args): | |
148 pmmf.discard(f) | |
149 def nop(f, args): | |
150 pass | |
151 | |
152 def renameop(f, args): | |
153 f2, fd, flags = args | |
154 if f: | |
155 pmmf.discard(f) | |
156 pmmf.add(fd) | |
157 def mergeop(f, args): | |
158 f2, fd, move = args | |
159 if move: | |
160 pmmf.discard(f) | |
161 pmmf.add(fd) | |
162 | |
163 opmap = { | |
164 "a": addop, | |
165 "d": renameop, | |
166 "dr": nop, | |
167 "e": nop, | |
168 "f": addop, # untracked file should be kept in working directory | |
169 "g": addop, | |
170 "m": mergeop, | |
171 "r": removeop, | |
172 "rd": nop, | |
173 } | |
174 for f, m, args, msg in actions: | |
175 op = opmap.get(m) | |
176 assert op, m | |
177 op(f, args) | |
178 | |
179 opmap = { | |
180 "cd": addop, | |
181 "dc": addop, | |
182 } | |
183 for f, m in prompts: | |
184 op = opmap.get(m) | |
185 assert op, m | |
186 op(f, None) | |
187 | |
188 # check case-folding collision in provisional merged manifest | |
189 foldmap = {} | |
190 for f in sorted(pmmf): | |
191 fold = util.normcase(f) | |
192 if fold in foldmap: | |
193 raise util.Abort(_("case-folding collision between %s and %s") | |
194 % (f, foldmap[fold])) | |
195 foldmap[fold] = f | |
188 | 196 |
189 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial, | 197 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial, |
190 acceptremote=False): | 198 acceptremote=False): |
191 """ | 199 """ |
192 Merge p1 and p2 with ancestor pa and generate merge action list | 200 Merge p1 and p2 with ancestor pa and generate merge action list |
340 else: assert False, m | 348 else: assert False, m |
341 if aborts: | 349 if aborts: |
342 raise util.Abort(_("untracked files in working directory differ " | 350 raise util.Abort(_("untracked files in working directory differ " |
343 "from files in requested revision")) | 351 "from files in requested revision")) |
344 | 352 |
353 if not util.checkcase(repo.path): | |
354 # check collision between files only in p2 for clean update | |
355 if (not branchmerge and | |
356 (force or not wctx.dirty(missing=True, branch=False))): | |
357 _checkcollision(repo, m2, [], []) | |
358 else: | |
359 _checkcollision(repo, m1, actions, prompts) | |
360 | |
345 for f, m in sorted(prompts): | 361 for f, m in sorted(prompts): |
346 if m == "cd": | 362 if m == "cd": |
347 if acceptremote: | 363 if acceptremote: |
348 actions.append((f, "r", None, "remote delete")) | 364 actions.append((f, "r", None, "remote delete")) |
349 elif repo.ui.promptchoice( | 365 elif repo.ui.promptchoice( |
539 | 555 |
540 def calculateupdates(repo, tctx, mctx, ancestor, branchmerge, force, partial, | 556 def calculateupdates(repo, tctx, mctx, ancestor, branchmerge, force, partial, |
541 acceptremote=False): | 557 acceptremote=False): |
542 "Calculate the actions needed to merge mctx into tctx" | 558 "Calculate the actions needed to merge mctx into tctx" |
543 actions = [] | 559 actions = [] |
544 folding = not util.checkcase(repo.path) | |
545 if folding: | |
546 # collision check is not needed for clean update | |
547 if (not branchmerge and | |
548 (force or not tctx.dirty(missing=True, branch=False))): | |
549 _checkcollision(mctx, None) | |
550 else: | |
551 _checkcollision(mctx, (tctx, ancestor)) | |
552 actions += manifestmerge(repo, tctx, mctx, | 560 actions += manifestmerge(repo, tctx, mctx, |
553 ancestor, | 561 ancestor, |
554 branchmerge, force, | 562 branchmerge, force, |
555 partial, acceptremote) | 563 partial, acceptremote) |
556 if tctx.rev() is None: | 564 if tctx.rev() is None: |