Mercurial > public > mercurial-scm > hg-stable
comparison mercurial/merge.py @ 37112:a532b2f54f95
merge: use constants for merge state record types
merge.py is using multiple discrete sets of 1 and 2 letter constants
to define types and behavior. To the uninitiated, the code is very
difficult to reason about. I didn't even realize there were multiple
sets of constants in play initially!
We begin our sanity injection with merge state records. The record
types (which are serialized to disk) are now defined in RECORD_*
constants.
Differential Revision: https://phab.mercurial-scm.org/D2698
author | Gregory Szorc <gregory.szorc@gmail.com> |
---|---|
date | Mon, 05 Mar 2018 14:09:23 -0500 |
parents | 71543b942eea |
children | 1b158ca37ea4 |
comparison
equal
deleted
inserted
replaced
37111:0351fb0153ba | 37112:a532b2f54f95 |
---|---|
45 # used for compatibility for v1 | 45 # used for compatibility for v1 |
46 bits = data.split('\0') | 46 bits = data.split('\0') |
47 bits = bits[:-2] + bits[-1:] | 47 bits = bits[:-2] + bits[-1:] |
48 return '\0'.join(bits) | 48 return '\0'.join(bits) |
49 | 49 |
50 # Merge state record types. See ``mergestate`` docs for more. | |
51 RECORD_LOCAL = b'L' | |
52 RECORD_OTHER = b'O' | |
53 RECORD_MERGED = b'F' | |
54 RECORD_CHANGEDELETE_CONFLICT = b'C' | |
55 RECORD_MERGE_DRIVER_MERGE = b'D' | |
56 RECORD_PATH_CONFLICT = b'P' | |
57 RECORD_MERGE_DRIVER_STATE = b'm' | |
58 RECORD_FILE_VALUES = b'f' | |
59 RECORD_LABELS = b'l' | |
60 RECORD_OVERRIDE = b't' | |
61 RECORD_UNSUPPORTED_MANDATORY = b'X' | |
62 RECORD_UNSUPPORTED_ADVISORY = b'x' | |
63 | |
50 class mergestate(object): | 64 class mergestate(object): |
51 '''track 3-way merge state of individual files | 65 '''track 3-way merge state of individual files |
52 | 66 |
53 The merge state is stored on disk when needed. Two files are used: one with | 67 The merge state is stored on disk when needed. Two files are used: one with |
54 an old format (version 1), and one with a new format (version 2). Version 2 | 68 an old format (version 1), and one with a new format (version 2). Version 2 |
156 self._readmergedriver = None | 170 self._readmergedriver = None |
157 self._mdstate = 's' | 171 self._mdstate = 's' |
158 unsupported = set() | 172 unsupported = set() |
159 records = self._readrecords() | 173 records = self._readrecords() |
160 for rtype, record in records: | 174 for rtype, record in records: |
161 if rtype == 'L': | 175 if rtype == RECORD_LOCAL: |
162 self._local = bin(record) | 176 self._local = bin(record) |
163 elif rtype == 'O': | 177 elif rtype == RECORD_OTHER: |
164 self._other = bin(record) | 178 self._other = bin(record) |
165 elif rtype == 'm': | 179 elif rtype == RECORD_MERGE_DRIVER_STATE: |
166 bits = record.split('\0', 1) | 180 bits = record.split('\0', 1) |
167 mdstate = bits[1] | 181 mdstate = bits[1] |
168 if len(mdstate) != 1 or mdstate not in 'ums': | 182 if len(mdstate) != 1 or mdstate not in 'ums': |
169 # the merge driver should be idempotent, so just rerun it | 183 # the merge driver should be idempotent, so just rerun it |
170 mdstate = 'u' | 184 mdstate = 'u' |
171 | 185 |
172 self._readmergedriver = bits[0] | 186 self._readmergedriver = bits[0] |
173 self._mdstate = mdstate | 187 self._mdstate = mdstate |
174 elif rtype in 'FDCP': | 188 elif rtype in (RECORD_MERGED, RECORD_CHANGEDELETE_CONFLICT, |
189 RECORD_PATH_CONFLICT, RECORD_MERGE_DRIVER_MERGE): | |
175 bits = record.split('\0') | 190 bits = record.split('\0') |
176 self._state[bits[0]] = bits[1:] | 191 self._state[bits[0]] = bits[1:] |
177 elif rtype == 'f': | 192 elif rtype == RECORD_FILE_VALUES: |
178 filename, rawextras = record.split('\0', 1) | 193 filename, rawextras = record.split('\0', 1) |
179 extraparts = rawextras.split('\0') | 194 extraparts = rawextras.split('\0') |
180 extras = {} | 195 extras = {} |
181 i = 0 | 196 i = 0 |
182 while i < len(extraparts): | 197 while i < len(extraparts): |
183 extras[extraparts[i]] = extraparts[i + 1] | 198 extras[extraparts[i]] = extraparts[i + 1] |
184 i += 2 | 199 i += 2 |
185 | 200 |
186 self._stateextras[filename] = extras | 201 self._stateextras[filename] = extras |
187 elif rtype == 'l': | 202 elif rtype == RECORD_LABELS: |
188 labels = record.split('\0', 2) | 203 labels = record.split('\0', 2) |
189 self._labels = [l for l in labels if len(l) > 0] | 204 self._labels = [l for l in labels if len(l) > 0] |
190 elif not rtype.islower(): | 205 elif not rtype.islower(): |
191 unsupported.add(rtype) | 206 unsupported.add(rtype) |
192 self._results = {} | 207 self._results = {} |
216 else: | 231 else: |
217 # v1 file is newer than v2 file, use it | 232 # v1 file is newer than v2 file, use it |
218 # we have to infer the "other" changeset of the merge | 233 # we have to infer the "other" changeset of the merge |
219 # we cannot do better than that with v1 of the format | 234 # we cannot do better than that with v1 of the format |
220 mctx = self._repo[None].parents()[-1] | 235 mctx = self._repo[None].parents()[-1] |
221 v1records.append(('O', mctx.hex())) | 236 v1records.append((RECORD_OTHER, mctx.hex())) |
222 # add place holder "other" file node information | 237 # add place holder "other" file node information |
223 # nobody is using it yet so we do no need to fetch the data | 238 # nobody is using it yet so we do no need to fetch the data |
224 # if mctx was wrong `mctx[bits[-2]]` may fails. | 239 # if mctx was wrong `mctx[bits[-2]]` may fails. |
225 for idx, r in enumerate(v1records): | 240 for idx, r in enumerate(v1records): |
226 if r[0] == 'F': | 241 if r[0] == RECORD_MERGED: |
227 bits = r[1].split('\0') | 242 bits = r[1].split('\0') |
228 bits.insert(-2, '') | 243 bits.insert(-2, '') |
229 v1records[idx] = (r[0], '\0'.join(bits)) | 244 v1records[idx] = (r[0], '\0'.join(bits)) |
230 return v1records | 245 return v1records |
231 | 246 |
232 def _v1v2match(self, v1records, v2records): | 247 def _v1v2match(self, v1records, v2records): |
233 oldv2 = set() # old format version of v2 record | 248 oldv2 = set() # old format version of v2 record |
234 for rec in v2records: | 249 for rec in v2records: |
235 if rec[0] == 'L': | 250 if rec[0] == RECORD_LOCAL: |
236 oldv2.add(rec) | 251 oldv2.add(rec) |
237 elif rec[0] == 'F': | 252 elif rec[0] == RECORD_MERGED: |
238 # drop the onode data (not contained in v1) | 253 # drop the onode data (not contained in v1) |
239 oldv2.add(('F', _droponode(rec[1]))) | 254 oldv2.add((RECORD_MERGED, _droponode(rec[1]))) |
240 for rec in v1records: | 255 for rec in v1records: |
241 if rec not in oldv2: | 256 if rec not in oldv2: |
242 return False | 257 return False |
243 else: | 258 else: |
244 return True | 259 return True |
254 records = [] | 269 records = [] |
255 try: | 270 try: |
256 f = self._repo.vfs(self.statepathv1) | 271 f = self._repo.vfs(self.statepathv1) |
257 for i, l in enumerate(f): | 272 for i, l in enumerate(f): |
258 if i == 0: | 273 if i == 0: |
259 records.append(('L', l[:-1])) | 274 records.append((RECORD_LOCAL, l[:-1])) |
260 else: | 275 else: |
261 records.append(('F', l[:-1])) | 276 records.append((RECORD_MERGED, l[:-1])) |
262 f.close() | 277 f.close() |
263 except IOError as err: | 278 except IOError as err: |
264 if err.errno != errno.ENOENT: | 279 if err.errno != errno.ENOENT: |
265 raise | 280 raise |
266 return records | 281 return records |
294 off += 1 | 309 off += 1 |
295 length = _unpack('>I', data[off:(off + 4)])[0] | 310 length = _unpack('>I', data[off:(off + 4)])[0] |
296 off += 4 | 311 off += 4 |
297 record = data[off:(off + length)] | 312 record = data[off:(off + length)] |
298 off += length | 313 off += length |
299 if rtype == 't': | 314 if rtype == RECORD_OVERRIDE: |
300 rtype, record = record[0:1], record[1:] | 315 rtype, record = record[0:1], record[1:] |
301 records.append((rtype, record)) | 316 records.append((rtype, record)) |
302 f.close() | 317 f.close() |
303 except IOError as err: | 318 except IOError as err: |
304 if err.errno != errno.ENOENT: | 319 if err.errno != errno.ENOENT: |
357 self._writerecords(records) | 372 self._writerecords(records) |
358 self._dirty = False | 373 self._dirty = False |
359 | 374 |
360 def _makerecords(self): | 375 def _makerecords(self): |
361 records = [] | 376 records = [] |
362 records.append(('L', hex(self._local))) | 377 records.append((RECORD_LOCAL, hex(self._local))) |
363 records.append(('O', hex(self._other))) | 378 records.append((RECORD_OTHER, hex(self._other))) |
364 if self.mergedriver: | 379 if self.mergedriver: |
365 records.append(('m', '\0'.join([ | 380 records.append((RECORD_MERGE_DRIVER_STATE, '\0'.join([ |
366 self.mergedriver, self._mdstate]))) | 381 self.mergedriver, self._mdstate]))) |
367 # Write out state items. In all cases, the value of the state map entry | 382 # Write out state items. In all cases, the value of the state map entry |
368 # is written as the contents of the record. The record type depends on | 383 # is written as the contents of the record. The record type depends on |
369 # the type of state that is stored, and capital-letter records are used | 384 # the type of state that is stored, and capital-letter records are used |
370 # to prevent older versions of Mercurial that do not support the feature | 385 # to prevent older versions of Mercurial that do not support the feature |
371 # from loading them. | 386 # from loading them. |
372 for filename, v in self._state.iteritems(): | 387 for filename, v in self._state.iteritems(): |
373 if v[0] == 'd': | 388 if v[0] == 'd': |
374 # Driver-resolved merge. These are stored in 'D' records. | 389 # Driver-resolved merge. These are stored in 'D' records. |
375 records.append(('D', '\0'.join([filename] + v))) | 390 records.append((RECORD_MERGE_DRIVER_MERGE, |
391 '\0'.join([filename] + v))) | |
376 elif v[0] in ('pu', 'pr'): | 392 elif v[0] in ('pu', 'pr'): |
377 # Path conflicts. These are stored in 'P' records. The current | 393 # Path conflicts. These are stored in 'P' records. The current |
378 # resolution state ('pu' or 'pr') is stored within the record. | 394 # resolution state ('pu' or 'pr') is stored within the record. |
379 records.append(('P', '\0'.join([filename] + v))) | 395 records.append((RECORD_PATH_CONFLICT, |
396 '\0'.join([filename] + v))) | |
380 elif v[1] == nullhex or v[6] == nullhex: | 397 elif v[1] == nullhex or v[6] == nullhex: |
381 # Change/Delete or Delete/Change conflicts. These are stored in | 398 # Change/Delete or Delete/Change conflicts. These are stored in |
382 # 'C' records. v[1] is the local file, and is nullhex when the | 399 # 'C' records. v[1] is the local file, and is nullhex when the |
383 # file is deleted locally ('dc'). v[6] is the remote file, and | 400 # file is deleted locally ('dc'). v[6] is the remote file, and |
384 # is nullhex when the file is deleted remotely ('cd'). | 401 # is nullhex when the file is deleted remotely ('cd'). |
385 records.append(('C', '\0'.join([filename] + v))) | 402 records.append((RECORD_CHANGEDELETE_CONFLICT, |
403 '\0'.join([filename] + v))) | |
386 else: | 404 else: |
387 # Normal files. These are stored in 'F' records. | 405 # Normal files. These are stored in 'F' records. |
388 records.append(('F', '\0'.join([filename] + v))) | 406 records.append((RECORD_MERGED, |
407 '\0'.join([filename] + v))) | |
389 for filename, extras in sorted(self._stateextras.iteritems()): | 408 for filename, extras in sorted(self._stateextras.iteritems()): |
390 rawextras = '\0'.join('%s\0%s' % (k, v) for k, v in | 409 rawextras = '\0'.join('%s\0%s' % (k, v) for k, v in |
391 extras.iteritems()) | 410 extras.iteritems()) |
392 records.append(('f', '%s\0%s' % (filename, rawextras))) | 411 records.append((RECORD_FILE_VALUES, |
412 '%s\0%s' % (filename, rawextras))) | |
393 if self._labels is not None: | 413 if self._labels is not None: |
394 labels = '\0'.join(self._labels) | 414 labels = '\0'.join(self._labels) |
395 records.append(('l', labels)) | 415 records.append((RECORD_LABELS, labels)) |
396 return records | 416 return records |
397 | 417 |
398 def _writerecords(self, records): | 418 def _writerecords(self, records): |
399 """Write current state on disk (both v1 and v2)""" | 419 """Write current state on disk (both v1 and v2)""" |
400 self._writerecordsv1(records) | 420 self._writerecordsv1(records) |
403 def _writerecordsv1(self, records): | 423 def _writerecordsv1(self, records): |
404 """Write current state on disk in a version 1 file""" | 424 """Write current state on disk in a version 1 file""" |
405 f = self._repo.vfs(self.statepathv1, 'wb') | 425 f = self._repo.vfs(self.statepathv1, 'wb') |
406 irecords = iter(records) | 426 irecords = iter(records) |
407 lrecords = next(irecords) | 427 lrecords = next(irecords) |
408 assert lrecords[0] == 'L' | 428 assert lrecords[0] == RECORD_LOCAL |
409 f.write(hex(self._local) + '\n') | 429 f.write(hex(self._local) + '\n') |
410 for rtype, data in irecords: | 430 for rtype, data in irecords: |
411 if rtype == 'F': | 431 if rtype == RECORD_MERGED: |
412 f.write('%s\n' % _droponode(data)) | 432 f.write('%s\n' % _droponode(data)) |
413 f.close() | 433 f.close() |
414 | 434 |
415 def _writerecordsv2(self, records): | 435 def _writerecordsv2(self, records): |
416 """Write current state on disk in a version 2 file | 436 """Write current state on disk in a version 2 file |
417 | 437 |
418 See the docstring for _readrecordsv2 for why we use 't'.""" | 438 See the docstring for _readrecordsv2 for why we use 't'.""" |
419 # these are the records that all version 2 clients can read | 439 # these are the records that all version 2 clients can read |
420 whitelist = 'LOF' | 440 allowlist = (RECORD_LOCAL, RECORD_OTHER, RECORD_MERGED) |
421 f = self._repo.vfs(self.statepathv2, 'wb') | 441 f = self._repo.vfs(self.statepathv2, 'wb') |
422 for key, data in records: | 442 for key, data in records: |
423 assert len(key) == 1 | 443 assert len(key) == 1 |
424 if key not in whitelist: | 444 if key not in allowlist: |
425 key, data = 't', '%s%s' % (key, data) | 445 key, data = RECORD_OVERRIDE, '%s%s' % (key, data) |
426 format = '>sI%is' % len(data) | 446 format = '>sI%is' % len(data) |
427 f.write(_pack(format, key, len(data), data)) | 447 f.write(_pack(format, key, len(data), data)) |
428 f.close() | 448 f.close() |
429 | 449 |
430 def add(self, fcl, fco, fca, fd): | 450 def add(self, fcl, fco, fca, fd): |