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