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