##// END OF EJS Templates
mergestate: implement unresolvedcount() in terms of unresolved()...
Martin von Zweigbergk -
r33311:f8f716da default
parent child Browse files
Show More
@@ -1,1748 +1,1747 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 hashlib
11 import hashlib
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 addednodeid,
17 addednodeid,
18 bin,
18 bin,
19 hex,
19 hex,
20 modifiednodeid,
20 modifiednodeid,
21 nullhex,
21 nullhex,
22 nullid,
22 nullid,
23 nullrev,
23 nullrev,
24 )
24 )
25 from . import (
25 from . import (
26 copies,
26 copies,
27 error,
27 error,
28 filemerge,
28 filemerge,
29 match as matchmod,
29 match as matchmod,
30 obsutil,
30 obsutil,
31 pycompat,
31 pycompat,
32 scmutil,
32 scmutil,
33 subrepo,
33 subrepo,
34 util,
34 util,
35 worker,
35 worker,
36 )
36 )
37
37
38 _pack = struct.pack
38 _pack = struct.pack
39 _unpack = struct.unpack
39 _unpack = struct.unpack
40
40
41 def _droponode(data):
41 def _droponode(data):
42 # used for compatibility for v1
42 # used for compatibility for v1
43 bits = data.split('\0')
43 bits = data.split('\0')
44 bits = bits[:-2] + bits[-1:]
44 bits = bits[:-2] + bits[-1:]
45 return '\0'.join(bits)
45 return '\0'.join(bits)
46
46
47 class mergestate(object):
47 class mergestate(object):
48 '''track 3-way merge state of individual files
48 '''track 3-way merge state of individual files
49
49
50 The merge state is stored on disk when needed. Two files are used: one with
50 The merge state is stored on disk when needed. Two files are used: one with
51 an old format (version 1), and one with a new format (version 2). Version 2
51 an old format (version 1), and one with a new format (version 2). Version 2
52 stores a superset of the data in version 1, including new kinds of records
52 stores a superset of the data in version 1, including new kinds of records
53 in the future. For more about the new format, see the documentation for
53 in the future. For more about the new format, see the documentation for
54 `_readrecordsv2`.
54 `_readrecordsv2`.
55
55
56 Each record can contain arbitrary content, and has an associated type. This
56 Each record can contain arbitrary content, and has an associated type. This
57 `type` should be a letter. If `type` is uppercase, the record is mandatory:
57 `type` should be a letter. If `type` is uppercase, the record is mandatory:
58 versions of Mercurial that don't support it should abort. If `type` is
58 versions of Mercurial that don't support it should abort. If `type` is
59 lowercase, the record can be safely ignored.
59 lowercase, the record can be safely ignored.
60
60
61 Currently known records:
61 Currently known records:
62
62
63 L: the node of the "local" part of the merge (hexified version)
63 L: the node of the "local" part of the merge (hexified version)
64 O: the node of the "other" part of the merge (hexified version)
64 O: the node of the "other" part of the merge (hexified version)
65 F: a file to be merged entry
65 F: a file to be merged entry
66 C: a change/delete or delete/change conflict
66 C: a change/delete or delete/change conflict
67 D: a file that the external merge driver will merge internally
67 D: a file that the external merge driver will merge internally
68 (experimental)
68 (experimental)
69 m: the external merge driver defined for this merge plus its run state
69 m: the external merge driver defined for this merge plus its run state
70 (experimental)
70 (experimental)
71 f: a (filename, dictionary) tuple of optional values for a given file
71 f: a (filename, dictionary) tuple of optional values for a given file
72 X: unsupported mandatory record type (used in tests)
72 X: unsupported mandatory record type (used in tests)
73 x: unsupported advisory record type (used in tests)
73 x: unsupported advisory record type (used in tests)
74 l: the labels for the parts of the merge.
74 l: the labels for the parts of the merge.
75
75
76 Merge driver run states (experimental):
76 Merge driver run states (experimental):
77 u: driver-resolved files unmarked -- needs to be run next time we're about
77 u: driver-resolved files unmarked -- needs to be run next time we're about
78 to resolve or commit
78 to resolve or commit
79 m: driver-resolved files marked -- only needs to be run before commit
79 m: driver-resolved files marked -- only needs to be run before commit
80 s: success/skipped -- does not need to be run any more
80 s: success/skipped -- does not need to be run any more
81
81
82 '''
82 '''
83 statepathv1 = 'merge/state'
83 statepathv1 = 'merge/state'
84 statepathv2 = 'merge/state2'
84 statepathv2 = 'merge/state2'
85
85
86 @staticmethod
86 @staticmethod
87 def clean(repo, node=None, other=None, labels=None):
87 def clean(repo, node=None, other=None, labels=None):
88 """Initialize a brand new merge state, removing any existing state on
88 """Initialize a brand new merge state, removing any existing state on
89 disk."""
89 disk."""
90 ms = mergestate(repo)
90 ms = mergestate(repo)
91 ms.reset(node, other, labels)
91 ms.reset(node, other, labels)
92 return ms
92 return ms
93
93
94 @staticmethod
94 @staticmethod
95 def read(repo):
95 def read(repo):
96 """Initialize the merge state, reading it from disk."""
96 """Initialize the merge state, reading it from disk."""
97 ms = mergestate(repo)
97 ms = mergestate(repo)
98 ms._read()
98 ms._read()
99 return ms
99 return ms
100
100
101 def __init__(self, repo):
101 def __init__(self, repo):
102 """Initialize the merge state.
102 """Initialize the merge state.
103
103
104 Do not use this directly! Instead call read() or clean()."""
104 Do not use this directly! Instead call read() or clean()."""
105 self._repo = repo
105 self._repo = repo
106 self._dirty = False
106 self._dirty = False
107 self._labels = None
107 self._labels = None
108
108
109 def reset(self, node=None, other=None, labels=None):
109 def reset(self, node=None, other=None, labels=None):
110 self._state = {}
110 self._state = {}
111 self._stateextras = {}
111 self._stateextras = {}
112 self._local = None
112 self._local = None
113 self._other = None
113 self._other = None
114 self._labels = labels
114 self._labels = labels
115 for var in ('localctx', 'otherctx'):
115 for var in ('localctx', 'otherctx'):
116 if var in vars(self):
116 if var in vars(self):
117 delattr(self, var)
117 delattr(self, var)
118 if node:
118 if node:
119 self._local = node
119 self._local = node
120 self._other = other
120 self._other = other
121 self._readmergedriver = None
121 self._readmergedriver = None
122 if self.mergedriver:
122 if self.mergedriver:
123 self._mdstate = 's'
123 self._mdstate = 's'
124 else:
124 else:
125 self._mdstate = 'u'
125 self._mdstate = 'u'
126 shutil.rmtree(self._repo.vfs.join('merge'), True)
126 shutil.rmtree(self._repo.vfs.join('merge'), True)
127 self._results = {}
127 self._results = {}
128 self._dirty = False
128 self._dirty = False
129
129
130 def _read(self):
130 def _read(self):
131 """Analyse each record content to restore a serialized state from disk
131 """Analyse each record content to restore a serialized state from disk
132
132
133 This function process "record" entry produced by the de-serialization
133 This function process "record" entry produced by the de-serialization
134 of on disk file.
134 of on disk file.
135 """
135 """
136 self._state = {}
136 self._state = {}
137 self._stateextras = {}
137 self._stateextras = {}
138 self._local = None
138 self._local = None
139 self._other = None
139 self._other = None
140 for var in ('localctx', 'otherctx'):
140 for var in ('localctx', 'otherctx'):
141 if var in vars(self):
141 if var in vars(self):
142 delattr(self, var)
142 delattr(self, var)
143 self._readmergedriver = None
143 self._readmergedriver = None
144 self._mdstate = 's'
144 self._mdstate = 's'
145 unsupported = set()
145 unsupported = set()
146 records = self._readrecords()
146 records = self._readrecords()
147 for rtype, record in records:
147 for rtype, record in records:
148 if rtype == 'L':
148 if rtype == 'L':
149 self._local = bin(record)
149 self._local = bin(record)
150 elif rtype == 'O':
150 elif rtype == 'O':
151 self._other = bin(record)
151 self._other = bin(record)
152 elif rtype == 'm':
152 elif rtype == 'm':
153 bits = record.split('\0', 1)
153 bits = record.split('\0', 1)
154 mdstate = bits[1]
154 mdstate = bits[1]
155 if len(mdstate) != 1 or mdstate not in 'ums':
155 if len(mdstate) != 1 or mdstate not in 'ums':
156 # the merge driver should be idempotent, so just rerun it
156 # the merge driver should be idempotent, so just rerun it
157 mdstate = 'u'
157 mdstate = 'u'
158
158
159 self._readmergedriver = bits[0]
159 self._readmergedriver = bits[0]
160 self._mdstate = mdstate
160 self._mdstate = mdstate
161 elif rtype in 'FDC':
161 elif rtype in 'FDC':
162 bits = record.split('\0')
162 bits = record.split('\0')
163 self._state[bits[0]] = bits[1:]
163 self._state[bits[0]] = bits[1:]
164 elif rtype == 'f':
164 elif rtype == 'f':
165 filename, rawextras = record.split('\0', 1)
165 filename, rawextras = record.split('\0', 1)
166 extraparts = rawextras.split('\0')
166 extraparts = rawextras.split('\0')
167 extras = {}
167 extras = {}
168 i = 0
168 i = 0
169 while i < len(extraparts):
169 while i < len(extraparts):
170 extras[extraparts[i]] = extraparts[i + 1]
170 extras[extraparts[i]] = extraparts[i + 1]
171 i += 2
171 i += 2
172
172
173 self._stateextras[filename] = extras
173 self._stateextras[filename] = extras
174 elif rtype == 'l':
174 elif rtype == 'l':
175 labels = record.split('\0', 2)
175 labels = record.split('\0', 2)
176 self._labels = [l for l in labels if len(l) > 0]
176 self._labels = [l for l in labels if len(l) > 0]
177 elif not rtype.islower():
177 elif not rtype.islower():
178 unsupported.add(rtype)
178 unsupported.add(rtype)
179 self._results = {}
179 self._results = {}
180 self._dirty = False
180 self._dirty = False
181
181
182 if unsupported:
182 if unsupported:
183 raise error.UnsupportedMergeRecords(unsupported)
183 raise error.UnsupportedMergeRecords(unsupported)
184
184
185 def _readrecords(self):
185 def _readrecords(self):
186 """Read merge state from disk and return a list of record (TYPE, data)
186 """Read merge state from disk and return a list of record (TYPE, data)
187
187
188 We read data from both v1 and v2 files and decide which one to use.
188 We read data from both v1 and v2 files and decide which one to use.
189
189
190 V1 has been used by version prior to 2.9.1 and contains less data than
190 V1 has been used by version prior to 2.9.1 and contains less data than
191 v2. We read both versions and check if no data in v2 contradicts
191 v2. We read both versions and check if no data in v2 contradicts
192 v1. If there is not contradiction we can safely assume that both v1
192 v1. If there is not contradiction we can safely assume that both v1
193 and v2 were written at the same time and use the extract data in v2. If
193 and v2 were written at the same time and use the extract data in v2. If
194 there is contradiction we ignore v2 content as we assume an old version
194 there is contradiction we ignore v2 content as we assume an old version
195 of Mercurial has overwritten the mergestate file and left an old v2
195 of Mercurial has overwritten the mergestate file and left an old v2
196 file around.
196 file around.
197
197
198 returns list of record [(TYPE, data), ...]"""
198 returns list of record [(TYPE, data), ...]"""
199 v1records = self._readrecordsv1()
199 v1records = self._readrecordsv1()
200 v2records = self._readrecordsv2()
200 v2records = self._readrecordsv2()
201 if self._v1v2match(v1records, v2records):
201 if self._v1v2match(v1records, v2records):
202 return v2records
202 return v2records
203 else:
203 else:
204 # v1 file is newer than v2 file, use it
204 # v1 file is newer than v2 file, use it
205 # we have to infer the "other" changeset of the merge
205 # we have to infer the "other" changeset of the merge
206 # we cannot do better than that with v1 of the format
206 # we cannot do better than that with v1 of the format
207 mctx = self._repo[None].parents()[-1]
207 mctx = self._repo[None].parents()[-1]
208 v1records.append(('O', mctx.hex()))
208 v1records.append(('O', mctx.hex()))
209 # add place holder "other" file node information
209 # add place holder "other" file node information
210 # nobody is using it yet so we do no need to fetch the data
210 # nobody is using it yet so we do no need to fetch the data
211 # if mctx was wrong `mctx[bits[-2]]` may fails.
211 # if mctx was wrong `mctx[bits[-2]]` may fails.
212 for idx, r in enumerate(v1records):
212 for idx, r in enumerate(v1records):
213 if r[0] == 'F':
213 if r[0] == 'F':
214 bits = r[1].split('\0')
214 bits = r[1].split('\0')
215 bits.insert(-2, '')
215 bits.insert(-2, '')
216 v1records[idx] = (r[0], '\0'.join(bits))
216 v1records[idx] = (r[0], '\0'.join(bits))
217 return v1records
217 return v1records
218
218
219 def _v1v2match(self, v1records, v2records):
219 def _v1v2match(self, v1records, v2records):
220 oldv2 = set() # old format version of v2 record
220 oldv2 = set() # old format version of v2 record
221 for rec in v2records:
221 for rec in v2records:
222 if rec[0] == 'L':
222 if rec[0] == 'L':
223 oldv2.add(rec)
223 oldv2.add(rec)
224 elif rec[0] == 'F':
224 elif rec[0] == 'F':
225 # drop the onode data (not contained in v1)
225 # drop the onode data (not contained in v1)
226 oldv2.add(('F', _droponode(rec[1])))
226 oldv2.add(('F', _droponode(rec[1])))
227 for rec in v1records:
227 for rec in v1records:
228 if rec not in oldv2:
228 if rec not in oldv2:
229 return False
229 return False
230 else:
230 else:
231 return True
231 return True
232
232
233 def _readrecordsv1(self):
233 def _readrecordsv1(self):
234 """read on disk merge state for version 1 file
234 """read on disk merge state for version 1 file
235
235
236 returns list of record [(TYPE, data), ...]
236 returns list of record [(TYPE, data), ...]
237
237
238 Note: the "F" data from this file are one entry short
238 Note: the "F" data from this file are one entry short
239 (no "other file node" entry)
239 (no "other file node" entry)
240 """
240 """
241 records = []
241 records = []
242 try:
242 try:
243 f = self._repo.vfs(self.statepathv1)
243 f = self._repo.vfs(self.statepathv1)
244 for i, l in enumerate(f):
244 for i, l in enumerate(f):
245 if i == 0:
245 if i == 0:
246 records.append(('L', l[:-1]))
246 records.append(('L', l[:-1]))
247 else:
247 else:
248 records.append(('F', l[:-1]))
248 records.append(('F', l[:-1]))
249 f.close()
249 f.close()
250 except IOError as err:
250 except IOError as err:
251 if err.errno != errno.ENOENT:
251 if err.errno != errno.ENOENT:
252 raise
252 raise
253 return records
253 return records
254
254
255 def _readrecordsv2(self):
255 def _readrecordsv2(self):
256 """read on disk merge state for version 2 file
256 """read on disk merge state for version 2 file
257
257
258 This format is a list of arbitrary records of the form:
258 This format is a list of arbitrary records of the form:
259
259
260 [type][length][content]
260 [type][length][content]
261
261
262 `type` is a single character, `length` is a 4 byte integer, and
262 `type` is a single character, `length` is a 4 byte integer, and
263 `content` is an arbitrary byte sequence of length `length`.
263 `content` is an arbitrary byte sequence of length `length`.
264
264
265 Mercurial versions prior to 3.7 have a bug where if there are
265 Mercurial versions prior to 3.7 have a bug where if there are
266 unsupported mandatory merge records, attempting to clear out the merge
266 unsupported mandatory merge records, attempting to clear out the merge
267 state with hg update --clean or similar aborts. The 't' record type
267 state with hg update --clean or similar aborts. The 't' record type
268 works around that by writing out what those versions treat as an
268 works around that by writing out what those versions treat as an
269 advisory record, but later versions interpret as special: the first
269 advisory record, but later versions interpret as special: the first
270 character is the 'real' record type and everything onwards is the data.
270 character is the 'real' record type and everything onwards is the data.
271
271
272 Returns list of records [(TYPE, data), ...]."""
272 Returns list of records [(TYPE, data), ...]."""
273 records = []
273 records = []
274 try:
274 try:
275 f = self._repo.vfs(self.statepathv2)
275 f = self._repo.vfs(self.statepathv2)
276 data = f.read()
276 data = f.read()
277 off = 0
277 off = 0
278 end = len(data)
278 end = len(data)
279 while off < end:
279 while off < end:
280 rtype = data[off]
280 rtype = data[off]
281 off += 1
281 off += 1
282 length = _unpack('>I', data[off:(off + 4)])[0]
282 length = _unpack('>I', data[off:(off + 4)])[0]
283 off += 4
283 off += 4
284 record = data[off:(off + length)]
284 record = data[off:(off + length)]
285 off += length
285 off += length
286 if rtype == 't':
286 if rtype == 't':
287 rtype, record = record[0], record[1:]
287 rtype, record = record[0], record[1:]
288 records.append((rtype, record))
288 records.append((rtype, record))
289 f.close()
289 f.close()
290 except IOError as err:
290 except IOError as err:
291 if err.errno != errno.ENOENT:
291 if err.errno != errno.ENOENT:
292 raise
292 raise
293 return records
293 return records
294
294
295 @util.propertycache
295 @util.propertycache
296 def mergedriver(self):
296 def mergedriver(self):
297 # protect against the following:
297 # protect against the following:
298 # - A configures a malicious merge driver in their hgrc, then
298 # - A configures a malicious merge driver in their hgrc, then
299 # pauses the merge
299 # pauses the merge
300 # - A edits their hgrc to remove references to the merge driver
300 # - A edits their hgrc to remove references to the merge driver
301 # - A gives a copy of their entire repo, including .hg, to B
301 # - A gives a copy of their entire repo, including .hg, to B
302 # - B inspects .hgrc and finds it to be clean
302 # - B inspects .hgrc and finds it to be clean
303 # - B then continues the merge and the malicious merge driver
303 # - B then continues the merge and the malicious merge driver
304 # gets invoked
304 # gets invoked
305 configmergedriver = self._repo.ui.config('experimental', 'mergedriver')
305 configmergedriver = self._repo.ui.config('experimental', 'mergedriver')
306 if (self._readmergedriver is not None
306 if (self._readmergedriver is not None
307 and self._readmergedriver != configmergedriver):
307 and self._readmergedriver != configmergedriver):
308 raise error.ConfigError(
308 raise error.ConfigError(
309 _("merge driver changed since merge started"),
309 _("merge driver changed since merge started"),
310 hint=_("revert merge driver change or abort merge"))
310 hint=_("revert merge driver change or abort merge"))
311
311
312 return configmergedriver
312 return configmergedriver
313
313
314 @util.propertycache
314 @util.propertycache
315 def localctx(self):
315 def localctx(self):
316 if self._local is None:
316 if self._local is None:
317 msg = "localctx accessed but self._local isn't set"
317 msg = "localctx accessed but self._local isn't set"
318 raise error.ProgrammingError(msg)
318 raise error.ProgrammingError(msg)
319 return self._repo[self._local]
319 return self._repo[self._local]
320
320
321 @util.propertycache
321 @util.propertycache
322 def otherctx(self):
322 def otherctx(self):
323 if self._other is None:
323 if self._other is None:
324 msg = "otherctx accessed but self._other isn't set"
324 msg = "otherctx accessed but self._other isn't set"
325 raise error.ProgrammingError(msg)
325 raise error.ProgrammingError(msg)
326 return self._repo[self._other]
326 return self._repo[self._other]
327
327
328 def active(self):
328 def active(self):
329 """Whether mergestate is active.
329 """Whether mergestate is active.
330
330
331 Returns True if there appears to be mergestate. This is a rough proxy
331 Returns True if there appears to be mergestate. This is a rough proxy
332 for "is a merge in progress."
332 for "is a merge in progress."
333 """
333 """
334 # Check local variables before looking at filesystem for performance
334 # Check local variables before looking at filesystem for performance
335 # reasons.
335 # reasons.
336 return bool(self._local) or bool(self._state) or \
336 return bool(self._local) or bool(self._state) or \
337 self._repo.vfs.exists(self.statepathv1) or \
337 self._repo.vfs.exists(self.statepathv1) or \
338 self._repo.vfs.exists(self.statepathv2)
338 self._repo.vfs.exists(self.statepathv2)
339
339
340 def commit(self):
340 def commit(self):
341 """Write current state on disk (if necessary)"""
341 """Write current state on disk (if necessary)"""
342 if self._dirty:
342 if self._dirty:
343 records = self._makerecords()
343 records = self._makerecords()
344 self._writerecords(records)
344 self._writerecords(records)
345 self._dirty = False
345 self._dirty = False
346
346
347 def _makerecords(self):
347 def _makerecords(self):
348 records = []
348 records = []
349 records.append(('L', hex(self._local)))
349 records.append(('L', hex(self._local)))
350 records.append(('O', hex(self._other)))
350 records.append(('O', hex(self._other)))
351 if self.mergedriver:
351 if self.mergedriver:
352 records.append(('m', '\0'.join([
352 records.append(('m', '\0'.join([
353 self.mergedriver, self._mdstate])))
353 self.mergedriver, self._mdstate])))
354 for d, v in self._state.iteritems():
354 for d, v in self._state.iteritems():
355 if v[0] == 'd':
355 if v[0] == 'd':
356 records.append(('D', '\0'.join([d] + v)))
356 records.append(('D', '\0'.join([d] + v)))
357 # v[1] == local ('cd'), v[6] == other ('dc') -- not supported by
357 # v[1] == local ('cd'), v[6] == other ('dc') -- not supported by
358 # older versions of Mercurial
358 # older versions of Mercurial
359 elif v[1] == nullhex or v[6] == nullhex:
359 elif v[1] == nullhex or v[6] == nullhex:
360 records.append(('C', '\0'.join([d] + v)))
360 records.append(('C', '\0'.join([d] + v)))
361 else:
361 else:
362 records.append(('F', '\0'.join([d] + v)))
362 records.append(('F', '\0'.join([d] + v)))
363 for filename, extras in sorted(self._stateextras.iteritems()):
363 for filename, extras in sorted(self._stateextras.iteritems()):
364 rawextras = '\0'.join('%s\0%s' % (k, v) for k, v in
364 rawextras = '\0'.join('%s\0%s' % (k, v) for k, v in
365 extras.iteritems())
365 extras.iteritems())
366 records.append(('f', '%s\0%s' % (filename, rawextras)))
366 records.append(('f', '%s\0%s' % (filename, rawextras)))
367 if self._labels is not None:
367 if self._labels is not None:
368 labels = '\0'.join(self._labels)
368 labels = '\0'.join(self._labels)
369 records.append(('l', labels))
369 records.append(('l', labels))
370 return records
370 return records
371
371
372 def _writerecords(self, records):
372 def _writerecords(self, records):
373 """Write current state on disk (both v1 and v2)"""
373 """Write current state on disk (both v1 and v2)"""
374 self._writerecordsv1(records)
374 self._writerecordsv1(records)
375 self._writerecordsv2(records)
375 self._writerecordsv2(records)
376
376
377 def _writerecordsv1(self, records):
377 def _writerecordsv1(self, records):
378 """Write current state on disk in a version 1 file"""
378 """Write current state on disk in a version 1 file"""
379 f = self._repo.vfs(self.statepathv1, 'w')
379 f = self._repo.vfs(self.statepathv1, 'w')
380 irecords = iter(records)
380 irecords = iter(records)
381 lrecords = next(irecords)
381 lrecords = next(irecords)
382 assert lrecords[0] == 'L'
382 assert lrecords[0] == 'L'
383 f.write(hex(self._local) + '\n')
383 f.write(hex(self._local) + '\n')
384 for rtype, data in irecords:
384 for rtype, data in irecords:
385 if rtype == 'F':
385 if rtype == 'F':
386 f.write('%s\n' % _droponode(data))
386 f.write('%s\n' % _droponode(data))
387 f.close()
387 f.close()
388
388
389 def _writerecordsv2(self, records):
389 def _writerecordsv2(self, records):
390 """Write current state on disk in a version 2 file
390 """Write current state on disk in a version 2 file
391
391
392 See the docstring for _readrecordsv2 for why we use 't'."""
392 See the docstring for _readrecordsv2 for why we use 't'."""
393 # these are the records that all version 2 clients can read
393 # these are the records that all version 2 clients can read
394 whitelist = 'LOF'
394 whitelist = 'LOF'
395 f = self._repo.vfs(self.statepathv2, 'w')
395 f = self._repo.vfs(self.statepathv2, 'w')
396 for key, data in records:
396 for key, data in records:
397 assert len(key) == 1
397 assert len(key) == 1
398 if key not in whitelist:
398 if key not in whitelist:
399 key, data = 't', '%s%s' % (key, data)
399 key, data = 't', '%s%s' % (key, data)
400 format = '>sI%is' % len(data)
400 format = '>sI%is' % len(data)
401 f.write(_pack(format, key, len(data), data))
401 f.write(_pack(format, key, len(data), data))
402 f.close()
402 f.close()
403
403
404 def add(self, fcl, fco, fca, fd):
404 def add(self, fcl, fco, fca, fd):
405 """add a new (potentially?) conflicting file the merge state
405 """add a new (potentially?) conflicting file the merge state
406 fcl: file context for local,
406 fcl: file context for local,
407 fco: file context for remote,
407 fco: file context for remote,
408 fca: file context for ancestors,
408 fca: file context for ancestors,
409 fd: file path of the resulting merge.
409 fd: file path of the resulting merge.
410
410
411 note: also write the local version to the `.hg/merge` directory.
411 note: also write the local version to the `.hg/merge` directory.
412 """
412 """
413 if fcl.isabsent():
413 if fcl.isabsent():
414 hash = nullhex
414 hash = nullhex
415 else:
415 else:
416 hash = hex(hashlib.sha1(fcl.path()).digest())
416 hash = hex(hashlib.sha1(fcl.path()).digest())
417 self._repo.vfs.write('merge/' + hash, fcl.data())
417 self._repo.vfs.write('merge/' + hash, fcl.data())
418 self._state[fd] = ['u', hash, fcl.path(),
418 self._state[fd] = ['u', hash, fcl.path(),
419 fca.path(), hex(fca.filenode()),
419 fca.path(), hex(fca.filenode()),
420 fco.path(), hex(fco.filenode()),
420 fco.path(), hex(fco.filenode()),
421 fcl.flags()]
421 fcl.flags()]
422 self._stateextras[fd] = { 'ancestorlinknode' : hex(fca.node()) }
422 self._stateextras[fd] = { 'ancestorlinknode' : hex(fca.node()) }
423 self._dirty = True
423 self._dirty = True
424
424
425 def __contains__(self, dfile):
425 def __contains__(self, dfile):
426 return dfile in self._state
426 return dfile in self._state
427
427
428 def __getitem__(self, dfile):
428 def __getitem__(self, dfile):
429 return self._state[dfile][0]
429 return self._state[dfile][0]
430
430
431 def __iter__(self):
431 def __iter__(self):
432 return iter(sorted(self._state))
432 return iter(sorted(self._state))
433
433
434 def files(self):
434 def files(self):
435 return self._state.keys()
435 return self._state.keys()
436
436
437 def mark(self, dfile, state):
437 def mark(self, dfile, state):
438 self._state[dfile][0] = state
438 self._state[dfile][0] = state
439 self._dirty = True
439 self._dirty = True
440
440
441 def mdstate(self):
441 def mdstate(self):
442 return self._mdstate
442 return self._mdstate
443
443
444 def unresolved(self):
444 def unresolved(self):
445 """Obtain the paths of unresolved files."""
445 """Obtain the paths of unresolved files."""
446
446
447 for f, entry in self._state.iteritems():
447 for f, entry in self._state.iteritems():
448 if entry[0] == 'u':
448 if entry[0] == 'u':
449 yield f
449 yield f
450
450
451 def driverresolved(self):
451 def driverresolved(self):
452 """Obtain the paths of driver-resolved files."""
452 """Obtain the paths of driver-resolved files."""
453
453
454 for f, entry in self._state.items():
454 for f, entry in self._state.items():
455 if entry[0] == 'd':
455 if entry[0] == 'd':
456 yield f
456 yield f
457
457
458 def extras(self, filename):
458 def extras(self, filename):
459 return self._stateextras.setdefault(filename, {})
459 return self._stateextras.setdefault(filename, {})
460
460
461 def _resolve(self, preresolve, dfile, wctx):
461 def _resolve(self, preresolve, dfile, wctx):
462 """rerun merge process for file path `dfile`"""
462 """rerun merge process for file path `dfile`"""
463 if self[dfile] in 'rd':
463 if self[dfile] in 'rd':
464 return True, 0
464 return True, 0
465 stateentry = self._state[dfile]
465 stateentry = self._state[dfile]
466 state, hash, lfile, afile, anode, ofile, onode, flags = stateentry
466 state, hash, lfile, afile, anode, ofile, onode, flags = stateentry
467 octx = self._repo[self._other]
467 octx = self._repo[self._other]
468 extras = self.extras(dfile)
468 extras = self.extras(dfile)
469 anccommitnode = extras.get('ancestorlinknode')
469 anccommitnode = extras.get('ancestorlinknode')
470 if anccommitnode:
470 if anccommitnode:
471 actx = self._repo[anccommitnode]
471 actx = self._repo[anccommitnode]
472 else:
472 else:
473 actx = None
473 actx = None
474 fcd = self._filectxorabsent(hash, wctx, dfile)
474 fcd = self._filectxorabsent(hash, wctx, dfile)
475 fco = self._filectxorabsent(onode, octx, ofile)
475 fco = self._filectxorabsent(onode, octx, ofile)
476 # TODO: move this to filectxorabsent
476 # TODO: move this to filectxorabsent
477 fca = self._repo.filectx(afile, fileid=anode, changeid=actx)
477 fca = self._repo.filectx(afile, fileid=anode, changeid=actx)
478 # "premerge" x flags
478 # "premerge" x flags
479 flo = fco.flags()
479 flo = fco.flags()
480 fla = fca.flags()
480 fla = fca.flags()
481 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
481 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
482 if fca.node() == nullid and flags != flo:
482 if fca.node() == nullid and flags != flo:
483 if preresolve:
483 if preresolve:
484 self._repo.ui.warn(
484 self._repo.ui.warn(
485 _('warning: cannot merge flags for %s '
485 _('warning: cannot merge flags for %s '
486 'without common ancestor - keeping local flags\n')
486 'without common ancestor - keeping local flags\n')
487 % afile)
487 % afile)
488 elif flags == fla:
488 elif flags == fla:
489 flags = flo
489 flags = flo
490 if preresolve:
490 if preresolve:
491 # restore local
491 # restore local
492 if hash != nullhex:
492 if hash != nullhex:
493 f = self._repo.vfs('merge/' + hash)
493 f = self._repo.vfs('merge/' + hash)
494 wctx[dfile].write(f.read(), flags)
494 wctx[dfile].write(f.read(), flags)
495 f.close()
495 f.close()
496 else:
496 else:
497 wctx[dfile].remove(ignoremissing=True)
497 wctx[dfile].remove(ignoremissing=True)
498 complete, r, deleted = filemerge.premerge(self._repo, self._local,
498 complete, r, deleted = filemerge.premerge(self._repo, self._local,
499 lfile, fcd, fco, fca,
499 lfile, fcd, fco, fca,
500 labels=self._labels)
500 labels=self._labels)
501 else:
501 else:
502 complete, r, deleted = filemerge.filemerge(self._repo, self._local,
502 complete, r, deleted = filemerge.filemerge(self._repo, self._local,
503 lfile, fcd, fco, fca,
503 lfile, fcd, fco, fca,
504 labels=self._labels)
504 labels=self._labels)
505 if r is None:
505 if r is None:
506 # no real conflict
506 # no real conflict
507 del self._state[dfile]
507 del self._state[dfile]
508 self._stateextras.pop(dfile, None)
508 self._stateextras.pop(dfile, None)
509 self._dirty = True
509 self._dirty = True
510 elif not r:
510 elif not r:
511 self.mark(dfile, 'r')
511 self.mark(dfile, 'r')
512
512
513 if complete:
513 if complete:
514 action = None
514 action = None
515 if deleted:
515 if deleted:
516 if fcd.isabsent():
516 if fcd.isabsent():
517 # dc: local picked. Need to drop if present, which may
517 # dc: local picked. Need to drop if present, which may
518 # happen on re-resolves.
518 # happen on re-resolves.
519 action = 'f'
519 action = 'f'
520 else:
520 else:
521 # cd: remote picked (or otherwise deleted)
521 # cd: remote picked (or otherwise deleted)
522 action = 'r'
522 action = 'r'
523 else:
523 else:
524 if fcd.isabsent(): # dc: remote picked
524 if fcd.isabsent(): # dc: remote picked
525 action = 'g'
525 action = 'g'
526 elif fco.isabsent(): # cd: local picked
526 elif fco.isabsent(): # cd: local picked
527 if dfile in self.localctx:
527 if dfile in self.localctx:
528 action = 'am'
528 action = 'am'
529 else:
529 else:
530 action = 'a'
530 action = 'a'
531 # else: regular merges (no action necessary)
531 # else: regular merges (no action necessary)
532 self._results[dfile] = r, action
532 self._results[dfile] = r, action
533
533
534 return complete, r
534 return complete, r
535
535
536 def _filectxorabsent(self, hexnode, ctx, f):
536 def _filectxorabsent(self, hexnode, ctx, f):
537 if hexnode == nullhex:
537 if hexnode == nullhex:
538 return filemerge.absentfilectx(ctx, f)
538 return filemerge.absentfilectx(ctx, f)
539 else:
539 else:
540 return ctx[f]
540 return ctx[f]
541
541
542 def preresolve(self, dfile, wctx):
542 def preresolve(self, dfile, wctx):
543 """run premerge process for dfile
543 """run premerge process for dfile
544
544
545 Returns whether the merge is complete, and the exit code."""
545 Returns whether the merge is complete, and the exit code."""
546 return self._resolve(True, dfile, wctx)
546 return self._resolve(True, dfile, wctx)
547
547
548 def resolve(self, dfile, wctx):
548 def resolve(self, dfile, wctx):
549 """run merge process (assuming premerge was run) for dfile
549 """run merge process (assuming premerge was run) for dfile
550
550
551 Returns the exit code of the merge."""
551 Returns the exit code of the merge."""
552 return self._resolve(False, dfile, wctx)[1]
552 return self._resolve(False, dfile, wctx)[1]
553
553
554 def counts(self):
554 def counts(self):
555 """return counts for updated, merged and removed files in this
555 """return counts for updated, merged and removed files in this
556 session"""
556 session"""
557 updated, merged, removed = 0, 0, 0
557 updated, merged, removed = 0, 0, 0
558 for r, action in self._results.itervalues():
558 for r, action in self._results.itervalues():
559 if r is None:
559 if r is None:
560 updated += 1
560 updated += 1
561 elif r == 0:
561 elif r == 0:
562 if action == 'r':
562 if action == 'r':
563 removed += 1
563 removed += 1
564 else:
564 else:
565 merged += 1
565 merged += 1
566 return updated, merged, removed
566 return updated, merged, removed
567
567
568 def unresolvedcount(self):
568 def unresolvedcount(self):
569 """get unresolved count for this merge (persistent)"""
569 """get unresolved count for this merge (persistent)"""
570 return len([True for f, entry in self._state.iteritems()
570 return len(list(self.unresolved()))
571 if entry[0] == 'u'])
572
571
573 def actions(self):
572 def actions(self):
574 """return lists of actions to perform on the dirstate"""
573 """return lists of actions to perform on the dirstate"""
575 actions = {'r': [], 'f': [], 'a': [], 'am': [], 'g': []}
574 actions = {'r': [], 'f': [], 'a': [], 'am': [], 'g': []}
576 for f, (r, action) in self._results.iteritems():
575 for f, (r, action) in self._results.iteritems():
577 if action is not None:
576 if action is not None:
578 actions[action].append((f, None, "merge result"))
577 actions[action].append((f, None, "merge result"))
579 return actions
578 return actions
580
579
581 def recordactions(self):
580 def recordactions(self):
582 """record remove/add/get actions in the dirstate"""
581 """record remove/add/get actions in the dirstate"""
583 branchmerge = self._repo.dirstate.p2() != nullid
582 branchmerge = self._repo.dirstate.p2() != nullid
584 recordupdates(self._repo, self.actions(), branchmerge)
583 recordupdates(self._repo, self.actions(), branchmerge)
585
584
586 def queueremove(self, f):
585 def queueremove(self, f):
587 """queues a file to be removed from the dirstate
586 """queues a file to be removed from the dirstate
588
587
589 Meant for use by custom merge drivers."""
588 Meant for use by custom merge drivers."""
590 self._results[f] = 0, 'r'
589 self._results[f] = 0, 'r'
591
590
592 def queueadd(self, f):
591 def queueadd(self, f):
593 """queues a file to be added to the dirstate
592 """queues a file to be added to the dirstate
594
593
595 Meant for use by custom merge drivers."""
594 Meant for use by custom merge drivers."""
596 self._results[f] = 0, 'a'
595 self._results[f] = 0, 'a'
597
596
598 def queueget(self, f):
597 def queueget(self, f):
599 """queues a file to be marked modified in the dirstate
598 """queues a file to be marked modified in the dirstate
600
599
601 Meant for use by custom merge drivers."""
600 Meant for use by custom merge drivers."""
602 self._results[f] = 0, 'g'
601 self._results[f] = 0, 'g'
603
602
604 def _getcheckunknownconfig(repo, section, name):
603 def _getcheckunknownconfig(repo, section, name):
605 config = repo.ui.config(section, name, default='abort')
604 config = repo.ui.config(section, name, default='abort')
606 valid = ['abort', 'ignore', 'warn']
605 valid = ['abort', 'ignore', 'warn']
607 if config not in valid:
606 if config not in valid:
608 validstr = ', '.join(["'" + v + "'" for v in valid])
607 validstr = ', '.join(["'" + v + "'" for v in valid])
609 raise error.ConfigError(_("%s.%s not valid "
608 raise error.ConfigError(_("%s.%s not valid "
610 "('%s' is none of %s)")
609 "('%s' is none of %s)")
611 % (section, name, config, validstr))
610 % (section, name, config, validstr))
612 return config
611 return config
613
612
614 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
613 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
615 if f2 is None:
614 if f2 is None:
616 f2 = f
615 f2 = f
617 return (repo.wvfs.audit.check(f)
616 return (repo.wvfs.audit.check(f)
618 and repo.wvfs.isfileorlink(f)
617 and repo.wvfs.isfileorlink(f)
619 and repo.dirstate.normalize(f) not in repo.dirstate
618 and repo.dirstate.normalize(f) not in repo.dirstate
620 and mctx[f2].cmp(wctx[f]))
619 and mctx[f2].cmp(wctx[f]))
621
620
622 def _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce):
621 def _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce):
623 """
622 """
624 Considers any actions that care about the presence of conflicting unknown
623 Considers any actions that care about the presence of conflicting unknown
625 files. For some actions, the result is to abort; for others, it is to
624 files. For some actions, the result is to abort; for others, it is to
626 choose a different action.
625 choose a different action.
627 """
626 """
628 conflicts = set()
627 conflicts = set()
629 warnconflicts = set()
628 warnconflicts = set()
630 abortconflicts = set()
629 abortconflicts = set()
631 unknownconfig = _getcheckunknownconfig(repo, 'merge', 'checkunknown')
630 unknownconfig = _getcheckunknownconfig(repo, 'merge', 'checkunknown')
632 ignoredconfig = _getcheckunknownconfig(repo, 'merge', 'checkignored')
631 ignoredconfig = _getcheckunknownconfig(repo, 'merge', 'checkignored')
633 if not force:
632 if not force:
634 def collectconflicts(conflicts, config):
633 def collectconflicts(conflicts, config):
635 if config == 'abort':
634 if config == 'abort':
636 abortconflicts.update(conflicts)
635 abortconflicts.update(conflicts)
637 elif config == 'warn':
636 elif config == 'warn':
638 warnconflicts.update(conflicts)
637 warnconflicts.update(conflicts)
639
638
640 for f, (m, args, msg) in actions.iteritems():
639 for f, (m, args, msg) in actions.iteritems():
641 if m in ('c', 'dc'):
640 if m in ('c', 'dc'):
642 if _checkunknownfile(repo, wctx, mctx, f):
641 if _checkunknownfile(repo, wctx, mctx, f):
643 conflicts.add(f)
642 conflicts.add(f)
644 elif m == 'dg':
643 elif m == 'dg':
645 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
644 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
646 conflicts.add(f)
645 conflicts.add(f)
647
646
648 ignoredconflicts = set([c for c in conflicts
647 ignoredconflicts = set([c for c in conflicts
649 if repo.dirstate._ignore(c)])
648 if repo.dirstate._ignore(c)])
650 unknownconflicts = conflicts - ignoredconflicts
649 unknownconflicts = conflicts - ignoredconflicts
651 collectconflicts(ignoredconflicts, ignoredconfig)
650 collectconflicts(ignoredconflicts, ignoredconfig)
652 collectconflicts(unknownconflicts, unknownconfig)
651 collectconflicts(unknownconflicts, unknownconfig)
653 else:
652 else:
654 for f, (m, args, msg) in actions.iteritems():
653 for f, (m, args, msg) in actions.iteritems():
655 if m == 'cm':
654 if m == 'cm':
656 fl2, anc = args
655 fl2, anc = args
657 different = _checkunknownfile(repo, wctx, mctx, f)
656 different = _checkunknownfile(repo, wctx, mctx, f)
658 if repo.dirstate._ignore(f):
657 if repo.dirstate._ignore(f):
659 config = ignoredconfig
658 config = ignoredconfig
660 else:
659 else:
661 config = unknownconfig
660 config = unknownconfig
662
661
663 # The behavior when force is True is described by this table:
662 # The behavior when force is True is described by this table:
664 # config different mergeforce | action backup
663 # config different mergeforce | action backup
665 # * n * | get n
664 # * n * | get n
666 # * y y | merge -
665 # * y y | merge -
667 # abort y n | merge - (1)
666 # abort y n | merge - (1)
668 # warn y n | warn + get y
667 # warn y n | warn + get y
669 # ignore y n | get y
668 # ignore y n | get y
670 #
669 #
671 # (1) this is probably the wrong behavior here -- we should
670 # (1) this is probably the wrong behavior here -- we should
672 # probably abort, but some actions like rebases currently
671 # probably abort, but some actions like rebases currently
673 # don't like an abort happening in the middle of
672 # don't like an abort happening in the middle of
674 # merge.update.
673 # merge.update.
675 if not different:
674 if not different:
676 actions[f] = ('g', (fl2, False), "remote created")
675 actions[f] = ('g', (fl2, False), "remote created")
677 elif mergeforce or config == 'abort':
676 elif mergeforce or config == 'abort':
678 actions[f] = ('m', (f, f, None, False, anc),
677 actions[f] = ('m', (f, f, None, False, anc),
679 "remote differs from untracked local")
678 "remote differs from untracked local")
680 elif config == 'abort':
679 elif config == 'abort':
681 abortconflicts.add(f)
680 abortconflicts.add(f)
682 else:
681 else:
683 if config == 'warn':
682 if config == 'warn':
684 warnconflicts.add(f)
683 warnconflicts.add(f)
685 actions[f] = ('g', (fl2, True), "remote created")
684 actions[f] = ('g', (fl2, True), "remote created")
686
685
687 for f in sorted(abortconflicts):
686 for f in sorted(abortconflicts):
688 repo.ui.warn(_("%s: untracked file differs\n") % f)
687 repo.ui.warn(_("%s: untracked file differs\n") % f)
689 if abortconflicts:
688 if abortconflicts:
690 raise error.Abort(_("untracked files in working directory "
689 raise error.Abort(_("untracked files in working directory "
691 "differ from files in requested revision"))
690 "differ from files in requested revision"))
692
691
693 for f in sorted(warnconflicts):
692 for f in sorted(warnconflicts):
694 repo.ui.warn(_("%s: replacing untracked file\n") % f)
693 repo.ui.warn(_("%s: replacing untracked file\n") % f)
695
694
696 for f, (m, args, msg) in actions.iteritems():
695 for f, (m, args, msg) in actions.iteritems():
697 backup = f in conflicts
696 backup = f in conflicts
698 if m == 'c':
697 if m == 'c':
699 flags, = args
698 flags, = args
700 actions[f] = ('g', (flags, backup), msg)
699 actions[f] = ('g', (flags, backup), msg)
701
700
702 def _forgetremoved(wctx, mctx, branchmerge):
701 def _forgetremoved(wctx, mctx, branchmerge):
703 """
702 """
704 Forget removed files
703 Forget removed files
705
704
706 If we're jumping between revisions (as opposed to merging), and if
705 If we're jumping between revisions (as opposed to merging), and if
707 neither the working directory nor the target rev has the file,
706 neither the working directory nor the target rev has the file,
708 then we need to remove it from the dirstate, to prevent the
707 then we need to remove it from the dirstate, to prevent the
709 dirstate from listing the file when it is no longer in the
708 dirstate from listing the file when it is no longer in the
710 manifest.
709 manifest.
711
710
712 If we're merging, and the other revision has removed a file
711 If we're merging, and the other revision has removed a file
713 that is not present in the working directory, we need to mark it
712 that is not present in the working directory, we need to mark it
714 as removed.
713 as removed.
715 """
714 """
716
715
717 actions = {}
716 actions = {}
718 m = 'f'
717 m = 'f'
719 if branchmerge:
718 if branchmerge:
720 m = 'r'
719 m = 'r'
721 for f in wctx.deleted():
720 for f in wctx.deleted():
722 if f not in mctx:
721 if f not in mctx:
723 actions[f] = m, None, "forget deleted"
722 actions[f] = m, None, "forget deleted"
724
723
725 if not branchmerge:
724 if not branchmerge:
726 for f in wctx.removed():
725 for f in wctx.removed():
727 if f not in mctx:
726 if f not in mctx:
728 actions[f] = 'f', None, "forget removed"
727 actions[f] = 'f', None, "forget removed"
729
728
730 return actions
729 return actions
731
730
732 def _checkcollision(repo, wmf, actions):
731 def _checkcollision(repo, wmf, actions):
733 # build provisional merged manifest up
732 # build provisional merged manifest up
734 pmmf = set(wmf)
733 pmmf = set(wmf)
735
734
736 if actions:
735 if actions:
737 # k, dr, e and rd are no-op
736 # k, dr, e and rd are no-op
738 for m in 'a', 'am', 'f', 'g', 'cd', 'dc':
737 for m in 'a', 'am', 'f', 'g', 'cd', 'dc':
739 for f, args, msg in actions[m]:
738 for f, args, msg in actions[m]:
740 pmmf.add(f)
739 pmmf.add(f)
741 for f, args, msg in actions['r']:
740 for f, args, msg in actions['r']:
742 pmmf.discard(f)
741 pmmf.discard(f)
743 for f, args, msg in actions['dm']:
742 for f, args, msg in actions['dm']:
744 f2, flags = args
743 f2, flags = args
745 pmmf.discard(f2)
744 pmmf.discard(f2)
746 pmmf.add(f)
745 pmmf.add(f)
747 for f, args, msg in actions['dg']:
746 for f, args, msg in actions['dg']:
748 pmmf.add(f)
747 pmmf.add(f)
749 for f, args, msg in actions['m']:
748 for f, args, msg in actions['m']:
750 f1, f2, fa, move, anc = args
749 f1, f2, fa, move, anc = args
751 if move:
750 if move:
752 pmmf.discard(f1)
751 pmmf.discard(f1)
753 pmmf.add(f)
752 pmmf.add(f)
754
753
755 # check case-folding collision in provisional merged manifest
754 # check case-folding collision in provisional merged manifest
756 foldmap = {}
755 foldmap = {}
757 for f in sorted(pmmf):
756 for f in sorted(pmmf):
758 fold = util.normcase(f)
757 fold = util.normcase(f)
759 if fold in foldmap:
758 if fold in foldmap:
760 raise error.Abort(_("case-folding collision between %s and %s")
759 raise error.Abort(_("case-folding collision between %s and %s")
761 % (f, foldmap[fold]))
760 % (f, foldmap[fold]))
762 foldmap[fold] = f
761 foldmap[fold] = f
763
762
764 # check case-folding of directories
763 # check case-folding of directories
765 foldprefix = unfoldprefix = lastfull = ''
764 foldprefix = unfoldprefix = lastfull = ''
766 for fold, f in sorted(foldmap.items()):
765 for fold, f in sorted(foldmap.items()):
767 if fold.startswith(foldprefix) and not f.startswith(unfoldprefix):
766 if fold.startswith(foldprefix) and not f.startswith(unfoldprefix):
768 # the folded prefix matches but actual casing is different
767 # the folded prefix matches but actual casing is different
769 raise error.Abort(_("case-folding collision between "
768 raise error.Abort(_("case-folding collision between "
770 "%s and directory of %s") % (lastfull, f))
769 "%s and directory of %s") % (lastfull, f))
771 foldprefix = fold + '/'
770 foldprefix = fold + '/'
772 unfoldprefix = f + '/'
771 unfoldprefix = f + '/'
773 lastfull = f
772 lastfull = f
774
773
775 def driverpreprocess(repo, ms, wctx, labels=None):
774 def driverpreprocess(repo, ms, wctx, labels=None):
776 """run the preprocess step of the merge driver, if any
775 """run the preprocess step of the merge driver, if any
777
776
778 This is currently not implemented -- it's an extension point."""
777 This is currently not implemented -- it's an extension point."""
779 return True
778 return True
780
779
781 def driverconclude(repo, ms, wctx, labels=None):
780 def driverconclude(repo, ms, wctx, labels=None):
782 """run the conclude step of the merge driver, if any
781 """run the conclude step of the merge driver, if any
783
782
784 This is currently not implemented -- it's an extension point."""
783 This is currently not implemented -- it's an extension point."""
785 return True
784 return True
786
785
787 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, matcher,
786 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, matcher,
788 acceptremote, followcopies, forcefulldiff=False):
787 acceptremote, followcopies, forcefulldiff=False):
789 """
788 """
790 Merge wctx and p2 with ancestor pa and generate merge action list
789 Merge wctx and p2 with ancestor pa and generate merge action list
791
790
792 branchmerge and force are as passed in to update
791 branchmerge and force are as passed in to update
793 matcher = matcher to filter file lists
792 matcher = matcher to filter file lists
794 acceptremote = accept the incoming changes without prompting
793 acceptremote = accept the incoming changes without prompting
795 """
794 """
796 if matcher is not None and matcher.always():
795 if matcher is not None and matcher.always():
797 matcher = None
796 matcher = None
798
797
799 copy, movewithdir, diverge, renamedelete, dirmove = {}, {}, {}, {}, {}
798 copy, movewithdir, diverge, renamedelete, dirmove = {}, {}, {}, {}, {}
800
799
801 # manifests fetched in order are going to be faster, so prime the caches
800 # manifests fetched in order are going to be faster, so prime the caches
802 [x.manifest() for x in
801 [x.manifest() for x in
803 sorted(wctx.parents() + [p2, pa], key=scmutil.intrev)]
802 sorted(wctx.parents() + [p2, pa], key=scmutil.intrev)]
804
803
805 if followcopies:
804 if followcopies:
806 ret = copies.mergecopies(repo, wctx, p2, pa)
805 ret = copies.mergecopies(repo, wctx, p2, pa)
807 copy, movewithdir, diverge, renamedelete, dirmove = ret
806 copy, movewithdir, diverge, renamedelete, dirmove = ret
808
807
809 boolbm = pycompat.bytestr(bool(branchmerge))
808 boolbm = pycompat.bytestr(bool(branchmerge))
810 boolf = pycompat.bytestr(bool(force))
809 boolf = pycompat.bytestr(bool(force))
811 boolm = pycompat.bytestr(bool(matcher))
810 boolm = pycompat.bytestr(bool(matcher))
812 repo.ui.note(_("resolving manifests\n"))
811 repo.ui.note(_("resolving manifests\n"))
813 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
812 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
814 % (boolbm, boolf, boolm))
813 % (boolbm, boolf, boolm))
815 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
814 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
816
815
817 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
816 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
818 copied = set(copy.values())
817 copied = set(copy.values())
819 copied.update(movewithdir.values())
818 copied.update(movewithdir.values())
820
819
821 if '.hgsubstate' in m1:
820 if '.hgsubstate' in m1:
822 # check whether sub state is modified
821 # check whether sub state is modified
823 if any(wctx.sub(s).dirty() for s in wctx.substate):
822 if any(wctx.sub(s).dirty() for s in wctx.substate):
824 m1['.hgsubstate'] = modifiednodeid
823 m1['.hgsubstate'] = modifiednodeid
825
824
826 # Don't use m2-vs-ma optimization if:
825 # Don't use m2-vs-ma optimization if:
827 # - ma is the same as m1 or m2, which we're just going to diff again later
826 # - ma is the same as m1 or m2, which we're just going to diff again later
828 # - The caller specifically asks for a full diff, which is useful during bid
827 # - The caller specifically asks for a full diff, which is useful during bid
829 # merge.
828 # merge.
830 if (pa not in ([wctx, p2] + wctx.parents()) and not forcefulldiff):
829 if (pa not in ([wctx, p2] + wctx.parents()) and not forcefulldiff):
831 # Identify which files are relevant to the merge, so we can limit the
830 # Identify which files are relevant to the merge, so we can limit the
832 # total m1-vs-m2 diff to just those files. This has significant
831 # total m1-vs-m2 diff to just those files. This has significant
833 # performance benefits in large repositories.
832 # performance benefits in large repositories.
834 relevantfiles = set(ma.diff(m2).keys())
833 relevantfiles = set(ma.diff(m2).keys())
835
834
836 # For copied and moved files, we need to add the source file too.
835 # For copied and moved files, we need to add the source file too.
837 for copykey, copyvalue in copy.iteritems():
836 for copykey, copyvalue in copy.iteritems():
838 if copyvalue in relevantfiles:
837 if copyvalue in relevantfiles:
839 relevantfiles.add(copykey)
838 relevantfiles.add(copykey)
840 for movedirkey in movewithdir:
839 for movedirkey in movewithdir:
841 relevantfiles.add(movedirkey)
840 relevantfiles.add(movedirkey)
842 filesmatcher = scmutil.matchfiles(repo, relevantfiles)
841 filesmatcher = scmutil.matchfiles(repo, relevantfiles)
843 matcher = matchmod.intersectmatchers(matcher, filesmatcher)
842 matcher = matchmod.intersectmatchers(matcher, filesmatcher)
844
843
845 diff = m1.diff(m2, match=matcher)
844 diff = m1.diff(m2, match=matcher)
846
845
847 if matcher is None:
846 if matcher is None:
848 matcher = matchmod.always('', '')
847 matcher = matchmod.always('', '')
849
848
850 actions = {}
849 actions = {}
851 for f, ((n1, fl1), (n2, fl2)) in diff.iteritems():
850 for f, ((n1, fl1), (n2, fl2)) in diff.iteritems():
852 if n1 and n2: # file exists on both local and remote side
851 if n1 and n2: # file exists on both local and remote side
853 if f not in ma:
852 if f not in ma:
854 fa = copy.get(f, None)
853 fa = copy.get(f, None)
855 if fa is not None:
854 if fa is not None:
856 actions[f] = ('m', (f, f, fa, False, pa.node()),
855 actions[f] = ('m', (f, f, fa, False, pa.node()),
857 "both renamed from " + fa)
856 "both renamed from " + fa)
858 else:
857 else:
859 actions[f] = ('m', (f, f, None, False, pa.node()),
858 actions[f] = ('m', (f, f, None, False, pa.node()),
860 "both created")
859 "both created")
861 else:
860 else:
862 a = ma[f]
861 a = ma[f]
863 fla = ma.flags(f)
862 fla = ma.flags(f)
864 nol = 'l' not in fl1 + fl2 + fla
863 nol = 'l' not in fl1 + fl2 + fla
865 if n2 == a and fl2 == fla:
864 if n2 == a and fl2 == fla:
866 actions[f] = ('k' , (), "remote unchanged")
865 actions[f] = ('k' , (), "remote unchanged")
867 elif n1 == a and fl1 == fla: # local unchanged - use remote
866 elif n1 == a and fl1 == fla: # local unchanged - use remote
868 if n1 == n2: # optimization: keep local content
867 if n1 == n2: # optimization: keep local content
869 actions[f] = ('e', (fl2,), "update permissions")
868 actions[f] = ('e', (fl2,), "update permissions")
870 else:
869 else:
871 actions[f] = ('g', (fl2, False), "remote is newer")
870 actions[f] = ('g', (fl2, False), "remote is newer")
872 elif nol and n2 == a: # remote only changed 'x'
871 elif nol and n2 == a: # remote only changed 'x'
873 actions[f] = ('e', (fl2,), "update permissions")
872 actions[f] = ('e', (fl2,), "update permissions")
874 elif nol and n1 == a: # local only changed 'x'
873 elif nol and n1 == a: # local only changed 'x'
875 actions[f] = ('g', (fl1, False), "remote is newer")
874 actions[f] = ('g', (fl1, False), "remote is newer")
876 else: # both changed something
875 else: # both changed something
877 actions[f] = ('m', (f, f, f, False, pa.node()),
876 actions[f] = ('m', (f, f, f, False, pa.node()),
878 "versions differ")
877 "versions differ")
879 elif n1: # file exists only on local side
878 elif n1: # file exists only on local side
880 if f in copied:
879 if f in copied:
881 pass # we'll deal with it on m2 side
880 pass # we'll deal with it on m2 side
882 elif f in movewithdir: # directory rename, move local
881 elif f in movewithdir: # directory rename, move local
883 f2 = movewithdir[f]
882 f2 = movewithdir[f]
884 if f2 in m2:
883 if f2 in m2:
885 actions[f2] = ('m', (f, f2, None, True, pa.node()),
884 actions[f2] = ('m', (f, f2, None, True, pa.node()),
886 "remote directory rename, both created")
885 "remote directory rename, both created")
887 else:
886 else:
888 actions[f2] = ('dm', (f, fl1),
887 actions[f2] = ('dm', (f, fl1),
889 "remote directory rename - move from " + f)
888 "remote directory rename - move from " + f)
890 elif f in copy:
889 elif f in copy:
891 f2 = copy[f]
890 f2 = copy[f]
892 actions[f] = ('m', (f, f2, f2, False, pa.node()),
891 actions[f] = ('m', (f, f2, f2, False, pa.node()),
893 "local copied/moved from " + f2)
892 "local copied/moved from " + f2)
894 elif f in ma: # clean, a different, no remote
893 elif f in ma: # clean, a different, no remote
895 if n1 != ma[f]:
894 if n1 != ma[f]:
896 if acceptremote:
895 if acceptremote:
897 actions[f] = ('r', None, "remote delete")
896 actions[f] = ('r', None, "remote delete")
898 else:
897 else:
899 actions[f] = ('cd', (f, None, f, False, pa.node()),
898 actions[f] = ('cd', (f, None, f, False, pa.node()),
900 "prompt changed/deleted")
899 "prompt changed/deleted")
901 elif n1 == addednodeid:
900 elif n1 == addednodeid:
902 # This extra 'a' is added by working copy manifest to mark
901 # This extra 'a' is added by working copy manifest to mark
903 # the file as locally added. We should forget it instead of
902 # the file as locally added. We should forget it instead of
904 # deleting it.
903 # deleting it.
905 actions[f] = ('f', None, "remote deleted")
904 actions[f] = ('f', None, "remote deleted")
906 else:
905 else:
907 actions[f] = ('r', None, "other deleted")
906 actions[f] = ('r', None, "other deleted")
908 elif n2: # file exists only on remote side
907 elif n2: # file exists only on remote side
909 if f in copied:
908 if f in copied:
910 pass # we'll deal with it on m1 side
909 pass # we'll deal with it on m1 side
911 elif f in movewithdir:
910 elif f in movewithdir:
912 f2 = movewithdir[f]
911 f2 = movewithdir[f]
913 if f2 in m1:
912 if f2 in m1:
914 actions[f2] = ('m', (f2, f, None, False, pa.node()),
913 actions[f2] = ('m', (f2, f, None, False, pa.node()),
915 "local directory rename, both created")
914 "local directory rename, both created")
916 else:
915 else:
917 actions[f2] = ('dg', (f, fl2),
916 actions[f2] = ('dg', (f, fl2),
918 "local directory rename - get from " + f)
917 "local directory rename - get from " + f)
919 elif f in copy:
918 elif f in copy:
920 f2 = copy[f]
919 f2 = copy[f]
921 if f2 in m2:
920 if f2 in m2:
922 actions[f] = ('m', (f2, f, f2, False, pa.node()),
921 actions[f] = ('m', (f2, f, f2, False, pa.node()),
923 "remote copied from " + f2)
922 "remote copied from " + f2)
924 else:
923 else:
925 actions[f] = ('m', (f2, f, f2, True, pa.node()),
924 actions[f] = ('m', (f2, f, f2, True, pa.node()),
926 "remote moved from " + f2)
925 "remote moved from " + f2)
927 elif f not in ma:
926 elif f not in ma:
928 # local unknown, remote created: the logic is described by the
927 # local unknown, remote created: the logic is described by the
929 # following table:
928 # following table:
930 #
929 #
931 # force branchmerge different | action
930 # force branchmerge different | action
932 # n * * | create
931 # n * * | create
933 # y n * | create
932 # y n * | create
934 # y y n | create
933 # y y n | create
935 # y y y | merge
934 # y y y | merge
936 #
935 #
937 # Checking whether the files are different is expensive, so we
936 # Checking whether the files are different is expensive, so we
938 # don't do that when we can avoid it.
937 # don't do that when we can avoid it.
939 if not force:
938 if not force:
940 actions[f] = ('c', (fl2,), "remote created")
939 actions[f] = ('c', (fl2,), "remote created")
941 elif not branchmerge:
940 elif not branchmerge:
942 actions[f] = ('c', (fl2,), "remote created")
941 actions[f] = ('c', (fl2,), "remote created")
943 else:
942 else:
944 actions[f] = ('cm', (fl2, pa.node()),
943 actions[f] = ('cm', (fl2, pa.node()),
945 "remote created, get or merge")
944 "remote created, get or merge")
946 elif n2 != ma[f]:
945 elif n2 != ma[f]:
947 df = None
946 df = None
948 for d in dirmove:
947 for d in dirmove:
949 if f.startswith(d):
948 if f.startswith(d):
950 # new file added in a directory that was moved
949 # new file added in a directory that was moved
951 df = dirmove[d] + f[len(d):]
950 df = dirmove[d] + f[len(d):]
952 break
951 break
953 if df is not None and df in m1:
952 if df is not None and df in m1:
954 actions[df] = ('m', (df, f, f, False, pa.node()),
953 actions[df] = ('m', (df, f, f, False, pa.node()),
955 "local directory rename - respect move from " + f)
954 "local directory rename - respect move from " + f)
956 elif acceptremote:
955 elif acceptremote:
957 actions[f] = ('c', (fl2,), "remote recreating")
956 actions[f] = ('c', (fl2,), "remote recreating")
958 else:
957 else:
959 actions[f] = ('dc', (None, f, f, False, pa.node()),
958 actions[f] = ('dc', (None, f, f, False, pa.node()),
960 "prompt deleted/changed")
959 "prompt deleted/changed")
961
960
962 return actions, diverge, renamedelete
961 return actions, diverge, renamedelete
963
962
964 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
963 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
965 """Resolves false conflicts where the nodeid changed but the content
964 """Resolves false conflicts where the nodeid changed but the content
966 remained the same."""
965 remained the same."""
967
966
968 for f, (m, args, msg) in actions.items():
967 for f, (m, args, msg) in actions.items():
969 if m == 'cd' and f in ancestor and not wctx[f].cmp(ancestor[f]):
968 if m == 'cd' and f in ancestor and not wctx[f].cmp(ancestor[f]):
970 # local did change but ended up with same content
969 # local did change but ended up with same content
971 actions[f] = 'r', None, "prompt same"
970 actions[f] = 'r', None, "prompt same"
972 elif m == 'dc' and f in ancestor and not mctx[f].cmp(ancestor[f]):
971 elif m == 'dc' and f in ancestor and not mctx[f].cmp(ancestor[f]):
973 # remote did change but ended up with same content
972 # remote did change but ended up with same content
974 del actions[f] # don't get = keep local deleted
973 del actions[f] # don't get = keep local deleted
975
974
976 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force,
975 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force,
977 acceptremote, followcopies, matcher=None,
976 acceptremote, followcopies, matcher=None,
978 mergeforce=False):
977 mergeforce=False):
979 "Calculate the actions needed to merge mctx into wctx using ancestors"
978 "Calculate the actions needed to merge mctx into wctx using ancestors"
980 if len(ancestors) == 1: # default
979 if len(ancestors) == 1: # default
981 actions, diverge, renamedelete = manifestmerge(
980 actions, diverge, renamedelete = manifestmerge(
982 repo, wctx, mctx, ancestors[0], branchmerge, force, matcher,
981 repo, wctx, mctx, ancestors[0], branchmerge, force, matcher,
983 acceptremote, followcopies)
982 acceptremote, followcopies)
984 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
983 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
985
984
986 else: # only when merge.preferancestor=* - the default
985 else: # only when merge.preferancestor=* - the default
987 repo.ui.note(
986 repo.ui.note(
988 _("note: merging %s and %s using bids from ancestors %s\n") %
987 _("note: merging %s and %s using bids from ancestors %s\n") %
989 (wctx, mctx, _(' and ').join(str(anc) for anc in ancestors)))
988 (wctx, mctx, _(' and ').join(str(anc) for anc in ancestors)))
990
989
991 # Call for bids
990 # Call for bids
992 fbids = {} # mapping filename to bids (action method to list af actions)
991 fbids = {} # mapping filename to bids (action method to list af actions)
993 diverge, renamedelete = None, None
992 diverge, renamedelete = None, None
994 for ancestor in ancestors:
993 for ancestor in ancestors:
995 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
994 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
996 actions, diverge1, renamedelete1 = manifestmerge(
995 actions, diverge1, renamedelete1 = manifestmerge(
997 repo, wctx, mctx, ancestor, branchmerge, force, matcher,
996 repo, wctx, mctx, ancestor, branchmerge, force, matcher,
998 acceptremote, followcopies, forcefulldiff=True)
997 acceptremote, followcopies, forcefulldiff=True)
999 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
998 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
1000
999
1001 # Track the shortest set of warning on the theory that bid
1000 # Track the shortest set of warning on the theory that bid
1002 # merge will correctly incorporate more information
1001 # merge will correctly incorporate more information
1003 if diverge is None or len(diverge1) < len(diverge):
1002 if diverge is None or len(diverge1) < len(diverge):
1004 diverge = diverge1
1003 diverge = diverge1
1005 if renamedelete is None or len(renamedelete) < len(renamedelete1):
1004 if renamedelete is None or len(renamedelete) < len(renamedelete1):
1006 renamedelete = renamedelete1
1005 renamedelete = renamedelete1
1007
1006
1008 for f, a in sorted(actions.iteritems()):
1007 for f, a in sorted(actions.iteritems()):
1009 m, args, msg = a
1008 m, args, msg = a
1010 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
1009 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
1011 if f in fbids:
1010 if f in fbids:
1012 d = fbids[f]
1011 d = fbids[f]
1013 if m in d:
1012 if m in d:
1014 d[m].append(a)
1013 d[m].append(a)
1015 else:
1014 else:
1016 d[m] = [a]
1015 d[m] = [a]
1017 else:
1016 else:
1018 fbids[f] = {m: [a]}
1017 fbids[f] = {m: [a]}
1019
1018
1020 # Pick the best bid for each file
1019 # Pick the best bid for each file
1021 repo.ui.note(_('\nauction for merging merge bids\n'))
1020 repo.ui.note(_('\nauction for merging merge bids\n'))
1022 actions = {}
1021 actions = {}
1023 dms = [] # filenames that have dm actions
1022 dms = [] # filenames that have dm actions
1024 for f, bids in sorted(fbids.items()):
1023 for f, bids in sorted(fbids.items()):
1025 # bids is a mapping from action method to list af actions
1024 # bids is a mapping from action method to list af actions
1026 # Consensus?
1025 # Consensus?
1027 if len(bids) == 1: # all bids are the same kind of method
1026 if len(bids) == 1: # all bids are the same kind of method
1028 m, l = bids.items()[0]
1027 m, l = bids.items()[0]
1029 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
1028 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
1030 repo.ui.note(_(" %s: consensus for %s\n") % (f, m))
1029 repo.ui.note(_(" %s: consensus for %s\n") % (f, m))
1031 actions[f] = l[0]
1030 actions[f] = l[0]
1032 if m == 'dm':
1031 if m == 'dm':
1033 dms.append(f)
1032 dms.append(f)
1034 continue
1033 continue
1035 # If keep is an option, just do it.
1034 # If keep is an option, just do it.
1036 if 'k' in bids:
1035 if 'k' in bids:
1037 repo.ui.note(_(" %s: picking 'keep' action\n") % f)
1036 repo.ui.note(_(" %s: picking 'keep' action\n") % f)
1038 actions[f] = bids['k'][0]
1037 actions[f] = bids['k'][0]
1039 continue
1038 continue
1040 # If there are gets and they all agree [how could they not?], do it.
1039 # If there are gets and they all agree [how could they not?], do it.
1041 if 'g' in bids:
1040 if 'g' in bids:
1042 ga0 = bids['g'][0]
1041 ga0 = bids['g'][0]
1043 if all(a == ga0 for a in bids['g'][1:]):
1042 if all(a == ga0 for a in bids['g'][1:]):
1044 repo.ui.note(_(" %s: picking 'get' action\n") % f)
1043 repo.ui.note(_(" %s: picking 'get' action\n") % f)
1045 actions[f] = ga0
1044 actions[f] = ga0
1046 continue
1045 continue
1047 # TODO: Consider other simple actions such as mode changes
1046 # TODO: Consider other simple actions such as mode changes
1048 # Handle inefficient democrazy.
1047 # Handle inefficient democrazy.
1049 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
1048 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
1050 for m, l in sorted(bids.items()):
1049 for m, l in sorted(bids.items()):
1051 for _f, args, msg in l:
1050 for _f, args, msg in l:
1052 repo.ui.note(' %s -> %s\n' % (msg, m))
1051 repo.ui.note(' %s -> %s\n' % (msg, m))
1053 # Pick random action. TODO: Instead, prompt user when resolving
1052 # Pick random action. TODO: Instead, prompt user when resolving
1054 m, l = bids.items()[0]
1053 m, l = bids.items()[0]
1055 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
1054 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
1056 (f, m))
1055 (f, m))
1057 actions[f] = l[0]
1056 actions[f] = l[0]
1058 if m == 'dm':
1057 if m == 'dm':
1059 dms.append(f)
1058 dms.append(f)
1060 continue
1059 continue
1061 # Work around 'dm' that can cause multiple actions for the same file
1060 # Work around 'dm' that can cause multiple actions for the same file
1062 for f in dms:
1061 for f in dms:
1063 dm, (f0, flags), msg = actions[f]
1062 dm, (f0, flags), msg = actions[f]
1064 assert dm == 'dm', dm
1063 assert dm == 'dm', dm
1065 if f0 in actions and actions[f0][0] == 'r':
1064 if f0 in actions and actions[f0][0] == 'r':
1066 # We have one bid for removing a file and another for moving it.
1065 # We have one bid for removing a file and another for moving it.
1067 # These two could be merged as first move and then delete ...
1066 # These two could be merged as first move and then delete ...
1068 # but instead drop moving and just delete.
1067 # but instead drop moving and just delete.
1069 del actions[f]
1068 del actions[f]
1070 repo.ui.note(_('end of auction\n\n'))
1069 repo.ui.note(_('end of auction\n\n'))
1071
1070
1072 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
1071 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
1073
1072
1074 if wctx.rev() is None:
1073 if wctx.rev() is None:
1075 fractions = _forgetremoved(wctx, mctx, branchmerge)
1074 fractions = _forgetremoved(wctx, mctx, branchmerge)
1076 actions.update(fractions)
1075 actions.update(fractions)
1077
1076
1078 return actions, diverge, renamedelete
1077 return actions, diverge, renamedelete
1079
1078
1080 def batchremove(repo, wctx, actions):
1079 def batchremove(repo, wctx, actions):
1081 """apply removes to the working directory
1080 """apply removes to the working directory
1082
1081
1083 yields tuples for progress updates
1082 yields tuples for progress updates
1084 """
1083 """
1085 verbose = repo.ui.verbose
1084 verbose = repo.ui.verbose
1086 try:
1085 try:
1087 cwd = pycompat.getcwd()
1086 cwd = pycompat.getcwd()
1088 except OSError as err:
1087 except OSError as err:
1089 if err.errno != errno.ENOENT:
1088 if err.errno != errno.ENOENT:
1090 raise
1089 raise
1091 cwd = None
1090 cwd = None
1092 i = 0
1091 i = 0
1093 for f, args, msg in actions:
1092 for f, args, msg in actions:
1094 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
1093 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
1095 if verbose:
1094 if verbose:
1096 repo.ui.note(_("removing %s\n") % f)
1095 repo.ui.note(_("removing %s\n") % f)
1097 wctx[f].audit()
1096 wctx[f].audit()
1098 try:
1097 try:
1099 wctx[f].remove(ignoremissing=True)
1098 wctx[f].remove(ignoremissing=True)
1100 except OSError as inst:
1099 except OSError as inst:
1101 repo.ui.warn(_("update failed to remove %s: %s!\n") %
1100 repo.ui.warn(_("update failed to remove %s: %s!\n") %
1102 (f, inst.strerror))
1101 (f, inst.strerror))
1103 if i == 100:
1102 if i == 100:
1104 yield i, f
1103 yield i, f
1105 i = 0
1104 i = 0
1106 i += 1
1105 i += 1
1107 if i > 0:
1106 if i > 0:
1108 yield i, f
1107 yield i, f
1109 if cwd:
1108 if cwd:
1110 # cwd was present before we started to remove files
1109 # cwd was present before we started to remove files
1111 # let's check if it is present after we removed them
1110 # let's check if it is present after we removed them
1112 try:
1111 try:
1113 pycompat.getcwd()
1112 pycompat.getcwd()
1114 except OSError as err:
1113 except OSError as err:
1115 if err.errno != errno.ENOENT:
1114 if err.errno != errno.ENOENT:
1116 raise
1115 raise
1117 # Print a warning if cwd was deleted
1116 # Print a warning if cwd was deleted
1118 repo.ui.warn(_("current directory was removed\n"
1117 repo.ui.warn(_("current directory was removed\n"
1119 "(consider changing to repo root: %s)\n") %
1118 "(consider changing to repo root: %s)\n") %
1120 repo.root)
1119 repo.root)
1121
1120
1122 def batchget(repo, mctx, wctx, actions):
1121 def batchget(repo, mctx, wctx, actions):
1123 """apply gets to the working directory
1122 """apply gets to the working directory
1124
1123
1125 mctx is the context to get from
1124 mctx is the context to get from
1126
1125
1127 yields tuples for progress updates
1126 yields tuples for progress updates
1128 """
1127 """
1129 verbose = repo.ui.verbose
1128 verbose = repo.ui.verbose
1130 fctx = mctx.filectx
1129 fctx = mctx.filectx
1131 ui = repo.ui
1130 ui = repo.ui
1132 i = 0
1131 i = 0
1133 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1132 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1134 for f, (flags, backup), msg in actions:
1133 for f, (flags, backup), msg in actions:
1135 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
1134 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
1136 if verbose:
1135 if verbose:
1137 repo.ui.note(_("getting %s\n") % f)
1136 repo.ui.note(_("getting %s\n") % f)
1138
1137
1139 if backup:
1138 if backup:
1140 absf = repo.wjoin(f)
1139 absf = repo.wjoin(f)
1141 orig = scmutil.origpath(ui, repo, absf)
1140 orig = scmutil.origpath(ui, repo, absf)
1142 try:
1141 try:
1143 if repo.wvfs.isfileorlink(f):
1142 if repo.wvfs.isfileorlink(f):
1144 util.rename(absf, orig)
1143 util.rename(absf, orig)
1145 except OSError as e:
1144 except OSError as e:
1146 if e.errno != errno.ENOENT:
1145 if e.errno != errno.ENOENT:
1147 raise
1146 raise
1148
1147
1149 if repo.wvfs.isdir(f) and not repo.wvfs.islink(f):
1148 if repo.wvfs.isdir(f) and not repo.wvfs.islink(f):
1150 repo.wvfs.removedirs(f)
1149 repo.wvfs.removedirs(f)
1151 wctx[f].write(fctx(f).data(), flags, backgroundclose=True)
1150 wctx[f].write(fctx(f).data(), flags, backgroundclose=True)
1152 if i == 100:
1151 if i == 100:
1153 yield i, f
1152 yield i, f
1154 i = 0
1153 i = 0
1155 i += 1
1154 i += 1
1156 if i > 0:
1155 if i > 0:
1157 yield i, f
1156 yield i, f
1158
1157
1159 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
1158 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
1160 """apply the merge action list to the working directory
1159 """apply the merge action list to the working directory
1161
1160
1162 wctx is the working copy context
1161 wctx is the working copy context
1163 mctx is the context to be merged into the working copy
1162 mctx is the context to be merged into the working copy
1164
1163
1165 Return a tuple of counts (updated, merged, removed, unresolved) that
1164 Return a tuple of counts (updated, merged, removed, unresolved) that
1166 describes how many files were affected by the update.
1165 describes how many files were affected by the update.
1167 """
1166 """
1168
1167
1169 updated, merged, removed = 0, 0, 0
1168 updated, merged, removed = 0, 0, 0
1170 ms = mergestate.clean(repo, wctx.p1().node(), mctx.node(), labels)
1169 ms = mergestate.clean(repo, wctx.p1().node(), mctx.node(), labels)
1171 moves = []
1170 moves = []
1172 for m, l in actions.items():
1171 for m, l in actions.items():
1173 l.sort()
1172 l.sort()
1174
1173
1175 # 'cd' and 'dc' actions are treated like other merge conflicts
1174 # 'cd' and 'dc' actions are treated like other merge conflicts
1176 mergeactions = sorted(actions['cd'])
1175 mergeactions = sorted(actions['cd'])
1177 mergeactions.extend(sorted(actions['dc']))
1176 mergeactions.extend(sorted(actions['dc']))
1178 mergeactions.extend(actions['m'])
1177 mergeactions.extend(actions['m'])
1179 for f, args, msg in mergeactions:
1178 for f, args, msg in mergeactions:
1180 f1, f2, fa, move, anc = args
1179 f1, f2, fa, move, anc = args
1181 if f == '.hgsubstate': # merged internally
1180 if f == '.hgsubstate': # merged internally
1182 continue
1181 continue
1183 if f1 is None:
1182 if f1 is None:
1184 fcl = filemerge.absentfilectx(wctx, fa)
1183 fcl = filemerge.absentfilectx(wctx, fa)
1185 else:
1184 else:
1186 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
1185 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
1187 fcl = wctx[f1]
1186 fcl = wctx[f1]
1188 if f2 is None:
1187 if f2 is None:
1189 fco = filemerge.absentfilectx(mctx, fa)
1188 fco = filemerge.absentfilectx(mctx, fa)
1190 else:
1189 else:
1191 fco = mctx[f2]
1190 fco = mctx[f2]
1192 actx = repo[anc]
1191 actx = repo[anc]
1193 if fa in actx:
1192 if fa in actx:
1194 fca = actx[fa]
1193 fca = actx[fa]
1195 else:
1194 else:
1196 # TODO: move to absentfilectx
1195 # TODO: move to absentfilectx
1197 fca = repo.filectx(f1, fileid=nullrev)
1196 fca = repo.filectx(f1, fileid=nullrev)
1198 ms.add(fcl, fco, fca, f)
1197 ms.add(fcl, fco, fca, f)
1199 if f1 != f and move:
1198 if f1 != f and move:
1200 moves.append(f1)
1199 moves.append(f1)
1201
1200
1202 _updating = _('updating')
1201 _updating = _('updating')
1203 _files = _('files')
1202 _files = _('files')
1204 progress = repo.ui.progress
1203 progress = repo.ui.progress
1205
1204
1206 # remove renamed files after safely stored
1205 # remove renamed files after safely stored
1207 for f in moves:
1206 for f in moves:
1208 if wctx[f].lexists():
1207 if wctx[f].lexists():
1209 repo.ui.debug("removing %s\n" % f)
1208 repo.ui.debug("removing %s\n" % f)
1210 wctx[f].audit()
1209 wctx[f].audit()
1211 wctx[f].remove()
1210 wctx[f].remove()
1212
1211
1213 numupdates = sum(len(l) for m, l in actions.items() if m != 'k')
1212 numupdates = sum(len(l) for m, l in actions.items() if m != 'k')
1214
1213
1215 if [a for a in actions['r'] if a[0] == '.hgsubstate']:
1214 if [a for a in actions['r'] if a[0] == '.hgsubstate']:
1216 subrepo.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1215 subrepo.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1217
1216
1218 # remove in parallel (must come first)
1217 # remove in parallel (must come first)
1219 z = 0
1218 z = 0
1220 prog = worker.worker(repo.ui, 0.001, batchremove, (repo, wctx),
1219 prog = worker.worker(repo.ui, 0.001, batchremove, (repo, wctx),
1221 actions['r'])
1220 actions['r'])
1222 for i, item in prog:
1221 for i, item in prog:
1223 z += i
1222 z += i
1224 progress(_updating, z, item=item, total=numupdates, unit=_files)
1223 progress(_updating, z, item=item, total=numupdates, unit=_files)
1225 removed = len(actions['r'])
1224 removed = len(actions['r'])
1226
1225
1227 # get in parallel
1226 # get in parallel
1228 prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx, wctx),
1227 prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx, wctx),
1229 actions['g'])
1228 actions['g'])
1230 for i, item in prog:
1229 for i, item in prog:
1231 z += i
1230 z += i
1232 progress(_updating, z, item=item, total=numupdates, unit=_files)
1231 progress(_updating, z, item=item, total=numupdates, unit=_files)
1233 updated = len(actions['g'])
1232 updated = len(actions['g'])
1234
1233
1235 if [a for a in actions['g'] if a[0] == '.hgsubstate']:
1234 if [a for a in actions['g'] if a[0] == '.hgsubstate']:
1236 subrepo.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1235 subrepo.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1237
1236
1238 # forget (manifest only, just log it) (must come first)
1237 # forget (manifest only, just log it) (must come first)
1239 for f, args, msg in actions['f']:
1238 for f, args, msg in actions['f']:
1240 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
1239 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
1241 z += 1
1240 z += 1
1242 progress(_updating, z, item=f, total=numupdates, unit=_files)
1241 progress(_updating, z, item=f, total=numupdates, unit=_files)
1243
1242
1244 # re-add (manifest only, just log it)
1243 # re-add (manifest only, just log it)
1245 for f, args, msg in actions['a']:
1244 for f, args, msg in actions['a']:
1246 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
1245 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
1247 z += 1
1246 z += 1
1248 progress(_updating, z, item=f, total=numupdates, unit=_files)
1247 progress(_updating, z, item=f, total=numupdates, unit=_files)
1249
1248
1250 # re-add/mark as modified (manifest only, just log it)
1249 # re-add/mark as modified (manifest only, just log it)
1251 for f, args, msg in actions['am']:
1250 for f, args, msg in actions['am']:
1252 repo.ui.debug(" %s: %s -> am\n" % (f, msg))
1251 repo.ui.debug(" %s: %s -> am\n" % (f, msg))
1253 z += 1
1252 z += 1
1254 progress(_updating, z, item=f, total=numupdates, unit=_files)
1253 progress(_updating, z, item=f, total=numupdates, unit=_files)
1255
1254
1256 # keep (noop, just log it)
1255 # keep (noop, just log it)
1257 for f, args, msg in actions['k']:
1256 for f, args, msg in actions['k']:
1258 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
1257 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
1259 # no progress
1258 # no progress
1260
1259
1261 # directory rename, move local
1260 # directory rename, move local
1262 for f, args, msg in actions['dm']:
1261 for f, args, msg in actions['dm']:
1263 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
1262 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
1264 z += 1
1263 z += 1
1265 progress(_updating, z, item=f, total=numupdates, unit=_files)
1264 progress(_updating, z, item=f, total=numupdates, unit=_files)
1266 f0, flags = args
1265 f0, flags = args
1267 repo.ui.note(_("moving %s to %s\n") % (f0, f))
1266 repo.ui.note(_("moving %s to %s\n") % (f0, f))
1268 wctx[f].audit()
1267 wctx[f].audit()
1269 wctx[f].write(wctx.filectx(f0).data(), flags)
1268 wctx[f].write(wctx.filectx(f0).data(), flags)
1270 wctx[f0].remove()
1269 wctx[f0].remove()
1271 updated += 1
1270 updated += 1
1272
1271
1273 # local directory rename, get
1272 # local directory rename, get
1274 for f, args, msg in actions['dg']:
1273 for f, args, msg in actions['dg']:
1275 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
1274 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
1276 z += 1
1275 z += 1
1277 progress(_updating, z, item=f, total=numupdates, unit=_files)
1276 progress(_updating, z, item=f, total=numupdates, unit=_files)
1278 f0, flags = args
1277 f0, flags = args
1279 repo.ui.note(_("getting %s to %s\n") % (f0, f))
1278 repo.ui.note(_("getting %s to %s\n") % (f0, f))
1280 wctx[f].write(mctx.filectx(f0).data(), flags)
1279 wctx[f].write(mctx.filectx(f0).data(), flags)
1281 updated += 1
1280 updated += 1
1282
1281
1283 # exec
1282 # exec
1284 for f, args, msg in actions['e']:
1283 for f, args, msg in actions['e']:
1285 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
1284 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
1286 z += 1
1285 z += 1
1287 progress(_updating, z, item=f, total=numupdates, unit=_files)
1286 progress(_updating, z, item=f, total=numupdates, unit=_files)
1288 flags, = args
1287 flags, = args
1289 wctx[f].audit()
1288 wctx[f].audit()
1290 wctx[f].setflags('l' in flags, 'x' in flags)
1289 wctx[f].setflags('l' in flags, 'x' in flags)
1291 updated += 1
1290 updated += 1
1292
1291
1293 # the ordering is important here -- ms.mergedriver will raise if the merge
1292 # the ordering is important here -- ms.mergedriver will raise if the merge
1294 # driver has changed, and we want to be able to bypass it when overwrite is
1293 # driver has changed, and we want to be able to bypass it when overwrite is
1295 # True
1294 # True
1296 usemergedriver = not overwrite and mergeactions and ms.mergedriver
1295 usemergedriver = not overwrite and mergeactions and ms.mergedriver
1297
1296
1298 if usemergedriver:
1297 if usemergedriver:
1299 ms.commit()
1298 ms.commit()
1300 proceed = driverpreprocess(repo, ms, wctx, labels=labels)
1299 proceed = driverpreprocess(repo, ms, wctx, labels=labels)
1301 # the driver might leave some files unresolved
1300 # the driver might leave some files unresolved
1302 unresolvedf = set(ms.unresolved())
1301 unresolvedf = set(ms.unresolved())
1303 if not proceed:
1302 if not proceed:
1304 # XXX setting unresolved to at least 1 is a hack to make sure we
1303 # XXX setting unresolved to at least 1 is a hack to make sure we
1305 # error out
1304 # error out
1306 return updated, merged, removed, max(len(unresolvedf), 1)
1305 return updated, merged, removed, max(len(unresolvedf), 1)
1307 newactions = []
1306 newactions = []
1308 for f, args, msg in mergeactions:
1307 for f, args, msg in mergeactions:
1309 if f in unresolvedf:
1308 if f in unresolvedf:
1310 newactions.append((f, args, msg))
1309 newactions.append((f, args, msg))
1311 mergeactions = newactions
1310 mergeactions = newactions
1312
1311
1313 # premerge
1312 # premerge
1314 tocomplete = []
1313 tocomplete = []
1315 for f, args, msg in mergeactions:
1314 for f, args, msg in mergeactions:
1316 repo.ui.debug(" %s: %s -> m (premerge)\n" % (f, msg))
1315 repo.ui.debug(" %s: %s -> m (premerge)\n" % (f, msg))
1317 z += 1
1316 z += 1
1318 progress(_updating, z, item=f, total=numupdates, unit=_files)
1317 progress(_updating, z, item=f, total=numupdates, unit=_files)
1319 if f == '.hgsubstate': # subrepo states need updating
1318 if f == '.hgsubstate': # subrepo states need updating
1320 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
1319 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
1321 overwrite, labels)
1320 overwrite, labels)
1322 continue
1321 continue
1323 wctx[f].audit()
1322 wctx[f].audit()
1324 complete, r = ms.preresolve(f, wctx)
1323 complete, r = ms.preresolve(f, wctx)
1325 if not complete:
1324 if not complete:
1326 numupdates += 1
1325 numupdates += 1
1327 tocomplete.append((f, args, msg))
1326 tocomplete.append((f, args, msg))
1328
1327
1329 # merge
1328 # merge
1330 for f, args, msg in tocomplete:
1329 for f, args, msg in tocomplete:
1331 repo.ui.debug(" %s: %s -> m (merge)\n" % (f, msg))
1330 repo.ui.debug(" %s: %s -> m (merge)\n" % (f, msg))
1332 z += 1
1331 z += 1
1333 progress(_updating, z, item=f, total=numupdates, unit=_files)
1332 progress(_updating, z, item=f, total=numupdates, unit=_files)
1334 ms.resolve(f, wctx)
1333 ms.resolve(f, wctx)
1335
1334
1336 ms.commit()
1335 ms.commit()
1337
1336
1338 unresolved = ms.unresolvedcount()
1337 unresolved = ms.unresolvedcount()
1339
1338
1340 if usemergedriver and not unresolved and ms.mdstate() != 's':
1339 if usemergedriver and not unresolved and ms.mdstate() != 's':
1341 if not driverconclude(repo, ms, wctx, labels=labels):
1340 if not driverconclude(repo, ms, wctx, labels=labels):
1342 # XXX setting unresolved to at least 1 is a hack to make sure we
1341 # XXX setting unresolved to at least 1 is a hack to make sure we
1343 # error out
1342 # error out
1344 unresolved = max(unresolved, 1)
1343 unresolved = max(unresolved, 1)
1345
1344
1346 ms.commit()
1345 ms.commit()
1347
1346
1348 msupdated, msmerged, msremoved = ms.counts()
1347 msupdated, msmerged, msremoved = ms.counts()
1349 updated += msupdated
1348 updated += msupdated
1350 merged += msmerged
1349 merged += msmerged
1351 removed += msremoved
1350 removed += msremoved
1352
1351
1353 extraactions = ms.actions()
1352 extraactions = ms.actions()
1354 if extraactions:
1353 if extraactions:
1355 mfiles = set(a[0] for a in actions['m'])
1354 mfiles = set(a[0] for a in actions['m'])
1356 for k, acts in extraactions.iteritems():
1355 for k, acts in extraactions.iteritems():
1357 actions[k].extend(acts)
1356 actions[k].extend(acts)
1358 # Remove these files from actions['m'] as well. This is important
1357 # Remove these files from actions['m'] as well. This is important
1359 # because in recordupdates, files in actions['m'] are processed
1358 # because in recordupdates, files in actions['m'] are processed
1360 # after files in other actions, and the merge driver might add
1359 # after files in other actions, and the merge driver might add
1361 # files to those actions via extraactions above. This can lead to a
1360 # files to those actions via extraactions above. This can lead to a
1362 # file being recorded twice, with poor results. This is especially
1361 # file being recorded twice, with poor results. This is especially
1363 # problematic for actions['r'] (currently only possible with the
1362 # problematic for actions['r'] (currently only possible with the
1364 # merge driver in the initial merge process; interrupted merges
1363 # merge driver in the initial merge process; interrupted merges
1365 # don't go through this flow).
1364 # don't go through this flow).
1366 #
1365 #
1367 # The real fix here is to have indexes by both file and action so
1366 # The real fix here is to have indexes by both file and action so
1368 # that when the action for a file is changed it is automatically
1367 # that when the action for a file is changed it is automatically
1369 # reflected in the other action lists. But that involves a more
1368 # reflected in the other action lists. But that involves a more
1370 # complex data structure, so this will do for now.
1369 # complex data structure, so this will do for now.
1371 #
1370 #
1372 # We don't need to do the same operation for 'dc' and 'cd' because
1371 # We don't need to do the same operation for 'dc' and 'cd' because
1373 # those lists aren't consulted again.
1372 # those lists aren't consulted again.
1374 mfiles.difference_update(a[0] for a in acts)
1373 mfiles.difference_update(a[0] for a in acts)
1375
1374
1376 actions['m'] = [a for a in actions['m'] if a[0] in mfiles]
1375 actions['m'] = [a for a in actions['m'] if a[0] in mfiles]
1377
1376
1378 progress(_updating, None, total=numupdates, unit=_files)
1377 progress(_updating, None, total=numupdates, unit=_files)
1379
1378
1380 return updated, merged, removed, unresolved
1379 return updated, merged, removed, unresolved
1381
1380
1382 def recordupdates(repo, actions, branchmerge):
1381 def recordupdates(repo, actions, branchmerge):
1383 "record merge actions to the dirstate"
1382 "record merge actions to the dirstate"
1384 # remove (must come first)
1383 # remove (must come first)
1385 for f, args, msg in actions.get('r', []):
1384 for f, args, msg in actions.get('r', []):
1386 if branchmerge:
1385 if branchmerge:
1387 repo.dirstate.remove(f)
1386 repo.dirstate.remove(f)
1388 else:
1387 else:
1389 repo.dirstate.drop(f)
1388 repo.dirstate.drop(f)
1390
1389
1391 # forget (must come first)
1390 # forget (must come first)
1392 for f, args, msg in actions.get('f', []):
1391 for f, args, msg in actions.get('f', []):
1393 repo.dirstate.drop(f)
1392 repo.dirstate.drop(f)
1394
1393
1395 # re-add
1394 # re-add
1396 for f, args, msg in actions.get('a', []):
1395 for f, args, msg in actions.get('a', []):
1397 repo.dirstate.add(f)
1396 repo.dirstate.add(f)
1398
1397
1399 # re-add/mark as modified
1398 # re-add/mark as modified
1400 for f, args, msg in actions.get('am', []):
1399 for f, args, msg in actions.get('am', []):
1401 if branchmerge:
1400 if branchmerge:
1402 repo.dirstate.normallookup(f)
1401 repo.dirstate.normallookup(f)
1403 else:
1402 else:
1404 repo.dirstate.add(f)
1403 repo.dirstate.add(f)
1405
1404
1406 # exec change
1405 # exec change
1407 for f, args, msg in actions.get('e', []):
1406 for f, args, msg in actions.get('e', []):
1408 repo.dirstate.normallookup(f)
1407 repo.dirstate.normallookup(f)
1409
1408
1410 # keep
1409 # keep
1411 for f, args, msg in actions.get('k', []):
1410 for f, args, msg in actions.get('k', []):
1412 pass
1411 pass
1413
1412
1414 # get
1413 # get
1415 for f, args, msg in actions.get('g', []):
1414 for f, args, msg in actions.get('g', []):
1416 if branchmerge:
1415 if branchmerge:
1417 repo.dirstate.otherparent(f)
1416 repo.dirstate.otherparent(f)
1418 else:
1417 else:
1419 repo.dirstate.normal(f)
1418 repo.dirstate.normal(f)
1420
1419
1421 # merge
1420 # merge
1422 for f, args, msg in actions.get('m', []):
1421 for f, args, msg in actions.get('m', []):
1423 f1, f2, fa, move, anc = args
1422 f1, f2, fa, move, anc = args
1424 if branchmerge:
1423 if branchmerge:
1425 # We've done a branch merge, mark this file as merged
1424 # We've done a branch merge, mark this file as merged
1426 # so that we properly record the merger later
1425 # so that we properly record the merger later
1427 repo.dirstate.merge(f)
1426 repo.dirstate.merge(f)
1428 if f1 != f2: # copy/rename
1427 if f1 != f2: # copy/rename
1429 if move:
1428 if move:
1430 repo.dirstate.remove(f1)
1429 repo.dirstate.remove(f1)
1431 if f1 != f:
1430 if f1 != f:
1432 repo.dirstate.copy(f1, f)
1431 repo.dirstate.copy(f1, f)
1433 else:
1432 else:
1434 repo.dirstate.copy(f2, f)
1433 repo.dirstate.copy(f2, f)
1435 else:
1434 else:
1436 # We've update-merged a locally modified file, so
1435 # We've update-merged a locally modified file, so
1437 # we set the dirstate to emulate a normal checkout
1436 # we set the dirstate to emulate a normal checkout
1438 # of that file some time in the past. Thus our
1437 # of that file some time in the past. Thus our
1439 # merge will appear as a normal local file
1438 # merge will appear as a normal local file
1440 # modification.
1439 # modification.
1441 if f2 == f: # file not locally copied/moved
1440 if f2 == f: # file not locally copied/moved
1442 repo.dirstate.normallookup(f)
1441 repo.dirstate.normallookup(f)
1443 if move:
1442 if move:
1444 repo.dirstate.drop(f1)
1443 repo.dirstate.drop(f1)
1445
1444
1446 # directory rename, move local
1445 # directory rename, move local
1447 for f, args, msg in actions.get('dm', []):
1446 for f, args, msg in actions.get('dm', []):
1448 f0, flag = args
1447 f0, flag = args
1449 if branchmerge:
1448 if branchmerge:
1450 repo.dirstate.add(f)
1449 repo.dirstate.add(f)
1451 repo.dirstate.remove(f0)
1450 repo.dirstate.remove(f0)
1452 repo.dirstate.copy(f0, f)
1451 repo.dirstate.copy(f0, f)
1453 else:
1452 else:
1454 repo.dirstate.normal(f)
1453 repo.dirstate.normal(f)
1455 repo.dirstate.drop(f0)
1454 repo.dirstate.drop(f0)
1456
1455
1457 # directory rename, get
1456 # directory rename, get
1458 for f, args, msg in actions.get('dg', []):
1457 for f, args, msg in actions.get('dg', []):
1459 f0, flag = args
1458 f0, flag = args
1460 if branchmerge:
1459 if branchmerge:
1461 repo.dirstate.add(f)
1460 repo.dirstate.add(f)
1462 repo.dirstate.copy(f0, f)
1461 repo.dirstate.copy(f0, f)
1463 else:
1462 else:
1464 repo.dirstate.normal(f)
1463 repo.dirstate.normal(f)
1465
1464
1466 def update(repo, node, branchmerge, force, ancestor=None,
1465 def update(repo, node, branchmerge, force, ancestor=None,
1467 mergeancestor=False, labels=None, matcher=None, mergeforce=False,
1466 mergeancestor=False, labels=None, matcher=None, mergeforce=False,
1468 updatecheck=None):
1467 updatecheck=None):
1469 """
1468 """
1470 Perform a merge between the working directory and the given node
1469 Perform a merge between the working directory and the given node
1471
1470
1472 node = the node to update to
1471 node = the node to update to
1473 branchmerge = whether to merge between branches
1472 branchmerge = whether to merge between branches
1474 force = whether to force branch merging or file overwriting
1473 force = whether to force branch merging or file overwriting
1475 matcher = a matcher to filter file lists (dirstate not updated)
1474 matcher = a matcher to filter file lists (dirstate not updated)
1476 mergeancestor = whether it is merging with an ancestor. If true,
1475 mergeancestor = whether it is merging with an ancestor. If true,
1477 we should accept the incoming changes for any prompts that occur.
1476 we should accept the incoming changes for any prompts that occur.
1478 If false, merging with an ancestor (fast-forward) is only allowed
1477 If false, merging with an ancestor (fast-forward) is only allowed
1479 between different named branches. This flag is used by rebase extension
1478 between different named branches. This flag is used by rebase extension
1480 as a temporary fix and should be avoided in general.
1479 as a temporary fix and should be avoided in general.
1481 labels = labels to use for base, local and other
1480 labels = labels to use for base, local and other
1482 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
1481 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
1483 this is True, then 'force' should be True as well.
1482 this is True, then 'force' should be True as well.
1484
1483
1485 The table below shows all the behaviors of the update command
1484 The table below shows all the behaviors of the update command
1486 given the -c and -C or no options, whether the working directory
1485 given the -c and -C or no options, whether the working directory
1487 is dirty, whether a revision is specified, and the relationship of
1486 is dirty, whether a revision is specified, and the relationship of
1488 the parent rev to the target rev (linear or not). Match from top first. The
1487 the parent rev to the target rev (linear or not). Match from top first. The
1489 -n option doesn't exist on the command line, but represents the
1488 -n option doesn't exist on the command line, but represents the
1490 experimental.updatecheck=noconflict option.
1489 experimental.updatecheck=noconflict option.
1491
1490
1492 This logic is tested by test-update-branches.t.
1491 This logic is tested by test-update-branches.t.
1493
1492
1494 -c -C -n -m dirty rev linear | result
1493 -c -C -n -m dirty rev linear | result
1495 y y * * * * * | (1)
1494 y y * * * * * | (1)
1496 y * y * * * * | (1)
1495 y * y * * * * | (1)
1497 y * * y * * * | (1)
1496 y * * y * * * | (1)
1498 * y y * * * * | (1)
1497 * y y * * * * | (1)
1499 * y * y * * * | (1)
1498 * y * y * * * | (1)
1500 * * y y * * * | (1)
1499 * * y y * * * | (1)
1501 * * * * * n n | x
1500 * * * * * n n | x
1502 * * * * n * * | ok
1501 * * * * n * * | ok
1503 n n n n y * y | merge
1502 n n n n y * y | merge
1504 n n n n y y n | (2)
1503 n n n n y y n | (2)
1505 n n n y y * * | merge
1504 n n n y y * * | merge
1506 n n y n y * * | merge if no conflict
1505 n n y n y * * | merge if no conflict
1507 n y n n y * * | discard
1506 n y n n y * * | discard
1508 y n n n y * * | (3)
1507 y n n n y * * | (3)
1509
1508
1510 x = can't happen
1509 x = can't happen
1511 * = don't-care
1510 * = don't-care
1512 1 = incompatible options (checked in commands.py)
1511 1 = incompatible options (checked in commands.py)
1513 2 = abort: uncommitted changes (commit or update --clean to discard changes)
1512 2 = abort: uncommitted changes (commit or update --clean to discard changes)
1514 3 = abort: uncommitted changes (checked in commands.py)
1513 3 = abort: uncommitted changes (checked in commands.py)
1515
1514
1516 Return the same tuple as applyupdates().
1515 Return the same tuple as applyupdates().
1517 """
1516 """
1518
1517
1519 # This function used to find the default destination if node was None, but
1518 # This function used to find the default destination if node was None, but
1520 # that's now in destutil.py.
1519 # that's now in destutil.py.
1521 assert node is not None
1520 assert node is not None
1522 if not branchmerge and not force:
1521 if not branchmerge and not force:
1523 # TODO: remove the default once all callers that pass branchmerge=False
1522 # TODO: remove the default once all callers that pass branchmerge=False
1524 # and force=False pass a value for updatecheck. We may want to allow
1523 # and force=False pass a value for updatecheck. We may want to allow
1525 # updatecheck='abort' to better suppport some of these callers.
1524 # updatecheck='abort' to better suppport some of these callers.
1526 if updatecheck is None:
1525 if updatecheck is None:
1527 updatecheck = 'linear'
1526 updatecheck = 'linear'
1528 assert updatecheck in ('none', 'linear', 'noconflict')
1527 assert updatecheck in ('none', 'linear', 'noconflict')
1529 # If we're doing a partial update, we need to skip updating
1528 # If we're doing a partial update, we need to skip updating
1530 # the dirstate, so make a note of any partial-ness to the
1529 # the dirstate, so make a note of any partial-ness to the
1531 # update here.
1530 # update here.
1532 if matcher is None or matcher.always():
1531 if matcher is None or matcher.always():
1533 partial = False
1532 partial = False
1534 else:
1533 else:
1535 partial = True
1534 partial = True
1536 with repo.wlock():
1535 with repo.wlock():
1537 wc = repo[None]
1536 wc = repo[None]
1538 pl = wc.parents()
1537 pl = wc.parents()
1539 p1 = pl[0]
1538 p1 = pl[0]
1540 pas = [None]
1539 pas = [None]
1541 if ancestor is not None:
1540 if ancestor is not None:
1542 pas = [repo[ancestor]]
1541 pas = [repo[ancestor]]
1543
1542
1544 overwrite = force and not branchmerge
1543 overwrite = force and not branchmerge
1545
1544
1546 p2 = repo[node]
1545 p2 = repo[node]
1547 if pas[0] is None:
1546 if pas[0] is None:
1548 if repo.ui.configlist('merge', 'preferancestor', ['*']) == ['*']:
1547 if repo.ui.configlist('merge', 'preferancestor', ['*']) == ['*']:
1549 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1548 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1550 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1549 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1551 else:
1550 else:
1552 pas = [p1.ancestor(p2, warn=branchmerge)]
1551 pas = [p1.ancestor(p2, warn=branchmerge)]
1553
1552
1554 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
1553 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
1555
1554
1556 ### check phase
1555 ### check phase
1557 if not overwrite:
1556 if not overwrite:
1558 if len(pl) > 1:
1557 if len(pl) > 1:
1559 raise error.Abort(_("outstanding uncommitted merge"))
1558 raise error.Abort(_("outstanding uncommitted merge"))
1560 ms = mergestate.read(repo)
1559 ms = mergestate.read(repo)
1561 if list(ms.unresolved()):
1560 if list(ms.unresolved()):
1562 raise error.Abort(_("outstanding merge conflicts"))
1561 raise error.Abort(_("outstanding merge conflicts"))
1563 if branchmerge:
1562 if branchmerge:
1564 if pas == [p2]:
1563 if pas == [p2]:
1565 raise error.Abort(_("merging with a working directory ancestor"
1564 raise error.Abort(_("merging with a working directory ancestor"
1566 " has no effect"))
1565 " has no effect"))
1567 elif pas == [p1]:
1566 elif pas == [p1]:
1568 if not mergeancestor and wc.branch() == p2.branch():
1567 if not mergeancestor and wc.branch() == p2.branch():
1569 raise error.Abort(_("nothing to merge"),
1568 raise error.Abort(_("nothing to merge"),
1570 hint=_("use 'hg update' "
1569 hint=_("use 'hg update' "
1571 "or check 'hg heads'"))
1570 "or check 'hg heads'"))
1572 if not force and (wc.files() or wc.deleted()):
1571 if not force and (wc.files() or wc.deleted()):
1573 raise error.Abort(_("uncommitted changes"),
1572 raise error.Abort(_("uncommitted changes"),
1574 hint=_("use 'hg status' to list changes"))
1573 hint=_("use 'hg status' to list changes"))
1575 for s in sorted(wc.substate):
1574 for s in sorted(wc.substate):
1576 wc.sub(s).bailifchanged()
1575 wc.sub(s).bailifchanged()
1577
1576
1578 elif not overwrite:
1577 elif not overwrite:
1579 if p1 == p2: # no-op update
1578 if p1 == p2: # no-op update
1580 # call the hooks and exit early
1579 # call the hooks and exit early
1581 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
1580 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
1582 repo.hook('update', parent1=xp2, parent2='', error=0)
1581 repo.hook('update', parent1=xp2, parent2='', error=0)
1583 return 0, 0, 0, 0
1582 return 0, 0, 0, 0
1584
1583
1585 if (updatecheck == 'linear' and
1584 if (updatecheck == 'linear' and
1586 pas not in ([p1], [p2])): # nonlinear
1585 pas not in ([p1], [p2])): # nonlinear
1587 dirty = wc.dirty(missing=True)
1586 dirty = wc.dirty(missing=True)
1588 if dirty:
1587 if dirty:
1589 # Branching is a bit strange to ensure we do the minimal
1588 # Branching is a bit strange to ensure we do the minimal
1590 # amount of call to obsutil.foreground.
1589 # amount of call to obsutil.foreground.
1591 foreground = obsutil.foreground(repo, [p1.node()])
1590 foreground = obsutil.foreground(repo, [p1.node()])
1592 # note: the <node> variable contains a random identifier
1591 # note: the <node> variable contains a random identifier
1593 if repo[node].node() in foreground:
1592 if repo[node].node() in foreground:
1594 pass # allow updating to successors
1593 pass # allow updating to successors
1595 else:
1594 else:
1596 msg = _("uncommitted changes")
1595 msg = _("uncommitted changes")
1597 hint = _("commit or update --clean to discard changes")
1596 hint = _("commit or update --clean to discard changes")
1598 raise error.UpdateAbort(msg, hint=hint)
1597 raise error.UpdateAbort(msg, hint=hint)
1599 else:
1598 else:
1600 # Allow jumping branches if clean and specific rev given
1599 # Allow jumping branches if clean and specific rev given
1601 pass
1600 pass
1602
1601
1603 if overwrite:
1602 if overwrite:
1604 pas = [wc]
1603 pas = [wc]
1605 elif not branchmerge:
1604 elif not branchmerge:
1606 pas = [p1]
1605 pas = [p1]
1607
1606
1608 # deprecated config: merge.followcopies
1607 # deprecated config: merge.followcopies
1609 followcopies = repo.ui.configbool('merge', 'followcopies', True)
1608 followcopies = repo.ui.configbool('merge', 'followcopies', True)
1610 if overwrite:
1609 if overwrite:
1611 followcopies = False
1610 followcopies = False
1612 elif not pas[0]:
1611 elif not pas[0]:
1613 followcopies = False
1612 followcopies = False
1614 if not branchmerge and not wc.dirty(missing=True):
1613 if not branchmerge and not wc.dirty(missing=True):
1615 followcopies = False
1614 followcopies = False
1616
1615
1617 ### calculate phase
1616 ### calculate phase
1618 actionbyfile, diverge, renamedelete = calculateupdates(
1617 actionbyfile, diverge, renamedelete = calculateupdates(
1619 repo, wc, p2, pas, branchmerge, force, mergeancestor,
1618 repo, wc, p2, pas, branchmerge, force, mergeancestor,
1620 followcopies, matcher=matcher, mergeforce=mergeforce)
1619 followcopies, matcher=matcher, mergeforce=mergeforce)
1621
1620
1622 if updatecheck == 'noconflict':
1621 if updatecheck == 'noconflict':
1623 for f, (m, args, msg) in actionbyfile.iteritems():
1622 for f, (m, args, msg) in actionbyfile.iteritems():
1624 if m not in ('g', 'k', 'e', 'r'):
1623 if m not in ('g', 'k', 'e', 'r'):
1625 msg = _("conflicting changes")
1624 msg = _("conflicting changes")
1626 hint = _("commit or update --clean to discard changes")
1625 hint = _("commit or update --clean to discard changes")
1627 raise error.Abort(msg, hint=hint)
1626 raise error.Abort(msg, hint=hint)
1628
1627
1629 # Prompt and create actions. Most of this is in the resolve phase
1628 # Prompt and create actions. Most of this is in the resolve phase
1630 # already, but we can't handle .hgsubstate in filemerge or
1629 # already, but we can't handle .hgsubstate in filemerge or
1631 # subrepo.submerge yet so we have to keep prompting for it.
1630 # subrepo.submerge yet so we have to keep prompting for it.
1632 if '.hgsubstate' in actionbyfile:
1631 if '.hgsubstate' in actionbyfile:
1633 f = '.hgsubstate'
1632 f = '.hgsubstate'
1634 m, args, msg = actionbyfile[f]
1633 m, args, msg = actionbyfile[f]
1635 prompts = filemerge.partextras(labels)
1634 prompts = filemerge.partextras(labels)
1636 prompts['f'] = f
1635 prompts['f'] = f
1637 if m == 'cd':
1636 if m == 'cd':
1638 if repo.ui.promptchoice(
1637 if repo.ui.promptchoice(
1639 _("local%(l)s changed %(f)s which other%(o)s deleted\n"
1638 _("local%(l)s changed %(f)s which other%(o)s deleted\n"
1640 "use (c)hanged version or (d)elete?"
1639 "use (c)hanged version or (d)elete?"
1641 "$$ &Changed $$ &Delete") % prompts, 0):
1640 "$$ &Changed $$ &Delete") % prompts, 0):
1642 actionbyfile[f] = ('r', None, "prompt delete")
1641 actionbyfile[f] = ('r', None, "prompt delete")
1643 elif f in p1:
1642 elif f in p1:
1644 actionbyfile[f] = ('am', None, "prompt keep")
1643 actionbyfile[f] = ('am', None, "prompt keep")
1645 else:
1644 else:
1646 actionbyfile[f] = ('a', None, "prompt keep")
1645 actionbyfile[f] = ('a', None, "prompt keep")
1647 elif m == 'dc':
1646 elif m == 'dc':
1648 f1, f2, fa, move, anc = args
1647 f1, f2, fa, move, anc = args
1649 flags = p2[f2].flags()
1648 flags = p2[f2].flags()
1650 if repo.ui.promptchoice(
1649 if repo.ui.promptchoice(
1651 _("other%(o)s changed %(f)s which local%(l)s deleted\n"
1650 _("other%(o)s changed %(f)s which local%(l)s deleted\n"
1652 "use (c)hanged version or leave (d)eleted?"
1651 "use (c)hanged version or leave (d)eleted?"
1653 "$$ &Changed $$ &Deleted") % prompts, 0) == 0:
1652 "$$ &Changed $$ &Deleted") % prompts, 0) == 0:
1654 actionbyfile[f] = ('g', (flags, False), "prompt recreating")
1653 actionbyfile[f] = ('g', (flags, False), "prompt recreating")
1655 else:
1654 else:
1656 del actionbyfile[f]
1655 del actionbyfile[f]
1657
1656
1658 # Convert to dictionary-of-lists format
1657 # Convert to dictionary-of-lists format
1659 actions = dict((m, []) for m in 'a am f g cd dc r dm dg m e k'.split())
1658 actions = dict((m, []) for m in 'a am f g cd dc r dm dg m e k'.split())
1660 for f, (m, args, msg) in actionbyfile.iteritems():
1659 for f, (m, args, msg) in actionbyfile.iteritems():
1661 if m not in actions:
1660 if m not in actions:
1662 actions[m] = []
1661 actions[m] = []
1663 actions[m].append((f, args, msg))
1662 actions[m].append((f, args, msg))
1664
1663
1665 if not util.fscasesensitive(repo.path):
1664 if not util.fscasesensitive(repo.path):
1666 # check collision between files only in p2 for clean update
1665 # check collision between files only in p2 for clean update
1667 if (not branchmerge and
1666 if (not branchmerge and
1668 (force or not wc.dirty(missing=True, branch=False))):
1667 (force or not wc.dirty(missing=True, branch=False))):
1669 _checkcollision(repo, p2.manifest(), None)
1668 _checkcollision(repo, p2.manifest(), None)
1670 else:
1669 else:
1671 _checkcollision(repo, wc.manifest(), actions)
1670 _checkcollision(repo, wc.manifest(), actions)
1672
1671
1673 # divergent renames
1672 # divergent renames
1674 for f, fl in sorted(diverge.iteritems()):
1673 for f, fl in sorted(diverge.iteritems()):
1675 repo.ui.warn(_("note: possible conflict - %s was renamed "
1674 repo.ui.warn(_("note: possible conflict - %s was renamed "
1676 "multiple times to:\n") % f)
1675 "multiple times to:\n") % f)
1677 for nf in fl:
1676 for nf in fl:
1678 repo.ui.warn(" %s\n" % nf)
1677 repo.ui.warn(" %s\n" % nf)
1679
1678
1680 # rename and delete
1679 # rename and delete
1681 for f, fl in sorted(renamedelete.iteritems()):
1680 for f, fl in sorted(renamedelete.iteritems()):
1682 repo.ui.warn(_("note: possible conflict - %s was deleted "
1681 repo.ui.warn(_("note: possible conflict - %s was deleted "
1683 "and renamed to:\n") % f)
1682 "and renamed to:\n") % f)
1684 for nf in fl:
1683 for nf in fl:
1685 repo.ui.warn(" %s\n" % nf)
1684 repo.ui.warn(" %s\n" % nf)
1686
1685
1687 ### apply phase
1686 ### apply phase
1688 if not branchmerge: # just jump to the new rev
1687 if not branchmerge: # just jump to the new rev
1689 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
1688 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
1690 if not partial:
1689 if not partial:
1691 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
1690 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
1692 # note that we're in the middle of an update
1691 # note that we're in the middle of an update
1693 repo.vfs.write('updatestate', p2.hex())
1692 repo.vfs.write('updatestate', p2.hex())
1694
1693
1695 stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
1694 stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
1696
1695
1697 if not partial:
1696 if not partial:
1698 with repo.dirstate.parentchange():
1697 with repo.dirstate.parentchange():
1699 repo.setparents(fp1, fp2)
1698 repo.setparents(fp1, fp2)
1700 recordupdates(repo, actions, branchmerge)
1699 recordupdates(repo, actions, branchmerge)
1701 # update completed, clear state
1700 # update completed, clear state
1702 util.unlink(repo.vfs.join('updatestate'))
1701 util.unlink(repo.vfs.join('updatestate'))
1703
1702
1704 if not branchmerge:
1703 if not branchmerge:
1705 repo.dirstate.setbranch(p2.branch())
1704 repo.dirstate.setbranch(p2.branch())
1706
1705
1707 if not partial:
1706 if not partial:
1708 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
1707 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
1709 return stats
1708 return stats
1710
1709
1711 def graft(repo, ctx, pctx, labels, keepparent=False):
1710 def graft(repo, ctx, pctx, labels, keepparent=False):
1712 """Do a graft-like merge.
1711 """Do a graft-like merge.
1713
1712
1714 This is a merge where the merge ancestor is chosen such that one
1713 This is a merge where the merge ancestor is chosen such that one
1715 or more changesets are grafted onto the current changeset. In
1714 or more changesets are grafted onto the current changeset. In
1716 addition to the merge, this fixes up the dirstate to include only
1715 addition to the merge, this fixes up the dirstate to include only
1717 a single parent (if keepparent is False) and tries to duplicate any
1716 a single parent (if keepparent is False) and tries to duplicate any
1718 renames/copies appropriately.
1717 renames/copies appropriately.
1719
1718
1720 ctx - changeset to rebase
1719 ctx - changeset to rebase
1721 pctx - merge base, usually ctx.p1()
1720 pctx - merge base, usually ctx.p1()
1722 labels - merge labels eg ['local', 'graft']
1721 labels - merge labels eg ['local', 'graft']
1723 keepparent - keep second parent if any
1722 keepparent - keep second parent if any
1724
1723
1725 """
1724 """
1726 # If we're grafting a descendant onto an ancestor, be sure to pass
1725 # If we're grafting a descendant onto an ancestor, be sure to pass
1727 # mergeancestor=True to update. This does two things: 1) allows the merge if
1726 # mergeancestor=True to update. This does two things: 1) allows the merge if
1728 # the destination is the same as the parent of the ctx (so we can use graft
1727 # the destination is the same as the parent of the ctx (so we can use graft
1729 # to copy commits), and 2) informs update that the incoming changes are
1728 # to copy commits), and 2) informs update that the incoming changes are
1730 # newer than the destination so it doesn't prompt about "remote changed foo
1729 # newer than the destination so it doesn't prompt about "remote changed foo
1731 # which local deleted".
1730 # which local deleted".
1732 mergeancestor = repo.changelog.isancestor(repo['.'].node(), ctx.node())
1731 mergeancestor = repo.changelog.isancestor(repo['.'].node(), ctx.node())
1733
1732
1734 stats = update(repo, ctx.node(), True, True, pctx.node(),
1733 stats = update(repo, ctx.node(), True, True, pctx.node(),
1735 mergeancestor=mergeancestor, labels=labels)
1734 mergeancestor=mergeancestor, labels=labels)
1736
1735
1737 pother = nullid
1736 pother = nullid
1738 parents = ctx.parents()
1737 parents = ctx.parents()
1739 if keepparent and len(parents) == 2 and pctx in parents:
1738 if keepparent and len(parents) == 2 and pctx in parents:
1740 parents.remove(pctx)
1739 parents.remove(pctx)
1741 pother = parents[0].node()
1740 pother = parents[0].node()
1742
1741
1743 with repo.dirstate.parentchange():
1742 with repo.dirstate.parentchange():
1744 repo.setparents(repo['.'].node(), pother)
1743 repo.setparents(repo['.'].node(), pother)
1745 repo.dirstate.write(repo.currenttransaction())
1744 repo.dirstate.write(repo.currenttransaction())
1746 # fix up dirstate for copies and renames
1745 # fix up dirstate for copies and renames
1747 copies.duplicatecopies(repo, ctx.rev(), pctx.rev())
1746 copies.duplicatecopies(repo, ctx.rev(), pctx.rev())
1748 return stats
1747 return stats
General Comments 0
You need to be logged in to leave comments. Login now