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