##// END OF EJS Templates
merge: consistently use single quotes for non-user-facing strings...
Martin von Zweigbergk -
r23380:90cc552c default
parent child Browse files
Show More
@@ -1,1145 +1,1145
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 bits = data.split("\0")
21 bits = data.split('\0')
22 22 bits = bits[:-2] + bits[-1:]
23 return "\0".join(bits)
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 statepathv1 = "merge/state"
49 statepathv2 = "merge/state2"
48 statepathv1 = 'merge/state'
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 shutil.rmtree(self._repo.join("merge"), True)
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 elif rtype == "F":
82 bits = record.split("\0")
81 elif rtype == 'F':
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 bits = r[1].split("\0")
124 bits = r[1].split('\0')
125 125 bits.insert(-2, '')
126 v1records[idx] = (r[0], "\0".join(bits))
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 records.append(("L", hex(self._local)))
195 records.append(("O", hex(self._other)))
194 records.append(('L', hex(self._local)))
195 records.append(('O', hex(self._other)))
196 196 for d, v in self._state.iteritems():
197 records.append(("F", "\0".join([d] + v)))
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 f = self._repo.opener(self.statepathv1, "w")
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 f.write(hex(self._local) + "\n")
212 f.write(hex(self._local) + '\n')
213 213 for rtype, data in irecords:
214 if rtype == "F":
215 f.write("%s\n" % _droponode(data))
214 if rtype == 'F':
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 f = self._repo.opener(self.statepathv2, "w")
220 f = self._repo.opener(self.statepathv2, 'w')
221 221 for key, data in records:
222 222 assert len(key) == 1
223 format = ">sI%is" % len(data)
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 self._repo.opener.write("merge/" + hash, fcl.data())
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 f = self._repo.opener("merge/" + hash)
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 (not repo.dirstate._ignore(f)
302 302 and os.path.isfile(repo.wjoin(f))
303 303 and repo.wopener.audit.check(f)
304 304 and repo.dirstate.normalize(f) not in repo.dirstate
305 305 and mctx[f].cmp(wctx[f]))
306 306
307 307 def _forgetremoved(wctx, mctx, branchmerge):
308 308 """
309 309 Forget removed files
310 310
311 311 If we're jumping between revisions (as opposed to merging), and if
312 312 neither the working directory nor the target rev has the file,
313 313 then we need to remove it from the dirstate, to prevent the
314 314 dirstate from listing the file when it is no longer in the
315 315 manifest.
316 316
317 317 If we're merging, and the other revision has removed a file
318 318 that is not present in the working directory, we need to mark it
319 319 as removed.
320 320 """
321 321
322 322 ractions = []
323 323 factions = xactions = []
324 324 if branchmerge:
325 325 xactions = ractions
326 326 for f in wctx.deleted():
327 327 if f not in mctx:
328 328 xactions.append((f, None, "forget deleted"))
329 329
330 330 if not branchmerge:
331 331 for f in wctx.removed():
332 332 if f not in mctx:
333 333 factions.append((f, None, "forget removed"))
334 334
335 335 return ractions, factions
336 336
337 337 def _checkcollision(repo, wmf, actions):
338 338 # build provisional merged manifest up
339 339 pmmf = set(wmf)
340 340
341 341 if actions:
342 342 # k, dr, e and rd are no-op
343 343 for m in 'a', 'f', 'g', 'cd', 'dc':
344 344 for f, args, msg in actions[m]:
345 345 pmmf.add(f)
346 346 for f, args, msg in actions['r']:
347 347 pmmf.discard(f)
348 348 for f, args, msg in actions['dm']:
349 349 f2, flags = args
350 350 pmmf.discard(f2)
351 351 pmmf.add(f)
352 352 for f, args, msg in actions['dg']:
353 353 f2, flags = args
354 354 pmmf.add(f)
355 355 for f, args, msg in actions['m']:
356 356 f1, f2, fa, move, anc = args
357 357 if move:
358 358 pmmf.discard(f1)
359 359 pmmf.add(f)
360 360
361 361 # check case-folding collision in provisional merged manifest
362 362 foldmap = {}
363 363 for f in sorted(pmmf):
364 364 fold = util.normcase(f)
365 365 if fold in foldmap:
366 366 raise util.Abort(_("case-folding collision between %s and %s")
367 367 % (f, foldmap[fold]))
368 368 foldmap[fold] = f
369 369
370 370 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial,
371 371 acceptremote, followcopies):
372 372 """
373 373 Merge p1 and p2 with ancestor pa and generate merge action list
374 374
375 375 branchmerge and force are as passed in to update
376 376 partial = function to filter file lists
377 377 acceptremote = accept the incoming changes without prompting
378 378 """
379 379
380 380 actions = dict((m, []) for m in 'a f g cd dc r dm dg m dr e rd k'.split())
381 381 copy, movewithdir = {}, {}
382 382
383 383 # manifests fetched in order are going to be faster, so prime the caches
384 384 [x.manifest() for x in
385 385 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
386 386
387 387 if followcopies:
388 388 ret = copies.mergecopies(repo, wctx, p2, pa)
389 389 copy, movewithdir, diverge, renamedelete = ret
390 390 for of, fl in diverge.iteritems():
391 391 actions['dr'].append((of, (fl,), "divergent renames"))
392 392 for of, fl in renamedelete.iteritems():
393 393 actions['rd'].append((of, (fl,), "rename and delete"))
394 394
395 395 repo.ui.note(_("resolving manifests\n"))
396 396 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
397 397 % (bool(branchmerge), bool(force), bool(partial)))
398 398 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
399 399
400 400 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
401 401 copied = set(copy.values())
402 402 copied.update(movewithdir.values())
403 403
404 404 if '.hgsubstate' in m1:
405 405 # check whether sub state is modified
406 406 for s in sorted(wctx.substate):
407 407 if wctx.sub(s).dirty():
408 m1['.hgsubstate'] += "+"
408 m1['.hgsubstate'] += '+'
409 409 break
410 410
411 411 aborts = []
412 412 # Compare manifests
413 413 diff = m1.diff(m2)
414 414
415 415 for f, ((n1, fl1), (n2, fl2)) in diff.iteritems():
416 416 if partial and not partial(f):
417 417 continue
418 418 if n1 and n2:
419 419 fa = f
420 420 a = ma.get(f, nullid)
421 421 if a == nullid:
422 422 fa = copy.get(f, f)
423 423 # Note: f as default is wrong - we can't really make a 3-way
424 424 # merge without an ancestor file.
425 425 fla = ma.flags(fa)
426 426 nol = 'l' not in fl1 + fl2 + fla
427 427 if n2 == a and fl2 == fla:
428 428 actions['k'].append((f, (), "keep")) # remote unchanged
429 429 elif n1 == a and fl1 == fla: # local unchanged - use remote
430 430 if n1 == n2: # optimization: keep local content
431 431 actions['e'].append((f, (fl2,), "update permissions"))
432 432 else:
433 433 actions['g'].append((f, (fl2,), "remote is newer"))
434 434 elif nol and n2 == a: # remote only changed 'x'
435 435 actions['e'].append((f, (fl2,), "update permissions"))
436 436 elif nol and n1 == a: # local only changed 'x'
437 437 actions['g'].append((f, (fl1,), "remote is newer"))
438 438 else: # both changed something
439 439 actions['m'].append((f, (f, f, fa, False, pa.node()),
440 440 "versions differ"))
441 441 elif f in copied: # files we'll deal with on m2 side
442 442 pass
443 443 elif n1 and f in movewithdir: # directory rename, move local
444 444 f2 = movewithdir[f]
445 445 actions['dm'].append((f2, (f, fl1),
446 446 "remote directory rename - move from " + f))
447 447 elif n1 and f in copy:
448 448 f2 = copy[f]
449 449 actions['m'].append((f, (f, f2, f2, False, pa.node()),
450 450 "local copied/moved from " + f2))
451 451 elif n1 and f in ma: # clean, a different, no remote
452 452 if n1 != ma[f]:
453 453 if acceptremote:
454 454 actions['r'].append((f, None, "remote delete"))
455 455 else:
456 456 actions['cd'].append((f, None, "prompt changed/deleted"))
457 elif n1[20:] == "a": # added, no remote
457 elif n1[20:] == 'a': # added, no remote
458 458 actions['f'].append((f, None, "remote deleted"))
459 459 else:
460 460 actions['r'].append((f, None, "other deleted"))
461 461 elif n2 and f in movewithdir:
462 462 f2 = movewithdir[f]
463 463 actions['dg'].append((f2, (f, fl2),
464 464 "local directory rename - get from " + f))
465 465 elif n2 and f in copy:
466 466 f2 = copy[f]
467 467 if f2 in m2:
468 468 actions['m'].append((f, (f2, f, f2, False, pa.node()),
469 469 "remote copied from " + f2))
470 470 else:
471 471 actions['m'].append((f, (f2, f, f2, True, pa.node()),
472 472 "remote moved from " + f2))
473 473 elif n2 and f not in ma:
474 474 # local unknown, remote created: the logic is described by the
475 475 # following table:
476 476 #
477 477 # force branchmerge different | action
478 478 # n * n | get
479 479 # n * y | abort
480 480 # y n * | get
481 481 # y y n | get
482 482 # y y y | merge
483 483 #
484 484 # Checking whether the files are different is expensive, so we
485 485 # don't do that when we can avoid it.
486 486 if force and not branchmerge:
487 487 actions['g'].append((f, (fl2,), "remote created"))
488 488 else:
489 489 different = _checkunknownfile(repo, wctx, p2, f)
490 490 if force and branchmerge and different:
491 491 # FIXME: This is wrong - f is not in ma ...
492 492 actions['m'].append((f, (f, f, f, False, pa.node()),
493 493 "remote differs from untracked local"))
494 494 elif not force and different:
495 aborts.append((f, "ud"))
495 aborts.append((f, 'ud'))
496 496 else:
497 497 actions['g'].append((f, (fl2,), "remote created"))
498 498 elif n2 and n2 != ma[f]:
499 499 different = _checkunknownfile(repo, wctx, p2, f)
500 500 if not force and different:
501 aborts.append((f, "ud"))
501 aborts.append((f, 'ud'))
502 502 else:
503 503 if acceptremote:
504 504 actions['g'].append((f, (fl2,), "remote recreating"))
505 505 else:
506 506 actions['dc'].append((f, (fl2,), "prompt deleted/changed"))
507 507
508 508 for f, m in sorted(aborts):
509 if m == "ud":
509 if m == 'ud':
510 510 repo.ui.warn(_("%s: untracked file differs\n") % f)
511 511 else: assert False, m
512 512 if aborts:
513 513 raise util.Abort(_("untracked files in working directory differ "
514 514 "from files in requested revision"))
515 515
516 516 if not util.checkcase(repo.path):
517 517 # check collision between files only in p2 for clean update
518 518 if (not branchmerge and
519 519 (force or not wctx.dirty(missing=True, branch=False))):
520 520 _checkcollision(repo, m2, None)
521 521 else:
522 522 _checkcollision(repo, m1, actions)
523 523
524 524 return actions
525 525
526 526 def batchremove(repo, actions):
527 527 """apply removes to the working directory
528 528
529 529 yields tuples for progress updates
530 530 """
531 531 verbose = repo.ui.verbose
532 532 unlink = util.unlinkpath
533 533 wjoin = repo.wjoin
534 534 audit = repo.wopener.audit
535 535 i = 0
536 536 for f, args, msg in actions:
537 537 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
538 538 if verbose:
539 539 repo.ui.note(_("removing %s\n") % f)
540 540 audit(f)
541 541 try:
542 542 unlink(wjoin(f), ignoremissing=True)
543 543 except OSError, inst:
544 544 repo.ui.warn(_("update failed to remove %s: %s!\n") %
545 545 (f, inst.strerror))
546 546 if i == 100:
547 547 yield i, f
548 548 i = 0
549 549 i += 1
550 550 if i > 0:
551 551 yield i, f
552 552
553 553 def batchget(repo, mctx, actions):
554 554 """apply gets to the working directory
555 555
556 556 mctx is the context to get from
557 557
558 558 yields tuples for progress updates
559 559 """
560 560 verbose = repo.ui.verbose
561 561 fctx = mctx.filectx
562 562 wwrite = repo.wwrite
563 563 i = 0
564 564 for f, args, msg in actions:
565 565 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
566 566 if verbose:
567 567 repo.ui.note(_("getting %s\n") % f)
568 568 wwrite(f, fctx(f).data(), args[0])
569 569 if i == 100:
570 570 yield i, f
571 571 i = 0
572 572 i += 1
573 573 if i > 0:
574 574 yield i, f
575 575
576 576 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
577 577 """apply the merge action list to the working directory
578 578
579 579 wctx is the working copy context
580 580 mctx is the context to be merged into the working copy
581 581
582 582 Return a tuple of counts (updated, merged, removed, unresolved) that
583 583 describes how many files were affected by the update.
584 584 """
585 585
586 586 updated, merged, removed, unresolved = 0, 0, 0, 0
587 587 ms = mergestate(repo)
588 588 ms.reset(wctx.p1().node(), mctx.node())
589 589 moves = []
590 590 for m, l in actions.items():
591 591 l.sort()
592 592
593 593 # prescan for merges
594 594 for f, args, msg in actions['m']:
595 595 f1, f2, fa, move, anc = args
596 596 if f == '.hgsubstate': # merged internally
597 597 continue
598 598 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
599 599 fcl = wctx[f1]
600 600 fco = mctx[f2]
601 601 actx = repo[anc]
602 602 if fa in actx:
603 603 fca = actx[fa]
604 604 else:
605 605 fca = repo.filectx(f1, fileid=nullrev)
606 606 ms.add(fcl, fco, fca, f)
607 607 if f1 != f and move:
608 608 moves.append(f1)
609 609
610 610 audit = repo.wopener.audit
611 611 _updating = _('updating')
612 612 _files = _('files')
613 613 progress = repo.ui.progress
614 614
615 615 # remove renamed files after safely stored
616 616 for f in moves:
617 617 if os.path.lexists(repo.wjoin(f)):
618 618 repo.ui.debug("removing %s\n" % f)
619 619 audit(f)
620 620 util.unlinkpath(repo.wjoin(f))
621 621
622 622 numupdates = sum(len(l) for m, l in actions.items() if m != 'k')
623 623
624 624 if [a for a in actions['r'] if a[0] == '.hgsubstate']:
625 625 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
626 626
627 627 # remove in parallel (must come first)
628 628 z = 0
629 629 prog = worker.worker(repo.ui, 0.001, batchremove, (repo,), actions['r'])
630 630 for i, item in prog:
631 631 z += i
632 632 progress(_updating, z, item=item, total=numupdates, unit=_files)
633 633 removed = len(actions['r'])
634 634
635 635 # get in parallel
636 636 prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx), actions['g'])
637 637 for i, item in prog:
638 638 z += i
639 639 progress(_updating, z, item=item, total=numupdates, unit=_files)
640 640 updated = len(actions['g'])
641 641
642 642 if [a for a in actions['g'] if a[0] == '.hgsubstate']:
643 643 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
644 644
645 645 # forget (manifest only, just log it) (must come first)
646 646 for f, args, msg in actions['f']:
647 647 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
648 648 z += 1
649 649 progress(_updating, z, item=f, total=numupdates, unit=_files)
650 650
651 651 # re-add (manifest only, just log it)
652 652 for f, args, msg in actions['a']:
653 653 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
654 654 z += 1
655 655 progress(_updating, z, item=f, total=numupdates, unit=_files)
656 656
657 657 # keep (noop, just log it)
658 658 for f, args, msg in actions['k']:
659 659 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
660 660 # no progress
661 661
662 662 # merge
663 663 for f, args, msg in actions['m']:
664 664 repo.ui.debug(" %s: %s -> m\n" % (f, msg))
665 665 z += 1
666 666 progress(_updating, z, item=f, total=numupdates, unit=_files)
667 667 f1, f2, fa, move, anc = args
668 668 if f == '.hgsubstate': # subrepo states need updating
669 669 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
670 670 overwrite)
671 671 continue
672 672 audit(f)
673 673 r = ms.resolve(f, wctx, labels=labels)
674 674 if r is not None and r > 0:
675 675 unresolved += 1
676 676 else:
677 677 if r is None:
678 678 updated += 1
679 679 else:
680 680 merged += 1
681 681
682 682 # directory rename, move local
683 683 for f, args, msg in actions['dm']:
684 684 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
685 685 z += 1
686 686 progress(_updating, z, item=f, total=numupdates, unit=_files)
687 687 f0, flags = args
688 688 repo.ui.note(_("moving %s to %s\n") % (f0, f))
689 689 audit(f)
690 690 repo.wwrite(f, wctx.filectx(f0).data(), flags)
691 691 util.unlinkpath(repo.wjoin(f0))
692 692 updated += 1
693 693
694 694 # local directory rename, get
695 695 for f, args, msg in actions['dg']:
696 696 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
697 697 z += 1
698 698 progress(_updating, z, item=f, total=numupdates, unit=_files)
699 699 f0, flags = args
700 700 repo.ui.note(_("getting %s to %s\n") % (f0, f))
701 701 repo.wwrite(f, mctx.filectx(f0).data(), flags)
702 702 updated += 1
703 703
704 704 # divergent renames
705 705 for f, args, msg in actions['dr']:
706 706 repo.ui.debug(" %s: %s -> dr\n" % (f, msg))
707 707 z += 1
708 708 progress(_updating, z, item=f, total=numupdates, unit=_files)
709 709 fl, = args
710 710 repo.ui.warn(_("note: possible conflict - %s was renamed "
711 711 "multiple times to:\n") % f)
712 712 for nf in fl:
713 713 repo.ui.warn(" %s\n" % nf)
714 714
715 715 # rename and delete
716 716 for f, args, msg in actions['rd']:
717 717 repo.ui.debug(" %s: %s -> rd\n" % (f, msg))
718 718 z += 1
719 719 progress(_updating, z, item=f, total=numupdates, unit=_files)
720 720 fl, = args
721 721 repo.ui.warn(_("note: possible conflict - %s was deleted "
722 722 "and renamed to:\n") % f)
723 723 for nf in fl:
724 724 repo.ui.warn(" %s\n" % nf)
725 725
726 726 # exec
727 727 for f, args, msg in actions['e']:
728 728 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
729 729 z += 1
730 730 progress(_updating, z, item=f, total=numupdates, unit=_files)
731 731 flags, = args
732 732 audit(f)
733 733 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
734 734 updated += 1
735 735
736 736 ms.commit()
737 737 progress(_updating, None, total=numupdates, unit=_files)
738 738
739 739 return updated, merged, removed, unresolved
740 740
741 741 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force, partial,
742 742 acceptremote, followcopies):
743 743 "Calculate the actions needed to merge mctx into wctx using ancestors"
744 744
745 745 if len(ancestors) == 1: # default
746 746 actions = manifestmerge(repo, wctx, mctx, ancestors[0],
747 747 branchmerge, force,
748 748 partial, acceptremote, followcopies)
749 749
750 750 else: # only when merge.preferancestor=* - the default
751 751 repo.ui.note(
752 752 _("note: merging %s and %s using bids from ancestors %s\n") %
753 753 (wctx, mctx, _(' and ').join(str(anc) for anc in ancestors)))
754 754
755 755 # Call for bids
756 756 fbids = {} # mapping filename to bids (action method to list af actions)
757 757 for ancestor in ancestors:
758 758 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
759 759 actions = manifestmerge(repo, wctx, mctx, ancestor,
760 760 branchmerge, force,
761 761 partial, acceptremote, followcopies)
762 762 for m, l in sorted(actions.items()):
763 763 for a in l:
764 764 f, args, msg = a
765 765 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
766 766 if f in fbids:
767 767 d = fbids[f]
768 768 if m in d:
769 769 d[m].append(a)
770 770 else:
771 771 d[m] = [a]
772 772 else:
773 773 fbids[f] = {m: [a]}
774 774
775 775 # Pick the best bid for each file
776 776 repo.ui.note(_('\nauction for merging merge bids\n'))
777 777 actions = dict((m, []) for m in actions.keys())
778 778 for f, bids in sorted(fbids.items()):
779 779 # bids is a mapping from action method to list af actions
780 780 # Consensus?
781 781 if len(bids) == 1: # all bids are the same kind of method
782 782 m, l = bids.items()[0]
783 783 if util.all(a == l[0] for a in l[1:]): # len(bids) is > 1
784 784 repo.ui.note(" %s: consensus for %s\n" % (f, m))
785 785 actions[m].append(l[0])
786 786 continue
787 787 # If keep is an option, just do it.
788 if "k" in bids:
788 if 'k' in bids:
789 789 repo.ui.note(" %s: picking 'keep' action\n" % f)
790 actions['k'].append(bids["k"][0])
790 actions['k'].append(bids['k'][0])
791 791 continue
792 792 # If there are gets and they all agree [how could they not?], do it.
793 if "g" in bids:
794 ga0 = bids["g"][0]
795 if util.all(a == ga0 for a in bids["g"][1:]):
793 if 'g' in bids:
794 ga0 = bids['g'][0]
795 if util.all(a == ga0 for a in bids['g'][1:]):
796 796 repo.ui.note(" %s: picking 'get' action\n" % f)
797 797 actions['g'].append(ga0)
798 798 continue
799 799 # TODO: Consider other simple actions such as mode changes
800 800 # Handle inefficient democrazy.
801 801 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
802 802 for m, l in sorted(bids.items()):
803 803 for _f, args, msg in l:
804 804 repo.ui.note(' %s -> %s\n' % (msg, m))
805 805 # Pick random action. TODO: Instead, prompt user when resolving
806 806 m, l = bids.items()[0]
807 807 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
808 808 (f, m))
809 809 actions[m].append(l[0])
810 810 continue
811 811 repo.ui.note(_('end of auction\n\n'))
812 812
813 813 # Prompt and create actions. TODO: Move this towards resolve phase.
814 814 for f, args, msg in actions['cd']:
815 815 if repo.ui.promptchoice(
816 816 _("local changed %s which remote deleted\n"
817 817 "use (c)hanged version or (d)elete?"
818 818 "$$ &Changed $$ &Delete") % f, 0):
819 819 actions['r'].append((f, None, "prompt delete"))
820 820 else:
821 821 actions['a'].append((f, None, "prompt keep"))
822 822 del actions['cd'][:]
823 823
824 824 for f, args, msg in actions['dc']:
825 825 flags, = args
826 826 if repo.ui.promptchoice(
827 827 _("remote changed %s which local deleted\n"
828 828 "use (c)hanged version or leave (d)eleted?"
829 829 "$$ &Changed $$ &Deleted") % f, 0) == 0:
830 830 actions['g'].append((f, (flags,), "prompt recreating"))
831 831 del actions['dc'][:]
832 832
833 833 if wctx.rev() is None:
834 834 ractions, factions = _forgetremoved(wctx, mctx, branchmerge)
835 835 actions['r'].extend(ractions)
836 836 actions['f'].extend(factions)
837 837
838 838 return actions
839 839
840 840 def recordupdates(repo, actions, branchmerge):
841 841 "record merge actions to the dirstate"
842 842 # remove (must come first)
843 843 for f, args, msg in actions['r']:
844 844 if branchmerge:
845 845 repo.dirstate.remove(f)
846 846 else:
847 847 repo.dirstate.drop(f)
848 848
849 849 # forget (must come first)
850 850 for f, args, msg in actions['f']:
851 851 repo.dirstate.drop(f)
852 852
853 853 # re-add
854 854 for f, args, msg in actions['a']:
855 855 if not branchmerge:
856 856 repo.dirstate.add(f)
857 857
858 858 # exec change
859 859 for f, args, msg in actions['e']:
860 860 repo.dirstate.normallookup(f)
861 861
862 862 # keep
863 863 for f, args, msg in actions['k']:
864 864 pass
865 865
866 866 # get
867 867 for f, args, msg in actions['g']:
868 868 if branchmerge:
869 869 repo.dirstate.otherparent(f)
870 870 else:
871 871 repo.dirstate.normal(f)
872 872
873 873 # merge
874 874 for f, args, msg in actions['m']:
875 875 f1, f2, fa, move, anc = args
876 876 if branchmerge:
877 877 # We've done a branch merge, mark this file as merged
878 878 # so that we properly record the merger later
879 879 repo.dirstate.merge(f)
880 880 if f1 != f2: # copy/rename
881 881 if move:
882 882 repo.dirstate.remove(f1)
883 883 if f1 != f:
884 884 repo.dirstate.copy(f1, f)
885 885 else:
886 886 repo.dirstate.copy(f2, f)
887 887 else:
888 888 # We've update-merged a locally modified file, so
889 889 # we set the dirstate to emulate a normal checkout
890 890 # of that file some time in the past. Thus our
891 891 # merge will appear as a normal local file
892 892 # modification.
893 893 if f2 == f: # file not locally copied/moved
894 894 repo.dirstate.normallookup(f)
895 895 if move:
896 896 repo.dirstate.drop(f1)
897 897
898 898 # directory rename, move local
899 899 for f, args, msg in actions['dm']:
900 900 f0, flag = args
901 901 if f0 not in repo.dirstate:
902 902 # untracked file moved
903 903 continue
904 904 if branchmerge:
905 905 repo.dirstate.add(f)
906 906 repo.dirstate.remove(f0)
907 907 repo.dirstate.copy(f0, f)
908 908 else:
909 909 repo.dirstate.normal(f)
910 910 repo.dirstate.drop(f0)
911 911
912 912 # directory rename, get
913 913 for f, args, msg in actions['dg']:
914 914 f0, flag = args
915 915 if branchmerge:
916 916 repo.dirstate.add(f)
917 917 repo.dirstate.copy(f0, f)
918 918 else:
919 919 repo.dirstate.normal(f)
920 920
921 921 def update(repo, node, branchmerge, force, partial, ancestor=None,
922 922 mergeancestor=False, labels=None):
923 923 """
924 924 Perform a merge between the working directory and the given node
925 925
926 926 node = the node to update to, or None if unspecified
927 927 branchmerge = whether to merge between branches
928 928 force = whether to force branch merging or file overwriting
929 929 partial = a function to filter file lists (dirstate not updated)
930 930 mergeancestor = whether it is merging with an ancestor. If true,
931 931 we should accept the incoming changes for any prompts that occur.
932 932 If false, merging with an ancestor (fast-forward) is only allowed
933 933 between different named branches. This flag is used by rebase extension
934 934 as a temporary fix and should be avoided in general.
935 935
936 936 The table below shows all the behaviors of the update command
937 937 given the -c and -C or no options, whether the working directory
938 938 is dirty, whether a revision is specified, and the relationship of
939 939 the parent rev to the target rev (linear, on the same named
940 940 branch, or on another named branch).
941 941
942 942 This logic is tested by test-update-branches.t.
943 943
944 944 -c -C dirty rev | linear same cross
945 945 n n n n | ok (1) x
946 946 n n n y | ok ok ok
947 947 n n y n | merge (2) (2)
948 948 n n y y | merge (3) (3)
949 949 n y * * | --- discard ---
950 950 y n y * | --- (4) ---
951 951 y n n * | --- ok ---
952 952 y y * * | --- (5) ---
953 953
954 954 x = can't happen
955 955 * = don't-care
956 956 1 = abort: not a linear update (merge or update --check to force update)
957 957 2 = abort: uncommitted changes (commit and merge, or update --clean to
958 958 discard changes)
959 959 3 = abort: uncommitted changes (commit or update --clean to discard changes)
960 960 4 = abort: uncommitted changes (checked in commands.py)
961 961 5 = incompatible options (checked in commands.py)
962 962
963 963 Return the same tuple as applyupdates().
964 964 """
965 965
966 966 onode = node
967 967 wlock = repo.wlock()
968 968 try:
969 969 wc = repo[None]
970 970 pl = wc.parents()
971 971 p1 = pl[0]
972 972 pas = [None]
973 973 if ancestor:
974 974 pas = [repo[ancestor]]
975 975
976 976 if node is None:
977 977 # Here is where we should consider bookmarks, divergent bookmarks,
978 978 # foreground changesets (successors), and tip of current branch;
979 979 # but currently we are only checking the branch tips.
980 980 try:
981 981 node = repo.branchtip(wc.branch())
982 982 except errormod.RepoLookupError:
983 if wc.branch() == "default": # no default branch!
984 node = repo.lookup("tip") # update to tip
983 if wc.branch() == 'default': # no default branch!
984 node = repo.lookup('tip') # update to tip
985 985 else:
986 986 raise util.Abort(_("branch %s not found") % wc.branch())
987 987
988 988 if p1.obsolete() and not p1.children():
989 989 # allow updating to successors
990 990 successors = obsolete.successorssets(repo, p1.node())
991 991
992 992 # behavior of certain cases is as follows,
993 993 #
994 994 # divergent changesets: update to highest rev, similar to what
995 995 # is currently done when there are more than one head
996 996 # (i.e. 'tip')
997 997 #
998 998 # replaced changesets: same as divergent except we know there
999 999 # is no conflict
1000 1000 #
1001 1001 # pruned changeset: no update is done; though, we could
1002 1002 # consider updating to the first non-obsolete parent,
1003 1003 # similar to what is current done for 'hg prune'
1004 1004
1005 1005 if successors:
1006 1006 # flatten the list here handles both divergent (len > 1)
1007 1007 # and the usual case (len = 1)
1008 1008 successors = [n for sub in successors for n in sub]
1009 1009
1010 1010 # get the max revision for the given successors set,
1011 1011 # i.e. the 'tip' of a set
1012 node = repo.revs("max(%ln)", successors).first()
1012 node = repo.revs('max(%ln)', successors).first()
1013 1013 pas = [p1]
1014 1014
1015 1015 overwrite = force and not branchmerge
1016 1016
1017 1017 p2 = repo[node]
1018 1018 if pas[0] is None:
1019 if repo.ui.config("merge", "preferancestor", '*') == '*':
1019 if repo.ui.config('merge', 'preferancestor', '*') == '*':
1020 1020 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1021 1021 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1022 1022 else:
1023 1023 pas = [p1.ancestor(p2, warn=branchmerge)]
1024 1024
1025 1025 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
1026 1026
1027 1027 ### check phase
1028 1028 if not overwrite and len(pl) > 1:
1029 1029 raise util.Abort(_("outstanding uncommitted merge"))
1030 1030 if branchmerge:
1031 1031 if pas == [p2]:
1032 1032 raise util.Abort(_("merging with a working directory ancestor"
1033 1033 " has no effect"))
1034 1034 elif pas == [p1]:
1035 1035 if not mergeancestor and p1.branch() == p2.branch():
1036 1036 raise util.Abort(_("nothing to merge"),
1037 1037 hint=_("use 'hg update' "
1038 1038 "or check 'hg heads'"))
1039 1039 if not force and (wc.files() or wc.deleted()):
1040 1040 raise util.Abort(_("uncommitted changes"),
1041 1041 hint=_("use 'hg status' to list changes"))
1042 1042 for s in sorted(wc.substate):
1043 1043 if wc.sub(s).dirty():
1044 1044 raise util.Abort(_("uncommitted changes in "
1045 1045 "subrepository '%s'") % s)
1046 1046
1047 1047 elif not overwrite:
1048 1048 if p1 == p2: # no-op update
1049 1049 # call the hooks and exit early
1050 1050 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
1051 1051 repo.hook('update', parent1=xp2, parent2='', error=0)
1052 1052 return 0, 0, 0, 0
1053 1053
1054 1054 if pas not in ([p1], [p2]): # nonlinear
1055 1055 dirty = wc.dirty(missing=True)
1056 1056 if dirty or onode is None:
1057 1057 # Branching is a bit strange to ensure we do the minimal
1058 1058 # amount of call to obsolete.background.
1059 1059 foreground = obsolete.foreground(repo, [p1.node()])
1060 1060 # note: the <node> variable contains a random identifier
1061 1061 if repo[node].node() in foreground:
1062 1062 pas = [p1] # allow updating to successors
1063 1063 elif dirty:
1064 1064 msg = _("uncommitted changes")
1065 1065 if onode is None:
1066 1066 hint = _("commit and merge, or update --clean to"
1067 1067 " discard changes")
1068 1068 else:
1069 1069 hint = _("commit or update --clean to discard"
1070 1070 " changes")
1071 1071 raise util.Abort(msg, hint=hint)
1072 1072 else: # node is none
1073 1073 msg = _("not a linear update")
1074 1074 hint = _("merge or update --check to force update")
1075 1075 raise util.Abort(msg, hint=hint)
1076 1076 else:
1077 1077 # Allow jumping branches if clean and specific rev given
1078 1078 pas = [p1]
1079 1079
1080 1080 followcopies = False
1081 1081 if overwrite:
1082 1082 pas = [wc]
1083 1083 elif pas == [p2]: # backwards
1084 1084 pas = [wc.p1()]
1085 1085 elif not branchmerge and not wc.dirty(missing=True):
1086 1086 pass
1087 elif pas[0] and repo.ui.configbool("merge", "followcopies", True):
1087 elif pas[0] and repo.ui.configbool('merge', 'followcopies', True):
1088 1088 followcopies = True
1089 1089
1090 1090 ### calculate phase
1091 1091 actions = calculateupdates(repo, wc, p2, pas, branchmerge, force,
1092 1092 partial, mergeancestor, followcopies)
1093 1093
1094 1094 ### apply phase
1095 1095 if not branchmerge: # just jump to the new rev
1096 1096 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
1097 1097 if not partial:
1098 1098 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
1099 1099 # note that we're in the middle of an update
1100 1100 repo.vfs.write('updatestate', p2.hex())
1101 1101
1102 1102 stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
1103 1103
1104 1104 if not partial:
1105 1105 repo.dirstate.beginparentchange()
1106 1106 repo.setparents(fp1, fp2)
1107 1107 recordupdates(repo, actions, branchmerge)
1108 1108 # update completed, clear state
1109 1109 util.unlink(repo.join('updatestate'))
1110 1110
1111 1111 if not branchmerge:
1112 1112 repo.dirstate.setbranch(p2.branch())
1113 1113 repo.dirstate.endparentchange()
1114 1114 finally:
1115 1115 wlock.release()
1116 1116
1117 1117 if not partial:
1118 1118 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
1119 1119 return stats
1120 1120
1121 1121 def graft(repo, ctx, pctx, labels):
1122 1122 """Do a graft-like merge.
1123 1123
1124 1124 This is a merge where the merge ancestor is chosen such that one
1125 1125 or more changesets are grafted onto the current changeset. In
1126 1126 addition to the merge, this fixes up the dirstate to include only
1127 1127 a single parent and tries to duplicate any renames/copies
1128 1128 appropriately.
1129 1129
1130 1130 ctx - changeset to rebase
1131 1131 pctx - merge base, usually ctx.p1()
1132 1132 labels - merge labels eg ['local', 'graft']
1133 1133
1134 1134 """
1135 1135
1136 1136 stats = update(repo, ctx.node(), True, True, False, pctx.node(),
1137 1137 labels=labels)
1138 1138 # drop the second merge parent
1139 1139 repo.dirstate.beginparentchange()
1140 1140 repo.setparents(repo['.'].node(), nullid)
1141 1141 repo.dirstate.write()
1142 1142 # fix up dirstate for copies and renames
1143 1143 copies.duplicatecopies(repo, ctx.rev(), pctx.rev())
1144 1144 repo.dirstate.endparentchange()
1145 1145 return stats
General Comments 0
You need to be logged in to leave comments. Login now