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