Mercurial > public > mercurial-scm > hg
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 |