##// END OF EJS Templates
merge: with merge.preferancestor=*, run an auction with bids from ancestors...
Mads Kiilerich -
r21128:f4014f64 default
parent child Browse files
Show More
@@ -1,1023 +1,1084 b''
1 # merge.py - directory-level update/merge handling for Mercurial
1 # merge.py - directory-level update/merge handling for Mercurial
2 #
2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import struct
8 import struct
9
9
10 from node import nullid, nullrev, hex, bin
10 from node import nullid, nullrev, hex, bin
11 from i18n import _
11 from i18n import _
12 from mercurial import obsolete
12 from mercurial import obsolete
13 import error, util, filemerge, copies, subrepo, worker, dicthelpers
13 import error, util, filemerge, copies, subrepo, worker, dicthelpers
14 import errno, os, shutil
14 import errno, os, shutil
15
15
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
29 format, one with a new format. Both contains similar data, but the new
29 format, one with a new format. Both contains similar data, but the new
30 format can store new kind of field.
30 format can store new kind of field.
31
31
32 Current new format is a list of arbitrary record of the form:
32 Current new format is a list of arbitrary record of the form:
33
33
34 [type][length][content]
34 [type][length][content]
35
35
36 Type is a single character, length is a 4 bytes integer, content is an
36 Type is a single character, length is a 4 bytes integer, content is an
37 arbitrary suites of bytes of length `length`.
37 arbitrary suites of bytes of length `length`.
38
38
39 Type should be a letter. Capital letter are mandatory record, Mercurial
39 Type should be a letter. Capital letter are mandatory record, Mercurial
40 should abort if they are unknown. lower case record can be safely ignored.
40 should abort if they are unknown. lower case record can be safely ignored.
41
41
42 Currently known record:
42 Currently known record:
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()
55
55
56 def reset(self, node=None, other=None):
56 def reset(self, node=None, other=None):
57 self._state = {}
57 self._state = {}
58 if node:
58 if node:
59 self._local = node
59 self._local = node
60 self._other = other
60 self._other = other
61 shutil.rmtree(self._repo.join("merge"), True)
61 shutil.rmtree(self._repo.join("merge"), True)
62 self._dirty = False
62 self._dirty = False
63
63
64 def _read(self):
64 def _read(self):
65 """Analyse each record content to restore a serialized state from disk
65 """Analyse each record content to restore a serialized state from disk
66
66
67 This function process "record" entry produced by the de-serialization
67 This function process "record" entry produced by the de-serialization
68 of on disk file.
68 of on disk file.
69 """
69 """
70 self._state = {}
70 self._state = {}
71 records = self._readrecords()
71 records = self._readrecords()
72 for rtype, record in records:
72 for rtype, record in records:
73 if rtype == 'L':
73 if rtype == 'L':
74 self._local = bin(record)
74 self._local = bin(record)
75 elif rtype == 'O':
75 elif rtype == 'O':
76 self._other = bin(record)
76 self._other = bin(record)
77 elif rtype == "F":
77 elif rtype == "F":
78 bits = record.split("\0")
78 bits = record.split("\0")
79 self._state[bits[0]] = bits[1:]
79 self._state[bits[0]] = bits[1:]
80 elif not rtype.islower():
80 elif not rtype.islower():
81 raise util.Abort(_('unsupported merge state record: %s')
81 raise util.Abort(_('unsupported merge state record: %s')
82 % rtype)
82 % rtype)
83 self._dirty = False
83 self._dirty = False
84
84
85 def _readrecords(self):
85 def _readrecords(self):
86 """Read merge state from disk and return a list of record (TYPE, data)
86 """Read merge state from disk and return a list of record (TYPE, data)
87
87
88 We read data from both v1 and v2 files and decide which one to use.
88 We read data from both v1 and v2 files and decide which one to use.
89
89
90 V1 has been used by version prior to 2.9.1 and contains less data than
90 V1 has been used by version prior to 2.9.1 and contains less data than
91 v2. We read both versions and check if no data in v2 contradicts
91 v2. We read both versions and check if no data in v2 contradicts
92 v1. If there is not contradiction we can safely assume that both v1
92 v1. If there is not contradiction we can safely assume that both v1
93 and v2 were written at the same time and use the extract data in v2. If
93 and v2 were written at the same time and use the extract data in v2. If
94 there is contradiction we ignore v2 content as we assume an old version
94 there is contradiction we ignore v2 content as we assume an old version
95 of Mercurial has overwritten the mergestate file and left an old v2
95 of Mercurial has overwritten the mergestate file and left an old v2
96 file around.
96 file around.
97
97
98 returns list of record [(TYPE, data), ...]"""
98 returns list of record [(TYPE, data), ...]"""
99 v1records = self._readrecordsv1()
99 v1records = self._readrecordsv1()
100 v2records = self._readrecordsv2()
100 v2records = self._readrecordsv2()
101 oldv2 = set() # old format version of v2 record
101 oldv2 = set() # old format version of v2 record
102 for rec in v2records:
102 for rec in v2records:
103 if rec[0] == 'L':
103 if rec[0] == 'L':
104 oldv2.add(rec)
104 oldv2.add(rec)
105 elif rec[0] == 'F':
105 elif rec[0] == 'F':
106 # drop the onode data (not contained in v1)
106 # drop the onode data (not contained in v1)
107 oldv2.add(('F', _droponode(rec[1])))
107 oldv2.add(('F', _droponode(rec[1])))
108 for rec in v1records:
108 for rec in v1records:
109 if rec not in oldv2:
109 if rec not in oldv2:
110 # v1 file is newer than v2 file, use it
110 # v1 file is newer than v2 file, use it
111 # we have to infer the "other" changeset of the merge
111 # we have to infer the "other" changeset of the merge
112 # we cannot do better than that with v1 of the format
112 # we cannot do better than that with v1 of the format
113 mctx = self._repo[None].parents()[-1]
113 mctx = self._repo[None].parents()[-1]
114 v1records.append(('O', mctx.hex()))
114 v1records.append(('O', mctx.hex()))
115 # add place holder "other" file node information
115 # add place holder "other" file node information
116 # nobody is using it yet so we do no need to fetch the data
116 # nobody is using it yet so we do no need to fetch the data
117 # if mctx was wrong `mctx[bits[-2]]` may fails.
117 # if mctx was wrong `mctx[bits[-2]]` may fails.
118 for idx, r in enumerate(v1records):
118 for idx, r in enumerate(v1records):
119 if r[0] == 'F':
119 if r[0] == 'F':
120 bits = r[1].split("\0")
120 bits = r[1].split("\0")
121 bits.insert(-2, '')
121 bits.insert(-2, '')
122 v1records[idx] = (r[0], "\0".join(bits))
122 v1records[idx] = (r[0], "\0".join(bits))
123 return v1records
123 return v1records
124 else:
124 else:
125 return v2records
125 return v2records
126
126
127 def _readrecordsv1(self):
127 def _readrecordsv1(self):
128 """read on disk merge state for version 1 file
128 """read on disk merge state for version 1 file
129
129
130 returns list of record [(TYPE, data), ...]
130 returns list of record [(TYPE, data), ...]
131
131
132 Note: the "F" data from this file are one entry short
132 Note: the "F" data from this file are one entry short
133 (no "other file node" entry)
133 (no "other file node" entry)
134 """
134 """
135 records = []
135 records = []
136 try:
136 try:
137 f = self._repo.opener(self.statepathv1)
137 f = self._repo.opener(self.statepathv1)
138 for i, l in enumerate(f):
138 for i, l in enumerate(f):
139 if i == 0:
139 if i == 0:
140 records.append(('L', l[:-1]))
140 records.append(('L', l[:-1]))
141 else:
141 else:
142 records.append(('F', l[:-1]))
142 records.append(('F', l[:-1]))
143 f.close()
143 f.close()
144 except IOError, err:
144 except IOError, err:
145 if err.errno != errno.ENOENT:
145 if err.errno != errno.ENOENT:
146 raise
146 raise
147 return records
147 return records
148
148
149 def _readrecordsv2(self):
149 def _readrecordsv2(self):
150 """read on disk merge state for version 2 file
150 """read on disk merge state for version 2 file
151
151
152 returns list of record [(TYPE, data), ...]
152 returns list of record [(TYPE, data), ...]
153 """
153 """
154 records = []
154 records = []
155 try:
155 try:
156 f = self._repo.opener(self.statepathv2)
156 f = self._repo.opener(self.statepathv2)
157 data = f.read()
157 data = f.read()
158 off = 0
158 off = 0
159 end = len(data)
159 end = len(data)
160 while off < end:
160 while off < end:
161 rtype = data[off]
161 rtype = data[off]
162 off += 1
162 off += 1
163 length = _unpack('>I', data[off:(off + 4)])[0]
163 length = _unpack('>I', data[off:(off + 4)])[0]
164 off += 4
164 off += 4
165 record = data[off:(off + length)]
165 record = data[off:(off + length)]
166 off += length
166 off += length
167 records.append((rtype, record))
167 records.append((rtype, record))
168 f.close()
168 f.close()
169 except IOError, err:
169 except IOError, err:
170 if err.errno != errno.ENOENT:
170 if err.errno != errno.ENOENT:
171 raise
171 raise
172 return records
172 return records
173
173
174 def commit(self):
174 def commit(self):
175 """Write current state on disk (if necessary)"""
175 """Write current state on disk (if necessary)"""
176 if self._dirty:
176 if self._dirty:
177 records = []
177 records = []
178 records.append(("L", hex(self._local)))
178 records.append(("L", hex(self._local)))
179 records.append(("O", hex(self._other)))
179 records.append(("O", hex(self._other)))
180 for d, v in self._state.iteritems():
180 for d, v in self._state.iteritems():
181 records.append(("F", "\0".join([d] + v)))
181 records.append(("F", "\0".join([d] + v)))
182 self._writerecords(records)
182 self._writerecords(records)
183 self._dirty = False
183 self._dirty = False
184
184
185 def _writerecords(self, records):
185 def _writerecords(self, records):
186 """Write current state on disk (both v1 and v2)"""
186 """Write current state on disk (both v1 and v2)"""
187 self._writerecordsv1(records)
187 self._writerecordsv1(records)
188 self._writerecordsv2(records)
188 self._writerecordsv2(records)
189
189
190 def _writerecordsv1(self, records):
190 def _writerecordsv1(self, records):
191 """Write current state on disk in a version 1 file"""
191 """Write current state on disk in a version 1 file"""
192 f = self._repo.opener(self.statepathv1, "w")
192 f = self._repo.opener(self.statepathv1, "w")
193 irecords = iter(records)
193 irecords = iter(records)
194 lrecords = irecords.next()
194 lrecords = irecords.next()
195 assert lrecords[0] == 'L'
195 assert lrecords[0] == 'L'
196 f.write(hex(self._local) + "\n")
196 f.write(hex(self._local) + "\n")
197 for rtype, data in irecords:
197 for rtype, data in irecords:
198 if rtype == "F":
198 if rtype == "F":
199 f.write("%s\n" % _droponode(data))
199 f.write("%s\n" % _droponode(data))
200 f.close()
200 f.close()
201
201
202 def _writerecordsv2(self, records):
202 def _writerecordsv2(self, records):
203 """Write current state on disk in a version 2 file"""
203 """Write current state on disk in a version 2 file"""
204 f = self._repo.opener(self.statepathv2, "w")
204 f = self._repo.opener(self.statepathv2, "w")
205 for key, data in records:
205 for key, data in records:
206 assert len(key) == 1
206 assert len(key) == 1
207 format = ">sI%is" % len(data)
207 format = ">sI%is" % len(data)
208 f.write(_pack(format, key, len(data), data))
208 f.write(_pack(format, key, len(data), data))
209 f.close()
209 f.close()
210
210
211 def add(self, fcl, fco, fca, fd):
211 def add(self, fcl, fco, fca, fd):
212 """add a new (potentially?) conflicting file the merge state
212 """add a new (potentially?) conflicting file the merge state
213 fcl: file context for local,
213 fcl: file context for local,
214 fco: file context for remote,
214 fco: file context for remote,
215 fca: file context for ancestors,
215 fca: file context for ancestors,
216 fd: file path of the resulting merge.
216 fd: file path of the resulting merge.
217
217
218 note: also write the local version to the `.hg/merge` directory.
218 note: also write the local version to the `.hg/merge` directory.
219 """
219 """
220 hash = util.sha1(fcl.path()).hexdigest()
220 hash = util.sha1(fcl.path()).hexdigest()
221 self._repo.opener.write("merge/" + hash, fcl.data())
221 self._repo.opener.write("merge/" + hash, fcl.data())
222 self._state[fd] = ['u', hash, fcl.path(),
222 self._state[fd] = ['u', hash, fcl.path(),
223 fca.path(), hex(fca.filenode()),
223 fca.path(), hex(fca.filenode()),
224 fco.path(), hex(fco.filenode()),
224 fco.path(), hex(fco.filenode()),
225 fcl.flags()]
225 fcl.flags()]
226 self._dirty = True
226 self._dirty = True
227
227
228 def __contains__(self, dfile):
228 def __contains__(self, dfile):
229 return dfile in self._state
229 return dfile in self._state
230
230
231 def __getitem__(self, dfile):
231 def __getitem__(self, dfile):
232 return self._state[dfile][0]
232 return self._state[dfile][0]
233
233
234 def __iter__(self):
234 def __iter__(self):
235 l = self._state.keys()
235 l = self._state.keys()
236 l.sort()
236 l.sort()
237 for f in l:
237 for f in l:
238 yield f
238 yield f
239
239
240 def files(self):
240 def files(self):
241 return self._state.keys()
241 return self._state.keys()
242
242
243 def mark(self, dfile, state):
243 def mark(self, dfile, state):
244 self._state[dfile][0] = state
244 self._state[dfile][0] = state
245 self._dirty = True
245 self._dirty = True
246
246
247 def resolve(self, dfile, wctx):
247 def resolve(self, dfile, wctx):
248 """rerun merge process for file path `dfile`"""
248 """rerun merge process for file path `dfile`"""
249 if self[dfile] == 'r':
249 if self[dfile] == 'r':
250 return 0
250 return 0
251 stateentry = self._state[dfile]
251 stateentry = self._state[dfile]
252 state, hash, lfile, afile, anode, ofile, onode, flags = stateentry
252 state, hash, lfile, afile, anode, ofile, onode, flags = stateentry
253 octx = self._repo[self._other]
253 octx = self._repo[self._other]
254 fcd = wctx[dfile]
254 fcd = wctx[dfile]
255 fco = octx[ofile]
255 fco = octx[ofile]
256 fca = self._repo.filectx(afile, fileid=anode)
256 fca = self._repo.filectx(afile, fileid=anode)
257 # "premerge" x flags
257 # "premerge" x flags
258 flo = fco.flags()
258 flo = fco.flags()
259 fla = fca.flags()
259 fla = fca.flags()
260 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
260 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
261 if fca.node() == nullid:
261 if fca.node() == nullid:
262 self._repo.ui.warn(_('warning: cannot merge flags for %s\n') %
262 self._repo.ui.warn(_('warning: cannot merge flags for %s\n') %
263 afile)
263 afile)
264 elif flags == fla:
264 elif flags == fla:
265 flags = flo
265 flags = flo
266 # restore local
266 # restore local
267 f = self._repo.opener("merge/" + hash)
267 f = self._repo.opener("merge/" + hash)
268 self._repo.wwrite(dfile, f.read(), flags)
268 self._repo.wwrite(dfile, f.read(), flags)
269 f.close()
269 f.close()
270 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
270 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
271 if r is None:
271 if r is None:
272 # no real conflict
272 # no real conflict
273 del self._state[dfile]
273 del self._state[dfile]
274 self._dirty = True
274 self._dirty = True
275 elif not r:
275 elif not r:
276 self.mark(dfile, 'r')
276 self.mark(dfile, 'r')
277 return r
277 return r
278
278
279 def _checkunknownfile(repo, wctx, mctx, f):
279 def _checkunknownfile(repo, wctx, mctx, f):
280 return (not repo.dirstate._ignore(f)
280 return (not repo.dirstate._ignore(f)
281 and os.path.isfile(repo.wjoin(f))
281 and os.path.isfile(repo.wjoin(f))
282 and repo.wopener.audit.check(f)
282 and repo.wopener.audit.check(f)
283 and repo.dirstate.normalize(f) not in repo.dirstate
283 and repo.dirstate.normalize(f) not in repo.dirstate
284 and mctx[f].cmp(wctx[f]))
284 and mctx[f].cmp(wctx[f]))
285
285
286 def _checkunknown(repo, wctx, mctx):
286 def _checkunknown(repo, wctx, mctx):
287 "check for collisions between unknown files and files in mctx"
287 "check for collisions between unknown files and files in mctx"
288
288
289 error = False
289 error = False
290 for f in mctx:
290 for f in mctx:
291 if f not in wctx and _checkunknownfile(repo, wctx, mctx, f):
291 if f not in wctx and _checkunknownfile(repo, wctx, mctx, f):
292 error = True
292 error = True
293 wctx._repo.ui.warn(_("%s: untracked file differs\n") % f)
293 wctx._repo.ui.warn(_("%s: untracked file differs\n") % f)
294 if error:
294 if error:
295 raise util.Abort(_("untracked files in working directory differ "
295 raise util.Abort(_("untracked files in working directory differ "
296 "from files in requested revision"))
296 "from files in requested revision"))
297
297
298 def _forgetremoved(wctx, mctx, branchmerge):
298 def _forgetremoved(wctx, mctx, branchmerge):
299 """
299 """
300 Forget removed files
300 Forget removed files
301
301
302 If we're jumping between revisions (as opposed to merging), and if
302 If we're jumping between revisions (as opposed to merging), and if
303 neither the working directory nor the target rev has the file,
303 neither the working directory nor the target rev has the file,
304 then we need to remove it from the dirstate, to prevent the
304 then we need to remove it from the dirstate, to prevent the
305 dirstate from listing the file when it is no longer in the
305 dirstate from listing the file when it is no longer in the
306 manifest.
306 manifest.
307
307
308 If we're merging, and the other revision has removed a file
308 If we're merging, and the other revision has removed a file
309 that is not present in the working directory, we need to mark it
309 that is not present in the working directory, we need to mark it
310 as removed.
310 as removed.
311 """
311 """
312
312
313 actions = []
313 actions = []
314 state = branchmerge and 'r' or 'f'
314 state = branchmerge and 'r' or 'f'
315 for f in wctx.deleted():
315 for f in wctx.deleted():
316 if f not in mctx:
316 if f not in mctx:
317 actions.append((f, state, None, "forget deleted"))
317 actions.append((f, state, None, "forget deleted"))
318
318
319 if not branchmerge:
319 if not branchmerge:
320 for f in wctx.removed():
320 for f in wctx.removed():
321 if f not in mctx:
321 if f not in mctx:
322 actions.append((f, "f", None, "forget removed"))
322 actions.append((f, "f", None, "forget removed"))
323
323
324 return actions
324 return actions
325
325
326 def _checkcollision(repo, wmf, actions):
326 def _checkcollision(repo, wmf, actions):
327 # build provisional merged manifest up
327 # build provisional merged manifest up
328 pmmf = set(wmf)
328 pmmf = set(wmf)
329
329
330 def addop(f, args):
330 def addop(f, args):
331 pmmf.add(f)
331 pmmf.add(f)
332 def removeop(f, args):
332 def removeop(f, args):
333 pmmf.discard(f)
333 pmmf.discard(f)
334 def nop(f, args):
334 def nop(f, args):
335 pass
335 pass
336
336
337 def renamemoveop(f, args):
337 def renamemoveop(f, args):
338 f2, flags = args
338 f2, flags = args
339 pmmf.discard(f2)
339 pmmf.discard(f2)
340 pmmf.add(f)
340 pmmf.add(f)
341 def renamegetop(f, args):
341 def renamegetop(f, args):
342 f2, flags = args
342 f2, flags = args
343 pmmf.add(f)
343 pmmf.add(f)
344 def mergeop(f, args):
344 def mergeop(f, args):
345 f1, f2, fa, move, anc = args
345 f1, f2, fa, move, anc = args
346 if move:
346 if move:
347 pmmf.discard(f1)
347 pmmf.discard(f1)
348 pmmf.add(f)
348 pmmf.add(f)
349
349
350 opmap = {
350 opmap = {
351 "a": addop,
351 "a": addop,
352 "dm": renamemoveop,
352 "dm": renamemoveop,
353 "dg": renamegetop,
353 "dg": renamegetop,
354 "dr": nop,
354 "dr": nop,
355 "e": nop,
355 "e": nop,
356 "f": addop, # untracked file should be kept in working directory
356 "f": addop, # untracked file should be kept in working directory
357 "g": addop,
357 "g": addop,
358 "m": mergeop,
358 "m": mergeop,
359 "r": removeop,
359 "r": removeop,
360 "rd": nop,
360 "rd": nop,
361 "cd": addop,
361 "cd": addop,
362 "dc": addop,
362 "dc": addop,
363 }
363 }
364 for f, m, args, msg in actions:
364 for f, m, args, msg in actions:
365 op = opmap.get(m)
365 op = opmap.get(m)
366 assert op, m
366 assert op, m
367 op(f, args)
367 op(f, args)
368
368
369 # check case-folding collision in provisional merged manifest
369 # check case-folding collision in provisional merged manifest
370 foldmap = {}
370 foldmap = {}
371 for f in sorted(pmmf):
371 for f in sorted(pmmf):
372 fold = util.normcase(f)
372 fold = util.normcase(f)
373 if fold in foldmap:
373 if fold in foldmap:
374 raise util.Abort(_("case-folding collision between %s and %s")
374 raise util.Abort(_("case-folding collision between %s and %s")
375 % (f, foldmap[fold]))
375 % (f, foldmap[fold]))
376 foldmap[fold] = f
376 foldmap[fold] = f
377
377
378 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial,
378 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial,
379 acceptremote, followcopies):
379 acceptremote, followcopies):
380 """
380 """
381 Merge p1 and p2 with ancestor pa and generate merge action list
381 Merge p1 and p2 with ancestor pa and generate merge action list
382
382
383 branchmerge and force are as passed in to update
383 branchmerge and force are as passed in to update
384 partial = function to filter file lists
384 partial = function to filter file lists
385 acceptremote = accept the incoming changes without prompting
385 acceptremote = accept the incoming changes without prompting
386 """
386 """
387
387
388 actions, copy, movewithdir = [], {}, {}
388 actions, copy, movewithdir = [], {}, {}
389
389
390 # manifests fetched in order are going to be faster, so prime the caches
390 # manifests fetched in order are going to be faster, so prime the caches
391 [x.manifest() for x in
391 [x.manifest() for x in
392 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
392 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
393
393
394 if followcopies:
394 if followcopies:
395 ret = copies.mergecopies(repo, wctx, p2, pa)
395 ret = copies.mergecopies(repo, wctx, p2, pa)
396 copy, movewithdir, diverge, renamedelete = ret
396 copy, movewithdir, diverge, renamedelete = ret
397 for of, fl in diverge.iteritems():
397 for of, fl in diverge.iteritems():
398 actions.append((of, "dr", (fl,), "divergent renames"))
398 actions.append((of, "dr", (fl,), "divergent renames"))
399 for of, fl in renamedelete.iteritems():
399 for of, fl in renamedelete.iteritems():
400 actions.append((of, "rd", (fl,), "rename and delete"))
400 actions.append((of, "rd", (fl,), "rename and delete"))
401
401
402 repo.ui.note(_("resolving manifests\n"))
402 repo.ui.note(_("resolving manifests\n"))
403 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
403 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
404 % (bool(branchmerge), bool(force), bool(partial)))
404 % (bool(branchmerge), bool(force), bool(partial)))
405 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
405 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
406
406
407 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
407 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
408 copied = set(copy.values())
408 copied = set(copy.values())
409 copied.update(movewithdir.values())
409 copied.update(movewithdir.values())
410
410
411 if '.hgsubstate' in m1:
411 if '.hgsubstate' in m1:
412 # check whether sub state is modified
412 # check whether sub state is modified
413 for s in sorted(wctx.substate):
413 for s in sorted(wctx.substate):
414 if wctx.sub(s).dirty():
414 if wctx.sub(s).dirty():
415 m1['.hgsubstate'] += "+"
415 m1['.hgsubstate'] += "+"
416 break
416 break
417
417
418 aborts = []
418 aborts = []
419 # Compare manifests
419 # Compare manifests
420 fdiff = dicthelpers.diff(m1, m2)
420 fdiff = dicthelpers.diff(m1, m2)
421 flagsdiff = m1.flagsdiff(m2)
421 flagsdiff = m1.flagsdiff(m2)
422 diff12 = dicthelpers.join(fdiff, flagsdiff)
422 diff12 = dicthelpers.join(fdiff, flagsdiff)
423
423
424 for f, (n12, fl12) in diff12.iteritems():
424 for f, (n12, fl12) in diff12.iteritems():
425 if n12:
425 if n12:
426 n1, n2 = n12
426 n1, n2 = n12
427 else: # file contents didn't change, but flags did
427 else: # file contents didn't change, but flags did
428 n1 = n2 = m1.get(f, None)
428 n1 = n2 = m1.get(f, None)
429 if n1 is None:
429 if n1 is None:
430 # Since n1 == n2, the file isn't present in m2 either. This
430 # Since n1 == n2, the file isn't present in m2 either. This
431 # means that the file was removed or deleted locally and
431 # means that the file was removed or deleted locally and
432 # removed remotely, but that residual entries remain in flags.
432 # removed remotely, but that residual entries remain in flags.
433 # This can happen in manifests generated by workingctx.
433 # This can happen in manifests generated by workingctx.
434 continue
434 continue
435 if fl12:
435 if fl12:
436 fl1, fl2 = fl12
436 fl1, fl2 = fl12
437 else: # flags didn't change, file contents did
437 else: # flags didn't change, file contents did
438 fl1 = fl2 = m1.flags(f)
438 fl1 = fl2 = m1.flags(f)
439
439
440 if partial and not partial(f):
440 if partial and not partial(f):
441 continue
441 continue
442 if n1 and n2:
442 if n1 and n2:
443 fa = f
443 fa = f
444 a = ma.get(f, nullid)
444 a = ma.get(f, nullid)
445 if a == nullid:
445 if a == nullid:
446 fa = copy.get(f, f)
446 fa = copy.get(f, f)
447 # Note: f as default is wrong - we can't really make a 3-way
447 # Note: f as default is wrong - we can't really make a 3-way
448 # merge without an ancestor file.
448 # merge without an ancestor file.
449 fla = ma.flags(fa)
449 fla = ma.flags(fa)
450 nol = 'l' not in fl1 + fl2 + fla
450 nol = 'l' not in fl1 + fl2 + fla
451 if n2 == a and fl2 == fla:
451 if n2 == a and fl2 == fla:
452 actions.append((f, "k", (), "keep")) # remote unchanged
452 actions.append((f, "k", (), "keep")) # remote unchanged
453 elif n1 == a and fl1 == fla: # local unchanged - use remote
453 elif n1 == a and fl1 == fla: # local unchanged - use remote
454 if n1 == n2: # optimization: keep local content
454 if n1 == n2: # optimization: keep local content
455 actions.append((f, "e", (fl2,), "update permissions"))
455 actions.append((f, "e", (fl2,), "update permissions"))
456 else:
456 else:
457 actions.append((f, "g", (fl2,), "remote is newer"))
457 actions.append((f, "g", (fl2,), "remote is newer"))
458 elif nol and n2 == a: # remote only changed 'x'
458 elif nol and n2 == a: # remote only changed 'x'
459 actions.append((f, "e", (fl2,), "update permissions"))
459 actions.append((f, "e", (fl2,), "update permissions"))
460 elif nol and n1 == a: # local only changed 'x'
460 elif nol and n1 == a: # local only changed 'x'
461 actions.append((f, "g", (fl1,), "remote is newer"))
461 actions.append((f, "g", (fl1,), "remote is newer"))
462 else: # both changed something
462 else: # both changed something
463 actions.append((f, "m", (f, f, fa, False, pa.node()),
463 actions.append((f, "m", (f, f, fa, False, pa.node()),
464 "versions differ"))
464 "versions differ"))
465 elif f in copied: # files we'll deal with on m2 side
465 elif f in copied: # files we'll deal with on m2 side
466 pass
466 pass
467 elif n1 and f in movewithdir: # directory rename, move local
467 elif n1 and f in movewithdir: # directory rename, move local
468 f2 = movewithdir[f]
468 f2 = movewithdir[f]
469 actions.append((f2, "dm", (f, fl1),
469 actions.append((f2, "dm", (f, fl1),
470 "remote directory rename - move from " + f))
470 "remote directory rename - move from " + f))
471 elif n1 and f in copy:
471 elif n1 and f in copy:
472 f2 = copy[f]
472 f2 = copy[f]
473 actions.append((f, "m", (f, f2, f2, False, pa.node()),
473 actions.append((f, "m", (f, f2, f2, False, pa.node()),
474 "local copied/moved from " + f2))
474 "local copied/moved from " + f2))
475 elif n1 and f in ma: # clean, a different, no remote
475 elif n1 and f in ma: # clean, a different, no remote
476 if n1 != ma[f]:
476 if n1 != ma[f]:
477 if acceptremote:
477 if acceptremote:
478 actions.append((f, "r", None, "remote delete"))
478 actions.append((f, "r", None, "remote delete"))
479 else:
479 else:
480 actions.append((f, "cd", None, "prompt changed/deleted"))
480 actions.append((f, "cd", None, "prompt changed/deleted"))
481 elif n1[20:] == "a": # added, no remote
481 elif n1[20:] == "a": # added, no remote
482 actions.append((f, "f", None, "remote deleted"))
482 actions.append((f, "f", None, "remote deleted"))
483 else:
483 else:
484 actions.append((f, "r", None, "other deleted"))
484 actions.append((f, "r", None, "other deleted"))
485 elif n2 and f in movewithdir:
485 elif n2 and f in movewithdir:
486 f2 = movewithdir[f]
486 f2 = movewithdir[f]
487 actions.append((f2, "dg", (f, fl2),
487 actions.append((f2, "dg", (f, fl2),
488 "local directory rename - get from " + f))
488 "local directory rename - get from " + f))
489 elif n2 and f in copy:
489 elif n2 and f in copy:
490 f2 = copy[f]
490 f2 = copy[f]
491 if f2 in m2:
491 if f2 in m2:
492 actions.append((f, "m", (f2, f, f2, False, pa.node()),
492 actions.append((f, "m", (f2, f, f2, False, pa.node()),
493 "remote copied from " + f2))
493 "remote copied from " + f2))
494 else:
494 else:
495 actions.append((f, "m", (f2, f, f2, True, pa.node()),
495 actions.append((f, "m", (f2, f, f2, True, pa.node()),
496 "remote moved from " + f2))
496 "remote moved from " + f2))
497 elif n2 and f not in ma:
497 elif n2 and f not in ma:
498 # local unknown, remote created: the logic is described by the
498 # local unknown, remote created: the logic is described by the
499 # following table:
499 # following table:
500 #
500 #
501 # force branchmerge different | action
501 # force branchmerge different | action
502 # n * n | get
502 # n * n | get
503 # n * y | abort
503 # n * y | abort
504 # y n * | get
504 # y n * | get
505 # y y n | get
505 # y y n | get
506 # y y y | merge
506 # y y y | merge
507 #
507 #
508 # Checking whether the files are different is expensive, so we
508 # Checking whether the files are different is expensive, so we
509 # don't do that when we can avoid it.
509 # don't do that when we can avoid it.
510 if force and not branchmerge:
510 if force and not branchmerge:
511 actions.append((f, "g", (fl2,), "remote created"))
511 actions.append((f, "g", (fl2,), "remote created"))
512 else:
512 else:
513 different = _checkunknownfile(repo, wctx, p2, f)
513 different = _checkunknownfile(repo, wctx, p2, f)
514 if force and branchmerge and different:
514 if force and branchmerge and different:
515 # FIXME: This is wrong - f is not in ma ...
515 # FIXME: This is wrong - f is not in ma ...
516 actions.append((f, "m", (f, f, f, False, pa.node()),
516 actions.append((f, "m", (f, f, f, False, pa.node()),
517 "remote differs from untracked local"))
517 "remote differs from untracked local"))
518 elif not force and different:
518 elif not force and different:
519 aborts.append((f, "ud"))
519 aborts.append((f, "ud"))
520 else:
520 else:
521 actions.append((f, "g", (fl2,), "remote created"))
521 actions.append((f, "g", (fl2,), "remote created"))
522 elif n2 and n2 != ma[f]:
522 elif n2 and n2 != ma[f]:
523 different = _checkunknownfile(repo, wctx, p2, f)
523 different = _checkunknownfile(repo, wctx, p2, f)
524 if not force and different:
524 if not force and different:
525 aborts.append((f, "ud"))
525 aborts.append((f, "ud"))
526 else:
526 else:
527 # if different: old untracked f may be overwritten and lost
527 # if different: old untracked f may be overwritten and lost
528 if acceptremote:
528 if acceptremote:
529 actions.append((f, "g", (m2.flags(f),),
529 actions.append((f, "g", (m2.flags(f),),
530 "remote recreating"))
530 "remote recreating"))
531 else:
531 else:
532 actions.append((f, "dc", (m2.flags(f),),
532 actions.append((f, "dc", (m2.flags(f),),
533 "prompt deleted/changed"))
533 "prompt deleted/changed"))
534
534
535 for f, m in sorted(aborts):
535 for f, m in sorted(aborts):
536 if m == "ud":
536 if m == "ud":
537 repo.ui.warn(_("%s: untracked file differs\n") % f)
537 repo.ui.warn(_("%s: untracked file differs\n") % f)
538 else: assert False, m
538 else: assert False, m
539 if aborts:
539 if aborts:
540 raise util.Abort(_("untracked files in working directory differ "
540 raise util.Abort(_("untracked files in working directory differ "
541 "from files in requested revision"))
541 "from files in requested revision"))
542
542
543 if not util.checkcase(repo.path):
543 if not util.checkcase(repo.path):
544 # check collision between files only in p2 for clean update
544 # check collision between files only in p2 for clean update
545 if (not branchmerge and
545 if (not branchmerge and
546 (force or not wctx.dirty(missing=True, branch=False))):
546 (force or not wctx.dirty(missing=True, branch=False))):
547 _checkcollision(repo, m2, [])
547 _checkcollision(repo, m2, [])
548 else:
548 else:
549 _checkcollision(repo, m1, actions)
549 _checkcollision(repo, m1, actions)
550
550
551 return actions
551 return actions
552
552
553 def actionkey(a):
553 def actionkey(a):
554 return a[1] in "rf" and -1 or 0, a
554 return a[1] in "rf" and -1 or 0, a
555
555
556 def getremove(repo, mctx, overwrite, args):
556 def getremove(repo, mctx, overwrite, args):
557 """apply usually-non-interactive updates to the working directory
557 """apply usually-non-interactive updates to the working directory
558
558
559 mctx is the context to be merged into the working copy
559 mctx is the context to be merged into the working copy
560
560
561 yields tuples for progress updates
561 yields tuples for progress updates
562 """
562 """
563 verbose = repo.ui.verbose
563 verbose = repo.ui.verbose
564 unlink = util.unlinkpath
564 unlink = util.unlinkpath
565 wjoin = repo.wjoin
565 wjoin = repo.wjoin
566 fctx = mctx.filectx
566 fctx = mctx.filectx
567 wwrite = repo.wwrite
567 wwrite = repo.wwrite
568 audit = repo.wopener.audit
568 audit = repo.wopener.audit
569 i = 0
569 i = 0
570 for arg in args:
570 for arg in args:
571 f = arg[0]
571 f = arg[0]
572 if arg[1] == 'r':
572 if arg[1] == 'r':
573 if verbose:
573 if verbose:
574 repo.ui.note(_("removing %s\n") % f)
574 repo.ui.note(_("removing %s\n") % f)
575 audit(f)
575 audit(f)
576 try:
576 try:
577 unlink(wjoin(f), ignoremissing=True)
577 unlink(wjoin(f), ignoremissing=True)
578 except OSError, inst:
578 except OSError, inst:
579 repo.ui.warn(_("update failed to remove %s: %s!\n") %
579 repo.ui.warn(_("update failed to remove %s: %s!\n") %
580 (f, inst.strerror))
580 (f, inst.strerror))
581 else:
581 else:
582 if verbose:
582 if verbose:
583 repo.ui.note(_("getting %s\n") % f)
583 repo.ui.note(_("getting %s\n") % f)
584 wwrite(f, fctx(f).data(), arg[2][0])
584 wwrite(f, fctx(f).data(), arg[2][0])
585 if i == 100:
585 if i == 100:
586 yield i, f
586 yield i, f
587 i = 0
587 i = 0
588 i += 1
588 i += 1
589 if i > 0:
589 if i > 0:
590 yield i, f
590 yield i, f
591
591
592 def applyupdates(repo, actions, wctx, mctx, overwrite):
592 def applyupdates(repo, actions, wctx, mctx, overwrite):
593 """apply the merge action list to the working directory
593 """apply the merge action list to the working directory
594
594
595 wctx is the working copy context
595 wctx is the working copy context
596 mctx is the context to be merged into the working copy
596 mctx is the context to be merged into the working copy
597
597
598 Return a tuple of counts (updated, merged, removed, unresolved) that
598 Return a tuple of counts (updated, merged, removed, unresolved) that
599 describes how many files were affected by the update.
599 describes how many files were affected by the update.
600 """
600 """
601
601
602 updated, merged, removed, unresolved = 0, 0, 0, 0
602 updated, merged, removed, unresolved = 0, 0, 0, 0
603 ms = mergestate(repo)
603 ms = mergestate(repo)
604 ms.reset(wctx.p1().node(), mctx.node())
604 ms.reset(wctx.p1().node(), mctx.node())
605 moves = []
605 moves = []
606 actions.sort(key=actionkey)
606 actions.sort(key=actionkey)
607
607
608 # prescan for merges
608 # prescan for merges
609 for a in actions:
609 for a in actions:
610 f, m, args, msg = a
610 f, m, args, msg = a
611 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
611 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
612 if m == "m": # merge
612 if m == "m": # merge
613 f1, f2, fa, move, anc = args
613 f1, f2, fa, move, anc = args
614 if f == '.hgsubstate': # merged internally
614 if f == '.hgsubstate': # merged internally
615 continue
615 continue
616 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
616 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
617 fcl = wctx[f1]
617 fcl = wctx[f1]
618 fco = mctx[f2]
618 fco = mctx[f2]
619 actx = repo[anc]
619 actx = repo[anc]
620 if fa in actx:
620 if fa in actx:
621 fca = actx[fa]
621 fca = actx[fa]
622 else:
622 else:
623 fca = repo.filectx(f1, fileid=nullrev)
623 fca = repo.filectx(f1, fileid=nullrev)
624 ms.add(fcl, fco, fca, f)
624 ms.add(fcl, fco, fca, f)
625 if f1 != f and move:
625 if f1 != f and move:
626 moves.append(f1)
626 moves.append(f1)
627
627
628 audit = repo.wopener.audit
628 audit = repo.wopener.audit
629
629
630 # remove renamed files after safely stored
630 # remove renamed files after safely stored
631 for f in moves:
631 for f in moves:
632 if os.path.lexists(repo.wjoin(f)):
632 if os.path.lexists(repo.wjoin(f)):
633 repo.ui.debug("removing %s\n" % f)
633 repo.ui.debug("removing %s\n" % f)
634 audit(f)
634 audit(f)
635 util.unlinkpath(repo.wjoin(f))
635 util.unlinkpath(repo.wjoin(f))
636
636
637 numupdates = len([a for a in actions if a[1] != 'k'])
637 numupdates = len([a for a in actions if a[1] != 'k'])
638 workeractions = [a for a in actions if a[1] in 'gr']
638 workeractions = [a for a in actions if a[1] in 'gr']
639 updateactions = [a for a in workeractions if a[1] == 'g']
639 updateactions = [a for a in workeractions if a[1] == 'g']
640 updated = len(updateactions)
640 updated = len(updateactions)
641 removeactions = [a for a in workeractions if a[1] == 'r']
641 removeactions = [a for a in workeractions if a[1] == 'r']
642 removed = len(removeactions)
642 removed = len(removeactions)
643 actions = [a for a in actions if a[1] not in 'grk']
643 actions = [a for a in actions if a[1] not in 'grk']
644
644
645 hgsub = [a[1] for a in workeractions if a[0] == '.hgsubstate']
645 hgsub = [a[1] for a in workeractions if a[0] == '.hgsubstate']
646 if hgsub and hgsub[0] == 'r':
646 if hgsub and hgsub[0] == 'r':
647 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
647 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
648
648
649 z = 0
649 z = 0
650 prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite),
650 prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite),
651 removeactions)
651 removeactions)
652 for i, item in prog:
652 for i, item in prog:
653 z += i
653 z += i
654 repo.ui.progress(_('updating'), z, item=item, total=numupdates,
654 repo.ui.progress(_('updating'), z, item=item, total=numupdates,
655 unit=_('files'))
655 unit=_('files'))
656 prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite),
656 prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite),
657 updateactions)
657 updateactions)
658 for i, item in prog:
658 for i, item in prog:
659 z += i
659 z += i
660 repo.ui.progress(_('updating'), z, item=item, total=numupdates,
660 repo.ui.progress(_('updating'), z, item=item, total=numupdates,
661 unit=_('files'))
661 unit=_('files'))
662
662
663 if hgsub and hgsub[0] == 'g':
663 if hgsub and hgsub[0] == 'g':
664 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
664 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
665
665
666 _updating = _('updating')
666 _updating = _('updating')
667 _files = _('files')
667 _files = _('files')
668 progress = repo.ui.progress
668 progress = repo.ui.progress
669
669
670 for i, a in enumerate(actions):
670 for i, a in enumerate(actions):
671 f, m, args, msg = a
671 f, m, args, msg = a
672 progress(_updating, z + i + 1, item=f, total=numupdates, unit=_files)
672 progress(_updating, z + i + 1, item=f, total=numupdates, unit=_files)
673 if m == "m": # merge
673 if m == "m": # merge
674 f1, f2, fa, move, anc = args
674 f1, f2, fa, move, anc = args
675 if f == '.hgsubstate': # subrepo states need updating
675 if f == '.hgsubstate': # subrepo states need updating
676 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
676 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
677 overwrite)
677 overwrite)
678 continue
678 continue
679 audit(f)
679 audit(f)
680 r = ms.resolve(f, wctx)
680 r = ms.resolve(f, wctx)
681 if r is not None and r > 0:
681 if r is not None and r > 0:
682 unresolved += 1
682 unresolved += 1
683 else:
683 else:
684 if r is None:
684 if r is None:
685 updated += 1
685 updated += 1
686 else:
686 else:
687 merged += 1
687 merged += 1
688 elif m == "dm": # directory rename, move local
688 elif m == "dm": # directory rename, move local
689 f0, flags = args
689 f0, flags = args
690 repo.ui.note(_("moving %s to %s\n") % (f0, f))
690 repo.ui.note(_("moving %s to %s\n") % (f0, f))
691 audit(f)
691 audit(f)
692 repo.wwrite(f, wctx.filectx(f0).data(), flags)
692 repo.wwrite(f, wctx.filectx(f0).data(), flags)
693 util.unlinkpath(repo.wjoin(f0))
693 util.unlinkpath(repo.wjoin(f0))
694 updated += 1
694 updated += 1
695 elif m == "dg": # local directory rename, get
695 elif m == "dg": # local directory rename, get
696 f0, flags = args
696 f0, flags = args
697 repo.ui.note(_("getting %s to %s\n") % (f0, f))
697 repo.ui.note(_("getting %s to %s\n") % (f0, f))
698 repo.wwrite(f, mctx.filectx(f0).data(), flags)
698 repo.wwrite(f, mctx.filectx(f0).data(), flags)
699 updated += 1
699 updated += 1
700 elif m == "dr": # divergent renames
700 elif m == "dr": # divergent renames
701 fl, = args
701 fl, = args
702 repo.ui.warn(_("note: possible conflict - %s was renamed "
702 repo.ui.warn(_("note: possible conflict - %s was renamed "
703 "multiple times to:\n") % f)
703 "multiple times to:\n") % f)
704 for nf in fl:
704 for nf in fl:
705 repo.ui.warn(" %s\n" % nf)
705 repo.ui.warn(" %s\n" % nf)
706 elif m == "rd": # rename and delete
706 elif m == "rd": # rename and delete
707 fl, = args
707 fl, = args
708 repo.ui.warn(_("note: possible conflict - %s was deleted "
708 repo.ui.warn(_("note: possible conflict - %s was deleted "
709 "and renamed to:\n") % f)
709 "and renamed to:\n") % f)
710 for nf in fl:
710 for nf in fl:
711 repo.ui.warn(" %s\n" % nf)
711 repo.ui.warn(" %s\n" % nf)
712 elif m == "e": # exec
712 elif m == "e": # exec
713 flags, = args
713 flags, = args
714 audit(f)
714 audit(f)
715 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
715 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
716 updated += 1
716 updated += 1
717 ms.commit()
717 ms.commit()
718 progress(_updating, None, total=numupdates, unit=_files)
718 progress(_updating, None, total=numupdates, unit=_files)
719
719
720 return updated, merged, removed, unresolved
720 return updated, merged, removed, unresolved
721
721
722 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force, partial,
722 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force, partial,
723 acceptremote, followcopies):
723 acceptremote, followcopies):
724 "Calculate the actions needed to merge mctx into wctx using ancestors"
724 "Calculate the actions needed to merge mctx into wctx using ancestors"
725
725
726 ancestor = ancestors[0]
726 if len(ancestors) == 1: # default
727 actions = manifestmerge(repo, wctx, mctx, ancestors[0],
728 branchmerge, force,
729 partial, acceptremote, followcopies)
730
731 else: # only when merge.preferancestor=* - experimentalish code
732 # Call for bids
733 fbids = {} # mapping filename to list af action bids
734 for ancestor in ancestors:
735 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
736 actions = manifestmerge(repo, wctx, mctx, ancestor,
737 branchmerge, force,
738 partial, acceptremote, followcopies)
739 for a in sorted(actions):
740 repo.ui.debug(' %s: %s\n' % (a[0], a[1]))
741 f = a[0]
742 if f in fbids:
743 fbids[f].append(a)
744 else:
745 fbids[f] = [a]
727
746
728 actions = manifestmerge(repo, wctx, mctx,
747 # Pick the best bid for each file
729 ancestor,
748 repo.ui.note(_('\nauction for merging merge bids\n'))
730 branchmerge, force,
749 actions = []
731 partial, acceptremote, followcopies)
750 for f, bidsl in sorted(fbids.items()):
751 # Consensus?
752 a0 = bidsl[0]
753 if util.all(a == a0 for a in bidsl[1:]): # len(bidsl) is > 1
754 repo.ui.note(" %s: consensus for %s\n" % (f, a0[1]))
755 actions.append(a0)
756 continue
757 # Group bids by kind of action
758 bids = {}
759 for a in bidsl:
760 m = a[1]
761 if m in bids:
762 bids[m].append(a)
763 else:
764 bids[m] = [a]
765 # If keep is an option, just do it.
766 if "k" in bids:
767 repo.ui.note(" %s: picking 'keep' action\n" % f)
768 actions.append(bids["k"][0])
769 continue
770 # If all gets agree [how could they not?], just do it.
771 if "g" in bids:
772 ga0 = bids["g"][0]
773 if util.all(a == ga0 for a in bids["g"][1:]):
774 repo.ui.note(" %s: picking 'get' action\n" % f)
775 actions.append(ga0)
776 continue
777 # TODO: Consider other simple actions such as mode changes
778 # Handle inefficient democrazy.
779 repo.ui.note(_(' %s: multiple merge bids:\n') % (f, m))
780 for a in bidsl:
781 repo.ui.note(' %s: %s\n' % (f, a[1]))
782 # Pick random action. TODO: Instead, prompt user when resolving
783 a0 = bidsl[0]
784 repo.ui.warn(_(' %s: ambiguous merge - picked %s action)\n') %
785 (f, a0[1]))
786 actions.append(a0)
787 continue
788 repo.ui.note(_('end of auction\n\n'))
732
789
733 # Filter out prompts.
790 # Filter out prompts.
734 newactions, prompts = [], []
791 newactions, prompts = [], []
735 for a in actions:
792 for a in actions:
736 if a[1] in ("cd", "dc"):
793 if a[1] in ("cd", "dc"):
737 prompts.append(a)
794 prompts.append(a)
738 else:
795 else:
739 newactions.append(a)
796 newactions.append(a)
740 # Prompt and create actions. TODO: Move this towards resolve phase.
797 # Prompt and create actions. TODO: Move this towards resolve phase.
741 for f, m, args, msg in sorted(prompts):
798 for f, m, args, msg in sorted(prompts):
742 if m == "cd":
799 if m == "cd":
743 if repo.ui.promptchoice(
800 if repo.ui.promptchoice(
744 _("local changed %s which remote deleted\n"
801 _("local changed %s which remote deleted\n"
745 "use (c)hanged version or (d)elete?"
802 "use (c)hanged version or (d)elete?"
746 "$$ &Changed $$ &Delete") % f, 0):
803 "$$ &Changed $$ &Delete") % f, 0):
747 newactions.append((f, "r", None, "prompt delete"))
804 newactions.append((f, "r", None, "prompt delete"))
748 else:
805 else:
749 newactions.append((f, "a", None, "prompt keep"))
806 newactions.append((f, "a", None, "prompt keep"))
750 elif m == "dc":
807 elif m == "dc":
751 flags, = args
808 flags, = args
752 if repo.ui.promptchoice(
809 if repo.ui.promptchoice(
753 _("remote changed %s which local deleted\n"
810 _("remote changed %s which local deleted\n"
754 "use (c)hanged version or leave (d)eleted?"
811 "use (c)hanged version or leave (d)eleted?"
755 "$$ &Changed $$ &Deleted") % f, 0) == 0:
812 "$$ &Changed $$ &Deleted") % f, 0) == 0:
756 newactions.append((f, "g", (flags,), "prompt recreating"))
813 newactions.append((f, "g", (flags,), "prompt recreating"))
757 else: assert False, m
814 else: assert False, m
758
815
759 if wctx.rev() is None:
816 if wctx.rev() is None:
760 newactions += _forgetremoved(wctx, mctx, branchmerge)
817 newactions += _forgetremoved(wctx, mctx, branchmerge)
761
818
762 return newactions
819 return newactions
763
820
764 def recordupdates(repo, actions, branchmerge):
821 def recordupdates(repo, actions, branchmerge):
765 "record merge actions to the dirstate"
822 "record merge actions to the dirstate"
766
823
767 for a in actions:
824 for a in actions:
768 f, m, args, msg = a
825 f, m, args, msg = a
769 if m == "r": # remove
826 if m == "r": # remove
770 if branchmerge:
827 if branchmerge:
771 repo.dirstate.remove(f)
828 repo.dirstate.remove(f)
772 else:
829 else:
773 repo.dirstate.drop(f)
830 repo.dirstate.drop(f)
774 elif m == "a": # re-add
831 elif m == "a": # re-add
775 if not branchmerge:
832 if not branchmerge:
776 repo.dirstate.add(f)
833 repo.dirstate.add(f)
777 elif m == "f": # forget
834 elif m == "f": # forget
778 repo.dirstate.drop(f)
835 repo.dirstate.drop(f)
779 elif m == "e": # exec change
836 elif m == "e": # exec change
780 repo.dirstate.normallookup(f)
837 repo.dirstate.normallookup(f)
781 elif m == "k": # keep
838 elif m == "k": # keep
782 pass
839 pass
783 elif m == "g": # get
840 elif m == "g": # get
784 if branchmerge:
841 if branchmerge:
785 repo.dirstate.otherparent(f)
842 repo.dirstate.otherparent(f)
786 else:
843 else:
787 repo.dirstate.normal(f)
844 repo.dirstate.normal(f)
788 elif m == "m": # merge
845 elif m == "m": # merge
789 f1, f2, fa, move, anc = args
846 f1, f2, fa, move, anc = args
790 if branchmerge:
847 if branchmerge:
791 # We've done a branch merge, mark this file as merged
848 # We've done a branch merge, mark this file as merged
792 # so that we properly record the merger later
849 # so that we properly record the merger later
793 repo.dirstate.merge(f)
850 repo.dirstate.merge(f)
794 if f1 != f2: # copy/rename
851 if f1 != f2: # copy/rename
795 if move:
852 if move:
796 repo.dirstate.remove(f1)
853 repo.dirstate.remove(f1)
797 if f1 != f:
854 if f1 != f:
798 repo.dirstate.copy(f1, f)
855 repo.dirstate.copy(f1, f)
799 else:
856 else:
800 repo.dirstate.copy(f2, f)
857 repo.dirstate.copy(f2, f)
801 else:
858 else:
802 # We've update-merged a locally modified file, so
859 # We've update-merged a locally modified file, so
803 # we set the dirstate to emulate a normal checkout
860 # we set the dirstate to emulate a normal checkout
804 # of that file some time in the past. Thus our
861 # of that file some time in the past. Thus our
805 # merge will appear as a normal local file
862 # merge will appear as a normal local file
806 # modification.
863 # modification.
807 if f2 == f: # file not locally copied/moved
864 if f2 == f: # file not locally copied/moved
808 repo.dirstate.normallookup(f)
865 repo.dirstate.normallookup(f)
809 if move:
866 if move:
810 repo.dirstate.drop(f1)
867 repo.dirstate.drop(f1)
811 elif m == "dm": # directory rename, move local
868 elif m == "dm": # directory rename, move local
812 f0, flag = args
869 f0, flag = args
813 if f0 not in repo.dirstate:
870 if f0 not in repo.dirstate:
814 # untracked file moved
871 # untracked file moved
815 continue
872 continue
816 if branchmerge:
873 if branchmerge:
817 repo.dirstate.add(f)
874 repo.dirstate.add(f)
818 repo.dirstate.remove(f0)
875 repo.dirstate.remove(f0)
819 repo.dirstate.copy(f0, f)
876 repo.dirstate.copy(f0, f)
820 else:
877 else:
821 repo.dirstate.normal(f)
878 repo.dirstate.normal(f)
822 repo.dirstate.drop(f0)
879 repo.dirstate.drop(f0)
823 elif m == "dg": # directory rename, get
880 elif m == "dg": # directory rename, get
824 f0, flag = args
881 f0, flag = args
825 if branchmerge:
882 if branchmerge:
826 repo.dirstate.add(f)
883 repo.dirstate.add(f)
827 repo.dirstate.copy(f0, f)
884 repo.dirstate.copy(f0, f)
828 else:
885 else:
829 repo.dirstate.normal(f)
886 repo.dirstate.normal(f)
830
887
831 def update(repo, node, branchmerge, force, partial, ancestor=None,
888 def update(repo, node, branchmerge, force, partial, ancestor=None,
832 mergeancestor=False):
889 mergeancestor=False):
833 """
890 """
834 Perform a merge between the working directory and the given node
891 Perform a merge between the working directory and the given node
835
892
836 node = the node to update to, or None if unspecified
893 node = the node to update to, or None if unspecified
837 branchmerge = whether to merge between branches
894 branchmerge = whether to merge between branches
838 force = whether to force branch merging or file overwriting
895 force = whether to force branch merging or file overwriting
839 partial = a function to filter file lists (dirstate not updated)
896 partial = a function to filter file lists (dirstate not updated)
840 mergeancestor = whether it is merging with an ancestor. If true,
897 mergeancestor = whether it is merging with an ancestor. If true,
841 we should accept the incoming changes for any prompts that occur.
898 we should accept the incoming changes for any prompts that occur.
842 If false, merging with an ancestor (fast-forward) is only allowed
899 If false, merging with an ancestor (fast-forward) is only allowed
843 between different named branches. This flag is used by rebase extension
900 between different named branches. This flag is used by rebase extension
844 as a temporary fix and should be avoided in general.
901 as a temporary fix and should be avoided in general.
845
902
846 The table below shows all the behaviors of the update command
903 The table below shows all the behaviors of the update command
847 given the -c and -C or no options, whether the working directory
904 given the -c and -C or no options, whether the working directory
848 is dirty, whether a revision is specified, and the relationship of
905 is dirty, whether a revision is specified, and the relationship of
849 the parent rev to the target rev (linear, on the same named
906 the parent rev to the target rev (linear, on the same named
850 branch, or on another named branch).
907 branch, or on another named branch).
851
908
852 This logic is tested by test-update-branches.t.
909 This logic is tested by test-update-branches.t.
853
910
854 -c -C dirty rev | linear same cross
911 -c -C dirty rev | linear same cross
855 n n n n | ok (1) x
912 n n n n | ok (1) x
856 n n n y | ok ok ok
913 n n n y | ok ok ok
857 n n y n | merge (2) (2)
914 n n y n | merge (2) (2)
858 n n y y | merge (3) (3)
915 n n y y | merge (3) (3)
859 n y * * | --- discard ---
916 n y * * | --- discard ---
860 y n y * | --- (4) ---
917 y n y * | --- (4) ---
861 y n n * | --- ok ---
918 y n n * | --- ok ---
862 y y * * | --- (5) ---
919 y y * * | --- (5) ---
863
920
864 x = can't happen
921 x = can't happen
865 * = don't-care
922 * = don't-care
866 1 = abort: not a linear update (merge or update --check to force update)
923 1 = abort: not a linear update (merge or update --check to force update)
867 2 = abort: uncommitted changes (commit and merge, or update --clean to
924 2 = abort: uncommitted changes (commit and merge, or update --clean to
868 discard changes)
925 discard changes)
869 3 = abort: uncommitted changes (commit or update --clean to discard changes)
926 3 = abort: uncommitted changes (commit or update --clean to discard changes)
870 4 = abort: uncommitted changes (checked in commands.py)
927 4 = abort: uncommitted changes (checked in commands.py)
871 5 = incompatible options (checked in commands.py)
928 5 = incompatible options (checked in commands.py)
872
929
873 Return the same tuple as applyupdates().
930 Return the same tuple as applyupdates().
874 """
931 """
875
932
876 onode = node
933 onode = node
877 wlock = repo.wlock()
934 wlock = repo.wlock()
878 try:
935 try:
879 wc = repo[None]
936 wc = repo[None]
880 pl = wc.parents()
937 pl = wc.parents()
881 p1 = pl[0]
938 p1 = pl[0]
882 pas = [None]
939 pas = [None]
883 if ancestor:
940 if ancestor:
884 pas = [repo[ancestor]]
941 pas = [repo[ancestor]]
885
942
886 if node is None:
943 if node is None:
887 # Here is where we should consider bookmarks, divergent bookmarks,
944 # Here is where we should consider bookmarks, divergent bookmarks,
888 # foreground changesets (successors), and tip of current branch;
945 # foreground changesets (successors), and tip of current branch;
889 # but currently we are only checking the branch tips.
946 # but currently we are only checking the branch tips.
890 try:
947 try:
891 node = repo.branchtip(wc.branch())
948 node = repo.branchtip(wc.branch())
892 except error.RepoLookupError:
949 except error.RepoLookupError:
893 if wc.branch() == "default": # no default branch!
950 if wc.branch() == "default": # no default branch!
894 node = repo.lookup("tip") # update to tip
951 node = repo.lookup("tip") # update to tip
895 else:
952 else:
896 raise util.Abort(_("branch %s not found") % wc.branch())
953 raise util.Abort(_("branch %s not found") % wc.branch())
897
954
898 if p1.obsolete() and not p1.children():
955 if p1.obsolete() and not p1.children():
899 # allow updating to successors
956 # allow updating to successors
900 successors = obsolete.successorssets(repo, p1.node())
957 successors = obsolete.successorssets(repo, p1.node())
901
958
902 # behavior of certain cases is as follows,
959 # behavior of certain cases is as follows,
903 #
960 #
904 # divergent changesets: update to highest rev, similar to what
961 # divergent changesets: update to highest rev, similar to what
905 # is currently done when there are more than one head
962 # is currently done when there are more than one head
906 # (i.e. 'tip')
963 # (i.e. 'tip')
907 #
964 #
908 # replaced changesets: same as divergent except we know there
965 # replaced changesets: same as divergent except we know there
909 # is no conflict
966 # is no conflict
910 #
967 #
911 # pruned changeset: no update is done; though, we could
968 # pruned changeset: no update is done; though, we could
912 # consider updating to the first non-obsolete parent,
969 # consider updating to the first non-obsolete parent,
913 # similar to what is current done for 'hg prune'
970 # similar to what is current done for 'hg prune'
914
971
915 if successors:
972 if successors:
916 # flatten the list here handles both divergent (len > 1)
973 # flatten the list here handles both divergent (len > 1)
917 # and the usual case (len = 1)
974 # and the usual case (len = 1)
918 successors = [n for sub in successors for n in sub]
975 successors = [n for sub in successors for n in sub]
919
976
920 # get the max revision for the given successors set,
977 # get the max revision for the given successors set,
921 # i.e. the 'tip' of a set
978 # i.e. the 'tip' of a set
922 node = repo.revs("max(%ln)", successors)[0]
979 node = repo.revs("max(%ln)", successors)[0]
923 pas = [p1]
980 pas = [p1]
924
981
925 overwrite = force and not branchmerge
982 overwrite = force and not branchmerge
926
983
927 p2 = repo[node]
984 p2 = repo[node]
928 if pas[0] is None:
985 if pas[0] is None:
929 pas = [p1.ancestor(p2)]
986 if repo.ui.config("merge", "preferancestor") == '*':
987 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
988 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
989 else:
990 pas = [p1.ancestor(p2)]
930
991
931 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
992 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
932
993
933 ### check phase
994 ### check phase
934 if not overwrite and len(pl) > 1:
995 if not overwrite and len(pl) > 1:
935 raise util.Abort(_("outstanding uncommitted merges"))
996 raise util.Abort(_("outstanding uncommitted merges"))
936 if branchmerge:
997 if branchmerge:
937 if pas == [p2]:
998 if pas == [p2]:
938 raise util.Abort(_("merging with a working directory ancestor"
999 raise util.Abort(_("merging with a working directory ancestor"
939 " has no effect"))
1000 " has no effect"))
940 elif pas == [p1]:
1001 elif pas == [p1]:
941 if not mergeancestor and p1.branch() == p2.branch():
1002 if not mergeancestor and p1.branch() == p2.branch():
942 raise util.Abort(_("nothing to merge"),
1003 raise util.Abort(_("nothing to merge"),
943 hint=_("use 'hg update' "
1004 hint=_("use 'hg update' "
944 "or check 'hg heads'"))
1005 "or check 'hg heads'"))
945 if not force and (wc.files() or wc.deleted()):
1006 if not force and (wc.files() or wc.deleted()):
946 raise util.Abort(_("uncommitted changes"),
1007 raise util.Abort(_("uncommitted changes"),
947 hint=_("use 'hg status' to list changes"))
1008 hint=_("use 'hg status' to list changes"))
948 for s in sorted(wc.substate):
1009 for s in sorted(wc.substate):
949 if wc.sub(s).dirty():
1010 if wc.sub(s).dirty():
950 raise util.Abort(_("uncommitted changes in "
1011 raise util.Abort(_("uncommitted changes in "
951 "subrepository '%s'") % s)
1012 "subrepository '%s'") % s)
952
1013
953 elif not overwrite:
1014 elif not overwrite:
954 if p1 == p2: # no-op update
1015 if p1 == p2: # no-op update
955 # call the hooks and exit early
1016 # call the hooks and exit early
956 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
1017 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
957 repo.hook('update', parent1=xp2, parent2='', error=0)
1018 repo.hook('update', parent1=xp2, parent2='', error=0)
958 return 0, 0, 0, 0
1019 return 0, 0, 0, 0
959
1020
960 if pas not in ([p1], [p2]): # nonlinear
1021 if pas not in ([p1], [p2]): # nonlinear
961 dirty = wc.dirty(missing=True)
1022 dirty = wc.dirty(missing=True)
962 if dirty or onode is None:
1023 if dirty or onode is None:
963 # Branching is a bit strange to ensure we do the minimal
1024 # Branching is a bit strange to ensure we do the minimal
964 # amount of call to obsolete.background.
1025 # amount of call to obsolete.background.
965 foreground = obsolete.foreground(repo, [p1.node()])
1026 foreground = obsolete.foreground(repo, [p1.node()])
966 # note: the <node> variable contains a random identifier
1027 # note: the <node> variable contains a random identifier
967 if repo[node].node() in foreground:
1028 if repo[node].node() in foreground:
968 pas = [p1] # allow updating to successors
1029 pas = [p1] # allow updating to successors
969 elif dirty:
1030 elif dirty:
970 msg = _("uncommitted changes")
1031 msg = _("uncommitted changes")
971 if onode is None:
1032 if onode is None:
972 hint = _("commit and merge, or update --clean to"
1033 hint = _("commit and merge, or update --clean to"
973 " discard changes")
1034 " discard changes")
974 else:
1035 else:
975 hint = _("commit or update --clean to discard"
1036 hint = _("commit or update --clean to discard"
976 " changes")
1037 " changes")
977 raise util.Abort(msg, hint=hint)
1038 raise util.Abort(msg, hint=hint)
978 else: # node is none
1039 else: # node is none
979 msg = _("not a linear update")
1040 msg = _("not a linear update")
980 hint = _("merge or update --check to force update")
1041 hint = _("merge or update --check to force update")
981 raise util.Abort(msg, hint=hint)
1042 raise util.Abort(msg, hint=hint)
982 else:
1043 else:
983 # Allow jumping branches if clean and specific rev given
1044 # Allow jumping branches if clean and specific rev given
984 pas = [p1]
1045 pas = [p1]
985
1046
986 followcopies = False
1047 followcopies = False
987 if overwrite:
1048 if overwrite:
988 pas = [wc]
1049 pas = [wc]
989 elif pas == [p2]: # backwards
1050 elif pas == [p2]: # backwards
990 pas = [wc.p1()]
1051 pas = [wc.p1()]
991 elif not branchmerge and not wc.dirty(missing=True):
1052 elif not branchmerge and not wc.dirty(missing=True):
992 pass
1053 pass
993 elif pas[0] and repo.ui.configbool("merge", "followcopies", True):
1054 elif pas[0] and repo.ui.configbool("merge", "followcopies", True):
994 followcopies = True
1055 followcopies = True
995
1056
996 ### calculate phase
1057 ### calculate phase
997 actions = calculateupdates(repo, wc, p2, pas, branchmerge, force,
1058 actions = calculateupdates(repo, wc, p2, pas, branchmerge, force,
998 partial, mergeancestor, followcopies)
1059 partial, mergeancestor, followcopies)
999
1060
1000 ### apply phase
1061 ### apply phase
1001 if not branchmerge: # just jump to the new rev
1062 if not branchmerge: # just jump to the new rev
1002 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
1063 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
1003 if not partial:
1064 if not partial:
1004 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
1065 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
1005 # note that we're in the middle of an update
1066 # note that we're in the middle of an update
1006 repo.vfs.write('updatestate', p2.hex())
1067 repo.vfs.write('updatestate', p2.hex())
1007
1068
1008 stats = applyupdates(repo, actions, wc, p2, overwrite)
1069 stats = applyupdates(repo, actions, wc, p2, overwrite)
1009
1070
1010 if not partial:
1071 if not partial:
1011 repo.setparents(fp1, fp2)
1072 repo.setparents(fp1, fp2)
1012 recordupdates(repo, actions, branchmerge)
1073 recordupdates(repo, actions, branchmerge)
1013 # update completed, clear state
1074 # update completed, clear state
1014 util.unlink(repo.join('updatestate'))
1075 util.unlink(repo.join('updatestate'))
1015
1076
1016 if not branchmerge:
1077 if not branchmerge:
1017 repo.dirstate.setbranch(p2.branch())
1078 repo.dirstate.setbranch(p2.branch())
1018 finally:
1079 finally:
1019 wlock.release()
1080 wlock.release()
1020
1081
1021 if not partial:
1082 if not partial:
1022 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
1083 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
1023 return stats
1084 return stats
@@ -1,126 +1,267 b''
1 Criss cross merging
1 Criss cross merging
2
2
3 $ hg init criss-cross
3 $ hg init criss-cross
4 $ cd criss-cross
4 $ cd criss-cross
5 $ echo '0 base' > f1
5 $ echo '0 base' > f1
6 $ echo '0 base' > f2
6 $ echo '0 base' > f2
7 $ hg ci -Aqm '0 base'
7 $ hg ci -Aqm '0 base'
8
8
9 $ echo '1 first change' > f1
9 $ echo '1 first change' > f1
10 $ hg ci -m '1 first change f1'
10 $ hg ci -m '1 first change f1'
11
11
12 $ hg up -qr0
12 $ hg up -qr0
13 $ echo '2 first change' > f2
13 $ echo '2 first change' > f2
14 $ hg ci -qm '2 first change f2'
14 $ hg ci -qm '2 first change f2'
15
15
16 $ hg merge -qr 1
16 $ hg merge -qr 1
17 $ hg ci -m '3 merge'
17 $ hg ci -m '3 merge'
18
18
19 $ hg up -qr2
19 $ hg up -qr2
20 $ hg merge -qr1
20 $ hg merge -qr1
21 $ hg ci -qm '4 merge'
21 $ hg ci -qm '4 merge'
22
22
23 $ echo '5 second change' > f1
23 $ echo '5 second change' > f1
24 $ hg ci -m '5 second change f1'
24 $ hg ci -m '5 second change f1'
25
25
26 $ hg up -r3
26 $ hg up -r3
27 note: using 0f6b37dbe527 as ancestor of adfe50279922 and cf89f02107e5
27 note: using 0f6b37dbe527 as ancestor of adfe50279922 and cf89f02107e5
28 alternatively, use --config merge.preferancestor=40663881a6dd
28 alternatively, use --config merge.preferancestor=40663881a6dd
29 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
29 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
30 $ echo '6 second change' > f2
30 $ echo '6 second change' > f2
31 $ hg ci -m '6 second change f2'
31 $ hg ci -m '6 second change f2'
32
32
33 $ hg log -G
33 $ hg log -G
34 @ changeset: 6:3b08d01b0ab5
34 @ changeset: 6:3b08d01b0ab5
35 | tag: tip
35 | tag: tip
36 | parent: 3:cf89f02107e5
36 | parent: 3:cf89f02107e5
37 | user: test
37 | user: test
38 | date: Thu Jan 01 00:00:00 1970 +0000
38 | date: Thu Jan 01 00:00:00 1970 +0000
39 | summary: 6 second change f2
39 | summary: 6 second change f2
40 |
40 |
41 | o changeset: 5:adfe50279922
41 | o changeset: 5:adfe50279922
42 | | user: test
42 | | user: test
43 | | date: Thu Jan 01 00:00:00 1970 +0000
43 | | date: Thu Jan 01 00:00:00 1970 +0000
44 | | summary: 5 second change f1
44 | | summary: 5 second change f1
45 | |
45 | |
46 | o changeset: 4:7d3e55501ae6
46 | o changeset: 4:7d3e55501ae6
47 | |\ parent: 2:40663881a6dd
47 | |\ parent: 2:40663881a6dd
48 | | | parent: 1:0f6b37dbe527
48 | | | parent: 1:0f6b37dbe527
49 | | | user: test
49 | | | user: test
50 | | | date: Thu Jan 01 00:00:00 1970 +0000
50 | | | date: Thu Jan 01 00:00:00 1970 +0000
51 | | | summary: 4 merge
51 | | | summary: 4 merge
52 | | |
52 | | |
53 o---+ changeset: 3:cf89f02107e5
53 o---+ changeset: 3:cf89f02107e5
54 | | | parent: 2:40663881a6dd
54 | | | parent: 2:40663881a6dd
55 |/ / parent: 1:0f6b37dbe527
55 |/ / parent: 1:0f6b37dbe527
56 | | user: test
56 | | user: test
57 | | date: Thu Jan 01 00:00:00 1970 +0000
57 | | date: Thu Jan 01 00:00:00 1970 +0000
58 | | summary: 3 merge
58 | | summary: 3 merge
59 | |
59 | |
60 | o changeset: 2:40663881a6dd
60 | o changeset: 2:40663881a6dd
61 | | parent: 0:40494bf2444c
61 | | parent: 0:40494bf2444c
62 | | user: test
62 | | user: test
63 | | date: Thu Jan 01 00:00:00 1970 +0000
63 | | date: Thu Jan 01 00:00:00 1970 +0000
64 | | summary: 2 first change f2
64 | | summary: 2 first change f2
65 | |
65 | |
66 o | changeset: 1:0f6b37dbe527
66 o | changeset: 1:0f6b37dbe527
67 |/ user: test
67 |/ user: test
68 | date: Thu Jan 01 00:00:00 1970 +0000
68 | date: Thu Jan 01 00:00:00 1970 +0000
69 | summary: 1 first change f1
69 | summary: 1 first change f1
70 |
70 |
71 o changeset: 0:40494bf2444c
71 o changeset: 0:40494bf2444c
72 user: test
72 user: test
73 date: Thu Jan 01 00:00:00 1970 +0000
73 date: Thu Jan 01 00:00:00 1970 +0000
74 summary: 0 base
74 summary: 0 base
75
75
76
76
77 $ hg merge -v --debug --tool internal:dump 5
77 $ hg merge -v --debug --tool internal:dump 5
78 note: using 0f6b37dbe527 as ancestor of 3b08d01b0ab5 and adfe50279922
78 note: using 0f6b37dbe527 as ancestor of 3b08d01b0ab5 and adfe50279922
79 alternatively, use --config merge.preferancestor=40663881a6dd
79 alternatively, use --config merge.preferancestor=40663881a6dd
80 searching for copies back to rev 3
80 searching for copies back to rev 3
81 resolving manifests
81 resolving manifests
82 branchmerge: True, force: False, partial: False
82 branchmerge: True, force: False, partial: False
83 ancestor: 0f6b37dbe527, local: 3b08d01b0ab5+, remote: adfe50279922
83 ancestor: 0f6b37dbe527, local: 3b08d01b0ab5+, remote: adfe50279922
84 f1: remote is newer -> g
84 f1: remote is newer -> g
85 f2: versions differ -> m
85 f2: versions differ -> m
86 preserving f2 for resolve of f2
86 preserving f2 for resolve of f2
87 getting f1
87 getting f1
88 updating: f1 1/2 files (50.00%)
88 updating: f1 1/2 files (50.00%)
89 updating: f2 2/2 files (100.00%)
89 updating: f2 2/2 files (100.00%)
90 picked tool 'internal:dump' for f2 (binary False symlink False)
90 picked tool 'internal:dump' for f2 (binary False symlink False)
91 merging f2
91 merging f2
92 my f2@3b08d01b0ab5+ other f2@adfe50279922 ancestor f2@40494bf2444c
92 my f2@3b08d01b0ab5+ other f2@adfe50279922 ancestor f2@40494bf2444c
93 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
93 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
94 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
94 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
95 [1]
95 [1]
96
96
97 $ head *
97 $ head *
98 ==> f1 <==
98 ==> f1 <==
99 5 second change
99 5 second change
100
100
101 ==> f2 <==
101 ==> f2 <==
102 6 second change
102 6 second change
103
103
104 ==> f2.base <==
104 ==> f2.base <==
105 0 base
105 0 base
106
106
107 ==> f2.local <==
107 ==> f2.local <==
108 6 second change
108 6 second change
109
109
110 ==> f2.orig <==
110 ==> f2.orig <==
111 6 second change
111 6 second change
112
112
113 ==> f2.other <==
113 ==> f2.other <==
114 2 first change
114 2 first change
115
115
116 $ hg up -qC .
116 $ hg up -qC .
117 $ hg merge -v --tool internal:dump 5 --config merge.preferancestor="null 40663881 3b08d"
117 $ hg merge -v --tool internal:dump 5 --config merge.preferancestor="null 40663881 3b08d"
118 note: using 40663881a6dd as ancestor of 3b08d01b0ab5 and adfe50279922
118 note: using 40663881a6dd as ancestor of 3b08d01b0ab5 and adfe50279922
119 alternatively, use --config merge.preferancestor=0f6b37dbe527
119 alternatively, use --config merge.preferancestor=0f6b37dbe527
120 resolving manifests
120 resolving manifests
121 merging f1
121 merging f1
122 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
122 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
123 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
123 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
124 [1]
124 [1]
125
125
126 Redo merge with merge.preferancestor="*" to enable bid merge
127
128 $ rm f*
129 $ hg up -qC .
130 $ hg merge -v --debug --tool internal:dump 5 --config merge.preferancestor="*"
131
132 calculating bids for ancestor 0f6b37dbe527
133 searching for copies back to rev 3
134 resolving manifests
135 branchmerge: True, force: False, partial: False
136 ancestor: 0f6b37dbe527, local: 3b08d01b0ab5+, remote: adfe50279922
137 f1: g
138 f2: m
139
140 calculating bids for ancestor 40663881a6dd
141 searching for copies back to rev 3
142 resolving manifests
143 branchmerge: True, force: False, partial: False
144 ancestor: 40663881a6dd, local: 3b08d01b0ab5+, remote: adfe50279922
145 f1: m
146 f2: k
147
148 auction for merging merge bids
149 f1: picking 'get' action
150 f2: picking 'keep' action
151 end of auction
152
153 f1: remote is newer -> g
154 f2: keep -> k
155 getting f1
156 updating: f1 1/1 files (100.00%)
157 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
158 (branch merge, don't forget to commit)
159
160 $ head *
161 ==> f1 <==
162 5 second change
163
164 ==> f2 <==
165 6 second change
166
167
168 The other way around:
169
170 $ hg up -C -r5
171 note: using 0f6b37dbe527 as ancestor of 3b08d01b0ab5 and adfe50279922
172 alternatively, use --config merge.preferancestor=40663881a6dd
173 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
174 $ hg merge -v --debug --config merge.preferancestor="*"
175
176 calculating bids for ancestor 0f6b37dbe527
177 searching for copies back to rev 3
178 resolving manifests
179 branchmerge: True, force: False, partial: False
180 ancestor: 0f6b37dbe527, local: adfe50279922+, remote: 3b08d01b0ab5
181 f1: k
182 f2: m
183
184 calculating bids for ancestor 40663881a6dd
185 searching for copies back to rev 3
186 resolving manifests
187 branchmerge: True, force: False, partial: False
188 ancestor: 40663881a6dd, local: adfe50279922+, remote: 3b08d01b0ab5
189 f1: m
190 f2: g
191
192 auction for merging merge bids
193 f1: picking 'keep' action
194 f2: picking 'get' action
195 end of auction
196
197 f1: keep -> k
198 f2: remote is newer -> g
199 getting f2
200 updating: f2 1/1 files (100.00%)
201 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
202 (branch merge, don't forget to commit)
203
204 $ head *
205 ==> f1 <==
206 5 second change
207
208 ==> f2 <==
209 6 second change
210
211 Verify how the output looks and and how verbose it is:
212
213 $ hg up -qC
214 $ hg merge --config merge.preferancestor="*"
215 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
216 (branch merge, don't forget to commit)
217
218 $ hg up -qC
219 $ hg merge -v --config merge.preferancestor="*"
220
221 calculating bids for ancestor 0f6b37dbe527
222 resolving manifests
223
224 calculating bids for ancestor 40663881a6dd
225 resolving manifests
226
227 auction for merging merge bids
228 f1: picking 'get' action
229 f2: picking 'keep' action
230 end of auction
231
232 getting f1
233 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
234 (branch merge, don't forget to commit)
235
236 $ hg up -qC
237 $ hg merge -v --debug --config merge.preferancestor="*"
238
239 calculating bids for ancestor 0f6b37dbe527
240 searching for copies back to rev 3
241 resolving manifests
242 branchmerge: True, force: False, partial: False
243 ancestor: 0f6b37dbe527, local: 3b08d01b0ab5+, remote: adfe50279922
244 f1: g
245 f2: m
246
247 calculating bids for ancestor 40663881a6dd
248 searching for copies back to rev 3
249 resolving manifests
250 branchmerge: True, force: False, partial: False
251 ancestor: 40663881a6dd, local: 3b08d01b0ab5+, remote: adfe50279922
252 f1: m
253 f2: k
254
255 auction for merging merge bids
256 f1: picking 'get' action
257 f2: picking 'keep' action
258 end of auction
259
260 f1: remote is newer -> g
261 f2: keep -> k
262 getting f1
263 updating: f1 1/1 files (100.00%)
264 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
265 (branch merge, don't forget to commit)
266
126 $ cd ..
267 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now