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