mercurial/merge.py
changeset 37109 a532b2f54f95
parent 37107 71543b942eea
child 37110 1b158ca37ea4
equal deleted inserted replaced
37108:0351fb0153ba 37109: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):