##// END OF EJS Templates
merge: update some docstrings
Matt Mackall -
r3315:38be819a default
parent child Browse files
Show More
@@ -1,428 +1,435 b''
1 1 # merge.py - directory-level update/merge handling for Mercurial
2 2 #
3 3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from node import *
9 9 from i18n import gettext as _
10 10 from demandload import *
11 11 demandload(globals(), "errno util os tempfile")
12 12
13 13 def filemerge(repo, fw, fo, wctx, mctx):
14 14 """perform a 3-way merge in the working directory
15 15
16 fw = filename in the working directory and first parent
16 fw = filename in the working directory
17 17 fo = filename in other parent
18 18 wctx, mctx = working and merge changecontexts
19
20 TODO:
21 if fw is copied in the working directory, we get confused
22 implement move and fd
23 19 """
24 20
25 21 def temp(prefix, ctx):
26 22 pre = "%s~%s." % (os.path.basename(ctx.path()), prefix)
27 23 (fd, name) = tempfile.mkstemp(prefix=pre)
28 24 f = os.fdopen(fd, "wb")
29 25 repo.wwrite(ctx.path(), ctx.data(), f)
30 26 f.close()
31 27 return name
32 28
33 29 fcm = wctx.filectx(fw)
34 30 fco = mctx.filectx(fo)
35 31
36 32 if not fco.cmp(fcm.data()): # files identical?
37 33 return 0
38 34
39 35 fca = fcm.ancestor(fco)
40 36 if not fca:
41 37 fca = repo.filectx(fw, fileid=-1)
42 38 a = repo.wjoin(fw)
43 39 b = temp("base", fca)
44 40 c = temp("other", fco)
45 41
46 42 if fw != fo:
47 43 repo.ui.status(_("merging %s and %s\n") % (fw, fo))
48 44 else:
49 45 repo.ui.status(_("merging %s\n") % fw)
50 46
51 47 repo.ui.debug(_("my %s other %s ancestor %s\n") % (fcm, fco, fca))
52 48
53 49 cmd = (os.environ.get("HGMERGE") or repo.ui.config("ui", "merge")
54 50 or "hgmerge")
55 51 r = util.system('%s "%s" "%s" "%s"' % (cmd, a, b, c), cwd=repo.root,
56 52 environ={'HG_FILE': fw,
57 53 'HG_MY_NODE': str(wctx.parents()[0]),
58 54 'HG_OTHER_NODE': str(mctx)})
59 55 if r:
60 56 repo.ui.warn(_("merging %s failed!\n") % fw)
61 57
62 58 os.unlink(b)
63 59 os.unlink(c)
64 60 return r
65 61
66 62 def checkunknown(wctx, mctx):
67 """
68 check for collisions between unknown files and files in m2
69 """
63 "check for collisions between unknown files and files in mctx"
70 64 man = mctx.manifest()
71 65 for f in wctx.unknown():
72 66 if f in man:
73 67 if mctx.filectx(f).cmp(wctx.filectx(f).data()):
74 68 raise util.Abort(_("'%s' already exists in the working"
75 69 " dir and differs from remote") % f)
76 70
77 71 def forgetremoved(wctx, mctx):
78 72 """
79 73 Forget removed files
80 74
81 75 If we're jumping between revisions (as opposed to merging), and if
82 76 neither the working directory nor the target rev has the file,
83 77 then we need to remove it from the dirstate, to prevent the
84 78 dirstate from listing the file when it is no longer in the
85 79 manifest.
86 80 """
87 81
88 82 action = []
89 83 man = mctx.manifest()
90 84 for f in wctx.deleted() + wctx.removed():
91 85 if f not in man:
92 86 action.append((f, "f"))
93 87
94 88 return action
95 89
96 90 def nonoverlap(d1, d2):
97 """
98 Return list of elements in d1 not in d2
99 """
91 "Return list of elements in d1 not in d2"
100 92
101 93 l = []
102 94 for d in d1:
103 95 if d not in d2:
104 96 l.append(d)
105 97
106 98 l.sort()
107 99 return l
108 100
109 101 def findold(fctx, limit):
110 """
111 find files that path was copied from, back to linkrev limit
112 """
102 "find files that path was copied from, back to linkrev limit"
113 103
114 104 old = {}
115 105 orig = fctx.path()
116 106 visit = [fctx]
117 107 while visit:
118 108 fc = visit.pop()
119 109 if fc.rev() < limit:
120 110 continue
121 111 if fc.path() != orig and fc.path() not in old:
122 112 old[fc.path()] = 1
123 113 visit += fc.parents()
124 114
125 115 old = old.keys()
126 116 old.sort()
127 117 return old
128 118
129 119 def findcopies(repo, m1, m2, limit):
130 120 """
131 121 Find moves and copies between m1 and m2 back to limit linkrev
132 122 """
133 123
134 124 if not repo.ui.config("merge", "followcopies"):
135 125 return {}
136 126
137 127 # avoid silly behavior for update from empty dir
138 128 if not m1:
139 129 return {}
140 130
141 131 dcopies = repo.dirstate.copies()
142 132 copy = {}
143 133 match = {}
144 134 u1 = nonoverlap(m1, m2)
145 135 u2 = nonoverlap(m2, m1)
146 136 ctx = util.cachefunc(lambda f,n: repo.filectx(f, fileid=n[:20]))
147 137
148 138 def checkpair(c, f2, man):
149 139 ''' check if an apparent pair actually matches '''
150 140 c2 = ctx(f2, man[f2])
151 141 ca = c.ancestor(c2)
152 142 if ca and ca.path() == c.path() or ca.path() == c2.path():
153 143 copy[c.path()] = f2
154 144 copy[f2] = c.path()
155 145
156 146 for f in u1:
157 147 c = ctx(dcopies.get(f, f), m1[f])
158 148 for of in findold(c, limit):
159 149 if of in m2:
160 150 checkpair(c, of, m2)
161 151 else:
162 152 match.setdefault(of, []).append(f)
163 153
164 154 for f in u2:
165 155 c = ctx(f, m2[f])
166 156 for of in findold(c, limit):
167 157 if of in m1:
168 158 checkpair(c, of, m1)
169 159 elif of in match:
170 160 for mf in match[of]:
171 161 checkpair(c, mf, m1)
172 162
173 163 return copy
174 164
175 165 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
176 166 """
177 Merge manifest m1 with m2 using ancestor ma and generate merge action list
167 Merge p1 and p2 with ancestor ma and generate merge action list
168
169 overwrite = whether we clobber working files
170 partial = function to filter file lists
178 171 """
179 172
180 173 repo.ui.note(_("resolving manifests\n"))
181 174 repo.ui.debug(_(" overwrite %s partial %s\n") % (overwrite, bool(partial)))
182 175 repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (pa, p1, p2))
183 176
184 177 m1 = p1.manifest()
185 178 m2 = p2.manifest()
186 179 ma = pa.manifest()
187 180 backwards = (pa == p2)
188 181 action = []
189 182 copy = {}
190 183
191 184 def fmerge(f, f2=None, fa=None):
192 185 """merge executable flags"""
193 186 if not f2:
194 187 f2 = f
195 188 fa = f
196 189 a, b, c = ma.execf(fa), m1.execf(f), m2.execf(f2)
197 190 return ((a^b) | (a^c)) ^ a
198 191
199 192 def act(msg, m, f, *args):
200 193 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
201 194 action.append((f, m) + args)
202 195
203 196 if not (backwards or overwrite):
204 197 copy = findcopies(repo, m1, m2, pa.rev())
205 198
206 199 # Compare manifests
207 200 for f, n in m1.iteritems():
208 201 if partial and not partial(f):
209 202 continue
210 203 if f in m2:
211 204 # are files different?
212 205 if n != m2[f]:
213 206 a = ma.get(f, nullid)
214 207 # are both different from the ancestor?
215 208 if not overwrite and n != a and m2[f] != a:
216 209 act("versions differ", "m", f, f, f, fmerge(f), False)
217 210 # are we clobbering?
218 211 # is remote's version newer?
219 212 # or are we going back in time and clean?
220 213 elif overwrite or m2[f] != a or (backwards and not n[20:]):
221 214 act("remote is newer", "g", f, m2.execf(f))
222 215 # local is newer, not overwrite, check mode bits
223 216 elif fmerge(f) != m1.execf(f):
224 217 act("update permissions", "e", f, m2.execf(f))
225 218 # contents same, check mode bits
226 219 elif m1.execf(f) != m2.execf(f):
227 220 if overwrite or fmerge(f) != m1.execf(f):
228 221 act("update permissions", "e", f, m2.execf(f))
229 222 elif f in copy:
230 223 f2 = copy[f]
231 224 if f in ma: # case 3,20 A/B/A
232 225 act("remote moved", "m", f, f2, f2, fmerge(f, f2, f), True)
233 226 else:
234 227 if f2 in m1: # case 2 A,B/B/B
235 228 act("local copied", "m",
236 229 f, f2, f, fmerge(f, f2, f2), False)
237 230 else: # case 4,21 A/B/B
238 231 act("local moved", "m",
239 232 f, f2, f, fmerge(f, f2, f2), False)
240 233 elif f in ma:
241 234 if n != ma[f] and not overwrite:
242 235 if repo.ui.prompt(
243 236 (_(" local changed %s which remote deleted\n") % f) +
244 237 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("d"):
245 238 act("prompt delete", "r", f)
246 239 else:
247 240 act("other deleted", "r", f)
248 241 else:
249 242 # file is created on branch or in working directory
250 243 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
251 244 act("remote deleted", "r", f)
252 245
253 246 for f, n in m2.iteritems():
254 247 if partial and not partial(f):
255 248 continue
256 249 if f in m1:
257 250 continue
258 251 if f in copy:
259 252 f2 = copy[f]
260 253 if f2 not in m2: # already seen
261 254 continue
262 255 # rename case 1, A/A,B/A
263 256 act("remote copied", "m", f2, f, f, fmerge(f2, f, f2), False)
264 257 elif f in ma:
265 258 if overwrite or backwards:
266 259 act("recreating", "g", f, m2.execf(f))
267 260 elif n != ma[f]:
268 261 if repo.ui.prompt(
269 262 (_("remote changed %s which local deleted\n") % f) +
270 263 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("k"):
271 264 act("prompt recreating", "g", f, m2.execf(f))
272 265 else:
273 266 act("remote created", "g", f, m2.execf(f))
274 267
275 268 return action
276 269
277 270 def applyupdates(repo, action, wctx, mctx):
271 "apply the merge action list to the working directory"
272
278 273 updated, merged, removed, unresolved = 0, 0, 0, 0
279 274 action.sort()
280 275 for a in action:
281 276 f, m = a[:2]
282 277 if f[0] == "/":
283 278 continue
284 279 if m == "r": # remove
285 280 repo.ui.note(_("removing %s\n") % f)
286 281 util.audit_path(f)
287 282 try:
288 283 util.unlink(repo.wjoin(f))
289 284 except OSError, inst:
290 285 if inst.errno != errno.ENOENT:
291 286 repo.ui.warn(_("update failed to remove %s: %s!\n") %
292 287 (f, inst.strerror))
293 288 removed +=1
294 289 elif m == "m": # merge
295 290 f2, fd, flag, move = a[2:]
296 291 if filemerge(repo, f, f2, wctx, mctx):
297 292 unresolved += 1
298 293 else:
299 294 if f != fd:
300 295 repo.ui.debug(_("copying %s to %s\n") % (f, fd))
301 296 repo.wwrite(fd, repo.wread(f))
302 297 if move:
303 298 repo.ui.debug(_("removing %s\n") % f)
304 299 os.unlink(repo.wjoin(f))
305 300
306 301 util.set_exec(repo.wjoin(fd), flag)
307 302 merged += 1
308 303 elif m == "g": # get
309 304 flag = a[2]
310 305 repo.ui.note(_("getting %s\n") % f)
311 306 t = mctx.filectx(f).data()
312 307 repo.wwrite(f, t)
313 308 util.set_exec(repo.wjoin(f), flag)
314 309 updated += 1
315 310 elif m == "e": # exec
316 311 flag = a[2]
317 312 util.set_exec(repo.wjoin(f), flag)
318 313
319 314 return updated, merged, removed, unresolved
320 315
321 316 def recordupdates(repo, action, branchmerge, mctx):
317 "record merge actions to the dirstate"
318
322 319 for a in action:
323 320 f, m = a[:2]
324 321 if m == "r": # remove
325 322 if branchmerge:
326 323 repo.dirstate.update([f], 'r')
327 324 else:
328 325 repo.dirstate.forget([f])
329 326 elif m == "f": # forget
330 327 repo.dirstate.forget([f])
331 328 elif m == "g": # get
332 329 if branchmerge:
333 330 repo.dirstate.update([f], 'n', st_mtime=-1)
334 331 else:
335 332 repo.dirstate.update([f], 'n')
336 333 elif m == "m": # merge
337 334 f2, fd, flag, move = a[2:]
338 335 if branchmerge:
339 336 # We've done a branch merge, mark this file as merged
340 337 # so that we properly record the merger later
341 338 repo.dirstate.update([fd], 'm')
342 339 else:
343 340 # We've update-merged a locally modified file, so
344 341 # we set the dirstate to emulate a normal checkout
345 342 # of that file some time in the past. Thus our
346 343 # merge will appear as a normal local file
347 344 # modification.
348 345 f_len = mctx.filectx(f).size()
349 346 repo.dirstate.update([fd], 'n', st_size=f_len, st_mtime=-1)
350 347 if f != f2: # copy/rename
351 348 if move:
352 349 repo.dirstate.update([f], 'r')
353 350 if f != fd:
354 351 repo.dirstate.copy(f, fd)
355 352 else:
356 353 repo.dirstate.copy(f2, fd)
357 354
358 355 def update(repo, node, branchmerge=False, force=False, partial=None,
359 356 wlock=None, show_stats=True, remind=True):
357 """
358 Perform a merge between the working directory and the given node
359
360 branchmerge = whether to merge between branches
361 force = whether to force branch merging or file overwriting
362 partial = a function to filter file lists (dirstate not updated)
363 wlock = working dir lock, if already held
364 show_stats = whether to report merge statistics
365 remind = whether to remind about merge
366 """
360 367
361 368 if not wlock:
362 369 wlock = repo.wlock()
363 370
364 371 overwrite = force and not branchmerge
365 372 forcemerge = force and branchmerge
366 373 wc = repo.workingctx()
367 374 pl = wc.parents()
368 375 p1, p2 = pl[0], repo.changectx(node)
369 376 pa = p1.ancestor(p2)
370 377 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
371 378
372 379 ### check phase
373 380 if not overwrite and len(pl) > 1:
374 381 raise util.Abort(_("outstanding uncommitted merges"))
375 382 if pa == p1 or pa == p2: # is there a linear path from p1 to p2?
376 383 if branchmerge:
377 384 raise util.Abort(_("there is nothing to merge, just use "
378 385 "'hg update' or look at 'hg heads'"))
379 386 elif not (overwrite or branchmerge):
380 387 raise util.Abort(_("update spans branches, use 'hg merge' "
381 388 "or 'hg update -C' to lose changes"))
382 389 if branchmerge and not forcemerge:
383 390 if wc.modified() or wc.added() or wc.removed():
384 391 raise util.Abort(_("outstanding uncommitted changes"))
385 392
386 393 ### calculate phase
387 394 action = []
388 395 if not force:
389 396 checkunknown(wc, p2)
390 397 if not branchmerge:
391 398 action += forgetremoved(wc, p2)
392 399 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
393 400
394 401 ### apply phase
395 402 if not branchmerge: # just jump to the new rev
396 403 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
397 404 if not partial:
398 405 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
399 406
400 407 updated, merged, removed, unresolved = applyupdates(repo, action, wc, p2)
401 408
402 409 if show_stats:
403 410 stats = ((updated, _("updated")),
404 411 (merged - unresolved, _("merged")),
405 412 (removed, _("removed")),
406 413 (unresolved, _("unresolved")))
407 414 note = ", ".join([_("%d files %s") % s for s in stats])
408 415 repo.ui.status("%s\n" % note)
409 416 if not partial:
410 417 recordupdates(repo, action, branchmerge, p2)
411 418 repo.dirstate.setparents(fp1, fp2)
412 419 repo.hook('update', parent1=xp1, parent2=xp2, error=unresolved)
413 420
414 421 if branchmerge:
415 422 if unresolved:
416 423 repo.ui.status(_("There are unresolved merges,"
417 424 " you can redo the full merge using:\n"
418 425 " hg update -C %s\n"
419 426 " hg merge %s\n"
420 427 % (p1.rev(), p2.rev())))
421 428 elif remind:
422 429 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
423 430 elif unresolved:
424 431 repo.ui.status(_("There are unresolved merges with"
425 432 " locally modified files.\n"))
426 433
427 434 return unresolved
428 435
General Comments 0
You need to be logged in to leave comments. Login now