##// END OF EJS Templates
mergestate: remove unrequired RECORD_RESOLVED_OTHER record...
Pulkit Goyal -
r45720:fcd0cff3 default
parent child Browse files
Show More
@@ -1,850 +1,846 b''
1 1 from __future__ import absolute_import
2 2
3 3 import errno
4 4 import shutil
5 5 import struct
6 6
7 7 from .i18n import _
8 8 from .node import (
9 9 bin,
10 10 hex,
11 11 nullhex,
12 12 nullid,
13 13 )
14 14 from .pycompat import delattr
15 15 from . import (
16 16 error,
17 17 filemerge,
18 18 pycompat,
19 19 util,
20 20 )
21 21 from .utils import hashutil
22 22
23 23 _pack = struct.pack
24 24 _unpack = struct.unpack
25 25
26 26
27 27 def _droponode(data):
28 28 # used for compatibility for v1
29 29 bits = data.split(b'\0')
30 30 bits = bits[:-2] + bits[-1:]
31 31 return b'\0'.join(bits)
32 32
33 33
34 34 def _filectxorabsent(hexnode, ctx, f):
35 35 if hexnode == nullhex:
36 36 return filemerge.absentfilectx(ctx, f)
37 37 else:
38 38 return ctx[f]
39 39
40 40
41 41 # Merge state record types. See ``mergestate`` docs for more.
42 42 RECORD_LOCAL = b'L'
43 43 RECORD_OTHER = b'O'
44 44 RECORD_MERGED = b'F'
45 45 RECORD_CHANGEDELETE_CONFLICT = b'C'
46 46 RECORD_MERGE_DRIVER_MERGE = b'D'
47 47 RECORD_PATH_CONFLICT = b'P'
48 48 RECORD_MERGE_DRIVER_STATE = b'm'
49 49 RECORD_FILE_VALUES = b'f'
50 50 RECORD_LABELS = b'l'
51 51 RECORD_OVERRIDE = b't'
52 52 RECORD_UNSUPPORTED_MANDATORY = b'X'
53 53 RECORD_UNSUPPORTED_ADVISORY = b'x'
54 RECORD_RESOLVED_OTHER = b'R'
55 54
56 55 MERGE_DRIVER_STATE_UNMARKED = b'u'
57 56 MERGE_DRIVER_STATE_MARKED = b'm'
58 57 MERGE_DRIVER_STATE_SUCCESS = b's'
59 58
60 59 MERGE_RECORD_UNRESOLVED = b'u'
61 60 MERGE_RECORD_RESOLVED = b'r'
62 61 MERGE_RECORD_UNRESOLVED_PATH = b'pu'
63 62 MERGE_RECORD_RESOLVED_PATH = b'pr'
64 63 MERGE_RECORD_DRIVER_RESOLVED = b'd'
65 64 # represents that the file was automatically merged in favor
66 65 # of other version. This info is used on commit.
67 66 MERGE_RECORD_MERGED_OTHER = b'o'
68 67
69 68 ACTION_FORGET = b'f'
70 69 ACTION_REMOVE = b'r'
71 70 ACTION_ADD = b'a'
72 71 ACTION_GET = b'g'
73 72 ACTION_PATH_CONFLICT = b'p'
74 73 ACTION_PATH_CONFLICT_RESOLVE = b'pr'
75 74 ACTION_ADD_MODIFIED = b'am'
76 75 ACTION_CREATED = b'c'
77 76 ACTION_DELETED_CHANGED = b'dc'
78 77 ACTION_CHANGED_DELETED = b'cd'
79 78 ACTION_MERGE = b'm'
80 79 ACTION_LOCAL_DIR_RENAME_GET = b'dg'
81 80 ACTION_DIR_RENAME_MOVE_LOCAL = b'dm'
82 81 ACTION_KEEP = b'k'
83 82 ACTION_EXEC = b'e'
84 83 ACTION_CREATED_MERGE = b'cm'
85 84 # GET the other/remote side and store this info in mergestate
86 85 ACTION_GET_OTHER_AND_STORE = b'gs'
87 86
88 87
89 88 class mergestate(object):
90 89 '''track 3-way merge state of individual files
91 90
92 91 The merge state is stored on disk when needed. Two files are used: one with
93 92 an old format (version 1), and one with a new format (version 2). Version 2
94 93 stores a superset of the data in version 1, including new kinds of records
95 94 in the future. For more about the new format, see the documentation for
96 95 `_readrecordsv2`.
97 96
98 97 Each record can contain arbitrary content, and has an associated type. This
99 98 `type` should be a letter. If `type` is uppercase, the record is mandatory:
100 99 versions of Mercurial that don't support it should abort. If `type` is
101 100 lowercase, the record can be safely ignored.
102 101
103 102 Currently known records:
104 103
105 104 L: the node of the "local" part of the merge (hexified version)
106 105 O: the node of the "other" part of the merge (hexified version)
107 106 F: a file to be merged entry
108 107 C: a change/delete or delete/change conflict
109 108 D: a file that the external merge driver will merge internally
110 109 (experimental)
111 110 P: a path conflict (file vs directory)
112 111 m: the external merge driver defined for this merge plus its run state
113 112 (experimental)
114 113 f: a (filename, dictionary) tuple of optional values for a given file
115 114 X: unsupported mandatory record type (used in tests)
116 115 x: unsupported advisory record type (used in tests)
117 116 l: the labels for the parts of the merge.
118 117
119 118 Merge driver run states (experimental):
120 119 u: driver-resolved files unmarked -- needs to be run next time we're about
121 120 to resolve or commit
122 121 m: driver-resolved files marked -- only needs to be run before commit
123 122 s: success/skipped -- does not need to be run any more
124 123
125 124 Merge record states (stored in self._state, indexed by filename):
126 125 u: unresolved conflict
127 126 r: resolved conflict
128 127 pu: unresolved path conflict (file conflicts with directory)
129 128 pr: resolved path conflict
130 129 d: driver-resolved conflict
131 130
132 131 The resolve command transitions between 'u' and 'r' for conflicts and
133 132 'pu' and 'pr' for path conflicts.
134 133 '''
135 134
136 135 statepathv1 = b'merge/state'
137 136 statepathv2 = b'merge/state2'
138 137
139 138 @staticmethod
140 139 def clean(repo, node=None, other=None, labels=None):
141 140 """Initialize a brand new merge state, removing any existing state on
142 141 disk."""
143 142 ms = mergestate(repo)
144 143 ms.reset(node, other, labels)
145 144 return ms
146 145
147 146 @staticmethod
148 147 def read(repo):
149 148 """Initialize the merge state, reading it from disk."""
150 149 ms = mergestate(repo)
151 150 ms._read()
152 151 return ms
153 152
154 153 def __init__(self, repo):
155 154 """Initialize the merge state.
156 155
157 156 Do not use this directly! Instead call read() or clean()."""
158 157 self._repo = repo
159 158 self._dirty = False
160 159 self._labels = None
161 160
162 161 def reset(self, node=None, other=None, labels=None):
163 162 self._state = {}
164 163 self._stateextras = {}
165 164 self._local = None
166 165 self._other = None
167 166 self._labels = labels
168 167 for var in ('localctx', 'otherctx'):
169 168 if var in vars(self):
170 169 delattr(self, var)
171 170 if node:
172 171 self._local = node
173 172 self._other = other
174 173 self._readmergedriver = None
175 174 if self.mergedriver:
176 175 self._mdstate = MERGE_DRIVER_STATE_SUCCESS
177 176 else:
178 177 self._mdstate = MERGE_DRIVER_STATE_UNMARKED
179 178 shutil.rmtree(self._repo.vfs.join(b'merge'), True)
180 179 self._results = {}
181 180 self._dirty = False
182 181
183 182 def _read(self):
184 183 """Analyse each record content to restore a serialized state from disk
185 184
186 185 This function process "record" entry produced by the de-serialization
187 186 of on disk file.
188 187 """
189 188 self._state = {}
190 189 self._stateextras = {}
191 190 self._local = None
192 191 self._other = None
193 192 for var in ('localctx', 'otherctx'):
194 193 if var in vars(self):
195 194 delattr(self, var)
196 195 self._readmergedriver = None
197 196 self._mdstate = MERGE_DRIVER_STATE_SUCCESS
198 197 unsupported = set()
199 198 records = self._readrecords()
200 199 for rtype, record in records:
201 200 if rtype == RECORD_LOCAL:
202 201 self._local = bin(record)
203 202 elif rtype == RECORD_OTHER:
204 203 self._other = bin(record)
205 204 elif rtype == RECORD_MERGE_DRIVER_STATE:
206 205 bits = record.split(b'\0', 1)
207 206 mdstate = bits[1]
208 207 if len(mdstate) != 1 or mdstate not in (
209 208 MERGE_DRIVER_STATE_UNMARKED,
210 209 MERGE_DRIVER_STATE_MARKED,
211 210 MERGE_DRIVER_STATE_SUCCESS,
212 211 ):
213 212 # the merge driver should be idempotent, so just rerun it
214 213 mdstate = MERGE_DRIVER_STATE_UNMARKED
215 214
216 215 self._readmergedriver = bits[0]
217 216 self._mdstate = mdstate
218 217 elif rtype in (
219 218 RECORD_MERGED,
220 219 RECORD_CHANGEDELETE_CONFLICT,
221 220 RECORD_PATH_CONFLICT,
222 221 RECORD_MERGE_DRIVER_MERGE,
223 RECORD_RESOLVED_OTHER,
224 222 ):
225 223 bits = record.split(b'\0')
226 224 self._state[bits[0]] = bits[1:]
227 225 elif rtype == RECORD_FILE_VALUES:
228 226 filename, rawextras = record.split(b'\0', 1)
229 227 extraparts = rawextras.split(b'\0')
230 228 extras = {}
231 229 i = 0
232 230 while i < len(extraparts):
233 231 extras[extraparts[i]] = extraparts[i + 1]
234 232 i += 2
235 233
236 234 self._stateextras[filename] = extras
237 235 elif rtype == RECORD_LABELS:
238 236 labels = record.split(b'\0', 2)
239 237 self._labels = [l for l in labels if len(l) > 0]
240 238 elif not rtype.islower():
241 239 unsupported.add(rtype)
242 240 self._results = {}
243 241 self._dirty = False
244 242
245 243 if unsupported:
246 244 raise error.UnsupportedMergeRecords(unsupported)
247 245
248 246 def _readrecords(self):
249 247 """Read merge state from disk and return a list of record (TYPE, data)
250 248
251 249 We read data from both v1 and v2 files and decide which one to use.
252 250
253 251 V1 has been used by version prior to 2.9.1 and contains less data than
254 252 v2. We read both versions and check if no data in v2 contradicts
255 253 v1. If there is not contradiction we can safely assume that both v1
256 254 and v2 were written at the same time and use the extract data in v2. If
257 255 there is contradiction we ignore v2 content as we assume an old version
258 256 of Mercurial has overwritten the mergestate file and left an old v2
259 257 file around.
260 258
261 259 returns list of record [(TYPE, data), ...]"""
262 260 v1records = self._readrecordsv1()
263 261 v2records = self._readrecordsv2()
264 262 if self._v1v2match(v1records, v2records):
265 263 return v2records
266 264 else:
267 265 # v1 file is newer than v2 file, use it
268 266 # we have to infer the "other" changeset of the merge
269 267 # we cannot do better than that with v1 of the format
270 268 mctx = self._repo[None].parents()[-1]
271 269 v1records.append((RECORD_OTHER, mctx.hex()))
272 270 # add place holder "other" file node information
273 271 # nobody is using it yet so we do no need to fetch the data
274 272 # if mctx was wrong `mctx[bits[-2]]` may fails.
275 273 for idx, r in enumerate(v1records):
276 274 if r[0] == RECORD_MERGED:
277 275 bits = r[1].split(b'\0')
278 276 bits.insert(-2, b'')
279 277 v1records[idx] = (r[0], b'\0'.join(bits))
280 278 return v1records
281 279
282 280 def _v1v2match(self, v1records, v2records):
283 281 oldv2 = set() # old format version of v2 record
284 282 for rec in v2records:
285 283 if rec[0] == RECORD_LOCAL:
286 284 oldv2.add(rec)
287 285 elif rec[0] == RECORD_MERGED:
288 286 # drop the onode data (not contained in v1)
289 287 oldv2.add((RECORD_MERGED, _droponode(rec[1])))
290 288 for rec in v1records:
291 289 if rec not in oldv2:
292 290 return False
293 291 else:
294 292 return True
295 293
296 294 def _readrecordsv1(self):
297 295 """read on disk merge state for version 1 file
298 296
299 297 returns list of record [(TYPE, data), ...]
300 298
301 299 Note: the "F" data from this file are one entry short
302 300 (no "other file node" entry)
303 301 """
304 302 records = []
305 303 try:
306 304 f = self._repo.vfs(self.statepathv1)
307 305 for i, l in enumerate(f):
308 306 if i == 0:
309 307 records.append((RECORD_LOCAL, l[:-1]))
310 308 else:
311 309 records.append((RECORD_MERGED, l[:-1]))
312 310 f.close()
313 311 except IOError as err:
314 312 if err.errno != errno.ENOENT:
315 313 raise
316 314 return records
317 315
318 316 def _readrecordsv2(self):
319 317 """read on disk merge state for version 2 file
320 318
321 319 This format is a list of arbitrary records of the form:
322 320
323 321 [type][length][content]
324 322
325 323 `type` is a single character, `length` is a 4 byte integer, and
326 324 `content` is an arbitrary byte sequence of length `length`.
327 325
328 326 Mercurial versions prior to 3.7 have a bug where if there are
329 327 unsupported mandatory merge records, attempting to clear out the merge
330 328 state with hg update --clean or similar aborts. The 't' record type
331 329 works around that by writing out what those versions treat as an
332 330 advisory record, but later versions interpret as special: the first
333 331 character is the 'real' record type and everything onwards is the data.
334 332
335 333 Returns list of records [(TYPE, data), ...]."""
336 334 records = []
337 335 try:
338 336 f = self._repo.vfs(self.statepathv2)
339 337 data = f.read()
340 338 off = 0
341 339 end = len(data)
342 340 while off < end:
343 341 rtype = data[off : off + 1]
344 342 off += 1
345 343 length = _unpack(b'>I', data[off : (off + 4)])[0]
346 344 off += 4
347 345 record = data[off : (off + length)]
348 346 off += length
349 347 if rtype == RECORD_OVERRIDE:
350 348 rtype, record = record[0:1], record[1:]
351 349 records.append((rtype, record))
352 350 f.close()
353 351 except IOError as err:
354 352 if err.errno != errno.ENOENT:
355 353 raise
356 354 return records
357 355
358 356 @util.propertycache
359 357 def mergedriver(self):
360 358 # protect against the following:
361 359 # - A configures a malicious merge driver in their hgrc, then
362 360 # pauses the merge
363 361 # - A edits their hgrc to remove references to the merge driver
364 362 # - A gives a copy of their entire repo, including .hg, to B
365 363 # - B inspects .hgrc and finds it to be clean
366 364 # - B then continues the merge and the malicious merge driver
367 365 # gets invoked
368 366 configmergedriver = self._repo.ui.config(
369 367 b'experimental', b'mergedriver'
370 368 )
371 369 if (
372 370 self._readmergedriver is not None
373 371 and self._readmergedriver != configmergedriver
374 372 ):
375 373 raise error.ConfigError(
376 374 _(b"merge driver changed since merge started"),
377 375 hint=_(b"revert merge driver change or abort merge"),
378 376 )
379 377
380 378 return configmergedriver
381 379
382 380 @util.propertycache
383 381 def local(self):
384 382 if self._local is None:
385 383 msg = b"local accessed but self._local isn't set"
386 384 raise error.ProgrammingError(msg)
387 385 return self._local
388 386
389 387 @util.propertycache
390 388 def localctx(self):
391 389 return self._repo[self.local]
392 390
393 391 @util.propertycache
394 392 def other(self):
395 393 if self._other is None:
396 394 msg = b"other accessed but self._other isn't set"
397 395 raise error.ProgrammingError(msg)
398 396 return self._other
399 397
400 398 @util.propertycache
401 399 def otherctx(self):
402 400 return self._repo[self.other]
403 401
404 402 def active(self):
405 403 """Whether mergestate is active.
406 404
407 405 Returns True if there appears to be mergestate. This is a rough proxy
408 406 for "is a merge in progress."
409 407 """
410 408 return bool(self._local) or bool(self._state)
411 409
412 410 def commit(self):
413 411 """Write current state on disk (if necessary)"""
414 412 if self._dirty:
415 413 records = self._makerecords()
416 414 self._writerecords(records)
417 415 self._dirty = False
418 416
419 417 def _makerecords(self):
420 418 records = []
421 419 records.append((RECORD_LOCAL, hex(self._local)))
422 420 records.append((RECORD_OTHER, hex(self._other)))
423 421 if self.mergedriver:
424 422 records.append(
425 423 (
426 424 RECORD_MERGE_DRIVER_STATE,
427 425 b'\0'.join([self.mergedriver, self._mdstate]),
428 426 )
429 427 )
430 428 # Write out state items. In all cases, the value of the state map entry
431 429 # is written as the contents of the record. The record type depends on
432 430 # the type of state that is stored, and capital-letter records are used
433 431 # to prevent older versions of Mercurial that do not support the feature
434 432 # from loading them.
435 433 for filename, v in pycompat.iteritems(self._state):
436 434 if v[0] == MERGE_RECORD_DRIVER_RESOLVED:
437 435 # Driver-resolved merge. These are stored in 'D' records.
438 436 records.append(
439 437 (RECORD_MERGE_DRIVER_MERGE, b'\0'.join([filename] + v))
440 438 )
441 439 elif v[0] in (
442 440 MERGE_RECORD_UNRESOLVED_PATH,
443 441 MERGE_RECORD_RESOLVED_PATH,
444 442 ):
445 443 # Path conflicts. These are stored in 'P' records. The current
446 444 # resolution state ('pu' or 'pr') is stored within the record.
447 445 records.append(
448 446 (RECORD_PATH_CONFLICT, b'\0'.join([filename] + v))
449 447 )
450 448 elif v[0] == MERGE_RECORD_MERGED_OTHER:
451 records.append(
452 (RECORD_RESOLVED_OTHER, b'\0'.join([filename] + v))
453 )
449 records.append((RECORD_MERGED, b'\0'.join([filename] + v)))
454 450 elif v[1] == nullhex or v[6] == nullhex:
455 451 # Change/Delete or Delete/Change conflicts. These are stored in
456 452 # 'C' records. v[1] is the local file, and is nullhex when the
457 453 # file is deleted locally ('dc'). v[6] is the remote file, and
458 454 # is nullhex when the file is deleted remotely ('cd').
459 455 records.append(
460 456 (RECORD_CHANGEDELETE_CONFLICT, b'\0'.join([filename] + v))
461 457 )
462 458 else:
463 459 # Normal files. These are stored in 'F' records.
464 460 records.append((RECORD_MERGED, b'\0'.join([filename] + v)))
465 461 for filename, extras in sorted(pycompat.iteritems(self._stateextras)):
466 462 rawextras = b'\0'.join(
467 463 b'%s\0%s' % (k, v) for k, v in pycompat.iteritems(extras)
468 464 )
469 465 records.append(
470 466 (RECORD_FILE_VALUES, b'%s\0%s' % (filename, rawextras))
471 467 )
472 468 if self._labels is not None:
473 469 labels = b'\0'.join(self._labels)
474 470 records.append((RECORD_LABELS, labels))
475 471 return records
476 472
477 473 def _writerecords(self, records):
478 474 """Write current state on disk (both v1 and v2)"""
479 475 self._writerecordsv1(records)
480 476 self._writerecordsv2(records)
481 477
482 478 def _writerecordsv1(self, records):
483 479 """Write current state on disk in a version 1 file"""
484 480 f = self._repo.vfs(self.statepathv1, b'wb')
485 481 irecords = iter(records)
486 482 lrecords = next(irecords)
487 483 assert lrecords[0] == RECORD_LOCAL
488 484 f.write(hex(self._local) + b'\n')
489 485 for rtype, data in irecords:
490 486 if rtype == RECORD_MERGED:
491 487 f.write(b'%s\n' % _droponode(data))
492 488 f.close()
493 489
494 490 def _writerecordsv2(self, records):
495 491 """Write current state on disk in a version 2 file
496 492
497 493 See the docstring for _readrecordsv2 for why we use 't'."""
498 494 # these are the records that all version 2 clients can read
499 495 allowlist = (RECORD_LOCAL, RECORD_OTHER, RECORD_MERGED)
500 496 f = self._repo.vfs(self.statepathv2, b'wb')
501 497 for key, data in records:
502 498 assert len(key) == 1
503 499 if key not in allowlist:
504 500 key, data = RECORD_OVERRIDE, b'%s%s' % (key, data)
505 501 format = b'>sI%is' % len(data)
506 502 f.write(_pack(format, key, len(data), data))
507 503 f.close()
508 504
509 505 @staticmethod
510 506 def getlocalkey(path):
511 507 """hash the path of a local file context for storage in the .hg/merge
512 508 directory."""
513 509
514 510 return hex(hashutil.sha1(path).digest())
515 511
516 512 def add(self, fcl, fco, fca, fd):
517 513 """add a new (potentially?) conflicting file the merge state
518 514 fcl: file context for local,
519 515 fco: file context for remote,
520 516 fca: file context for ancestors,
521 517 fd: file path of the resulting merge.
522 518
523 519 note: also write the local version to the `.hg/merge` directory.
524 520 """
525 521 if fcl.isabsent():
526 522 localkey = nullhex
527 523 else:
528 524 localkey = mergestate.getlocalkey(fcl.path())
529 525 self._repo.vfs.write(b'merge/' + localkey, fcl.data())
530 526 self._state[fd] = [
531 527 MERGE_RECORD_UNRESOLVED,
532 528 localkey,
533 529 fcl.path(),
534 530 fca.path(),
535 531 hex(fca.filenode()),
536 532 fco.path(),
537 533 hex(fco.filenode()),
538 534 fcl.flags(),
539 535 ]
540 536 self._stateextras[fd] = {b'ancestorlinknode': hex(fca.node())}
541 537 self._dirty = True
542 538
543 539 def addpathconflict(self, path, frename, forigin):
544 540 """add a new conflicting path to the merge state
545 541 path: the path that conflicts
546 542 frename: the filename the conflicting file was renamed to
547 543 forigin: origin of the file ('l' or 'r' for local/remote)
548 544 """
549 545 self._state[path] = [MERGE_RECORD_UNRESOLVED_PATH, frename, forigin]
550 546 self._dirty = True
551 547
552 548 def addmergedother(self, path):
553 549 self._state[path] = [MERGE_RECORD_MERGED_OTHER, nullhex, nullhex]
554 550 self._dirty = True
555 551
556 552 def __contains__(self, dfile):
557 553 return dfile in self._state
558 554
559 555 def __getitem__(self, dfile):
560 556 return self._state[dfile][0]
561 557
562 558 def __iter__(self):
563 559 return iter(sorted(self._state))
564 560
565 561 def files(self):
566 562 return self._state.keys()
567 563
568 564 def mark(self, dfile, state):
569 565 self._state[dfile][0] = state
570 566 self._dirty = True
571 567
572 568 def mdstate(self):
573 569 return self._mdstate
574 570
575 571 def unresolved(self):
576 572 """Obtain the paths of unresolved files."""
577 573
578 574 for f, entry in pycompat.iteritems(self._state):
579 575 if entry[0] in (
580 576 MERGE_RECORD_UNRESOLVED,
581 577 MERGE_RECORD_UNRESOLVED_PATH,
582 578 ):
583 579 yield f
584 580
585 581 def driverresolved(self):
586 582 """Obtain the paths of driver-resolved files."""
587 583
588 584 for f, entry in self._state.items():
589 585 if entry[0] == MERGE_RECORD_DRIVER_RESOLVED:
590 586 yield f
591 587
592 588 def extras(self, filename):
593 589 return self._stateextras.setdefault(filename, {})
594 590
595 591 def _resolve(self, preresolve, dfile, wctx):
596 592 """rerun merge process for file path `dfile`"""
597 593 if self[dfile] in (MERGE_RECORD_RESOLVED, MERGE_RECORD_DRIVER_RESOLVED):
598 594 return True, 0
599 595 if self._state[dfile][0] == MERGE_RECORD_MERGED_OTHER:
600 596 return True, 0
601 597 stateentry = self._state[dfile]
602 598 state, localkey, lfile, afile, anode, ofile, onode, flags = stateentry
603 599 octx = self._repo[self._other]
604 600 extras = self.extras(dfile)
605 601 anccommitnode = extras.get(b'ancestorlinknode')
606 602 if anccommitnode:
607 603 actx = self._repo[anccommitnode]
608 604 else:
609 605 actx = None
610 606 fcd = _filectxorabsent(localkey, wctx, dfile)
611 607 fco = _filectxorabsent(onode, octx, ofile)
612 608 # TODO: move this to filectxorabsent
613 609 fca = self._repo.filectx(afile, fileid=anode, changectx=actx)
614 610 # "premerge" x flags
615 611 flo = fco.flags()
616 612 fla = fca.flags()
617 613 if b'x' in flags + flo + fla and b'l' not in flags + flo + fla:
618 614 if fca.node() == nullid and flags != flo:
619 615 if preresolve:
620 616 self._repo.ui.warn(
621 617 _(
622 618 b'warning: cannot merge flags for %s '
623 619 b'without common ancestor - keeping local flags\n'
624 620 )
625 621 % afile
626 622 )
627 623 elif flags == fla:
628 624 flags = flo
629 625 if preresolve:
630 626 # restore local
631 627 if localkey != nullhex:
632 628 f = self._repo.vfs(b'merge/' + localkey)
633 629 wctx[dfile].write(f.read(), flags)
634 630 f.close()
635 631 else:
636 632 wctx[dfile].remove(ignoremissing=True)
637 633 complete, r, deleted = filemerge.premerge(
638 634 self._repo,
639 635 wctx,
640 636 self._local,
641 637 lfile,
642 638 fcd,
643 639 fco,
644 640 fca,
645 641 labels=self._labels,
646 642 )
647 643 else:
648 644 complete, r, deleted = filemerge.filemerge(
649 645 self._repo,
650 646 wctx,
651 647 self._local,
652 648 lfile,
653 649 fcd,
654 650 fco,
655 651 fca,
656 652 labels=self._labels,
657 653 )
658 654 if r is None:
659 655 # no real conflict
660 656 del self._state[dfile]
661 657 self._stateextras.pop(dfile, None)
662 658 self._dirty = True
663 659 elif not r:
664 660 self.mark(dfile, MERGE_RECORD_RESOLVED)
665 661
666 662 if complete:
667 663 action = None
668 664 if deleted:
669 665 if fcd.isabsent():
670 666 # dc: local picked. Need to drop if present, which may
671 667 # happen on re-resolves.
672 668 action = ACTION_FORGET
673 669 else:
674 670 # cd: remote picked (or otherwise deleted)
675 671 action = ACTION_REMOVE
676 672 else:
677 673 if fcd.isabsent(): # dc: remote picked
678 674 action = ACTION_GET
679 675 elif fco.isabsent(): # cd: local picked
680 676 if dfile in self.localctx:
681 677 action = ACTION_ADD_MODIFIED
682 678 else:
683 679 action = ACTION_ADD
684 680 # else: regular merges (no action necessary)
685 681 self._results[dfile] = r, action
686 682
687 683 return complete, r
688 684
689 685 def preresolve(self, dfile, wctx):
690 686 """run premerge process for dfile
691 687
692 688 Returns whether the merge is complete, and the exit code."""
693 689 return self._resolve(True, dfile, wctx)
694 690
695 691 def resolve(self, dfile, wctx):
696 692 """run merge process (assuming premerge was run) for dfile
697 693
698 694 Returns the exit code of the merge."""
699 695 return self._resolve(False, dfile, wctx)[1]
700 696
701 697 def counts(self):
702 698 """return counts for updated, merged and removed files in this
703 699 session"""
704 700 updated, merged, removed = 0, 0, 0
705 701 for r, action in pycompat.itervalues(self._results):
706 702 if r is None:
707 703 updated += 1
708 704 elif r == 0:
709 705 if action == ACTION_REMOVE:
710 706 removed += 1
711 707 else:
712 708 merged += 1
713 709 return updated, merged, removed
714 710
715 711 def unresolvedcount(self):
716 712 """get unresolved count for this merge (persistent)"""
717 713 return len(list(self.unresolved()))
718 714
719 715 def actions(self):
720 716 """return lists of actions to perform on the dirstate"""
721 717 actions = {
722 718 ACTION_REMOVE: [],
723 719 ACTION_FORGET: [],
724 720 ACTION_ADD: [],
725 721 ACTION_ADD_MODIFIED: [],
726 722 ACTION_GET: [],
727 723 }
728 724 for f, (r, action) in pycompat.iteritems(self._results):
729 725 if action is not None:
730 726 actions[action].append((f, None, b"merge result"))
731 727 return actions
732 728
733 729 def recordactions(self):
734 730 """record remove/add/get actions in the dirstate"""
735 731 branchmerge = self._repo.dirstate.p2() != nullid
736 732 recordupdates(self._repo, self.actions(), branchmerge, None)
737 733
738 734 def queueremove(self, f):
739 735 """queues a file to be removed from the dirstate
740 736
741 737 Meant for use by custom merge drivers."""
742 738 self._results[f] = 0, ACTION_REMOVE
743 739
744 740 def queueadd(self, f):
745 741 """queues a file to be added to the dirstate
746 742
747 743 Meant for use by custom merge drivers."""
748 744 self._results[f] = 0, ACTION_ADD
749 745
750 746 def queueget(self, f):
751 747 """queues a file to be marked modified in the dirstate
752 748
753 749 Meant for use by custom merge drivers."""
754 750 self._results[f] = 0, ACTION_GET
755 751
756 752
757 753 def recordupdates(repo, actions, branchmerge, getfiledata):
758 754 """record merge actions to the dirstate"""
759 755 # remove (must come first)
760 756 for f, args, msg in actions.get(ACTION_REMOVE, []):
761 757 if branchmerge:
762 758 repo.dirstate.remove(f)
763 759 else:
764 760 repo.dirstate.drop(f)
765 761
766 762 # forget (must come first)
767 763 for f, args, msg in actions.get(ACTION_FORGET, []):
768 764 repo.dirstate.drop(f)
769 765
770 766 # resolve path conflicts
771 767 for f, args, msg in actions.get(ACTION_PATH_CONFLICT_RESOLVE, []):
772 768 (f0, origf0) = args
773 769 repo.dirstate.add(f)
774 770 repo.dirstate.copy(origf0, f)
775 771 if f0 == origf0:
776 772 repo.dirstate.remove(f0)
777 773 else:
778 774 repo.dirstate.drop(f0)
779 775
780 776 # re-add
781 777 for f, args, msg in actions.get(ACTION_ADD, []):
782 778 repo.dirstate.add(f)
783 779
784 780 # re-add/mark as modified
785 781 for f, args, msg in actions.get(ACTION_ADD_MODIFIED, []):
786 782 if branchmerge:
787 783 repo.dirstate.normallookup(f)
788 784 else:
789 785 repo.dirstate.add(f)
790 786
791 787 # exec change
792 788 for f, args, msg in actions.get(ACTION_EXEC, []):
793 789 repo.dirstate.normallookup(f)
794 790
795 791 # keep
796 792 for f, args, msg in actions.get(ACTION_KEEP, []):
797 793 pass
798 794
799 795 # get
800 796 for f, args, msg in actions.get(ACTION_GET, []):
801 797 if branchmerge:
802 798 repo.dirstate.otherparent(f)
803 799 else:
804 800 parentfiledata = getfiledata[f] if getfiledata else None
805 801 repo.dirstate.normal(f, parentfiledata=parentfiledata)
806 802
807 803 # merge
808 804 for f, args, msg in actions.get(ACTION_MERGE, []):
809 805 f1, f2, fa, move, anc = args
810 806 if branchmerge:
811 807 # We've done a branch merge, mark this file as merged
812 808 # so that we properly record the merger later
813 809 repo.dirstate.merge(f)
814 810 if f1 != f2: # copy/rename
815 811 if move:
816 812 repo.dirstate.remove(f1)
817 813 if f1 != f:
818 814 repo.dirstate.copy(f1, f)
819 815 else:
820 816 repo.dirstate.copy(f2, f)
821 817 else:
822 818 # We've update-merged a locally modified file, so
823 819 # we set the dirstate to emulate a normal checkout
824 820 # of that file some time in the past. Thus our
825 821 # merge will appear as a normal local file
826 822 # modification.
827 823 if f2 == f: # file not locally copied/moved
828 824 repo.dirstate.normallookup(f)
829 825 if move:
830 826 repo.dirstate.drop(f1)
831 827
832 828 # directory rename, move local
833 829 for f, args, msg in actions.get(ACTION_DIR_RENAME_MOVE_LOCAL, []):
834 830 f0, flag = args
835 831 if branchmerge:
836 832 repo.dirstate.add(f)
837 833 repo.dirstate.remove(f0)
838 834 repo.dirstate.copy(f0, f)
839 835 else:
840 836 repo.dirstate.normal(f)
841 837 repo.dirstate.drop(f0)
842 838
843 839 # directory rename, get
844 840 for f, args, msg in actions.get(ACTION_LOCAL_DIR_RENAME_GET, []):
845 841 f0, flag = args
846 842 if branchmerge:
847 843 repo.dirstate.add(f)
848 844 repo.dirstate.copy(f0, f)
849 845 else:
850 846 repo.dirstate.normal(f)
General Comments 0
You need to be logged in to leave comments. Login now