comparison mercurial/merge.py @ 3733:9e67fecbfd16

merge: handle directory renames commit: handle new copy dirstate case correctly findcopies: keep a map of all copies found for directory logic add dirs filter check for merge:followdirs config option generate a directory move map find files that match directory move map manifestmerge: add directory rename cases applyupdates: skip actions with None file add "d" action recordupdates: add "d" action add simple directory rename test
author Matt Mackall <mpm@selenic.com>
date Thu, 30 Nov 2006 17:36:33 -0600
parents ffe9fef84801
children b1eeaeb936ae
comparison
equal deleted inserted replaced
3732:ffe9fef84801 3733:9e67fecbfd16
120 for of in findold(c): 120 for of in findold(c):
121 if of not in man: 121 if of not in man:
122 return 122 return
123 c2 = ctx(of, man[of]) 123 c2 = ctx(of, man[of])
124 ca = c.ancestor(c2) 124 ca = c.ancestor(c2)
125 if not ca or c == ca or c2 == ca: 125 if not ca: # unrelated
126 return 126 return
127 if ca.path() == c.path() or ca.path() == c2.path(): 127 if ca.path() == c.path() or ca.path() == c2.path():
128 fullcopy[c.path()] = of
129 if c == ca or c2 == ca: # no merge needed, ignore copy
130 return
128 copy[c.path()] = of 131 copy[c.path()] = of
132
133 def dirs(files):
134 d = {}
135 for f in files:
136 d[os.path.dirname(f)] = True
137 return d
129 138
130 if not repo.ui.configbool("merge", "followcopies", True): 139 if not repo.ui.configbool("merge", "followcopies", True):
131 return {} 140 return {}
132 141
133 # avoid silly behavior for update from empty dir 142 # avoid silly behavior for update from empty dir
134 if not m1 or not m2 or not ma: 143 if not m1 or not m2 or not ma:
135 return {} 144 return {}
136 145
137 dcopies = repo.dirstate.copies() 146 dcopies = repo.dirstate.copies()
138 copy = {} 147 copy = {}
148 fullcopy = {}
139 u1 = nonoverlap(m1, m2, ma) 149 u1 = nonoverlap(m1, m2, ma)
140 u2 = nonoverlap(m2, m1, ma) 150 u2 = nonoverlap(m2, m1, ma)
141 ctx = util.cachefunc(lambda f, n: repo.filectx(f, fileid=n[:20])) 151 ctx = util.cachefunc(lambda f, n: repo.filectx(f, fileid=n[:20]))
142 152
143 for f in u1: 153 for f in u1:
144 checkcopies(ctx(dcopies.get(f, f), m1[f]), m2) 154 checkcopies(ctx(dcopies.get(f, f), m1[f]), m2)
145 155
146 for f in u2: 156 for f in u2:
147 checkcopies(ctx(f, m2[f]), m1) 157 checkcopies(ctx(f, m2[f]), m1)
158
159 if not fullcopy or not repo.ui.configbool("merge", "followdirs", True):
160 return copy
161
162 # generate a directory move map
163 d1, d2 = dirs(m1), dirs(m2)
164 invalid = {}
165 dirmove = {}
166
167 for dst, src in fullcopy.items():
168 dsrc, ddst = os.path.dirname(src), os.path.dirname(dst)
169 if dsrc in invalid:
170 continue
171 elif (dsrc in d1 and ddst in d1) or (dsrc in d2 and ddst in d2):
172 invalid[dsrc] = True
173 elif dsrc in dirmove and dirmove[dsrc] != ddst:
174 invalid[dsrc] = True
175 del dirmove[dsrc]
176 else:
177 dirmove[dsrc] = ddst
178
179 del d1, d2, invalid
180
181 if not dirmove:
182 return copy
183
184 # check unaccounted nonoverlapping files
185 for f in u1 + u2:
186 if f not in fullcopy:
187 d = os.path.dirname(f)
188 if d in dirmove:
189 copy[f] = dirmove[d] + "/" + os.path.basename(f)
148 190
149 return copy 191 return copy
150 192
151 def manifestmerge(repo, p1, p2, pa, overwrite, partial): 193 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
152 """ 194 """
208 act("update permissions", "e", f, m2.execf(f)) 250 act("update permissions", "e", f, m2.execf(f))
209 elif f in copied: 251 elif f in copied:
210 continue 252 continue
211 elif f in copy: 253 elif f in copy:
212 f2 = copy[f] 254 f2 = copy[f]
213 if f2 in m1: # case 2 A,B/B/B 255 if f2 not in m2: # directory rename
256 act("remote renamed directory to " + f2, "d",
257 f, None, f2, m1.execf(f))
258 elif f2 in m1: # case 2 A,B/B/B
214 act("local copied to " + f2, "m", 259 act("local copied to " + f2, "m",
215 f, f2, f, fmerge(f, f2, f2), False) 260 f, f2, f, fmerge(f, f2, f2), False)
216 else: # case 4,21 A/B/B 261 else: # case 4,21 A/B/B
217 act("local moved to " + f2, "m", 262 act("local moved to " + f2, "m",
218 f, f2, f, fmerge(f, f2, f2), False) 263 f, f2, f, fmerge(f, f2, f2), False)
236 continue 281 continue
237 if f in copied: 282 if f in copied:
238 continue 283 continue
239 if f in copy: 284 if f in copy:
240 f2 = copy[f] 285 f2 = copy[f]
241 if f2 in m2: # rename case 1, A/A,B/A 286 if f2 not in m1: # directory rename
287 act("local renamed directory to " + f2, "d",
288 None, f, f2, m2.execf(f))
289 elif f2 in m2: # rename case 1, A/A,B/A
242 act("remote copied to " + f, "m", 290 act("remote copied to " + f, "m",
243 f2, f, f, fmerge(f2, f, f2), False) 291 f2, f, f, fmerge(f2, f, f2), False)
244 else: # case 3,20 A/B/A 292 else: # case 3,20 A/B/A
245 act("remote moved to " + f, "m", 293 act("remote moved to " + f, "m",
246 f2, f, f, fmerge(f2, f, f2), True) 294 f2, f, f, fmerge(f2, f, f2), True)
262 310
263 updated, merged, removed, unresolved = 0, 0, 0, 0 311 updated, merged, removed, unresolved = 0, 0, 0, 0
264 action.sort() 312 action.sort()
265 for a in action: 313 for a in action:
266 f, m = a[:2] 314 f, m = a[:2]
267 if f[0] == "/": 315 if f and f[0] == "/":
268 continue 316 continue
269 if m == "r": # remove 317 if m == "r": # remove
270 repo.ui.note(_("removing %s\n") % f) 318 repo.ui.note(_("removing %s\n") % f)
271 util.audit_path(f) 319 util.audit_path(f)
272 try: 320 try:
298 repo.ui.note(_("getting %s\n") % f) 346 repo.ui.note(_("getting %s\n") % f)
299 t = mctx.filectx(f).data() 347 t = mctx.filectx(f).data()
300 repo.wwrite(f, t) 348 repo.wwrite(f, t)
301 util.set_exec(repo.wjoin(f), flag) 349 util.set_exec(repo.wjoin(f), flag)
302 updated += 1 350 updated += 1
351 elif m == "d": # directory rename
352 f2, fd, flag = a[2:]
353 if f:
354 repo.ui.note(_("moving %s to %s\n") % (f, fd))
355 t = wctx.filectx(f).data()
356 repo.wwrite(fd, t)
357 util.set_exec(repo.wjoin(fd), flag)
358 util.unlink(repo.wjoin(f))
359 if f2:
360 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
361 t = mctx.filectx(f2).data()
362 repo.wwrite(fd, t)
363 util.set_exec(repo.wjoin(fd), flag)
364 updated += 1
303 elif m == "e": # exec 365 elif m == "e": # exec
304 flag = a[2] 366 flag = a[2]
305 util.set_exec(repo.wjoin(f), flag) 367 util.set_exec(repo.wjoin(f), flag)
306 368
307 return updated, merged, removed, unresolved 369 return updated, merged, removed, unresolved
343 # merge will appear as a normal local file 405 # merge will appear as a normal local file
344 # modification. 406 # modification.
345 repo.dirstate.update([fd], 'n', st_size=-1, st_mtime=-1) 407 repo.dirstate.update([fd], 'n', st_size=-1, st_mtime=-1)
346 if move: 408 if move:
347 repo.dirstate.forget([f]) 409 repo.dirstate.forget([f])
410 elif m == "d": # directory rename
411 f2, fd, flag = a[2:]
412 if branchmerge:
413 repo.dirstate.update([fd], 'a')
414 if f:
415 repo.dirstate.update([f], 'r')
416 repo.dirstate.copy(f, fd)
417 if f2:
418 repo.dirstate.copy(f2, fd)
419 else:
420 repo.dirstate.update([fd], 'n')
421 if f:
422 repo.dirstate.forget([f])
348 423
349 def update(repo, node, branchmerge, force, partial, wlock): 424 def update(repo, node, branchmerge, force, partial, wlock):
350 """ 425 """
351 Perform a merge between the working directory and the given node 426 Perform a merge between the working directory and the given node
352 427