##// END OF EJS Templates
merge: collect checking for unknown files at end of manifestmerge()...
Martin von Zweigbergk -
r23651:72da02d7 default
parent child Browse files
Show More
@@ -1,1177 +1,1174 b''
1 1 # merge.py - directory-level update/merge handling for Mercurial
2 2 #
3 3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import struct
9 9
10 10 from node import nullid, nullrev, hex, bin
11 11 from i18n import _
12 12 from mercurial import obsolete
13 13 import error as errormod, util, filemerge, copies, subrepo, worker
14 14 import errno, os, shutil
15 15
16 16 _pack = struct.pack
17 17 _unpack = struct.unpack
18 18
19 19 def _droponode(data):
20 20 # used for compatibility for v1
21 21 bits = data.split('\0')
22 22 bits = bits[:-2] + bits[-1:]
23 23 return '\0'.join(bits)
24 24
25 25 class mergestate(object):
26 26 '''track 3-way merge state of individual files
27 27
28 28 it is stored on disk when needed. Two file are used, one with an old
29 29 format, one with a new format. Both contains similar data, but the new
30 30 format can store new kind of field.
31 31
32 32 Current new format is a list of arbitrary record of the form:
33 33
34 34 [type][length][content]
35 35
36 36 Type is a single character, length is a 4 bytes integer, content is an
37 37 arbitrary suites of bytes of length `length`.
38 38
39 39 Type should be a letter. Capital letter are mandatory record, Mercurial
40 40 should abort if they are unknown. lower case record can be safely ignored.
41 41
42 42 Currently known record:
43 43
44 44 L: the node of the "local" part of the merge (hexified version)
45 45 O: the node of the "other" part of the merge (hexified version)
46 46 F: a file to be merged entry
47 47 '''
48 48 statepathv1 = 'merge/state'
49 49 statepathv2 = 'merge/state2'
50 50
51 51 def __init__(self, repo):
52 52 self._repo = repo
53 53 self._dirty = False
54 54 self._read()
55 55
56 56 def reset(self, node=None, other=None):
57 57 self._state = {}
58 58 self._local = None
59 59 self._other = None
60 60 if node:
61 61 self._local = node
62 62 self._other = other
63 63 shutil.rmtree(self._repo.join('merge'), True)
64 64 self._dirty = False
65 65
66 66 def _read(self):
67 67 """Analyse each record content to restore a serialized state from disk
68 68
69 69 This function process "record" entry produced by the de-serialization
70 70 of on disk file.
71 71 """
72 72 self._state = {}
73 73 self._local = None
74 74 self._other = None
75 75 records = self._readrecords()
76 76 for rtype, record in records:
77 77 if rtype == 'L':
78 78 self._local = bin(record)
79 79 elif rtype == 'O':
80 80 self._other = bin(record)
81 81 elif rtype == 'F':
82 82 bits = record.split('\0')
83 83 self._state[bits[0]] = bits[1:]
84 84 elif not rtype.islower():
85 85 raise util.Abort(_('unsupported merge state record: %s')
86 86 % rtype)
87 87 self._dirty = False
88 88
89 89 def _readrecords(self):
90 90 """Read merge state from disk and return a list of record (TYPE, data)
91 91
92 92 We read data from both v1 and v2 files and decide which one to use.
93 93
94 94 V1 has been used by version prior to 2.9.1 and contains less data than
95 95 v2. We read both versions and check if no data in v2 contradicts
96 96 v1. If there is not contradiction we can safely assume that both v1
97 97 and v2 were written at the same time and use the extract data in v2. If
98 98 there is contradiction we ignore v2 content as we assume an old version
99 99 of Mercurial has overwritten the mergestate file and left an old v2
100 100 file around.
101 101
102 102 returns list of record [(TYPE, data), ...]"""
103 103 v1records = self._readrecordsv1()
104 104 v2records = self._readrecordsv2()
105 105 oldv2 = set() # old format version of v2 record
106 106 for rec in v2records:
107 107 if rec[0] == 'L':
108 108 oldv2.add(rec)
109 109 elif rec[0] == 'F':
110 110 # drop the onode data (not contained in v1)
111 111 oldv2.add(('F', _droponode(rec[1])))
112 112 for rec in v1records:
113 113 if rec not in oldv2:
114 114 # v1 file is newer than v2 file, use it
115 115 # we have to infer the "other" changeset of the merge
116 116 # we cannot do better than that with v1 of the format
117 117 mctx = self._repo[None].parents()[-1]
118 118 v1records.append(('O', mctx.hex()))
119 119 # add place holder "other" file node information
120 120 # nobody is using it yet so we do no need to fetch the data
121 121 # if mctx was wrong `mctx[bits[-2]]` may fails.
122 122 for idx, r in enumerate(v1records):
123 123 if r[0] == 'F':
124 124 bits = r[1].split('\0')
125 125 bits.insert(-2, '')
126 126 v1records[idx] = (r[0], '\0'.join(bits))
127 127 return v1records
128 128 else:
129 129 return v2records
130 130
131 131 def _readrecordsv1(self):
132 132 """read on disk merge state for version 1 file
133 133
134 134 returns list of record [(TYPE, data), ...]
135 135
136 136 Note: the "F" data from this file are one entry short
137 137 (no "other file node" entry)
138 138 """
139 139 records = []
140 140 try:
141 141 f = self._repo.opener(self.statepathv1)
142 142 for i, l in enumerate(f):
143 143 if i == 0:
144 144 records.append(('L', l[:-1]))
145 145 else:
146 146 records.append(('F', l[:-1]))
147 147 f.close()
148 148 except IOError, err:
149 149 if err.errno != errno.ENOENT:
150 150 raise
151 151 return records
152 152
153 153 def _readrecordsv2(self):
154 154 """read on disk merge state for version 2 file
155 155
156 156 returns list of record [(TYPE, data), ...]
157 157 """
158 158 records = []
159 159 try:
160 160 f = self._repo.opener(self.statepathv2)
161 161 data = f.read()
162 162 off = 0
163 163 end = len(data)
164 164 while off < end:
165 165 rtype = data[off]
166 166 off += 1
167 167 length = _unpack('>I', data[off:(off + 4)])[0]
168 168 off += 4
169 169 record = data[off:(off + length)]
170 170 off += length
171 171 records.append((rtype, record))
172 172 f.close()
173 173 except IOError, err:
174 174 if err.errno != errno.ENOENT:
175 175 raise
176 176 return records
177 177
178 178 def active(self):
179 179 """Whether mergestate is active.
180 180
181 181 Returns True if there appears to be mergestate. This is a rough proxy
182 182 for "is a merge in progress."
183 183 """
184 184 # Check local variables before looking at filesystem for performance
185 185 # reasons.
186 186 return bool(self._local) or bool(self._state) or \
187 187 self._repo.opener.exists(self.statepathv1) or \
188 188 self._repo.opener.exists(self.statepathv2)
189 189
190 190 def commit(self):
191 191 """Write current state on disk (if necessary)"""
192 192 if self._dirty:
193 193 records = []
194 194 records.append(('L', hex(self._local)))
195 195 records.append(('O', hex(self._other)))
196 196 for d, v in self._state.iteritems():
197 197 records.append(('F', '\0'.join([d] + v)))
198 198 self._writerecords(records)
199 199 self._dirty = False
200 200
201 201 def _writerecords(self, records):
202 202 """Write current state on disk (both v1 and v2)"""
203 203 self._writerecordsv1(records)
204 204 self._writerecordsv2(records)
205 205
206 206 def _writerecordsv1(self, records):
207 207 """Write current state on disk in a version 1 file"""
208 208 f = self._repo.opener(self.statepathv1, 'w')
209 209 irecords = iter(records)
210 210 lrecords = irecords.next()
211 211 assert lrecords[0] == 'L'
212 212 f.write(hex(self._local) + '\n')
213 213 for rtype, data in irecords:
214 214 if rtype == 'F':
215 215 f.write('%s\n' % _droponode(data))
216 216 f.close()
217 217
218 218 def _writerecordsv2(self, records):
219 219 """Write current state on disk in a version 2 file"""
220 220 f = self._repo.opener(self.statepathv2, 'w')
221 221 for key, data in records:
222 222 assert len(key) == 1
223 223 format = '>sI%is' % len(data)
224 224 f.write(_pack(format, key, len(data), data))
225 225 f.close()
226 226
227 227 def add(self, fcl, fco, fca, fd):
228 228 """add a new (potentially?) conflicting file the merge state
229 229 fcl: file context for local,
230 230 fco: file context for remote,
231 231 fca: file context for ancestors,
232 232 fd: file path of the resulting merge.
233 233
234 234 note: also write the local version to the `.hg/merge` directory.
235 235 """
236 236 hash = util.sha1(fcl.path()).hexdigest()
237 237 self._repo.opener.write('merge/' + hash, fcl.data())
238 238 self._state[fd] = ['u', hash, fcl.path(),
239 239 fca.path(), hex(fca.filenode()),
240 240 fco.path(), hex(fco.filenode()),
241 241 fcl.flags()]
242 242 self._dirty = True
243 243
244 244 def __contains__(self, dfile):
245 245 return dfile in self._state
246 246
247 247 def __getitem__(self, dfile):
248 248 return self._state[dfile][0]
249 249
250 250 def __iter__(self):
251 251 return iter(sorted(self._state))
252 252
253 253 def files(self):
254 254 return self._state.keys()
255 255
256 256 def mark(self, dfile, state):
257 257 self._state[dfile][0] = state
258 258 self._dirty = True
259 259
260 260 def unresolved(self):
261 261 """Obtain the paths of unresolved files."""
262 262
263 263 for f, entry in self._state.items():
264 264 if entry[0] == 'u':
265 265 yield f
266 266
267 267 def resolve(self, dfile, wctx, labels=None):
268 268 """rerun merge process for file path `dfile`"""
269 269 if self[dfile] == 'r':
270 270 return 0
271 271 stateentry = self._state[dfile]
272 272 state, hash, lfile, afile, anode, ofile, onode, flags = stateentry
273 273 octx = self._repo[self._other]
274 274 fcd = wctx[dfile]
275 275 fco = octx[ofile]
276 276 fca = self._repo.filectx(afile, fileid=anode)
277 277 # "premerge" x flags
278 278 flo = fco.flags()
279 279 fla = fca.flags()
280 280 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
281 281 if fca.node() == nullid:
282 282 self._repo.ui.warn(_('warning: cannot merge flags for %s\n') %
283 283 afile)
284 284 elif flags == fla:
285 285 flags = flo
286 286 # restore local
287 287 f = self._repo.opener('merge/' + hash)
288 288 self._repo.wwrite(dfile, f.read(), flags)
289 289 f.close()
290 290 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca,
291 291 labels=labels)
292 292 if r is None:
293 293 # no real conflict
294 294 del self._state[dfile]
295 295 self._dirty = True
296 296 elif not r:
297 297 self.mark(dfile, 'r')
298 298 return r
299 299
300 300 def _checkunknownfile(repo, wctx, mctx, f):
301 301 return (os.path.isfile(repo.wjoin(f))
302 302 and repo.wopener.audit.check(f)
303 303 and repo.dirstate.normalize(f) not in repo.dirstate
304 304 and mctx[f].cmp(wctx[f]))
305 305
306 306 def _forgetremoved(wctx, mctx, branchmerge):
307 307 """
308 308 Forget removed files
309 309
310 310 If we're jumping between revisions (as opposed to merging), and if
311 311 neither the working directory nor the target rev has the file,
312 312 then we need to remove it from the dirstate, to prevent the
313 313 dirstate from listing the file when it is no longer in the
314 314 manifest.
315 315
316 316 If we're merging, and the other revision has removed a file
317 317 that is not present in the working directory, we need to mark it
318 318 as removed.
319 319 """
320 320
321 321 actions = {}
322 322 m = 'f'
323 323 if branchmerge:
324 324 m = 'r'
325 325 for f in wctx.deleted():
326 326 if f not in mctx:
327 327 actions[f] = m, None, "forget deleted"
328 328
329 329 if not branchmerge:
330 330 for f in wctx.removed():
331 331 if f not in mctx:
332 332 actions[f] = 'f', None, "forget removed"
333 333
334 334 return actions
335 335
336 336 def _checkcollision(repo, wmf, actions):
337 337 # build provisional merged manifest up
338 338 pmmf = set(wmf)
339 339
340 340 if actions:
341 341 # k, dr, e and rd are no-op
342 342 for m in 'a', 'f', 'g', 'cd', 'dc':
343 343 for f, args, msg in actions[m]:
344 344 pmmf.add(f)
345 345 for f, args, msg in actions['r']:
346 346 pmmf.discard(f)
347 347 for f, args, msg in actions['dm']:
348 348 f2, flags = args
349 349 pmmf.discard(f2)
350 350 pmmf.add(f)
351 351 for f, args, msg in actions['dg']:
352 352 pmmf.add(f)
353 353 for f, args, msg in actions['m']:
354 354 f1, f2, fa, move, anc = args
355 355 if move:
356 356 pmmf.discard(f1)
357 357 pmmf.add(f)
358 358
359 359 # check case-folding collision in provisional merged manifest
360 360 foldmap = {}
361 361 for f in sorted(pmmf):
362 362 fold = util.normcase(f)
363 363 if fold in foldmap:
364 364 raise util.Abort(_("case-folding collision between %s and %s")
365 365 % (f, foldmap[fold]))
366 366 foldmap[fold] = f
367 367
368 368 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial,
369 369 acceptremote, followcopies):
370 370 """
371 371 Merge p1 and p2 with ancestor pa and generate merge action list
372 372
373 373 branchmerge and force are as passed in to update
374 374 partial = function to filter file lists
375 375 acceptremote = accept the incoming changes without prompting
376 376 """
377 377
378 378 copy, movewithdir, diverge, renamedelete = {}, {}, {}, {}
379 379
380 380 # manifests fetched in order are going to be faster, so prime the caches
381 381 [x.manifest() for x in
382 382 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
383 383
384 384 if followcopies:
385 385 ret = copies.mergecopies(repo, wctx, p2, pa)
386 386 copy, movewithdir, diverge, renamedelete = ret
387 387
388 388 repo.ui.note(_("resolving manifests\n"))
389 389 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
390 390 % (bool(branchmerge), bool(force), bool(partial)))
391 391 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
392 392
393 393 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
394 394 copied = set(copy.values())
395 395 copied.update(movewithdir.values())
396 396
397 397 if '.hgsubstate' in m1:
398 398 # check whether sub state is modified
399 399 for s in sorted(wctx.substate):
400 400 if wctx.sub(s).dirty():
401 401 m1['.hgsubstate'] += '+'
402 402 break
403 403
404 aborts = []
405 404 # Compare manifests
406 405 diff = m1.diff(m2)
407 406
408 407 actions = {}
409 408 for f, ((n1, fl1), (n2, fl2)) in diff.iteritems():
410 409 if partial and not partial(f):
411 410 continue
412 411 if n1 and n2: # file exists on both local and remote side
413 412 if f not in ma:
414 413 fa = copy.get(f, None)
415 414 if fa is not None:
416 415 actions[f] = ('m', (f, f, fa, False, pa.node()),
417 416 "both renamed from " + fa)
418 417 else:
419 418 actions[f] = ('m', (f, f, None, False, pa.node()),
420 419 "both created")
421 420 else:
422 421 a = ma[f]
423 422 fla = ma.flags(f)
424 423 nol = 'l' not in fl1 + fl2 + fla
425 424 if n2 == a and fl2 == fla:
426 425 actions[f] = ('k' , (), "remote unchanged")
427 426 elif n1 == a and fl1 == fla: # local unchanged - use remote
428 427 if n1 == n2: # optimization: keep local content
429 428 actions[f] = ('e', (fl2,), "update permissions")
430 429 else:
431 430 actions[f] = ('g', (fl2,), "remote is newer")
432 431 elif nol and n2 == a: # remote only changed 'x'
433 432 actions[f] = ('e', (fl2,), "update permissions")
434 433 elif nol and n1 == a: # local only changed 'x'
435 434 actions[f] = ('g', (fl1,), "remote is newer")
436 435 else: # both changed something
437 436 actions[f] = ('m', (f, f, f, False, pa.node()),
438 437 "versions differ")
439 438 elif n1: # file exists only on local side
440 439 if f in copied:
441 440 pass # we'll deal with it on m2 side
442 441 elif f in movewithdir: # directory rename, move local
443 442 f2 = movewithdir[f]
444 443 if f2 in m2:
445 444 actions[f2] = ('m', (f, f2, None, True, pa.node()),
446 445 "remote directory rename, both created")
447 446 else:
448 447 actions[f2] = ('dm', (f, fl1),
449 448 "remote directory rename - move from " + f)
450 449 elif f in copy:
451 450 f2 = copy[f]
452 451 actions[f] = ('m', (f, f2, f2, False, pa.node()),
453 452 "local copied/moved from " + f2)
454 453 elif f in ma: # clean, a different, no remote
455 454 if n1 != ma[f]:
456 455 if acceptremote:
457 456 actions[f] = ('r', None, "remote delete")
458 457 else:
459 458 actions[f] = ('cd', None, "prompt changed/deleted")
460 459 elif n1[20:] == 'a':
461 460 # This extra 'a' is added by working copy manifest to mark
462 461 # the file as locally added. We should forget it instead of
463 462 # deleting it.
464 463 actions[f] = ('f', None, "remote deleted")
465 464 else:
466 465 actions[f] = ('r', None, "other deleted")
467 466 elif n2: # file exists only on remote side
468 467 if f in copied:
469 468 pass # we'll deal with it on m1 side
470 469 elif f in movewithdir:
471 470 f2 = movewithdir[f]
472 471 if f2 in m1:
473 472 actions[f2] = ('m', (f2, f, None, False, pa.node()),
474 473 "local directory rename, both created")
475 474 else:
476 475 actions[f2] = ('dg', (f, fl2),
477 476 "local directory rename - get from " + f)
478 477 elif f in copy:
479 478 f2 = copy[f]
480 479 if f2 in m2:
481 480 actions[f] = ('m', (f2, f, f2, False, pa.node()),
482 481 "remote copied from " + f2)
483 482 else:
484 483 actions[f] = ('m', (f2, f, f2, True, pa.node()),
485 484 "remote moved from " + f2)
486 485 elif f not in ma:
487 486 # local unknown, remote created: the logic is described by the
488 487 # following table:
489 488 #
490 489 # force branchmerge different | action
491 # n * n | create
492 # n * y | abort
490 # n * * | create
493 491 # y n * | create
494 492 # y y n | create
495 493 # y y y | merge
496 494 #
497 495 # Checking whether the files are different is expensive, so we
498 496 # don't do that when we can avoid it.
499 497 if not force:
500 different = _checkunknownfile(repo, wctx, p2, f)
501 if different:
502 aborts.append((f, "ud"))
503 else:
504 actions[f] = ('c', (fl2,), "remote created")
498 actions[f] = ('c', (fl2,), "remote created")
505 499 elif not branchmerge:
506 500 actions[f] = ('c', (fl2,), "remote created")
507 501 else:
508 502 different = _checkunknownfile(repo, wctx, p2, f)
509 503 if different:
510 504 actions[f] = ('m', (f, f, None, False, pa.node()),
511 505 "remote differs from untracked local")
512 506 else:
513 507 actions[f] = ('g', (fl2,), "remote created")
514 508 elif n2 != ma[f]:
515 different = _checkunknownfile(repo, wctx, p2, f)
516 if not force and different:
517 aborts.append((f, 'ud'))
509 if acceptremote:
510 actions[f] = ('c', (fl2,), "remote recreating")
518 511 else:
519 if acceptremote:
520 actions[f] = ('c', (fl2,), "remote recreating")
521 else:
522 actions[f] = ('dc', (fl2,), "prompt deleted/changed")
512 actions[f] = ('dc', (fl2,), "prompt deleted/changed")
513
514 aborts = []
515 if not force:
516 for f, (m, args, msg) in actions.iteritems():
517 if m in ('c', 'dc'):
518 if _checkunknownfile(repo, wctx, p2, f):
519 aborts.append((f, "ud"))
523 520
524 521 for f, m in sorted(aborts):
525 522 if m == 'ud':
526 523 repo.ui.warn(_("%s: untracked file differs\n") % f)
527 524 else: assert False, m
528 525 if aborts:
529 526 raise util.Abort(_("untracked files in working directory differ "
530 527 "from files in requested revision"))
531 528
532 529 for f, (m, args, msg) in actions.iteritems():
533 530 if m == 'c':
534 531 actions[f] = ('g', args, msg)
535 532
536 533 return actions, diverge, renamedelete
537 534
538 535 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
539 536 """Resolves false conflicts where the nodeid changed but the content
540 537 remained the same."""
541 538
542 539 for f, (m, args, msg) in actions.items():
543 540 if m == 'cd' and f in ancestor and not wctx[f].cmp(ancestor[f]):
544 541 # local did change but ended up with same content
545 542 actions[f] = 'r', None, "prompt same"
546 543 elif m == 'dc' and f in ancestor and not mctx[f].cmp(ancestor[f]):
547 544 # remote did change but ended up with same content
548 545 del actions[f] # don't get = keep local deleted
549 546
550 547 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force, partial,
551 548 acceptremote, followcopies):
552 549 "Calculate the actions needed to merge mctx into wctx using ancestors"
553 550
554 551 if len(ancestors) == 1: # default
555 552 actions, diverge, renamedelete = manifestmerge(
556 553 repo, wctx, mctx, ancestors[0], branchmerge, force, partial,
557 554 acceptremote, followcopies)
558 555
559 556 else: # only when merge.preferancestor=* - the default
560 557 repo.ui.note(
561 558 _("note: merging %s and %s using bids from ancestors %s\n") %
562 559 (wctx, mctx, _(' and ').join(str(anc) for anc in ancestors)))
563 560
564 561 # Call for bids
565 562 fbids = {} # mapping filename to bids (action method to list af actions)
566 563 diverge, renamedelete = None, None
567 564 for ancestor in ancestors:
568 565 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
569 566 actions, diverge1, renamedelete1 = manifestmerge(
570 567 repo, wctx, mctx, ancestor, branchmerge, force, partial,
571 568 acceptremote, followcopies)
572 569 if diverge is None: # and renamedelete is None.
573 570 # Arbitrarily pick warnings from first iteration
574 571 diverge = diverge1
575 572 renamedelete = renamedelete1
576 573 for f, a in sorted(actions.iteritems()):
577 574 m, args, msg = a
578 575 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
579 576 if f in fbids:
580 577 d = fbids[f]
581 578 if m in d:
582 579 d[m].append(a)
583 580 else:
584 581 d[m] = [a]
585 582 else:
586 583 fbids[f] = {m: [a]}
587 584
588 585 # Pick the best bid for each file
589 586 repo.ui.note(_('\nauction for merging merge bids\n'))
590 587 actions = {}
591 588 for f, bids in sorted(fbids.items()):
592 589 # bids is a mapping from action method to list af actions
593 590 # Consensus?
594 591 if len(bids) == 1: # all bids are the same kind of method
595 592 m, l = bids.items()[0]
596 593 if util.all(a == l[0] for a in l[1:]): # len(bids) is > 1
597 594 repo.ui.note(" %s: consensus for %s\n" % (f, m))
598 595 actions[f] = l[0]
599 596 continue
600 597 # If keep is an option, just do it.
601 598 if 'k' in bids:
602 599 repo.ui.note(" %s: picking 'keep' action\n" % f)
603 600 actions[f] = bids['k'][0]
604 601 continue
605 602 # If there are gets and they all agree [how could they not?], do it.
606 603 if 'g' in bids:
607 604 ga0 = bids['g'][0]
608 605 if util.all(a == ga0 for a in bids['g'][1:]):
609 606 repo.ui.note(" %s: picking 'get' action\n" % f)
610 607 actions[f] = ga0
611 608 continue
612 609 # TODO: Consider other simple actions such as mode changes
613 610 # Handle inefficient democrazy.
614 611 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
615 612 for m, l in sorted(bids.items()):
616 613 for _f, args, msg in l:
617 614 repo.ui.note(' %s -> %s\n' % (msg, m))
618 615 # Pick random action. TODO: Instead, prompt user when resolving
619 616 m, l = bids.items()[0]
620 617 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
621 618 (f, m))
622 619 actions[f] = l[0]
623 620 continue
624 621 repo.ui.note(_('end of auction\n\n'))
625 622
626 623 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
627 624
628 625 if wctx.rev() is None:
629 626 fractions = _forgetremoved(wctx, mctx, branchmerge)
630 627 actions.update(fractions)
631 628
632 629 return actions, diverge, renamedelete
633 630
634 631 def batchremove(repo, actions):
635 632 """apply removes to the working directory
636 633
637 634 yields tuples for progress updates
638 635 """
639 636 verbose = repo.ui.verbose
640 637 unlink = util.unlinkpath
641 638 wjoin = repo.wjoin
642 639 audit = repo.wopener.audit
643 640 i = 0
644 641 for f, args, msg in actions:
645 642 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
646 643 if verbose:
647 644 repo.ui.note(_("removing %s\n") % f)
648 645 audit(f)
649 646 try:
650 647 unlink(wjoin(f), ignoremissing=True)
651 648 except OSError, inst:
652 649 repo.ui.warn(_("update failed to remove %s: %s!\n") %
653 650 (f, inst.strerror))
654 651 if i == 100:
655 652 yield i, f
656 653 i = 0
657 654 i += 1
658 655 if i > 0:
659 656 yield i, f
660 657
661 658 def batchget(repo, mctx, actions):
662 659 """apply gets to the working directory
663 660
664 661 mctx is the context to get from
665 662
666 663 yields tuples for progress updates
667 664 """
668 665 verbose = repo.ui.verbose
669 666 fctx = mctx.filectx
670 667 wwrite = repo.wwrite
671 668 i = 0
672 669 for f, args, msg in actions:
673 670 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
674 671 if verbose:
675 672 repo.ui.note(_("getting %s\n") % f)
676 673 wwrite(f, fctx(f).data(), args[0])
677 674 if i == 100:
678 675 yield i, f
679 676 i = 0
680 677 i += 1
681 678 if i > 0:
682 679 yield i, f
683 680
684 681 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
685 682 """apply the merge action list to the working directory
686 683
687 684 wctx is the working copy context
688 685 mctx is the context to be merged into the working copy
689 686
690 687 Return a tuple of counts (updated, merged, removed, unresolved) that
691 688 describes how many files were affected by the update.
692 689 """
693 690
694 691 updated, merged, removed, unresolved = 0, 0, 0, 0
695 692 ms = mergestate(repo)
696 693 ms.reset(wctx.p1().node(), mctx.node())
697 694 moves = []
698 695 for m, l in actions.items():
699 696 l.sort()
700 697
701 698 # prescan for merges
702 699 for f, args, msg in actions['m']:
703 700 f1, f2, fa, move, anc = args
704 701 if f == '.hgsubstate': # merged internally
705 702 continue
706 703 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
707 704 fcl = wctx[f1]
708 705 fco = mctx[f2]
709 706 actx = repo[anc]
710 707 if fa in actx:
711 708 fca = actx[fa]
712 709 else:
713 710 fca = repo.filectx(f1, fileid=nullrev)
714 711 ms.add(fcl, fco, fca, f)
715 712 if f1 != f and move:
716 713 moves.append(f1)
717 714
718 715 audit = repo.wopener.audit
719 716 _updating = _('updating')
720 717 _files = _('files')
721 718 progress = repo.ui.progress
722 719
723 720 # remove renamed files after safely stored
724 721 for f in moves:
725 722 if os.path.lexists(repo.wjoin(f)):
726 723 repo.ui.debug("removing %s\n" % f)
727 724 audit(f)
728 725 util.unlinkpath(repo.wjoin(f))
729 726
730 727 numupdates = sum(len(l) for m, l in actions.items() if m != 'k')
731 728
732 729 if [a for a in actions['r'] if a[0] == '.hgsubstate']:
733 730 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
734 731
735 732 # remove in parallel (must come first)
736 733 z = 0
737 734 prog = worker.worker(repo.ui, 0.001, batchremove, (repo,), actions['r'])
738 735 for i, item in prog:
739 736 z += i
740 737 progress(_updating, z, item=item, total=numupdates, unit=_files)
741 738 removed = len(actions['r'])
742 739
743 740 # get in parallel
744 741 prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx), actions['g'])
745 742 for i, item in prog:
746 743 z += i
747 744 progress(_updating, z, item=item, total=numupdates, unit=_files)
748 745 updated = len(actions['g'])
749 746
750 747 if [a for a in actions['g'] if a[0] == '.hgsubstate']:
751 748 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
752 749
753 750 # forget (manifest only, just log it) (must come first)
754 751 for f, args, msg in actions['f']:
755 752 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
756 753 z += 1
757 754 progress(_updating, z, item=f, total=numupdates, unit=_files)
758 755
759 756 # re-add (manifest only, just log it)
760 757 for f, args, msg in actions['a']:
761 758 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
762 759 z += 1
763 760 progress(_updating, z, item=f, total=numupdates, unit=_files)
764 761
765 762 # keep (noop, just log it)
766 763 for f, args, msg in actions['k']:
767 764 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
768 765 # no progress
769 766
770 767 # merge
771 768 for f, args, msg in actions['m']:
772 769 repo.ui.debug(" %s: %s -> m\n" % (f, msg))
773 770 z += 1
774 771 progress(_updating, z, item=f, total=numupdates, unit=_files)
775 772 if f == '.hgsubstate': # subrepo states need updating
776 773 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
777 774 overwrite)
778 775 continue
779 776 audit(f)
780 777 r = ms.resolve(f, wctx, labels=labels)
781 778 if r is not None and r > 0:
782 779 unresolved += 1
783 780 else:
784 781 if r is None:
785 782 updated += 1
786 783 else:
787 784 merged += 1
788 785
789 786 # directory rename, move local
790 787 for f, args, msg in actions['dm']:
791 788 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
792 789 z += 1
793 790 progress(_updating, z, item=f, total=numupdates, unit=_files)
794 791 f0, flags = args
795 792 repo.ui.note(_("moving %s to %s\n") % (f0, f))
796 793 audit(f)
797 794 repo.wwrite(f, wctx.filectx(f0).data(), flags)
798 795 util.unlinkpath(repo.wjoin(f0))
799 796 updated += 1
800 797
801 798 # local directory rename, get
802 799 for f, args, msg in actions['dg']:
803 800 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
804 801 z += 1
805 802 progress(_updating, z, item=f, total=numupdates, unit=_files)
806 803 f0, flags = args
807 804 repo.ui.note(_("getting %s to %s\n") % (f0, f))
808 805 repo.wwrite(f, mctx.filectx(f0).data(), flags)
809 806 updated += 1
810 807
811 808 # exec
812 809 for f, args, msg in actions['e']:
813 810 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
814 811 z += 1
815 812 progress(_updating, z, item=f, total=numupdates, unit=_files)
816 813 flags, = args
817 814 audit(f)
818 815 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
819 816 updated += 1
820 817
821 818 ms.commit()
822 819 progress(_updating, None, total=numupdates, unit=_files)
823 820
824 821 return updated, merged, removed, unresolved
825 822
826 823 def recordupdates(repo, actions, branchmerge):
827 824 "record merge actions to the dirstate"
828 825 # remove (must come first)
829 826 for f, args, msg in actions['r']:
830 827 if branchmerge:
831 828 repo.dirstate.remove(f)
832 829 else:
833 830 repo.dirstate.drop(f)
834 831
835 832 # forget (must come first)
836 833 for f, args, msg in actions['f']:
837 834 repo.dirstate.drop(f)
838 835
839 836 # re-add
840 837 for f, args, msg in actions['a']:
841 838 if not branchmerge:
842 839 repo.dirstate.add(f)
843 840
844 841 # exec change
845 842 for f, args, msg in actions['e']:
846 843 repo.dirstate.normallookup(f)
847 844
848 845 # keep
849 846 for f, args, msg in actions['k']:
850 847 pass
851 848
852 849 # get
853 850 for f, args, msg in actions['g']:
854 851 if branchmerge:
855 852 repo.dirstate.otherparent(f)
856 853 else:
857 854 repo.dirstate.normal(f)
858 855
859 856 # merge
860 857 for f, args, msg in actions['m']:
861 858 f1, f2, fa, move, anc = args
862 859 if branchmerge:
863 860 # We've done a branch merge, mark this file as merged
864 861 # so that we properly record the merger later
865 862 repo.dirstate.merge(f)
866 863 if f1 != f2: # copy/rename
867 864 if move:
868 865 repo.dirstate.remove(f1)
869 866 if f1 != f:
870 867 repo.dirstate.copy(f1, f)
871 868 else:
872 869 repo.dirstate.copy(f2, f)
873 870 else:
874 871 # We've update-merged a locally modified file, so
875 872 # we set the dirstate to emulate a normal checkout
876 873 # of that file some time in the past. Thus our
877 874 # merge will appear as a normal local file
878 875 # modification.
879 876 if f2 == f: # file not locally copied/moved
880 877 repo.dirstate.normallookup(f)
881 878 if move:
882 879 repo.dirstate.drop(f1)
883 880
884 881 # directory rename, move local
885 882 for f, args, msg in actions['dm']:
886 883 f0, flag = args
887 884 if branchmerge:
888 885 repo.dirstate.add(f)
889 886 repo.dirstate.remove(f0)
890 887 repo.dirstate.copy(f0, f)
891 888 else:
892 889 repo.dirstate.normal(f)
893 890 repo.dirstate.drop(f0)
894 891
895 892 # directory rename, get
896 893 for f, args, msg in actions['dg']:
897 894 f0, flag = args
898 895 if branchmerge:
899 896 repo.dirstate.add(f)
900 897 repo.dirstate.copy(f0, f)
901 898 else:
902 899 repo.dirstate.normal(f)
903 900
904 901 def update(repo, node, branchmerge, force, partial, ancestor=None,
905 902 mergeancestor=False, labels=None):
906 903 """
907 904 Perform a merge between the working directory and the given node
908 905
909 906 node = the node to update to, or None if unspecified
910 907 branchmerge = whether to merge between branches
911 908 force = whether to force branch merging or file overwriting
912 909 partial = a function to filter file lists (dirstate not updated)
913 910 mergeancestor = whether it is merging with an ancestor. If true,
914 911 we should accept the incoming changes for any prompts that occur.
915 912 If false, merging with an ancestor (fast-forward) is only allowed
916 913 between different named branches. This flag is used by rebase extension
917 914 as a temporary fix and should be avoided in general.
918 915
919 916 The table below shows all the behaviors of the update command
920 917 given the -c and -C or no options, whether the working directory
921 918 is dirty, whether a revision is specified, and the relationship of
922 919 the parent rev to the target rev (linear, on the same named
923 920 branch, or on another named branch).
924 921
925 922 This logic is tested by test-update-branches.t.
926 923
927 924 -c -C dirty rev | linear same cross
928 925 n n n n | ok (1) x
929 926 n n n y | ok ok ok
930 927 n n y n | merge (2) (2)
931 928 n n y y | merge (3) (3)
932 929 n y * * | --- discard ---
933 930 y n y * | --- (4) ---
934 931 y n n * | --- ok ---
935 932 y y * * | --- (5) ---
936 933
937 934 x = can't happen
938 935 * = don't-care
939 936 1 = abort: not a linear update (merge or update --check to force update)
940 937 2 = abort: uncommitted changes (commit and merge, or update --clean to
941 938 discard changes)
942 939 3 = abort: uncommitted changes (commit or update --clean to discard changes)
943 940 4 = abort: uncommitted changes (checked in commands.py)
944 941 5 = incompatible options (checked in commands.py)
945 942
946 943 Return the same tuple as applyupdates().
947 944 """
948 945
949 946 onode = node
950 947 wlock = repo.wlock()
951 948 try:
952 949 wc = repo[None]
953 950 pl = wc.parents()
954 951 p1 = pl[0]
955 952 pas = [None]
956 953 if ancestor is not None:
957 954 pas = [repo[ancestor]]
958 955
959 956 if node is None:
960 957 # Here is where we should consider bookmarks, divergent bookmarks,
961 958 # foreground changesets (successors), and tip of current branch;
962 959 # but currently we are only checking the branch tips.
963 960 try:
964 961 node = repo.branchtip(wc.branch())
965 962 except errormod.RepoLookupError:
966 963 if wc.branch() == 'default': # no default branch!
967 964 node = repo.lookup('tip') # update to tip
968 965 else:
969 966 raise util.Abort(_("branch %s not found") % wc.branch())
970 967
971 968 if p1.obsolete() and not p1.children():
972 969 # allow updating to successors
973 970 successors = obsolete.successorssets(repo, p1.node())
974 971
975 972 # behavior of certain cases is as follows,
976 973 #
977 974 # divergent changesets: update to highest rev, similar to what
978 975 # is currently done when there are more than one head
979 976 # (i.e. 'tip')
980 977 #
981 978 # replaced changesets: same as divergent except we know there
982 979 # is no conflict
983 980 #
984 981 # pruned changeset: no update is done; though, we could
985 982 # consider updating to the first non-obsolete parent,
986 983 # similar to what is current done for 'hg prune'
987 984
988 985 if successors:
989 986 # flatten the list here handles both divergent (len > 1)
990 987 # and the usual case (len = 1)
991 988 successors = [n for sub in successors for n in sub]
992 989
993 990 # get the max revision for the given successors set,
994 991 # i.e. the 'tip' of a set
995 992 node = repo.revs('max(%ln)', successors).first()
996 993 pas = [p1]
997 994
998 995 overwrite = force and not branchmerge
999 996
1000 997 p2 = repo[node]
1001 998 if pas[0] is None:
1002 999 if repo.ui.config('merge', 'preferancestor', '*') == '*':
1003 1000 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1004 1001 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1005 1002 else:
1006 1003 pas = [p1.ancestor(p2, warn=branchmerge)]
1007 1004
1008 1005 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
1009 1006
1010 1007 ### check phase
1011 1008 if not overwrite and len(pl) > 1:
1012 1009 raise util.Abort(_("outstanding uncommitted merge"))
1013 1010 if branchmerge:
1014 1011 if pas == [p2]:
1015 1012 raise util.Abort(_("merging with a working directory ancestor"
1016 1013 " has no effect"))
1017 1014 elif pas == [p1]:
1018 1015 if not mergeancestor and p1.branch() == p2.branch():
1019 1016 raise util.Abort(_("nothing to merge"),
1020 1017 hint=_("use 'hg update' "
1021 1018 "or check 'hg heads'"))
1022 1019 if not force and (wc.files() or wc.deleted()):
1023 1020 raise util.Abort(_("uncommitted changes"),
1024 1021 hint=_("use 'hg status' to list changes"))
1025 1022 for s in sorted(wc.substate):
1026 1023 if wc.sub(s).dirty():
1027 1024 raise util.Abort(_("uncommitted changes in "
1028 1025 "subrepository '%s'") % s)
1029 1026
1030 1027 elif not overwrite:
1031 1028 if p1 == p2: # no-op update
1032 1029 # call the hooks and exit early
1033 1030 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
1034 1031 repo.hook('update', parent1=xp2, parent2='', error=0)
1035 1032 return 0, 0, 0, 0
1036 1033
1037 1034 if pas not in ([p1], [p2]): # nonlinear
1038 1035 dirty = wc.dirty(missing=True)
1039 1036 if dirty or onode is None:
1040 1037 # Branching is a bit strange to ensure we do the minimal
1041 1038 # amount of call to obsolete.background.
1042 1039 foreground = obsolete.foreground(repo, [p1.node()])
1043 1040 # note: the <node> variable contains a random identifier
1044 1041 if repo[node].node() in foreground:
1045 1042 pas = [p1] # allow updating to successors
1046 1043 elif dirty:
1047 1044 msg = _("uncommitted changes")
1048 1045 if onode is None:
1049 1046 hint = _("commit and merge, or update --clean to"
1050 1047 " discard changes")
1051 1048 else:
1052 1049 hint = _("commit or update --clean to discard"
1053 1050 " changes")
1054 1051 raise util.Abort(msg, hint=hint)
1055 1052 else: # node is none
1056 1053 msg = _("not a linear update")
1057 1054 hint = _("merge or update --check to force update")
1058 1055 raise util.Abort(msg, hint=hint)
1059 1056 else:
1060 1057 # Allow jumping branches if clean and specific rev given
1061 1058 pas = [p1]
1062 1059
1063 1060 followcopies = False
1064 1061 if overwrite:
1065 1062 pas = [wc]
1066 1063 elif pas == [p2]: # backwards
1067 1064 pas = [wc.p1()]
1068 1065 elif not branchmerge and not wc.dirty(missing=True):
1069 1066 pass
1070 1067 elif pas[0] and repo.ui.configbool('merge', 'followcopies', True):
1071 1068 followcopies = True
1072 1069
1073 1070 ### calculate phase
1074 1071 actionbyfile, diverge, renamedelete = calculateupdates(
1075 1072 repo, wc, p2, pas, branchmerge, force, partial, mergeancestor,
1076 1073 followcopies)
1077 1074 # Convert to dictionary-of-lists format
1078 1075 actions = dict((m, []) for m in 'a f g cd dc r dm dg m e k'.split())
1079 1076 for f, (m, args, msg) in actionbyfile.iteritems():
1080 1077 if m not in actions:
1081 1078 actions[m] = []
1082 1079 actions[m].append((f, args, msg))
1083 1080
1084 1081 if not util.checkcase(repo.path):
1085 1082 # check collision between files only in p2 for clean update
1086 1083 if (not branchmerge and
1087 1084 (force or not wc.dirty(missing=True, branch=False))):
1088 1085 _checkcollision(repo, p2.manifest(), None)
1089 1086 else:
1090 1087 _checkcollision(repo, wc.manifest(), actions)
1091 1088
1092 1089 # Prompt and create actions. TODO: Move this towards resolve phase.
1093 1090 for f, args, msg in sorted(actions['cd']):
1094 1091 if repo.ui.promptchoice(
1095 1092 _("local changed %s which remote deleted\n"
1096 1093 "use (c)hanged version or (d)elete?"
1097 1094 "$$ &Changed $$ &Delete") % f, 0):
1098 1095 actions['r'].append((f, None, "prompt delete"))
1099 1096 else:
1100 1097 actions['a'].append((f, None, "prompt keep"))
1101 1098 del actions['cd'][:]
1102 1099
1103 1100 for f, args, msg in sorted(actions['dc']):
1104 1101 flags, = args
1105 1102 if repo.ui.promptchoice(
1106 1103 _("remote changed %s which local deleted\n"
1107 1104 "use (c)hanged version or leave (d)eleted?"
1108 1105 "$$ &Changed $$ &Deleted") % f, 0) == 0:
1109 1106 actions['g'].append((f, (flags,), "prompt recreating"))
1110 1107 del actions['dc'][:]
1111 1108
1112 1109 ### apply phase
1113 1110 if not branchmerge: # just jump to the new rev
1114 1111 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
1115 1112 if not partial:
1116 1113 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
1117 1114 # note that we're in the middle of an update
1118 1115 repo.vfs.write('updatestate', p2.hex())
1119 1116
1120 1117 stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
1121 1118
1122 1119 # divergent renames
1123 1120 for f, fl in sorted(diverge.iteritems()):
1124 1121 repo.ui.warn(_("note: possible conflict - %s was renamed "
1125 1122 "multiple times to:\n") % f)
1126 1123 for nf in fl:
1127 1124 repo.ui.warn(" %s\n" % nf)
1128 1125
1129 1126 # rename and delete
1130 1127 for f, fl in sorted(renamedelete.iteritems()):
1131 1128 repo.ui.warn(_("note: possible conflict - %s was deleted "
1132 1129 "and renamed to:\n") % f)
1133 1130 for nf in fl:
1134 1131 repo.ui.warn(" %s\n" % nf)
1135 1132
1136 1133 if not partial:
1137 1134 repo.dirstate.beginparentchange()
1138 1135 repo.setparents(fp1, fp2)
1139 1136 recordupdates(repo, actions, branchmerge)
1140 1137 # update completed, clear state
1141 1138 util.unlink(repo.join('updatestate'))
1142 1139
1143 1140 if not branchmerge:
1144 1141 repo.dirstate.setbranch(p2.branch())
1145 1142 repo.dirstate.endparentchange()
1146 1143 finally:
1147 1144 wlock.release()
1148 1145
1149 1146 if not partial:
1150 1147 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
1151 1148 return stats
1152 1149
1153 1150 def graft(repo, ctx, pctx, labels):
1154 1151 """Do a graft-like merge.
1155 1152
1156 1153 This is a merge where the merge ancestor is chosen such that one
1157 1154 or more changesets are grafted onto the current changeset. In
1158 1155 addition to the merge, this fixes up the dirstate to include only
1159 1156 a single parent and tries to duplicate any renames/copies
1160 1157 appropriately.
1161 1158
1162 1159 ctx - changeset to rebase
1163 1160 pctx - merge base, usually ctx.p1()
1164 1161 labels - merge labels eg ['local', 'graft']
1165 1162
1166 1163 """
1167 1164
1168 1165 stats = update(repo, ctx.node(), True, True, False, pctx.node(),
1169 1166 labels=labels)
1170 1167 # drop the second merge parent
1171 1168 repo.dirstate.beginparentchange()
1172 1169 repo.setparents(repo['.'].node(), nullid)
1173 1170 repo.dirstate.write()
1174 1171 # fix up dirstate for copies and renames
1175 1172 copies.duplicatecopies(repo, ctx.rev(), pctx.rev())
1176 1173 repo.dirstate.endparentchange()
1177 1174 return stats
General Comments 0
You need to be logged in to leave comments. Login now