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 |
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: |
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) |