##// END OF EJS Templates
mergestate: remove unused unsupported related mergestate records...
Pulkit Goyal -
r45722:8530022f 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 extra information about files
45 45 RECORD_FILE_VALUES = b'f'
46 46 # record merge labels
47 47 RECORD_LABELS = b'l'
48 48
49 49 RECORD_MERGED = b'F'
50 50 RECORD_CHANGEDELETE_CONFLICT = b'C'
51 51 RECORD_MERGE_DRIVER_MERGE = b'D'
52 52 RECORD_PATH_CONFLICT = b'P'
53 53
54 54 RECORD_MERGE_DRIVER_STATE = b'm'
55 55 RECORD_OVERRIDE = b't'
56 RECORD_UNSUPPORTED_MANDATORY = b'X'
57 RECORD_UNSUPPORTED_ADVISORY = b'x'
58 56
59 57 MERGE_DRIVER_STATE_UNMARKED = b'u'
60 58 MERGE_DRIVER_STATE_MARKED = b'm'
61 59 MERGE_DRIVER_STATE_SUCCESS = b's'
62 60
63 61 MERGE_RECORD_UNRESOLVED = b'u'
64 62 MERGE_RECORD_RESOLVED = b'r'
65 63 MERGE_RECORD_UNRESOLVED_PATH = b'pu'
66 64 MERGE_RECORD_RESOLVED_PATH = b'pr'
67 65 MERGE_RECORD_DRIVER_RESOLVED = b'd'
68 66 # represents that the file was automatically merged in favor
69 67 # of other version. This info is used on commit.
70 68 MERGE_RECORD_MERGED_OTHER = b'o'
71 69
72 70 ACTION_FORGET = b'f'
73 71 ACTION_REMOVE = b'r'
74 72 ACTION_ADD = b'a'
75 73 ACTION_GET = b'g'
76 74 ACTION_PATH_CONFLICT = b'p'
77 75 ACTION_PATH_CONFLICT_RESOLVE = b'pr'
78 76 ACTION_ADD_MODIFIED = b'am'
79 77 ACTION_CREATED = b'c'
80 78 ACTION_DELETED_CHANGED = b'dc'
81 79 ACTION_CHANGED_DELETED = b'cd'
82 80 ACTION_MERGE = b'm'
83 81 ACTION_LOCAL_DIR_RENAME_GET = b'dg'
84 82 ACTION_DIR_RENAME_MOVE_LOCAL = b'dm'
85 83 ACTION_KEEP = b'k'
86 84 ACTION_EXEC = b'e'
87 85 ACTION_CREATED_MERGE = b'cm'
88 86 # GET the other/remote side and store this info in mergestate
89 87 ACTION_GET_OTHER_AND_STORE = b'gs'
90 88
91 89
92 90 class mergestate(object):
93 91 '''track 3-way merge state of individual files
94 92
95 93 The merge state is stored on disk when needed. Two files are used: one with
96 94 an old format (version 1), and one with a new format (version 2). Version 2
97 95 stores a superset of the data in version 1, including new kinds of records
98 96 in the future. For more about the new format, see the documentation for
99 97 `_readrecordsv2`.
100 98
101 99 Each record can contain arbitrary content, and has an associated type. This
102 100 `type` should be a letter. If `type` is uppercase, the record is mandatory:
103 101 versions of Mercurial that don't support it should abort. If `type` is
104 102 lowercase, the record can be safely ignored.
105 103
106 104 Currently known records:
107 105
108 106 L: the node of the "local" part of the merge (hexified version)
109 107 O: the node of the "other" part of the merge (hexified version)
110 108 F: a file to be merged entry
111 109 C: a change/delete or delete/change conflict
112 110 D: a file that the external merge driver will merge internally
113 111 (experimental)
114 112 P: a path conflict (file vs directory)
115 113 m: the external merge driver defined for this merge plus its run state
116 114 (experimental)
117 115 f: a (filename, dictionary) tuple of optional values for a given file
118 X: unsupported mandatory record type (used in tests)
119 x: unsupported advisory record type (used in tests)
120 116 l: the labels for the parts of the merge.
121 117
122 118 Merge driver run states (experimental):
123 119 u: driver-resolved files unmarked -- needs to be run next time we're about
124 120 to resolve or commit
125 121 m: driver-resolved files marked -- only needs to be run before commit
126 122 s: success/skipped -- does not need to be run any more
127 123
128 124 Merge record states (stored in self._state, indexed by filename):
129 125 u: unresolved conflict
130 126 r: resolved conflict
131 127 pu: unresolved path conflict (file conflicts with directory)
132 128 pr: resolved path conflict
133 129 d: driver-resolved conflict
134 130
135 131 The resolve command transitions between 'u' and 'r' for conflicts and
136 132 'pu' and 'pr' for path conflicts.
137 133 '''
138 134
139 135 statepathv1 = b'merge/state'
140 136 statepathv2 = b'merge/state2'
141 137
142 138 @staticmethod
143 139 def clean(repo, node=None, other=None, labels=None):
144 140 """Initialize a brand new merge state, removing any existing state on
145 141 disk."""
146 142 ms = mergestate(repo)
147 143 ms.reset(node, other, labels)
148 144 return ms
149 145
150 146 @staticmethod
151 147 def read(repo):
152 148 """Initialize the merge state, reading it from disk."""
153 149 ms = mergestate(repo)
154 150 ms._read()
155 151 return ms
156 152
157 153 def __init__(self, repo):
158 154 """Initialize the merge state.
159 155
160 156 Do not use this directly! Instead call read() or clean()."""
161 157 self._repo = repo
162 158 self._dirty = False
163 159 self._labels = None
164 160
165 161 def reset(self, node=None, other=None, labels=None):
166 162 self._state = {}
167 163 self._stateextras = {}
168 164 self._local = None
169 165 self._other = None
170 166 self._labels = labels
171 167 for var in ('localctx', 'otherctx'):
172 168 if var in vars(self):
173 169 delattr(self, var)
174 170 if node:
175 171 self._local = node
176 172 self._other = other
177 173 self._readmergedriver = None
178 174 if self.mergedriver:
179 175 self._mdstate = MERGE_DRIVER_STATE_SUCCESS
180 176 else:
181 177 self._mdstate = MERGE_DRIVER_STATE_UNMARKED
182 178 shutil.rmtree(self._repo.vfs.join(b'merge'), True)
183 179 self._results = {}
184 180 self._dirty = False
185 181
186 182 def _read(self):
187 183 """Analyse each record content to restore a serialized state from disk
188 184
189 185 This function process "record" entry produced by the de-serialization
190 186 of on disk file.
191 187 """
192 188 self._state = {}
193 189 self._stateextras = {}
194 190 self._local = None
195 191 self._other = None
196 192 for var in ('localctx', 'otherctx'):
197 193 if var in vars(self):
198 194 delattr(self, var)
199 195 self._readmergedriver = None
200 196 self._mdstate = MERGE_DRIVER_STATE_SUCCESS
201 197 unsupported = set()
202 198 records = self._readrecords()
203 199 for rtype, record in records:
204 200 if rtype == RECORD_LOCAL:
205 201 self._local = bin(record)
206 202 elif rtype == RECORD_OTHER:
207 203 self._other = bin(record)
208 204 elif rtype == RECORD_MERGE_DRIVER_STATE:
209 205 bits = record.split(b'\0', 1)
210 206 mdstate = bits[1]
211 207 if len(mdstate) != 1 or mdstate not in (
212 208 MERGE_DRIVER_STATE_UNMARKED,
213 209 MERGE_DRIVER_STATE_MARKED,
214 210 MERGE_DRIVER_STATE_SUCCESS,
215 211 ):
216 212 # the merge driver should be idempotent, so just rerun it
217 213 mdstate = MERGE_DRIVER_STATE_UNMARKED
218 214
219 215 self._readmergedriver = bits[0]
220 216 self._mdstate = mdstate
221 217 elif rtype in (
222 218 RECORD_MERGED,
223 219 RECORD_CHANGEDELETE_CONFLICT,
224 220 RECORD_PATH_CONFLICT,
225 221 RECORD_MERGE_DRIVER_MERGE,
226 222 ):
227 223 bits = record.split(b'\0')
228 224 self._state[bits[0]] = bits[1:]
229 225 elif rtype == RECORD_FILE_VALUES:
230 226 filename, rawextras = record.split(b'\0', 1)
231 227 extraparts = rawextras.split(b'\0')
232 228 extras = {}
233 229 i = 0
234 230 while i < len(extraparts):
235 231 extras[extraparts[i]] = extraparts[i + 1]
236 232 i += 2
237 233
238 234 self._stateextras[filename] = extras
239 235 elif rtype == RECORD_LABELS:
240 236 labels = record.split(b'\0', 2)
241 237 self._labels = [l for l in labels if len(l) > 0]
242 238 elif not rtype.islower():
243 239 unsupported.add(rtype)
244 240 self._results = {}
245 241 self._dirty = False
246 242
247 243 if unsupported:
248 244 raise error.UnsupportedMergeRecords(unsupported)
249 245
250 246 def _readrecords(self):
251 247 """Read merge state from disk and return a list of record (TYPE, data)
252 248
253 249 We read data from both v1 and v2 files and decide which one to use.
254 250
255 251 V1 has been used by version prior to 2.9.1 and contains less data than
256 252 v2. We read both versions and check if no data in v2 contradicts
257 253 v1. If there is not contradiction we can safely assume that both v1
258 254 and v2 were written at the same time and use the extract data in v2. If
259 255 there is contradiction we ignore v2 content as we assume an old version
260 256 of Mercurial has overwritten the mergestate file and left an old v2
261 257 file around.
262 258
263 259 returns list of record [(TYPE, data), ...]"""
264 260 v1records = self._readrecordsv1()
265 261 v2records = self._readrecordsv2()
266 262 if self._v1v2match(v1records, v2records):
267 263 return v2records
268 264 else:
269 265 # v1 file is newer than v2 file, use it
270 266 # we have to infer the "other" changeset of the merge
271 267 # we cannot do better than that with v1 of the format
272 268 mctx = self._repo[None].parents()[-1]
273 269 v1records.append((RECORD_OTHER, mctx.hex()))
274 270 # add place holder "other" file node information
275 271 # nobody is using it yet so we do no need to fetch the data
276 272 # if mctx was wrong `mctx[bits[-2]]` may fails.
277 273 for idx, r in enumerate(v1records):
278 274 if r[0] == RECORD_MERGED:
279 275 bits = r[1].split(b'\0')
280 276 bits.insert(-2, b'')
281 277 v1records[idx] = (r[0], b'\0'.join(bits))
282 278 return v1records
283 279
284 280 def _v1v2match(self, v1records, v2records):
285 281 oldv2 = set() # old format version of v2 record
286 282 for rec in v2records:
287 283 if rec[0] == RECORD_LOCAL:
288 284 oldv2.add(rec)
289 285 elif rec[0] == RECORD_MERGED:
290 286 # drop the onode data (not contained in v1)
291 287 oldv2.add((RECORD_MERGED, _droponode(rec[1])))
292 288 for rec in v1records:
293 289 if rec not in oldv2:
294 290 return False
295 291 else:
296 292 return True
297 293
298 294 def _readrecordsv1(self):
299 295 """read on disk merge state for version 1 file
300 296
301 297 returns list of record [(TYPE, data), ...]
302 298
303 299 Note: the "F" data from this file are one entry short
304 300 (no "other file node" entry)
305 301 """
306 302 records = []
307 303 try:
308 304 f = self._repo.vfs(self.statepathv1)
309 305 for i, l in enumerate(f):
310 306 if i == 0:
311 307 records.append((RECORD_LOCAL, l[:-1]))
312 308 else:
313 309 records.append((RECORD_MERGED, l[:-1]))
314 310 f.close()
315 311 except IOError as err:
316 312 if err.errno != errno.ENOENT:
317 313 raise
318 314 return records
319 315
320 316 def _readrecordsv2(self):
321 317 """read on disk merge state for version 2 file
322 318
323 319 This format is a list of arbitrary records of the form:
324 320
325 321 [type][length][content]
326 322
327 323 `type` is a single character, `length` is a 4 byte integer, and
328 324 `content` is an arbitrary byte sequence of length `length`.
329 325
330 326 Mercurial versions prior to 3.7 have a bug where if there are
331 327 unsupported mandatory merge records, attempting to clear out the merge
332 328 state with hg update --clean or similar aborts. The 't' record type
333 329 works around that by writing out what those versions treat as an
334 330 advisory record, but later versions interpret as special: the first
335 331 character is the 'real' record type and everything onwards is the data.
336 332
337 333 Returns list of records [(TYPE, data), ...]."""
338 334 records = []
339 335 try:
340 336 f = self._repo.vfs(self.statepathv2)
341 337 data = f.read()
342 338 off = 0
343 339 end = len(data)
344 340 while off < end:
345 341 rtype = data[off : off + 1]
346 342 off += 1
347 343 length = _unpack(b'>I', data[off : (off + 4)])[0]
348 344 off += 4
349 345 record = data[off : (off + length)]
350 346 off += length
351 347 if rtype == RECORD_OVERRIDE:
352 348 rtype, record = record[0:1], record[1:]
353 349 records.append((rtype, record))
354 350 f.close()
355 351 except IOError as err:
356 352 if err.errno != errno.ENOENT:
357 353 raise
358 354 return records
359 355
360 356 @util.propertycache
361 357 def mergedriver(self):
362 358 # protect against the following:
363 359 # - A configures a malicious merge driver in their hgrc, then
364 360 # pauses the merge
365 361 # - A edits their hgrc to remove references to the merge driver
366 362 # - A gives a copy of their entire repo, including .hg, to B
367 363 # - B inspects .hgrc and finds it to be clean
368 364 # - B then continues the merge and the malicious merge driver
369 365 # gets invoked
370 366 configmergedriver = self._repo.ui.config(
371 367 b'experimental', b'mergedriver'
372 368 )
373 369 if (
374 370 self._readmergedriver is not None
375 371 and self._readmergedriver != configmergedriver
376 372 ):
377 373 raise error.ConfigError(
378 374 _(b"merge driver changed since merge started"),
379 375 hint=_(b"revert merge driver change or abort merge"),
380 376 )
381 377
382 378 return configmergedriver
383 379
384 380 @util.propertycache
385 381 def local(self):
386 382 if self._local is None:
387 383 msg = b"local accessed but self._local isn't set"
388 384 raise error.ProgrammingError(msg)
389 385 return self._local
390 386
391 387 @util.propertycache
392 388 def localctx(self):
393 389 return self._repo[self.local]
394 390
395 391 @util.propertycache
396 392 def other(self):
397 393 if self._other is None:
398 394 msg = b"other accessed but self._other isn't set"
399 395 raise error.ProgrammingError(msg)
400 396 return self._other
401 397
402 398 @util.propertycache
403 399 def otherctx(self):
404 400 return self._repo[self.other]
405 401
406 402 def active(self):
407 403 """Whether mergestate is active.
408 404
409 405 Returns True if there appears to be mergestate. This is a rough proxy
410 406 for "is a merge in progress."
411 407 """
412 408 return bool(self._local) or bool(self._state)
413 409
414 410 def commit(self):
415 411 """Write current state on disk (if necessary)"""
416 412 if self._dirty:
417 413 records = self._makerecords()
418 414 self._writerecords(records)
419 415 self._dirty = False
420 416
421 417 def _makerecords(self):
422 418 records = []
423 419 records.append((RECORD_LOCAL, hex(self._local)))
424 420 records.append((RECORD_OTHER, hex(self._other)))
425 421 if self.mergedriver:
426 422 records.append(
427 423 (
428 424 RECORD_MERGE_DRIVER_STATE,
429 425 b'\0'.join([self.mergedriver, self._mdstate]),
430 426 )
431 427 )
432 428 # Write out state items. In all cases, the value of the state map entry
433 429 # is written as the contents of the record. The record type depends on
434 430 # the type of state that is stored, and capital-letter records are used
435 431 # to prevent older versions of Mercurial that do not support the feature
436 432 # from loading them.
437 433 for filename, v in pycompat.iteritems(self._state):
438 434 if v[0] == MERGE_RECORD_DRIVER_RESOLVED:
439 435 # Driver-resolved merge. These are stored in 'D' records.
440 436 records.append(
441 437 (RECORD_MERGE_DRIVER_MERGE, b'\0'.join([filename] + v))
442 438 )
443 439 elif v[0] in (
444 440 MERGE_RECORD_UNRESOLVED_PATH,
445 441 MERGE_RECORD_RESOLVED_PATH,
446 442 ):
447 443 # Path conflicts. These are stored in 'P' records. The current
448 444 # resolution state ('pu' or 'pr') is stored within the record.
449 445 records.append(
450 446 (RECORD_PATH_CONFLICT, b'\0'.join([filename] + v))
451 447 )
452 448 elif v[0] == MERGE_RECORD_MERGED_OTHER:
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