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