##// END OF EJS Templates
merge: fix mergestate comment
timeless@mozdev.org -
r26361:6b4a0c60 default
parent child Browse files
Show More
@@ -1,1185 +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 kinds 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
599
600 # Track the shortest set of warning on the theory that bid
600 # Track the shortest set of warning on the theory that bid
601 # merge will correctly incorporate more information
601 # merge will correctly incorporate more information
602 if diverge is None or len(diverge1) < len(diverge):
602 if diverge is None or len(diverge1) < len(diverge):
603 diverge = diverge1
603 diverge = diverge1
604 if renamedelete is None or len(renamedelete) < len(renamedelete1):
604 if renamedelete is None or len(renamedelete) < len(renamedelete1):
605 renamedelete = renamedelete1
605 renamedelete = renamedelete1
606
606
607 for f, a in sorted(actions.iteritems()):
607 for f, a in sorted(actions.iteritems()):
608 m, args, msg = a
608 m, args, msg = a
609 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
609 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
610 if f in fbids:
610 if f in fbids:
611 d = fbids[f]
611 d = fbids[f]
612 if m in d:
612 if m in d:
613 d[m].append(a)
613 d[m].append(a)
614 else:
614 else:
615 d[m] = [a]
615 d[m] = [a]
616 else:
616 else:
617 fbids[f] = {m: [a]}
617 fbids[f] = {m: [a]}
618
618
619 # Pick the best bid for each file
619 # Pick the best bid for each file
620 repo.ui.note(_('\nauction for merging merge bids\n'))
620 repo.ui.note(_('\nauction for merging merge bids\n'))
621 actions = {}
621 actions = {}
622 for f, bids in sorted(fbids.items()):
622 for f, bids in sorted(fbids.items()):
623 # bids is a mapping from action method to list af actions
623 # bids is a mapping from action method to list af actions
624 # Consensus?
624 # Consensus?
625 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
626 m, l = bids.items()[0]
626 m, l = bids.items()[0]
627 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
628 repo.ui.note(" %s: consensus for %s\n" % (f, m))
628 repo.ui.note(" %s: consensus for %s\n" % (f, m))
629 actions[f] = l[0]
629 actions[f] = l[0]
630 continue
630 continue
631 # If keep is an option, just do it.
631 # If keep is an option, just do it.
632 if 'k' in bids:
632 if 'k' in bids:
633 repo.ui.note(" %s: picking 'keep' action\n" % f)
633 repo.ui.note(" %s: picking 'keep' action\n" % f)
634 actions[f] = bids['k'][0]
634 actions[f] = bids['k'][0]
635 continue
635 continue
636 # 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.
637 if 'g' in bids:
637 if 'g' in bids:
638 ga0 = bids['g'][0]
638 ga0 = bids['g'][0]
639 if all(a == ga0 for a in bids['g'][1:]):
639 if all(a == ga0 for a in bids['g'][1:]):
640 repo.ui.note(" %s: picking 'get' action\n" % f)
640 repo.ui.note(" %s: picking 'get' action\n" % f)
641 actions[f] = ga0
641 actions[f] = ga0
642 continue
642 continue
643 # TODO: Consider other simple actions such as mode changes
643 # TODO: Consider other simple actions such as mode changes
644 # Handle inefficient democrazy.
644 # Handle inefficient democrazy.
645 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
645 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
646 for m, l in sorted(bids.items()):
646 for m, l in sorted(bids.items()):
647 for _f, args, msg in l:
647 for _f, args, msg in l:
648 repo.ui.note(' %s -> %s\n' % (msg, m))
648 repo.ui.note(' %s -> %s\n' % (msg, m))
649 # Pick random action. TODO: Instead, prompt user when resolving
649 # Pick random action. TODO: Instead, prompt user when resolving
650 m, l = bids.items()[0]
650 m, l = bids.items()[0]
651 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
651 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
652 (f, m))
652 (f, m))
653 actions[f] = l[0]
653 actions[f] = l[0]
654 continue
654 continue
655 repo.ui.note(_('end of auction\n\n'))
655 repo.ui.note(_('end of auction\n\n'))
656
656
657 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
657 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
658
658
659 if wctx.rev() is None:
659 if wctx.rev() is None:
660 fractions = _forgetremoved(wctx, mctx, branchmerge)
660 fractions = _forgetremoved(wctx, mctx, branchmerge)
661 actions.update(fractions)
661 actions.update(fractions)
662
662
663 return actions, diverge, renamedelete
663 return actions, diverge, renamedelete
664
664
665 def batchremove(repo, actions):
665 def batchremove(repo, actions):
666 """apply removes to the working directory
666 """apply removes to the working directory
667
667
668 yields tuples for progress updates
668 yields tuples for progress updates
669 """
669 """
670 verbose = repo.ui.verbose
670 verbose = repo.ui.verbose
671 unlink = util.unlinkpath
671 unlink = util.unlinkpath
672 wjoin = repo.wjoin
672 wjoin = repo.wjoin
673 audit = repo.wvfs.audit
673 audit = repo.wvfs.audit
674 i = 0
674 i = 0
675 for f, args, msg in actions:
675 for f, args, msg in actions:
676 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
676 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
677 if verbose:
677 if verbose:
678 repo.ui.note(_("removing %s\n") % f)
678 repo.ui.note(_("removing %s\n") % f)
679 audit(f)
679 audit(f)
680 try:
680 try:
681 unlink(wjoin(f), ignoremissing=True)
681 unlink(wjoin(f), ignoremissing=True)
682 except OSError as inst:
682 except OSError as inst:
683 repo.ui.warn(_("update failed to remove %s: %s!\n") %
683 repo.ui.warn(_("update failed to remove %s: %s!\n") %
684 (f, inst.strerror))
684 (f, inst.strerror))
685 if i == 100:
685 if i == 100:
686 yield i, f
686 yield i, f
687 i = 0
687 i = 0
688 i += 1
688 i += 1
689 if i > 0:
689 if i > 0:
690 yield i, f
690 yield i, f
691
691
692 def batchget(repo, mctx, actions):
692 def batchget(repo, mctx, actions):
693 """apply gets to the working directory
693 """apply gets to the working directory
694
694
695 mctx is the context to get from
695 mctx is the context to get from
696
696
697 yields tuples for progress updates
697 yields tuples for progress updates
698 """
698 """
699 verbose = repo.ui.verbose
699 verbose = repo.ui.verbose
700 fctx = mctx.filectx
700 fctx = mctx.filectx
701 wwrite = repo.wwrite
701 wwrite = repo.wwrite
702 i = 0
702 i = 0
703 for f, args, msg in actions:
703 for f, args, msg in actions:
704 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
704 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
705 if verbose:
705 if verbose:
706 repo.ui.note(_("getting %s\n") % f)
706 repo.ui.note(_("getting %s\n") % f)
707 wwrite(f, fctx(f).data(), args[0])
707 wwrite(f, fctx(f).data(), args[0])
708 if i == 100:
708 if i == 100:
709 yield i, f
709 yield i, f
710 i = 0
710 i = 0
711 i += 1
711 i += 1
712 if i > 0:
712 if i > 0:
713 yield i, f
713 yield i, f
714
714
715 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
715 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
716 """apply the merge action list to the working directory
716 """apply the merge action list to the working directory
717
717
718 wctx is the working copy context
718 wctx is the working copy context
719 mctx is the context to be merged into the working copy
719 mctx is the context to be merged into the working copy
720
720
721 Return a tuple of counts (updated, merged, removed, unresolved) that
721 Return a tuple of counts (updated, merged, removed, unresolved) that
722 describes how many files were affected by the update.
722 describes how many files were affected by the update.
723 """
723 """
724
724
725 updated, merged, removed, unresolved = 0, 0, 0, 0
725 updated, merged, removed, unresolved = 0, 0, 0, 0
726 ms = mergestate(repo)
726 ms = mergestate(repo)
727 ms.reset(wctx.p1().node(), mctx.node())
727 ms.reset(wctx.p1().node(), mctx.node())
728 moves = []
728 moves = []
729 for m, l in actions.items():
729 for m, l in actions.items():
730 l.sort()
730 l.sort()
731
731
732 # prescan for merges
732 # prescan for merges
733 for f, args, msg in actions['m']:
733 for f, args, msg in actions['m']:
734 f1, f2, fa, move, anc = args
734 f1, f2, fa, move, anc = args
735 if f == '.hgsubstate': # merged internally
735 if f == '.hgsubstate': # merged internally
736 continue
736 continue
737 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))
738 fcl = wctx[f1]
738 fcl = wctx[f1]
739 fco = mctx[f2]
739 fco = mctx[f2]
740 actx = repo[anc]
740 actx = repo[anc]
741 if fa in actx:
741 if fa in actx:
742 fca = actx[fa]
742 fca = actx[fa]
743 else:
743 else:
744 fca = repo.filectx(f1, fileid=nullrev)
744 fca = repo.filectx(f1, fileid=nullrev)
745 ms.add(fcl, fco, fca, f)
745 ms.add(fcl, fco, fca, f)
746 if f1 != f and move:
746 if f1 != f and move:
747 moves.append(f1)
747 moves.append(f1)
748
748
749 audit = repo.wvfs.audit
749 audit = repo.wvfs.audit
750 _updating = _('updating')
750 _updating = _('updating')
751 _files = _('files')
751 _files = _('files')
752 progress = repo.ui.progress
752 progress = repo.ui.progress
753
753
754 # remove renamed files after safely stored
754 # remove renamed files after safely stored
755 for f in moves:
755 for f in moves:
756 if os.path.lexists(repo.wjoin(f)):
756 if os.path.lexists(repo.wjoin(f)):
757 repo.ui.debug("removing %s\n" % f)
757 repo.ui.debug("removing %s\n" % f)
758 audit(f)
758 audit(f)
759 util.unlinkpath(repo.wjoin(f))
759 util.unlinkpath(repo.wjoin(f))
760
760
761 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')
762
762
763 if [a for a in actions['r'] if a[0] == '.hgsubstate']:
763 if [a for a in actions['r'] if a[0] == '.hgsubstate']:
764 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
764 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
765
765
766 # remove in parallel (must come first)
766 # remove in parallel (must come first)
767 z = 0
767 z = 0
768 prog = worker.worker(repo.ui, 0.001, batchremove, (repo,), actions['r'])
768 prog = worker.worker(repo.ui, 0.001, batchremove, (repo,), actions['r'])
769 for i, item in prog:
769 for i, item in prog:
770 z += i
770 z += i
771 progress(_updating, z, item=item, total=numupdates, unit=_files)
771 progress(_updating, z, item=item, total=numupdates, unit=_files)
772 removed = len(actions['r'])
772 removed = len(actions['r'])
773
773
774 # get in parallel
774 # get in parallel
775 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'])
776 for i, item in prog:
776 for i, item in prog:
777 z += i
777 z += i
778 progress(_updating, z, item=item, total=numupdates, unit=_files)
778 progress(_updating, z, item=item, total=numupdates, unit=_files)
779 updated = len(actions['g'])
779 updated = len(actions['g'])
780
780
781 if [a for a in actions['g'] if a[0] == '.hgsubstate']:
781 if [a for a in actions['g'] if a[0] == '.hgsubstate']:
782 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
782 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
783
783
784 # forget (manifest only, just log it) (must come first)
784 # forget (manifest only, just log it) (must come first)
785 for f, args, msg in actions['f']:
785 for f, args, msg in actions['f']:
786 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
786 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
787 z += 1
787 z += 1
788 progress(_updating, z, item=f, total=numupdates, unit=_files)
788 progress(_updating, z, item=f, total=numupdates, unit=_files)
789
789
790 # re-add (manifest only, just log it)
790 # re-add (manifest only, just log it)
791 for f, args, msg in actions['a']:
791 for f, args, msg in actions['a']:
792 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
792 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
793 z += 1
793 z += 1
794 progress(_updating, z, item=f, total=numupdates, unit=_files)
794 progress(_updating, z, item=f, total=numupdates, unit=_files)
795
795
796 # keep (noop, just log it)
796 # keep (noop, just log it)
797 for f, args, msg in actions['k']:
797 for f, args, msg in actions['k']:
798 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
798 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
799 # no progress
799 # no progress
800
800
801 # directory rename, move local
801 # directory rename, move local
802 for f, args, msg in actions['dm']:
802 for f, args, msg in actions['dm']:
803 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
803 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
804 z += 1
804 z += 1
805 progress(_updating, z, item=f, total=numupdates, unit=_files)
805 progress(_updating, z, item=f, total=numupdates, unit=_files)
806 f0, flags = args
806 f0, flags = args
807 repo.ui.note(_("moving %s to %s\n") % (f0, f))
807 repo.ui.note(_("moving %s to %s\n") % (f0, f))
808 audit(f)
808 audit(f)
809 repo.wwrite(f, wctx.filectx(f0).data(), flags)
809 repo.wwrite(f, wctx.filectx(f0).data(), flags)
810 util.unlinkpath(repo.wjoin(f0))
810 util.unlinkpath(repo.wjoin(f0))
811 updated += 1
811 updated += 1
812
812
813 # local directory rename, get
813 # local directory rename, get
814 for f, args, msg in actions['dg']:
814 for f, args, msg in actions['dg']:
815 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
815 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
816 z += 1
816 z += 1
817 progress(_updating, z, item=f, total=numupdates, unit=_files)
817 progress(_updating, z, item=f, total=numupdates, unit=_files)
818 f0, flags = args
818 f0, flags = args
819 repo.ui.note(_("getting %s to %s\n") % (f0, f))
819 repo.ui.note(_("getting %s to %s\n") % (f0, f))
820 repo.wwrite(f, mctx.filectx(f0).data(), flags)
820 repo.wwrite(f, mctx.filectx(f0).data(), flags)
821 updated += 1
821 updated += 1
822
822
823 # exec
823 # exec
824 for f, args, msg in actions['e']:
824 for f, args, msg in actions['e']:
825 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
825 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
826 z += 1
826 z += 1
827 progress(_updating, z, item=f, total=numupdates, unit=_files)
827 progress(_updating, z, item=f, total=numupdates, unit=_files)
828 flags, = args
828 flags, = args
829 audit(f)
829 audit(f)
830 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
830 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
831 updated += 1
831 updated += 1
832
832
833 # merge
833 # merge
834 for f, args, msg in actions['m']:
834 for f, args, msg in actions['m']:
835 repo.ui.debug(" %s: %s -> m\n" % (f, msg))
835 repo.ui.debug(" %s: %s -> m\n" % (f, msg))
836 z += 1
836 z += 1
837 progress(_updating, z, item=f, total=numupdates, unit=_files)
837 progress(_updating, z, item=f, total=numupdates, unit=_files)
838 if f == '.hgsubstate': # subrepo states need updating
838 if f == '.hgsubstate': # subrepo states need updating
839 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
839 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
840 overwrite)
840 overwrite)
841 continue
841 continue
842 audit(f)
842 audit(f)
843 r = ms.resolve(f, wctx, labels=labels)
843 r = ms.resolve(f, wctx, labels=labels)
844 if r is not None and r > 0:
844 if r is not None and r > 0:
845 unresolved += 1
845 unresolved += 1
846 else:
846 else:
847 if r is None:
847 if r is None:
848 updated += 1
848 updated += 1
849 else:
849 else:
850 merged += 1
850 merged += 1
851
851
852 ms.commit()
852 ms.commit()
853 progress(_updating, None, total=numupdates, unit=_files)
853 progress(_updating, None, total=numupdates, unit=_files)
854
854
855 return updated, merged, removed, unresolved
855 return updated, merged, removed, unresolved
856
856
857 def recordupdates(repo, actions, branchmerge):
857 def recordupdates(repo, actions, branchmerge):
858 "record merge actions to the dirstate"
858 "record merge actions to the dirstate"
859 # remove (must come first)
859 # remove (must come first)
860 for f, args, msg in actions['r']:
860 for f, args, msg in actions['r']:
861 if branchmerge:
861 if branchmerge:
862 repo.dirstate.remove(f)
862 repo.dirstate.remove(f)
863 else:
863 else:
864 repo.dirstate.drop(f)
864 repo.dirstate.drop(f)
865
865
866 # forget (must come first)
866 # forget (must come first)
867 for f, args, msg in actions['f']:
867 for f, args, msg in actions['f']:
868 repo.dirstate.drop(f)
868 repo.dirstate.drop(f)
869
869
870 # re-add
870 # re-add
871 for f, args, msg in actions['a']:
871 for f, args, msg in actions['a']:
872 if not branchmerge:
872 if not branchmerge:
873 repo.dirstate.add(f)
873 repo.dirstate.add(f)
874
874
875 # exec change
875 # exec change
876 for f, args, msg in actions['e']:
876 for f, args, msg in actions['e']:
877 repo.dirstate.normallookup(f)
877 repo.dirstate.normallookup(f)
878
878
879 # keep
879 # keep
880 for f, args, msg in actions['k']:
880 for f, args, msg in actions['k']:
881 pass
881 pass
882
882
883 # get
883 # get
884 for f, args, msg in actions['g']:
884 for f, args, msg in actions['g']:
885 if branchmerge:
885 if branchmerge:
886 repo.dirstate.otherparent(f)
886 repo.dirstate.otherparent(f)
887 else:
887 else:
888 repo.dirstate.normal(f)
888 repo.dirstate.normal(f)
889
889
890 # merge
890 # merge
891 for f, args, msg in actions['m']:
891 for f, args, msg in actions['m']:
892 f1, f2, fa, move, anc = args
892 f1, f2, fa, move, anc = args
893 if branchmerge:
893 if branchmerge:
894 # We've done a branch merge, mark this file as merged
894 # We've done a branch merge, mark this file as merged
895 # so that we properly record the merger later
895 # so that we properly record the merger later
896 repo.dirstate.merge(f)
896 repo.dirstate.merge(f)
897 if f1 != f2: # copy/rename
897 if f1 != f2: # copy/rename
898 if move:
898 if move:
899 repo.dirstate.remove(f1)
899 repo.dirstate.remove(f1)
900 if f1 != f:
900 if f1 != f:
901 repo.dirstate.copy(f1, f)
901 repo.dirstate.copy(f1, f)
902 else:
902 else:
903 repo.dirstate.copy(f2, f)
903 repo.dirstate.copy(f2, f)
904 else:
904 else:
905 # We've update-merged a locally modified file, so
905 # We've update-merged a locally modified file, so
906 # we set the dirstate to emulate a normal checkout
906 # we set the dirstate to emulate a normal checkout
907 # of that file some time in the past. Thus our
907 # of that file some time in the past. Thus our
908 # merge will appear as a normal local file
908 # merge will appear as a normal local file
909 # modification.
909 # modification.
910 if f2 == f: # file not locally copied/moved
910 if f2 == f: # file not locally copied/moved
911 repo.dirstate.normallookup(f)
911 repo.dirstate.normallookup(f)
912 if move:
912 if move:
913 repo.dirstate.drop(f1)
913 repo.dirstate.drop(f1)
914
914
915 # directory rename, move local
915 # directory rename, move local
916 for f, args, msg in actions['dm']:
916 for f, args, msg in actions['dm']:
917 f0, flag = args
917 f0, flag = args
918 if branchmerge:
918 if branchmerge:
919 repo.dirstate.add(f)
919 repo.dirstate.add(f)
920 repo.dirstate.remove(f0)
920 repo.dirstate.remove(f0)
921 repo.dirstate.copy(f0, f)
921 repo.dirstate.copy(f0, f)
922 else:
922 else:
923 repo.dirstate.normal(f)
923 repo.dirstate.normal(f)
924 repo.dirstate.drop(f0)
924 repo.dirstate.drop(f0)
925
925
926 # directory rename, get
926 # directory rename, get
927 for f, args, msg in actions['dg']:
927 for f, args, msg in actions['dg']:
928 f0, flag = args
928 f0, flag = args
929 if branchmerge:
929 if branchmerge:
930 repo.dirstate.add(f)
930 repo.dirstate.add(f)
931 repo.dirstate.copy(f0, f)
931 repo.dirstate.copy(f0, f)
932 else:
932 else:
933 repo.dirstate.normal(f)
933 repo.dirstate.normal(f)
934
934
935 def update(repo, node, branchmerge, force, partial, ancestor=None,
935 def update(repo, node, branchmerge, force, partial, ancestor=None,
936 mergeancestor=False, labels=None):
936 mergeancestor=False, labels=None):
937 """
937 """
938 Perform a merge between the working directory and the given node
938 Perform a merge between the working directory and the given node
939
939
940 node = the node to update to, or None if unspecified
940 node = the node to update to, or None if unspecified
941 branchmerge = whether to merge between branches
941 branchmerge = whether to merge between branches
942 force = whether to force branch merging or file overwriting
942 force = whether to force branch merging or file overwriting
943 partial = a function to filter file lists (dirstate not updated)
943 partial = a function to filter file lists (dirstate not updated)
944 mergeancestor = whether it is merging with an ancestor. If true,
944 mergeancestor = whether it is merging with an ancestor. If true,
945 we should accept the incoming changes for any prompts that occur.
945 we should accept the incoming changes for any prompts that occur.
946 If false, merging with an ancestor (fast-forward) is only allowed
946 If false, merging with an ancestor (fast-forward) is only allowed
947 between different named branches. This flag is used by rebase extension
947 between different named branches. This flag is used by rebase extension
948 as a temporary fix and should be avoided in general.
948 as a temporary fix and should be avoided in general.
949
949
950 The table below shows all the behaviors of the update command
950 The table below shows all the behaviors of the update command
951 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
952 is dirty, whether a revision is specified, and the relationship of
952 is dirty, whether a revision is specified, and the relationship of
953 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
954 branch, or on another named branch).
954 branch, or on another named branch).
955
955
956 This logic is tested by test-update-branches.t.
956 This logic is tested by test-update-branches.t.
957
957
958 -c -C dirty rev | linear same cross
958 -c -C dirty rev | linear same cross
959 n n n n | ok (1) x
959 n n n n | ok (1) x
960 n n n y | ok ok ok
960 n n n y | ok ok ok
961 n n y n | merge (2) (2)
961 n n y n | merge (2) (2)
962 n n y y | merge (3) (3)
962 n n y y | merge (3) (3)
963 n y * * | --- discard ---
963 n y * * | --- discard ---
964 y n y * | --- (4) ---
964 y n y * | --- (4) ---
965 y n n * | --- ok ---
965 y n n * | --- ok ---
966 y y * * | --- (5) ---
966 y y * * | --- (5) ---
967
967
968 x = can't happen
968 x = can't happen
969 * = don't-care
969 * = don't-care
970 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)
971 2 = abort: uncommitted changes (commit and merge, or update --clean to
971 2 = abort: uncommitted changes (commit and merge, or update --clean to
972 discard changes)
972 discard changes)
973 3 = abort: uncommitted changes (commit or update --clean to discard changes)
973 3 = abort: uncommitted changes (commit or update --clean to discard changes)
974 4 = abort: uncommitted changes (checked in commands.py)
974 4 = abort: uncommitted changes (checked in commands.py)
975 5 = incompatible options (checked in commands.py)
975 5 = incompatible options (checked in commands.py)
976
976
977 Return the same tuple as applyupdates().
977 Return the same tuple as applyupdates().
978 """
978 """
979
979
980 onode = node
980 onode = node
981 wlock = repo.wlock()
981 wlock = repo.wlock()
982 try:
982 try:
983 wc = repo[None]
983 wc = repo[None]
984 pl = wc.parents()
984 pl = wc.parents()
985 p1 = pl[0]
985 p1 = pl[0]
986 pas = [None]
986 pas = [None]
987 if ancestor is not None:
987 if ancestor is not None:
988 pas = [repo[ancestor]]
988 pas = [repo[ancestor]]
989
989
990 if node is None:
990 if node is None:
991 nodes = list(repo.set('_updatedefaultdest()'))
991 nodes = list(repo.set('_updatedefaultdest()'))
992 if nodes:
992 if nodes:
993 node = nodes[0].node()
993 node = nodes[0].node()
994 if p1.obsolete() and not p1.children():
994 if p1.obsolete() and not p1.children():
995 pas = [p1]
995 pas = [p1]
996
996
997 overwrite = force and not branchmerge
997 overwrite = force and not branchmerge
998
998
999 p2 = repo[node]
999 p2 = repo[node]
1000 if pas[0] is None:
1000 if pas[0] is None:
1001 if repo.ui.configlist('merge', 'preferancestor', ['*']) == ['*']:
1001 if repo.ui.configlist('merge', 'preferancestor', ['*']) == ['*']:
1002 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1002 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1003 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1003 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1004 else:
1004 else:
1005 pas = [p1.ancestor(p2, warn=branchmerge)]
1005 pas = [p1.ancestor(p2, warn=branchmerge)]
1006
1006
1007 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)
1008
1008
1009 ### check phase
1009 ### check phase
1010 if not overwrite and len(pl) > 1:
1010 if not overwrite and len(pl) > 1:
1011 raise util.Abort(_("outstanding uncommitted merge"))
1011 raise util.Abort(_("outstanding uncommitted merge"))
1012 if branchmerge:
1012 if branchmerge:
1013 if pas == [p2]:
1013 if pas == [p2]:
1014 raise util.Abort(_("merging with a working directory ancestor"
1014 raise util.Abort(_("merging with a working directory ancestor"
1015 " has no effect"))
1015 " has no effect"))
1016 elif pas == [p1]:
1016 elif pas == [p1]:
1017 if not mergeancestor and p1.branch() == p2.branch():
1017 if not mergeancestor and p1.branch() == p2.branch():
1018 raise util.Abort(_("nothing to merge"),
1018 raise util.Abort(_("nothing to merge"),
1019 hint=_("use 'hg update' "
1019 hint=_("use 'hg update' "
1020 "or check 'hg heads'"))
1020 "or check 'hg heads'"))
1021 if not force and (wc.files() or wc.deleted()):
1021 if not force and (wc.files() or wc.deleted()):
1022 raise util.Abort(_("uncommitted changes"),
1022 raise util.Abort(_("uncommitted changes"),
1023 hint=_("use 'hg status' to list changes"))
1023 hint=_("use 'hg status' to list changes"))
1024 for s in sorted(wc.substate):
1024 for s in sorted(wc.substate):
1025 wc.sub(s).bailifchanged()
1025 wc.sub(s).bailifchanged()
1026
1026
1027 elif not overwrite:
1027 elif not overwrite:
1028 if p1 == p2: # no-op update
1028 if p1 == p2: # no-op update
1029 # call the hooks and exit early
1029 # call the hooks and exit early
1030 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
1030 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
1031 repo.hook('update', parent1=xp2, parent2='', error=0)
1031 repo.hook('update', parent1=xp2, parent2='', error=0)
1032 return 0, 0, 0, 0
1032 return 0, 0, 0, 0
1033
1033
1034 if pas not in ([p1], [p2]): # nonlinear
1034 if pas not in ([p1], [p2]): # nonlinear
1035 dirty = wc.dirty(missing=True)
1035 dirty = wc.dirty(missing=True)
1036 if dirty or onode is None:
1036 if dirty or onode is None:
1037 # Branching is a bit strange to ensure we do the minimal
1037 # Branching is a bit strange to ensure we do the minimal
1038 # amount of call to obsolete.background.
1038 # amount of call to obsolete.background.
1039 foreground = obsolete.foreground(repo, [p1.node()])
1039 foreground = obsolete.foreground(repo, [p1.node()])
1040 # note: the <node> variable contains a random identifier
1040 # note: the <node> variable contains a random identifier
1041 if repo[node].node() in foreground:
1041 if repo[node].node() in foreground:
1042 pas = [p1] # allow updating to successors
1042 pas = [p1] # allow updating to successors
1043 elif dirty:
1043 elif dirty:
1044 msg = _("uncommitted changes")
1044 msg = _("uncommitted changes")
1045 if onode is None:
1045 if onode is None:
1046 hint = _("commit and merge, or update --clean to"
1046 hint = _("commit and merge, or update --clean to"
1047 " discard changes")
1047 " discard changes")
1048 else:
1048 else:
1049 hint = _("commit or update --clean to discard"
1049 hint = _("commit or update --clean to discard"
1050 " changes")
1050 " changes")
1051 raise util.Abort(msg, hint=hint)
1051 raise util.Abort(msg, hint=hint)
1052 else: # node is none
1052 else: # node is none
1053 msg = _("not a linear update")
1053 msg = _("not a linear update")
1054 hint = _("merge or update --check to force update")
1054 hint = _("merge or update --check to force update")
1055 raise util.Abort(msg, hint=hint)
1055 raise util.Abort(msg, hint=hint)
1056 else:
1056 else:
1057 # Allow jumping branches if clean and specific rev given
1057 # Allow jumping branches if clean and specific rev given
1058 pas = [p1]
1058 pas = [p1]
1059
1059
1060 # deprecated config: merge.followcopies
1060 # deprecated config: merge.followcopies
1061 followcopies = False
1061 followcopies = False
1062 if overwrite:
1062 if overwrite:
1063 pas = [wc]
1063 pas = [wc]
1064 elif pas == [p2]: # backwards
1064 elif pas == [p2]: # backwards
1065 pas = [wc.p1()]
1065 pas = [wc.p1()]
1066 elif not branchmerge and not wc.dirty(missing=True):
1066 elif not branchmerge and not wc.dirty(missing=True):
1067 pass
1067 pass
1068 elif pas[0] and repo.ui.configbool('merge', 'followcopies', True):
1068 elif pas[0] and repo.ui.configbool('merge', 'followcopies', True):
1069 followcopies = True
1069 followcopies = True
1070
1070
1071 ### calculate phase
1071 ### calculate phase
1072 actionbyfile, diverge, renamedelete = calculateupdates(
1072 actionbyfile, diverge, renamedelete = calculateupdates(
1073 repo, wc, p2, pas, branchmerge, force, partial, mergeancestor,
1073 repo, wc, p2, pas, branchmerge, force, partial, mergeancestor,
1074 followcopies)
1074 followcopies)
1075 # Convert to dictionary-of-lists format
1075 # Convert to dictionary-of-lists format
1076 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())
1077 for f, (m, args, msg) in actionbyfile.iteritems():
1077 for f, (m, args, msg) in actionbyfile.iteritems():
1078 if m not in actions:
1078 if m not in actions:
1079 actions[m] = []
1079 actions[m] = []
1080 actions[m].append((f, args, msg))
1080 actions[m].append((f, args, msg))
1081
1081
1082 if not util.checkcase(repo.path):
1082 if not util.checkcase(repo.path):
1083 # check collision between files only in p2 for clean update
1083 # check collision between files only in p2 for clean update
1084 if (not branchmerge and
1084 if (not branchmerge and
1085 (force or not wc.dirty(missing=True, branch=False))):
1085 (force or not wc.dirty(missing=True, branch=False))):
1086 _checkcollision(repo, p2.manifest(), None)
1086 _checkcollision(repo, p2.manifest(), None)
1087 else:
1087 else:
1088 _checkcollision(repo, wc.manifest(), actions)
1088 _checkcollision(repo, wc.manifest(), actions)
1089
1089
1090 # Prompt and create actions. TODO: Move this towards resolve phase.
1090 # Prompt and create actions. TODO: Move this towards resolve phase.
1091 for f, args, msg in sorted(actions['cd']):
1091 for f, args, msg in sorted(actions['cd']):
1092 if repo.ui.promptchoice(
1092 if repo.ui.promptchoice(
1093 _("local changed %s which remote deleted\n"
1093 _("local changed %s which remote deleted\n"
1094 "use (c)hanged version or (d)elete?"
1094 "use (c)hanged version or (d)elete?"
1095 "$$ &Changed $$ &Delete") % f, 0):
1095 "$$ &Changed $$ &Delete") % f, 0):
1096 actions['r'].append((f, None, "prompt delete"))
1096 actions['r'].append((f, None, "prompt delete"))
1097 else:
1097 else:
1098 actions['a'].append((f, None, "prompt keep"))
1098 actions['a'].append((f, None, "prompt keep"))
1099 del actions['cd'][:]
1099 del actions['cd'][:]
1100
1100
1101 for f, args, msg in sorted(actions['dc']):
1101 for f, args, msg in sorted(actions['dc']):
1102 flags, = args
1102 flags, = args
1103 if repo.ui.promptchoice(
1103 if repo.ui.promptchoice(
1104 _("remote changed %s which local deleted\n"
1104 _("remote changed %s which local deleted\n"
1105 "use (c)hanged version or leave (d)eleted?"
1105 "use (c)hanged version or leave (d)eleted?"
1106 "$$ &Changed $$ &Deleted") % f, 0) == 0:
1106 "$$ &Changed $$ &Deleted") % f, 0) == 0:
1107 actions['g'].append((f, (flags,), "prompt recreating"))
1107 actions['g'].append((f, (flags,), "prompt recreating"))
1108 del actions['dc'][:]
1108 del actions['dc'][:]
1109
1109
1110 ### apply phase
1110 ### apply phase
1111 if not branchmerge: # just jump to the new rev
1111 if not branchmerge: # just jump to the new rev
1112 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
1112 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
1113 if not partial:
1113 if not partial:
1114 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
1114 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
1115 # note that we're in the middle of an update
1115 # note that we're in the middle of an update
1116 repo.vfs.write('updatestate', p2.hex())
1116 repo.vfs.write('updatestate', p2.hex())
1117
1117
1118 stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
1118 stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
1119
1119
1120 # divergent renames
1120 # divergent renames
1121 for f, fl in sorted(diverge.iteritems()):
1121 for f, fl in sorted(diverge.iteritems()):
1122 repo.ui.warn(_("note: possible conflict - %s was renamed "
1122 repo.ui.warn(_("note: possible conflict - %s was renamed "
1123 "multiple times to:\n") % f)
1123 "multiple times to:\n") % f)
1124 for nf in fl:
1124 for nf in fl:
1125 repo.ui.warn(" %s\n" % nf)
1125 repo.ui.warn(" %s\n" % nf)
1126
1126
1127 # rename and delete
1127 # rename and delete
1128 for f, fl in sorted(renamedelete.iteritems()):
1128 for f, fl in sorted(renamedelete.iteritems()):
1129 repo.ui.warn(_("note: possible conflict - %s was deleted "
1129 repo.ui.warn(_("note: possible conflict - %s was deleted "
1130 "and renamed to:\n") % f)
1130 "and renamed to:\n") % f)
1131 for nf in fl:
1131 for nf in fl:
1132 repo.ui.warn(" %s\n" % nf)
1132 repo.ui.warn(" %s\n" % nf)
1133
1133
1134 if not partial:
1134 if not partial:
1135 repo.dirstate.beginparentchange()
1135 repo.dirstate.beginparentchange()
1136 repo.setparents(fp1, fp2)
1136 repo.setparents(fp1, fp2)
1137 recordupdates(repo, actions, branchmerge)
1137 recordupdates(repo, actions, branchmerge)
1138 # update completed, clear state
1138 # update completed, clear state
1139 util.unlink(repo.join('updatestate'))
1139 util.unlink(repo.join('updatestate'))
1140
1140
1141 if not branchmerge:
1141 if not branchmerge:
1142 repo.dirstate.setbranch(p2.branch())
1142 repo.dirstate.setbranch(p2.branch())
1143 repo.dirstate.endparentchange()
1143 repo.dirstate.endparentchange()
1144 finally:
1144 finally:
1145 wlock.release()
1145 wlock.release()
1146
1146
1147 if not partial:
1147 if not partial:
1148 def updatehook(parent1=xp1, parent2=xp2, error=stats[3]):
1148 def updatehook(parent1=xp1, parent2=xp2, error=stats[3]):
1149 repo.hook('update', parent1=parent1, parent2=parent2, error=error)
1149 repo.hook('update', parent1=parent1, parent2=parent2, error=error)
1150 repo._afterlock(updatehook)
1150 repo._afterlock(updatehook)
1151 return stats
1151 return stats
1152
1152
1153 def graft(repo, ctx, pctx, labels):
1153 def graft(repo, ctx, pctx, labels):
1154 """Do a graft-like merge.
1154 """Do a graft-like merge.
1155
1155
1156 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
1157 or more changesets are grafted onto the current changeset. In
1157 or more changesets are grafted onto the current changeset. In
1158 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
1159 a single parent and tries to duplicate any renames/copies
1159 a single parent and tries to duplicate any renames/copies
1160 appropriately.
1160 appropriately.
1161
1161
1162 ctx - changeset to rebase
1162 ctx - changeset to rebase
1163 pctx - merge base, usually ctx.p1()
1163 pctx - merge base, usually ctx.p1()
1164 labels - merge labels eg ['local', 'graft']
1164 labels - merge labels eg ['local', 'graft']
1165
1165
1166 """
1166 """
1167 # 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
1168 # 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
1169 # 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
1170 # 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
1171 # 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
1172 # which local deleted".
1172 # which local deleted".
1173 mergeancestor = repo.changelog.isancestor(repo['.'].node(), ctx.node())
1173 mergeancestor = repo.changelog.isancestor(repo['.'].node(), ctx.node())
1174
1174
1175 stats = update(repo, ctx.node(), True, True, False, pctx.node(),
1175 stats = update(repo, ctx.node(), True, True, False, pctx.node(),
1176 mergeancestor=mergeancestor, labels=labels)
1176 mergeancestor=mergeancestor, labels=labels)
1177
1177
1178 # drop the second merge parent
1178 # drop the second merge parent
1179 repo.dirstate.beginparentchange()
1179 repo.dirstate.beginparentchange()
1180 repo.setparents(repo['.'].node(), nullid)
1180 repo.setparents(repo['.'].node(), nullid)
1181 repo.dirstate.write()
1181 repo.dirstate.write()
1182 # fix up dirstate for copies and renames
1182 # fix up dirstate for copies and renames
1183 copies.duplicatecopies(repo, ctx.rev(), pctx.rev())
1183 copies.duplicatecopies(repo, ctx.rev(), pctx.rev())
1184 repo.dirstate.endparentchange()
1184 repo.dirstate.endparentchange()
1185 return stats
1185 return stats
General Comments 0
You need to be logged in to leave comments. Login now