mercurial/merge.py
changeset 23380 90cc552ceed5
parent 23362 cc83cac41619
child 23384 0791a10ad87c
equal deleted inserted replaced
23379:86c6f06feb04 23380:90cc552ceed5
    16 _pack = struct.pack
    16 _pack = struct.pack
    17 _unpack = struct.unpack
    17 _unpack = struct.unpack
    18 
    18 
    19 def _droponode(data):
    19 def _droponode(data):
    20     # used for compatibility for v1
    20     # used for compatibility for v1
    21     bits = data.split("\0")
    21     bits = data.split('\0')
    22     bits = bits[:-2] + bits[-1:]
    22     bits = bits[:-2] + bits[-1:]
    23     return "\0".join(bits)
    23     return '\0'.join(bits)
    24 
    24 
    25 class mergestate(object):
    25 class mergestate(object):
    26     '''track 3-way merge state of individual files
    26     '''track 3-way merge state of individual files
    27 
    27 
    28     it is stored on disk when needed. Two file are used, one with an old
    28     it is stored on disk when needed. Two file are used, one with an old
    43 
    43 
    44     L: the node of the "local" part of the merge (hexified version)
    44     L: the node of the "local" part of the merge (hexified version)
    45     O: the node of the "other" part of the merge (hexified version)
    45     O: the node of the "other" part of the merge (hexified version)
    46     F: a file to be merged entry
    46     F: a file to be merged entry
    47     '''
    47     '''
    48     statepathv1 = "merge/state"
    48     statepathv1 = 'merge/state'
    49     statepathv2 = "merge/state2"
    49     statepathv2 = 'merge/state2'
    50 
    50 
    51     def __init__(self, repo):
    51     def __init__(self, repo):
    52         self._repo = repo
    52         self._repo = repo
    53         self._dirty = False
    53         self._dirty = False
    54         self._read()
    54         self._read()
    58         self._local = None
    58         self._local = None
    59         self._other = None
    59         self._other = None
    60         if node:
    60         if node:
    61             self._local = node
    61             self._local = node
    62             self._other = other
    62             self._other = other
    63         shutil.rmtree(self._repo.join("merge"), True)
    63         shutil.rmtree(self._repo.join('merge'), True)
    64         self._dirty = False
    64         self._dirty = False
    65 
    65 
    66     def _read(self):
    66     def _read(self):
    67         """Analyse each record content to restore a serialized state from disk
    67         """Analyse each record content to restore a serialized state from disk
    68 
    68 
    76         for rtype, record in records:
    76         for rtype, record in records:
    77             if rtype == 'L':
    77             if rtype == 'L':
    78                 self._local = bin(record)
    78                 self._local = bin(record)
    79             elif rtype == 'O':
    79             elif rtype == 'O':
    80                 self._other = bin(record)
    80                 self._other = bin(record)
    81             elif rtype == "F":
    81             elif rtype == 'F':
    82                 bits = record.split("\0")
    82                 bits = record.split('\0')
    83                 self._state[bits[0]] = bits[1:]
    83                 self._state[bits[0]] = bits[1:]
    84             elif not rtype.islower():
    84             elif not rtype.islower():
    85                 raise util.Abort(_('unsupported merge state record: %s')
    85                 raise util.Abort(_('unsupported merge state record: %s')
    86                                    % rtype)
    86                                    % rtype)
    87         self._dirty = False
    87         self._dirty = False
   119                 # add place holder "other" file node information
   119                 # add place holder "other" file node information
   120                 # nobody is using it yet so we do no need to fetch the data
   120                 # nobody is using it yet so we do no need to fetch the data
   121                 # if mctx was wrong `mctx[bits[-2]]` may fails.
   121                 # if mctx was wrong `mctx[bits[-2]]` may fails.
   122                 for idx, r in enumerate(v1records):
   122                 for idx, r in enumerate(v1records):
   123                     if r[0] == 'F':
   123                     if r[0] == 'F':
   124                         bits = r[1].split("\0")
   124                         bits = r[1].split('\0')
   125                         bits.insert(-2, '')
   125                         bits.insert(-2, '')
   126                         v1records[idx] = (r[0], "\0".join(bits))
   126                         v1records[idx] = (r[0], '\0'.join(bits))
   127                 return v1records
   127                 return v1records
   128         else:
   128         else:
   129             return v2records
   129             return v2records
   130 
   130 
   131     def _readrecordsv1(self):
   131     def _readrecordsv1(self):
   189 
   189 
   190     def commit(self):
   190     def commit(self):
   191         """Write current state on disk (if necessary)"""
   191         """Write current state on disk (if necessary)"""
   192         if self._dirty:
   192         if self._dirty:
   193             records = []
   193             records = []
   194             records.append(("L", hex(self._local)))
   194             records.append(('L', hex(self._local)))
   195             records.append(("O", hex(self._other)))
   195             records.append(('O', hex(self._other)))
   196             for d, v in self._state.iteritems():
   196             for d, v in self._state.iteritems():
   197                 records.append(("F", "\0".join([d] + v)))
   197                 records.append(('F', '\0'.join([d] + v)))
   198             self._writerecords(records)
   198             self._writerecords(records)
   199             self._dirty = False
   199             self._dirty = False
   200 
   200 
   201     def _writerecords(self, records):
   201     def _writerecords(self, records):
   202         """Write current state on disk (both v1 and v2)"""
   202         """Write current state on disk (both v1 and v2)"""
   203         self._writerecordsv1(records)
   203         self._writerecordsv1(records)
   204         self._writerecordsv2(records)
   204         self._writerecordsv2(records)
   205 
   205 
   206     def _writerecordsv1(self, records):
   206     def _writerecordsv1(self, records):
   207         """Write current state on disk in a version 1 file"""
   207         """Write current state on disk in a version 1 file"""
   208         f = self._repo.opener(self.statepathv1, "w")
   208         f = self._repo.opener(self.statepathv1, 'w')
   209         irecords = iter(records)
   209         irecords = iter(records)
   210         lrecords = irecords.next()
   210         lrecords = irecords.next()
   211         assert lrecords[0] == 'L'
   211         assert lrecords[0] == 'L'
   212         f.write(hex(self._local) + "\n")
   212         f.write(hex(self._local) + '\n')
   213         for rtype, data in irecords:
   213         for rtype, data in irecords:
   214             if rtype == "F":
   214             if rtype == 'F':
   215                 f.write("%s\n" % _droponode(data))
   215                 f.write('%s\n' % _droponode(data))
   216         f.close()
   216         f.close()
   217 
   217 
   218     def _writerecordsv2(self, records):
   218     def _writerecordsv2(self, records):
   219         """Write current state on disk in a version 2 file"""
   219         """Write current state on disk in a version 2 file"""
   220         f = self._repo.opener(self.statepathv2, "w")
   220         f = self._repo.opener(self.statepathv2, 'w')
   221         for key, data in records:
   221         for key, data in records:
   222             assert len(key) == 1
   222             assert len(key) == 1
   223             format = ">sI%is" % len(data)
   223             format = '>sI%is' % len(data)
   224             f.write(_pack(format, key, len(data), data))
   224             f.write(_pack(format, key, len(data), data))
   225         f.close()
   225         f.close()
   226 
   226 
   227     def add(self, fcl, fco, fca, fd):
   227     def add(self, fcl, fco, fca, fd):
   228         """add a new (potentially?) conflicting file the merge state
   228         """add a new (potentially?) conflicting file the merge state
   232         fd:  file path of the resulting merge.
   232         fd:  file path of the resulting merge.
   233 
   233 
   234         note: also write the local version to the `.hg/merge` directory.
   234         note: also write the local version to the `.hg/merge` directory.
   235         """
   235         """
   236         hash = util.sha1(fcl.path()).hexdigest()
   236         hash = util.sha1(fcl.path()).hexdigest()
   237         self._repo.opener.write("merge/" + hash, fcl.data())
   237         self._repo.opener.write('merge/' + hash, fcl.data())
   238         self._state[fd] = ['u', hash, fcl.path(),
   238         self._state[fd] = ['u', hash, fcl.path(),
   239                            fca.path(), hex(fca.filenode()),
   239                            fca.path(), hex(fca.filenode()),
   240                            fco.path(), hex(fco.filenode()),
   240                            fco.path(), hex(fco.filenode()),
   241                            fcl.flags()]
   241                            fcl.flags()]
   242         self._dirty = True
   242         self._dirty = True
   282                 self._repo.ui.warn(_('warning: cannot merge flags for %s\n') %
   282                 self._repo.ui.warn(_('warning: cannot merge flags for %s\n') %
   283                                    afile)
   283                                    afile)
   284             elif flags == fla:
   284             elif flags == fla:
   285                 flags = flo
   285                 flags = flo
   286         # restore local
   286         # restore local
   287         f = self._repo.opener("merge/" + hash)
   287         f = self._repo.opener('merge/' + hash)
   288         self._repo.wwrite(dfile, f.read(), flags)
   288         self._repo.wwrite(dfile, f.read(), flags)
   289         f.close()
   289         f.close()
   290         r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca,
   290         r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca,
   291                                 labels=labels)
   291                                 labels=labels)
   292         if r is None:
   292         if r is None:
   403 
   403 
   404     if '.hgsubstate' in m1:
   404     if '.hgsubstate' in m1:
   405         # check whether sub state is modified
   405         # check whether sub state is modified
   406         for s in sorted(wctx.substate):
   406         for s in sorted(wctx.substate):
   407             if wctx.sub(s).dirty():
   407             if wctx.sub(s).dirty():
   408                 m1['.hgsubstate'] += "+"
   408                 m1['.hgsubstate'] += '+'
   409                 break
   409                 break
   410 
   410 
   411     aborts = []
   411     aborts = []
   412     # Compare manifests
   412     # Compare manifests
   413     diff = m1.diff(m2)
   413     diff = m1.diff(m2)
   452             if n1 != ma[f]:
   452             if n1 != ma[f]:
   453                 if acceptremote:
   453                 if acceptremote:
   454                     actions['r'].append((f, None, "remote delete"))
   454                     actions['r'].append((f, None, "remote delete"))
   455                 else:
   455                 else:
   456                     actions['cd'].append((f, None, "prompt changed/deleted"))
   456                     actions['cd'].append((f, None, "prompt changed/deleted"))
   457             elif n1[20:] == "a": # added, no remote
   457             elif n1[20:] == 'a': # added, no remote
   458                 actions['f'].append((f, None, "remote deleted"))
   458                 actions['f'].append((f, None, "remote deleted"))
   459             else:
   459             else:
   460                 actions['r'].append((f, None, "other deleted"))
   460                 actions['r'].append((f, None, "other deleted"))
   461         elif n2 and f in movewithdir:
   461         elif n2 and f in movewithdir:
   462             f2 = movewithdir[f]
   462             f2 = movewithdir[f]
   490                 if force and branchmerge and different:
   490                 if force and branchmerge and different:
   491                     # FIXME: This is wrong - f is not in ma ...
   491                     # FIXME: This is wrong - f is not in ma ...
   492                     actions['m'].append((f, (f, f, f, False, pa.node()),
   492                     actions['m'].append((f, (f, f, f, False, pa.node()),
   493                                     "remote differs from untracked local"))
   493                                     "remote differs from untracked local"))
   494                 elif not force and different:
   494                 elif not force and different:
   495                     aborts.append((f, "ud"))
   495                     aborts.append((f, 'ud'))
   496                 else:
   496                 else:
   497                     actions['g'].append((f, (fl2,), "remote created"))
   497                     actions['g'].append((f, (fl2,), "remote created"))
   498         elif n2 and n2 != ma[f]:
   498         elif n2 and n2 != ma[f]:
   499             different = _checkunknownfile(repo, wctx, p2, f)
   499             different = _checkunknownfile(repo, wctx, p2, f)
   500             if not force and different:
   500             if not force and different:
   501                 aborts.append((f, "ud"))
   501                 aborts.append((f, 'ud'))
   502             else:
   502             else:
   503                 if acceptremote:
   503                 if acceptremote:
   504                     actions['g'].append((f, (fl2,), "remote recreating"))
   504                     actions['g'].append((f, (fl2,), "remote recreating"))
   505                 else:
   505                 else:
   506                     actions['dc'].append((f, (fl2,), "prompt deleted/changed"))
   506                     actions['dc'].append((f, (fl2,), "prompt deleted/changed"))
   507 
   507 
   508     for f, m in sorted(aborts):
   508     for f, m in sorted(aborts):
   509         if m == "ud":
   509         if m == 'ud':
   510             repo.ui.warn(_("%s: untracked file differs\n") % f)
   510             repo.ui.warn(_("%s: untracked file differs\n") % f)
   511         else: assert False, m
   511         else: assert False, m
   512     if aborts:
   512     if aborts:
   513         raise util.Abort(_("untracked files in working directory differ "
   513         raise util.Abort(_("untracked files in working directory differ "
   514                            "from files in requested revision"))
   514                            "from files in requested revision"))
   783                 if util.all(a == l[0] for a in l[1:]): # len(bids) is > 1
   783                 if util.all(a == l[0] for a in l[1:]): # len(bids) is > 1
   784                     repo.ui.note(" %s: consensus for %s\n" % (f, m))
   784                     repo.ui.note(" %s: consensus for %s\n" % (f, m))
   785                     actions[m].append(l[0])
   785                     actions[m].append(l[0])
   786                     continue
   786                     continue
   787             # If keep is an option, just do it.
   787             # If keep is an option, just do it.
   788             if "k" in bids:
   788             if 'k' in bids:
   789                 repo.ui.note(" %s: picking 'keep' action\n" % f)
   789                 repo.ui.note(" %s: picking 'keep' action\n" % f)
   790                 actions['k'].append(bids["k"][0])
   790                 actions['k'].append(bids['k'][0])
   791                 continue
   791                 continue
   792             # If there are gets and they all agree [how could they not?], do it.
   792             # If there are gets and they all agree [how could they not?], do it.
   793             if "g" in bids:
   793             if 'g' in bids:
   794                 ga0 = bids["g"][0]
   794                 ga0 = bids['g'][0]
   795                 if util.all(a == ga0 for a in bids["g"][1:]):
   795                 if util.all(a == ga0 for a in bids['g'][1:]):
   796                     repo.ui.note(" %s: picking 'get' action\n" % f)
   796                     repo.ui.note(" %s: picking 'get' action\n" % f)
   797                     actions['g'].append(ga0)
   797                     actions['g'].append(ga0)
   798                     continue
   798                     continue
   799             # TODO: Consider other simple actions such as mode changes
   799             # TODO: Consider other simple actions such as mode changes
   800             # Handle inefficient democrazy.
   800             # Handle inefficient democrazy.
   978             # foreground changesets (successors), and tip of current branch;
   978             # foreground changesets (successors), and tip of current branch;
   979             # but currently we are only checking the branch tips.
   979             # but currently we are only checking the branch tips.
   980             try:
   980             try:
   981                 node = repo.branchtip(wc.branch())
   981                 node = repo.branchtip(wc.branch())
   982             except errormod.RepoLookupError:
   982             except errormod.RepoLookupError:
   983                 if wc.branch() == "default": # no default branch!
   983                 if wc.branch() == 'default': # no default branch!
   984                     node = repo.lookup("tip") # update to tip
   984                     node = repo.lookup('tip') # update to tip
   985                 else:
   985                 else:
   986                     raise util.Abort(_("branch %s not found") % wc.branch())
   986                     raise util.Abort(_("branch %s not found") % wc.branch())
   987 
   987 
   988             if p1.obsolete() and not p1.children():
   988             if p1.obsolete() and not p1.children():
   989                 # allow updating to successors
   989                 # allow updating to successors
  1007                     # and the usual case (len = 1)
  1007                     # and the usual case (len = 1)
  1008                     successors = [n for sub in successors for n in sub]
  1008                     successors = [n for sub in successors for n in sub]
  1009 
  1009 
  1010                     # get the max revision for the given successors set,
  1010                     # get the max revision for the given successors set,
  1011                     # i.e. the 'tip' of a set
  1011                     # i.e. the 'tip' of a set
  1012                     node = repo.revs("max(%ln)", successors).first()
  1012                     node = repo.revs('max(%ln)', successors).first()
  1013                     pas = [p1]
  1013                     pas = [p1]
  1014 
  1014 
  1015         overwrite = force and not branchmerge
  1015         overwrite = force and not branchmerge
  1016 
  1016 
  1017         p2 = repo[node]
  1017         p2 = repo[node]
  1018         if pas[0] is None:
  1018         if pas[0] is None:
  1019             if repo.ui.config("merge", "preferancestor", '*') == '*':
  1019             if repo.ui.config('merge', 'preferancestor', '*') == '*':
  1020                 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
  1020                 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
  1021                 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
  1021                 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
  1022             else:
  1022             else:
  1023                 pas = [p1.ancestor(p2, warn=branchmerge)]
  1023                 pas = [p1.ancestor(p2, warn=branchmerge)]
  1024 
  1024 
  1082             pas = [wc]
  1082             pas = [wc]
  1083         elif pas == [p2]: # backwards
  1083         elif pas == [p2]: # backwards
  1084             pas = [wc.p1()]
  1084             pas = [wc.p1()]
  1085         elif not branchmerge and not wc.dirty(missing=True):
  1085         elif not branchmerge and not wc.dirty(missing=True):
  1086             pass
  1086             pass
  1087         elif pas[0] and repo.ui.configbool("merge", "followcopies", True):
  1087         elif pas[0] and repo.ui.configbool('merge', 'followcopies', True):
  1088             followcopies = True
  1088             followcopies = True
  1089 
  1089 
  1090         ### calculate phase
  1090         ### calculate phase
  1091         actions = calculateupdates(repo, wc, p2, pas, branchmerge, force,
  1091         actions = calculateupdates(repo, wc, p2, pas, branchmerge, force,
  1092                                    partial, mergeancestor, followcopies)
  1092                                    partial, mergeancestor, followcopies)