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