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