##// END OF EJS Templates
graft: let caller pass in overlayworkingctx to merge.graft()...
Martin von Zweigbergk -
r44692:fc7175df default
parent child Browse files
Show More
@@ -1,2721 +1,2733 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 shutil
11 import shutil
12 import stat
12 import stat
13 import struct
13 import struct
14
14
15 from .i18n import _
15 from .i18n import _
16 from .node import (
16 from .node import (
17 addednodeid,
17 addednodeid,
18 bin,
18 bin,
19 hex,
19 hex,
20 modifiednodeid,
20 modifiednodeid,
21 nullhex,
21 nullhex,
22 nullid,
22 nullid,
23 nullrev,
23 nullrev,
24 )
24 )
25 from .pycompat import delattr
25 from .pycompat import delattr
26 from .thirdparty import attr
26 from .thirdparty import attr
27 from . import (
27 from . import (
28 copies,
28 copies,
29 encoding,
29 encoding,
30 error,
30 error,
31 filemerge,
31 filemerge,
32 match as matchmod,
32 match as matchmod,
33 obsutil,
33 obsutil,
34 pathutil,
34 pathutil,
35 pycompat,
35 pycompat,
36 scmutil,
36 scmutil,
37 subrepoutil,
37 subrepoutil,
38 util,
38 util,
39 worker,
39 worker,
40 )
40 )
41 from .utils import hashutil
41 from .utils import hashutil
42
42
43 _pack = struct.pack
43 _pack = struct.pack
44 _unpack = struct.unpack
44 _unpack = struct.unpack
45
45
46
46
47 def _droponode(data):
47 def _droponode(data):
48 # used for compatibility for v1
48 # used for compatibility for v1
49 bits = data.split(b'\0')
49 bits = data.split(b'\0')
50 bits = bits[:-2] + bits[-1:]
50 bits = bits[:-2] + bits[-1:]
51 return b'\0'.join(bits)
51 return b'\0'.join(bits)
52
52
53
53
54 # Merge state record types. See ``mergestate`` docs for more.
54 # Merge state record types. See ``mergestate`` docs for more.
55 RECORD_LOCAL = b'L'
55 RECORD_LOCAL = b'L'
56 RECORD_OTHER = b'O'
56 RECORD_OTHER = b'O'
57 RECORD_MERGED = b'F'
57 RECORD_MERGED = b'F'
58 RECORD_CHANGEDELETE_CONFLICT = b'C'
58 RECORD_CHANGEDELETE_CONFLICT = b'C'
59 RECORD_MERGE_DRIVER_MERGE = b'D'
59 RECORD_MERGE_DRIVER_MERGE = b'D'
60 RECORD_PATH_CONFLICT = b'P'
60 RECORD_PATH_CONFLICT = b'P'
61 RECORD_MERGE_DRIVER_STATE = b'm'
61 RECORD_MERGE_DRIVER_STATE = b'm'
62 RECORD_FILE_VALUES = b'f'
62 RECORD_FILE_VALUES = b'f'
63 RECORD_LABELS = b'l'
63 RECORD_LABELS = b'l'
64 RECORD_OVERRIDE = b't'
64 RECORD_OVERRIDE = b't'
65 RECORD_UNSUPPORTED_MANDATORY = b'X'
65 RECORD_UNSUPPORTED_MANDATORY = b'X'
66 RECORD_UNSUPPORTED_ADVISORY = b'x'
66 RECORD_UNSUPPORTED_ADVISORY = b'x'
67
67
68 MERGE_DRIVER_STATE_UNMARKED = b'u'
68 MERGE_DRIVER_STATE_UNMARKED = b'u'
69 MERGE_DRIVER_STATE_MARKED = b'm'
69 MERGE_DRIVER_STATE_MARKED = b'm'
70 MERGE_DRIVER_STATE_SUCCESS = b's'
70 MERGE_DRIVER_STATE_SUCCESS = b's'
71
71
72 MERGE_RECORD_UNRESOLVED = b'u'
72 MERGE_RECORD_UNRESOLVED = b'u'
73 MERGE_RECORD_RESOLVED = b'r'
73 MERGE_RECORD_RESOLVED = b'r'
74 MERGE_RECORD_UNRESOLVED_PATH = b'pu'
74 MERGE_RECORD_UNRESOLVED_PATH = b'pu'
75 MERGE_RECORD_RESOLVED_PATH = b'pr'
75 MERGE_RECORD_RESOLVED_PATH = b'pr'
76 MERGE_RECORD_DRIVER_RESOLVED = b'd'
76 MERGE_RECORD_DRIVER_RESOLVED = b'd'
77
77
78 ACTION_FORGET = b'f'
78 ACTION_FORGET = b'f'
79 ACTION_REMOVE = b'r'
79 ACTION_REMOVE = b'r'
80 ACTION_ADD = b'a'
80 ACTION_ADD = b'a'
81 ACTION_GET = b'g'
81 ACTION_GET = b'g'
82 ACTION_PATH_CONFLICT = b'p'
82 ACTION_PATH_CONFLICT = b'p'
83 ACTION_PATH_CONFLICT_RESOLVE = b'pr'
83 ACTION_PATH_CONFLICT_RESOLVE = b'pr'
84 ACTION_ADD_MODIFIED = b'am'
84 ACTION_ADD_MODIFIED = b'am'
85 ACTION_CREATED = b'c'
85 ACTION_CREATED = b'c'
86 ACTION_DELETED_CHANGED = b'dc'
86 ACTION_DELETED_CHANGED = b'dc'
87 ACTION_CHANGED_DELETED = b'cd'
87 ACTION_CHANGED_DELETED = b'cd'
88 ACTION_MERGE = b'm'
88 ACTION_MERGE = b'm'
89 ACTION_LOCAL_DIR_RENAME_GET = b'dg'
89 ACTION_LOCAL_DIR_RENAME_GET = b'dg'
90 ACTION_DIR_RENAME_MOVE_LOCAL = b'dm'
90 ACTION_DIR_RENAME_MOVE_LOCAL = b'dm'
91 ACTION_KEEP = b'k'
91 ACTION_KEEP = b'k'
92 ACTION_EXEC = b'e'
92 ACTION_EXEC = b'e'
93 ACTION_CREATED_MERGE = b'cm'
93 ACTION_CREATED_MERGE = b'cm'
94
94
95
95
96 class mergestate(object):
96 class mergestate(object):
97 '''track 3-way merge state of individual files
97 '''track 3-way merge state of individual files
98
98
99 The merge state is stored on disk when needed. Two files are used: one with
99 The merge state is stored on disk when needed. Two files are used: one with
100 an old format (version 1), and one with a new format (version 2). Version 2
100 an old format (version 1), and one with a new format (version 2). Version 2
101 stores a superset of the data in version 1, including new kinds of records
101 stores a superset of the data in version 1, including new kinds of records
102 in the future. For more about the new format, see the documentation for
102 in the future. For more about the new format, see the documentation for
103 `_readrecordsv2`.
103 `_readrecordsv2`.
104
104
105 Each record can contain arbitrary content, and has an associated type. This
105 Each record can contain arbitrary content, and has an associated type. This
106 `type` should be a letter. If `type` is uppercase, the record is mandatory:
106 `type` should be a letter. If `type` is uppercase, the record is mandatory:
107 versions of Mercurial that don't support it should abort. If `type` is
107 versions of Mercurial that don't support it should abort. If `type` is
108 lowercase, the record can be safely ignored.
108 lowercase, the record can be safely ignored.
109
109
110 Currently known records:
110 Currently known records:
111
111
112 L: the node of the "local" part of the merge (hexified version)
112 L: the node of the "local" part of the merge (hexified version)
113 O: the node of the "other" part of the merge (hexified version)
113 O: the node of the "other" part of the merge (hexified version)
114 F: a file to be merged entry
114 F: a file to be merged entry
115 C: a change/delete or delete/change conflict
115 C: a change/delete or delete/change conflict
116 D: a file that the external merge driver will merge internally
116 D: a file that the external merge driver will merge internally
117 (experimental)
117 (experimental)
118 P: a path conflict (file vs directory)
118 P: a path conflict (file vs directory)
119 m: the external merge driver defined for this merge plus its run state
119 m: the external merge driver defined for this merge plus its run state
120 (experimental)
120 (experimental)
121 f: a (filename, dictionary) tuple of optional values for a given file
121 f: a (filename, dictionary) tuple of optional values for a given file
122 X: unsupported mandatory record type (used in tests)
122 X: unsupported mandatory record type (used in tests)
123 x: unsupported advisory record type (used in tests)
123 x: unsupported advisory record type (used in tests)
124 l: the labels for the parts of the merge.
124 l: the labels for the parts of the merge.
125
125
126 Merge driver run states (experimental):
126 Merge driver run states (experimental):
127 u: driver-resolved files unmarked -- needs to be run next time we're about
127 u: driver-resolved files unmarked -- needs to be run next time we're about
128 to resolve or commit
128 to resolve or commit
129 m: driver-resolved files marked -- only needs to be run before commit
129 m: driver-resolved files marked -- only needs to be run before commit
130 s: success/skipped -- does not need to be run any more
130 s: success/skipped -- does not need to be run any more
131
131
132 Merge record states (stored in self._state, indexed by filename):
132 Merge record states (stored in self._state, indexed by filename):
133 u: unresolved conflict
133 u: unresolved conflict
134 r: resolved conflict
134 r: resolved conflict
135 pu: unresolved path conflict (file conflicts with directory)
135 pu: unresolved path conflict (file conflicts with directory)
136 pr: resolved path conflict
136 pr: resolved path conflict
137 d: driver-resolved conflict
137 d: driver-resolved conflict
138
138
139 The resolve command transitions between 'u' and 'r' for conflicts and
139 The resolve command transitions between 'u' and 'r' for conflicts and
140 'pu' and 'pr' for path conflicts.
140 'pu' and 'pr' for path conflicts.
141 '''
141 '''
142
142
143 statepathv1 = b'merge/state'
143 statepathv1 = b'merge/state'
144 statepathv2 = b'merge/state2'
144 statepathv2 = b'merge/state2'
145
145
146 @staticmethod
146 @staticmethod
147 def clean(repo, node=None, other=None, labels=None):
147 def clean(repo, node=None, other=None, labels=None):
148 """Initialize a brand new merge state, removing any existing state on
148 """Initialize a brand new merge state, removing any existing state on
149 disk."""
149 disk."""
150 ms = mergestate(repo)
150 ms = mergestate(repo)
151 ms.reset(node, other, labels)
151 ms.reset(node, other, labels)
152 return ms
152 return ms
153
153
154 @staticmethod
154 @staticmethod
155 def read(repo):
155 def read(repo):
156 """Initialize the merge state, reading it from disk."""
156 """Initialize the merge state, reading it from disk."""
157 ms = mergestate(repo)
157 ms = mergestate(repo)
158 ms._read()
158 ms._read()
159 return ms
159 return ms
160
160
161 def __init__(self, repo):
161 def __init__(self, repo):
162 """Initialize the merge state.
162 """Initialize the merge state.
163
163
164 Do not use this directly! Instead call read() or clean()."""
164 Do not use this directly! Instead call read() or clean()."""
165 self._repo = repo
165 self._repo = repo
166 self._dirty = False
166 self._dirty = False
167 self._labels = None
167 self._labels = None
168
168
169 def reset(self, node=None, other=None, labels=None):
169 def reset(self, node=None, other=None, labels=None):
170 self._state = {}
170 self._state = {}
171 self._stateextras = {}
171 self._stateextras = {}
172 self._local = None
172 self._local = None
173 self._other = None
173 self._other = None
174 self._labels = labels
174 self._labels = labels
175 for var in ('localctx', 'otherctx'):
175 for var in ('localctx', 'otherctx'):
176 if var in vars(self):
176 if var in vars(self):
177 delattr(self, var)
177 delattr(self, var)
178 if node:
178 if node:
179 self._local = node
179 self._local = node
180 self._other = other
180 self._other = other
181 self._readmergedriver = None
181 self._readmergedriver = None
182 if self.mergedriver:
182 if self.mergedriver:
183 self._mdstate = MERGE_DRIVER_STATE_SUCCESS
183 self._mdstate = MERGE_DRIVER_STATE_SUCCESS
184 else:
184 else:
185 self._mdstate = MERGE_DRIVER_STATE_UNMARKED
185 self._mdstate = MERGE_DRIVER_STATE_UNMARKED
186 shutil.rmtree(self._repo.vfs.join(b'merge'), True)
186 shutil.rmtree(self._repo.vfs.join(b'merge'), True)
187 self._results = {}
187 self._results = {}
188 self._dirty = False
188 self._dirty = False
189
189
190 def _read(self):
190 def _read(self):
191 """Analyse each record content to restore a serialized state from disk
191 """Analyse each record content to restore a serialized state from disk
192
192
193 This function process "record" entry produced by the de-serialization
193 This function process "record" entry produced by the de-serialization
194 of on disk file.
194 of on disk file.
195 """
195 """
196 self._state = {}
196 self._state = {}
197 self._stateextras = {}
197 self._stateextras = {}
198 self._local = None
198 self._local = None
199 self._other = None
199 self._other = None
200 for var in ('localctx', 'otherctx'):
200 for var in ('localctx', 'otherctx'):
201 if var in vars(self):
201 if var in vars(self):
202 delattr(self, var)
202 delattr(self, var)
203 self._readmergedriver = None
203 self._readmergedriver = None
204 self._mdstate = MERGE_DRIVER_STATE_SUCCESS
204 self._mdstate = MERGE_DRIVER_STATE_SUCCESS
205 unsupported = set()
205 unsupported = set()
206 records = self._readrecords()
206 records = self._readrecords()
207 for rtype, record in records:
207 for rtype, record in records:
208 if rtype == RECORD_LOCAL:
208 if rtype == RECORD_LOCAL:
209 self._local = bin(record)
209 self._local = bin(record)
210 elif rtype == RECORD_OTHER:
210 elif rtype == RECORD_OTHER:
211 self._other = bin(record)
211 self._other = bin(record)
212 elif rtype == RECORD_MERGE_DRIVER_STATE:
212 elif rtype == RECORD_MERGE_DRIVER_STATE:
213 bits = record.split(b'\0', 1)
213 bits = record.split(b'\0', 1)
214 mdstate = bits[1]
214 mdstate = bits[1]
215 if len(mdstate) != 1 or mdstate not in (
215 if len(mdstate) != 1 or mdstate not in (
216 MERGE_DRIVER_STATE_UNMARKED,
216 MERGE_DRIVER_STATE_UNMARKED,
217 MERGE_DRIVER_STATE_MARKED,
217 MERGE_DRIVER_STATE_MARKED,
218 MERGE_DRIVER_STATE_SUCCESS,
218 MERGE_DRIVER_STATE_SUCCESS,
219 ):
219 ):
220 # the merge driver should be idempotent, so just rerun it
220 # the merge driver should be idempotent, so just rerun it
221 mdstate = MERGE_DRIVER_STATE_UNMARKED
221 mdstate = MERGE_DRIVER_STATE_UNMARKED
222
222
223 self._readmergedriver = bits[0]
223 self._readmergedriver = bits[0]
224 self._mdstate = mdstate
224 self._mdstate = mdstate
225 elif rtype in (
225 elif rtype in (
226 RECORD_MERGED,
226 RECORD_MERGED,
227 RECORD_CHANGEDELETE_CONFLICT,
227 RECORD_CHANGEDELETE_CONFLICT,
228 RECORD_PATH_CONFLICT,
228 RECORD_PATH_CONFLICT,
229 RECORD_MERGE_DRIVER_MERGE,
229 RECORD_MERGE_DRIVER_MERGE,
230 ):
230 ):
231 bits = record.split(b'\0')
231 bits = record.split(b'\0')
232 self._state[bits[0]] = bits[1:]
232 self._state[bits[0]] = bits[1:]
233 elif rtype == RECORD_FILE_VALUES:
233 elif rtype == RECORD_FILE_VALUES:
234 filename, rawextras = record.split(b'\0', 1)
234 filename, rawextras = record.split(b'\0', 1)
235 extraparts = rawextras.split(b'\0')
235 extraparts = rawextras.split(b'\0')
236 extras = {}
236 extras = {}
237 i = 0
237 i = 0
238 while i < len(extraparts):
238 while i < len(extraparts):
239 extras[extraparts[i]] = extraparts[i + 1]
239 extras[extraparts[i]] = extraparts[i + 1]
240 i += 2
240 i += 2
241
241
242 self._stateextras[filename] = extras
242 self._stateextras[filename] = extras
243 elif rtype == RECORD_LABELS:
243 elif rtype == RECORD_LABELS:
244 labels = record.split(b'\0', 2)
244 labels = record.split(b'\0', 2)
245 self._labels = [l for l in labels if len(l) > 0]
245 self._labels = [l for l in labels if len(l) > 0]
246 elif not rtype.islower():
246 elif not rtype.islower():
247 unsupported.add(rtype)
247 unsupported.add(rtype)
248 self._results = {}
248 self._results = {}
249 self._dirty = False
249 self._dirty = False
250
250
251 if unsupported:
251 if unsupported:
252 raise error.UnsupportedMergeRecords(unsupported)
252 raise error.UnsupportedMergeRecords(unsupported)
253
253
254 def _readrecords(self):
254 def _readrecords(self):
255 """Read merge state from disk and return a list of record (TYPE, data)
255 """Read merge state from disk and return a list of record (TYPE, data)
256
256
257 We read data from both v1 and v2 files and decide which one to use.
257 We read data from both v1 and v2 files and decide which one to use.
258
258
259 V1 has been used by version prior to 2.9.1 and contains less data than
259 V1 has been used by version prior to 2.9.1 and contains less data than
260 v2. We read both versions and check if no data in v2 contradicts
260 v2. We read both versions and check if no data in v2 contradicts
261 v1. If there is not contradiction we can safely assume that both v1
261 v1. If there is not contradiction we can safely assume that both v1
262 and v2 were written at the same time and use the extract data in v2. If
262 and v2 were written at the same time and use the extract data in v2. If
263 there is contradiction we ignore v2 content as we assume an old version
263 there is contradiction we ignore v2 content as we assume an old version
264 of Mercurial has overwritten the mergestate file and left an old v2
264 of Mercurial has overwritten the mergestate file and left an old v2
265 file around.
265 file around.
266
266
267 returns list of record [(TYPE, data), ...]"""
267 returns list of record [(TYPE, data), ...]"""
268 v1records = self._readrecordsv1()
268 v1records = self._readrecordsv1()
269 v2records = self._readrecordsv2()
269 v2records = self._readrecordsv2()
270 if self._v1v2match(v1records, v2records):
270 if self._v1v2match(v1records, v2records):
271 return v2records
271 return v2records
272 else:
272 else:
273 # v1 file is newer than v2 file, use it
273 # v1 file is newer than v2 file, use it
274 # we have to infer the "other" changeset of the merge
274 # we have to infer the "other" changeset of the merge
275 # we cannot do better than that with v1 of the format
275 # we cannot do better than that with v1 of the format
276 mctx = self._repo[None].parents()[-1]
276 mctx = self._repo[None].parents()[-1]
277 v1records.append((RECORD_OTHER, mctx.hex()))
277 v1records.append((RECORD_OTHER, mctx.hex()))
278 # add place holder "other" file node information
278 # add place holder "other" file node information
279 # nobody is using it yet so we do no need to fetch the data
279 # nobody is using it yet so we do no need to fetch the data
280 # if mctx was wrong `mctx[bits[-2]]` may fails.
280 # if mctx was wrong `mctx[bits[-2]]` may fails.
281 for idx, r in enumerate(v1records):
281 for idx, r in enumerate(v1records):
282 if r[0] == RECORD_MERGED:
282 if r[0] == RECORD_MERGED:
283 bits = r[1].split(b'\0')
283 bits = r[1].split(b'\0')
284 bits.insert(-2, b'')
284 bits.insert(-2, b'')
285 v1records[idx] = (r[0], b'\0'.join(bits))
285 v1records[idx] = (r[0], b'\0'.join(bits))
286 return v1records
286 return v1records
287
287
288 def _v1v2match(self, v1records, v2records):
288 def _v1v2match(self, v1records, v2records):
289 oldv2 = set() # old format version of v2 record
289 oldv2 = set() # old format version of v2 record
290 for rec in v2records:
290 for rec in v2records:
291 if rec[0] == RECORD_LOCAL:
291 if rec[0] == RECORD_LOCAL:
292 oldv2.add(rec)
292 oldv2.add(rec)
293 elif rec[0] == RECORD_MERGED:
293 elif rec[0] == RECORD_MERGED:
294 # drop the onode data (not contained in v1)
294 # drop the onode data (not contained in v1)
295 oldv2.add((RECORD_MERGED, _droponode(rec[1])))
295 oldv2.add((RECORD_MERGED, _droponode(rec[1])))
296 for rec in v1records:
296 for rec in v1records:
297 if rec not in oldv2:
297 if rec not in oldv2:
298 return False
298 return False
299 else:
299 else:
300 return True
300 return True
301
301
302 def _readrecordsv1(self):
302 def _readrecordsv1(self):
303 """read on disk merge state for version 1 file
303 """read on disk merge state for version 1 file
304
304
305 returns list of record [(TYPE, data), ...]
305 returns list of record [(TYPE, data), ...]
306
306
307 Note: the "F" data from this file are one entry short
307 Note: the "F" data from this file are one entry short
308 (no "other file node" entry)
308 (no "other file node" entry)
309 """
309 """
310 records = []
310 records = []
311 try:
311 try:
312 f = self._repo.vfs(self.statepathv1)
312 f = self._repo.vfs(self.statepathv1)
313 for i, l in enumerate(f):
313 for i, l in enumerate(f):
314 if i == 0:
314 if i == 0:
315 records.append((RECORD_LOCAL, l[:-1]))
315 records.append((RECORD_LOCAL, l[:-1]))
316 else:
316 else:
317 records.append((RECORD_MERGED, l[:-1]))
317 records.append((RECORD_MERGED, l[:-1]))
318 f.close()
318 f.close()
319 except IOError as err:
319 except IOError as err:
320 if err.errno != errno.ENOENT:
320 if err.errno != errno.ENOENT:
321 raise
321 raise
322 return records
322 return records
323
323
324 def _readrecordsv2(self):
324 def _readrecordsv2(self):
325 """read on disk merge state for version 2 file
325 """read on disk merge state for version 2 file
326
326
327 This format is a list of arbitrary records of the form:
327 This format is a list of arbitrary records of the form:
328
328
329 [type][length][content]
329 [type][length][content]
330
330
331 `type` is a single character, `length` is a 4 byte integer, and
331 `type` is a single character, `length` is a 4 byte integer, and
332 `content` is an arbitrary byte sequence of length `length`.
332 `content` is an arbitrary byte sequence of length `length`.
333
333
334 Mercurial versions prior to 3.7 have a bug where if there are
334 Mercurial versions prior to 3.7 have a bug where if there are
335 unsupported mandatory merge records, attempting to clear out the merge
335 unsupported mandatory merge records, attempting to clear out the merge
336 state with hg update --clean or similar aborts. The 't' record type
336 state with hg update --clean or similar aborts. The 't' record type
337 works around that by writing out what those versions treat as an
337 works around that by writing out what those versions treat as an
338 advisory record, but later versions interpret as special: the first
338 advisory record, but later versions interpret as special: the first
339 character is the 'real' record type and everything onwards is the data.
339 character is the 'real' record type and everything onwards is the data.
340
340
341 Returns list of records [(TYPE, data), ...]."""
341 Returns list of records [(TYPE, data), ...]."""
342 records = []
342 records = []
343 try:
343 try:
344 f = self._repo.vfs(self.statepathv2)
344 f = self._repo.vfs(self.statepathv2)
345 data = f.read()
345 data = f.read()
346 off = 0
346 off = 0
347 end = len(data)
347 end = len(data)
348 while off < end:
348 while off < end:
349 rtype = data[off : off + 1]
349 rtype = data[off : off + 1]
350 off += 1
350 off += 1
351 length = _unpack(b'>I', data[off : (off + 4)])[0]
351 length = _unpack(b'>I', data[off : (off + 4)])[0]
352 off += 4
352 off += 4
353 record = data[off : (off + length)]
353 record = data[off : (off + length)]
354 off += length
354 off += length
355 if rtype == RECORD_OVERRIDE:
355 if rtype == RECORD_OVERRIDE:
356 rtype, record = record[0:1], record[1:]
356 rtype, record = record[0:1], record[1:]
357 records.append((rtype, record))
357 records.append((rtype, record))
358 f.close()
358 f.close()
359 except IOError as err:
359 except IOError as err:
360 if err.errno != errno.ENOENT:
360 if err.errno != errno.ENOENT:
361 raise
361 raise
362 return records
362 return records
363
363
364 @util.propertycache
364 @util.propertycache
365 def mergedriver(self):
365 def mergedriver(self):
366 # protect against the following:
366 # protect against the following:
367 # - A configures a malicious merge driver in their hgrc, then
367 # - A configures a malicious merge driver in their hgrc, then
368 # pauses the merge
368 # pauses the merge
369 # - A edits their hgrc to remove references to the merge driver
369 # - A edits their hgrc to remove references to the merge driver
370 # - A gives a copy of their entire repo, including .hg, to B
370 # - A gives a copy of their entire repo, including .hg, to B
371 # - B inspects .hgrc and finds it to be clean
371 # - B inspects .hgrc and finds it to be clean
372 # - B then continues the merge and the malicious merge driver
372 # - B then continues the merge and the malicious merge driver
373 # gets invoked
373 # gets invoked
374 configmergedriver = self._repo.ui.config(
374 configmergedriver = self._repo.ui.config(
375 b'experimental', b'mergedriver'
375 b'experimental', b'mergedriver'
376 )
376 )
377 if (
377 if (
378 self._readmergedriver is not None
378 self._readmergedriver is not None
379 and self._readmergedriver != configmergedriver
379 and self._readmergedriver != configmergedriver
380 ):
380 ):
381 raise error.ConfigError(
381 raise error.ConfigError(
382 _(b"merge driver changed since merge started"),
382 _(b"merge driver changed since merge started"),
383 hint=_(b"revert merge driver change or abort merge"),
383 hint=_(b"revert merge driver change or abort merge"),
384 )
384 )
385
385
386 return configmergedriver
386 return configmergedriver
387
387
388 @util.propertycache
388 @util.propertycache
389 def localctx(self):
389 def localctx(self):
390 if self._local is None:
390 if self._local is None:
391 msg = b"localctx accessed but self._local isn't set"
391 msg = b"localctx accessed but self._local isn't set"
392 raise error.ProgrammingError(msg)
392 raise error.ProgrammingError(msg)
393 return self._repo[self._local]
393 return self._repo[self._local]
394
394
395 @util.propertycache
395 @util.propertycache
396 def otherctx(self):
396 def otherctx(self):
397 if self._other is None:
397 if self._other is None:
398 msg = b"otherctx accessed but self._other isn't set"
398 msg = b"otherctx accessed but self._other isn't set"
399 raise error.ProgrammingError(msg)
399 raise error.ProgrammingError(msg)
400 return self._repo[self._other]
400 return self._repo[self._other]
401
401
402 def active(self):
402 def active(self):
403 """Whether mergestate is active.
403 """Whether mergestate is active.
404
404
405 Returns True if there appears to be mergestate. This is a rough proxy
405 Returns True if there appears to be mergestate. This is a rough proxy
406 for "is a merge in progress."
406 for "is a merge in progress."
407 """
407 """
408 # Check local variables before looking at filesystem for performance
408 # Check local variables before looking at filesystem for performance
409 # reasons.
409 # reasons.
410 return (
410 return (
411 bool(self._local)
411 bool(self._local)
412 or bool(self._state)
412 or bool(self._state)
413 or self._repo.vfs.exists(self.statepathv1)
413 or self._repo.vfs.exists(self.statepathv1)
414 or self._repo.vfs.exists(self.statepathv2)
414 or self._repo.vfs.exists(self.statepathv2)
415 )
415 )
416
416
417 def commit(self):
417 def commit(self):
418 """Write current state on disk (if necessary)"""
418 """Write current state on disk (if necessary)"""
419 if self._dirty:
419 if self._dirty:
420 records = self._makerecords()
420 records = self._makerecords()
421 self._writerecords(records)
421 self._writerecords(records)
422 self._dirty = False
422 self._dirty = False
423
423
424 def _makerecords(self):
424 def _makerecords(self):
425 records = []
425 records = []
426 records.append((RECORD_LOCAL, hex(self._local)))
426 records.append((RECORD_LOCAL, hex(self._local)))
427 records.append((RECORD_OTHER, hex(self._other)))
427 records.append((RECORD_OTHER, hex(self._other)))
428 if self.mergedriver:
428 if self.mergedriver:
429 records.append(
429 records.append(
430 (
430 (
431 RECORD_MERGE_DRIVER_STATE,
431 RECORD_MERGE_DRIVER_STATE,
432 b'\0'.join([self.mergedriver, self._mdstate]),
432 b'\0'.join([self.mergedriver, self._mdstate]),
433 )
433 )
434 )
434 )
435 # Write out state items. In all cases, the value of the state map entry
435 # Write out state items. In all cases, the value of the state map entry
436 # is written as the contents of the record. The record type depends on
436 # is written as the contents of the record. The record type depends on
437 # the type of state that is stored, and capital-letter records are used
437 # the type of state that is stored, and capital-letter records are used
438 # to prevent older versions of Mercurial that do not support the feature
438 # to prevent older versions of Mercurial that do not support the feature
439 # from loading them.
439 # from loading them.
440 for filename, v in pycompat.iteritems(self._state):
440 for filename, v in pycompat.iteritems(self._state):
441 if v[0] == MERGE_RECORD_DRIVER_RESOLVED:
441 if v[0] == MERGE_RECORD_DRIVER_RESOLVED:
442 # Driver-resolved merge. These are stored in 'D' records.
442 # Driver-resolved merge. These are stored in 'D' records.
443 records.append(
443 records.append(
444 (RECORD_MERGE_DRIVER_MERGE, b'\0'.join([filename] + v))
444 (RECORD_MERGE_DRIVER_MERGE, b'\0'.join([filename] + v))
445 )
445 )
446 elif v[0] in (
446 elif v[0] in (
447 MERGE_RECORD_UNRESOLVED_PATH,
447 MERGE_RECORD_UNRESOLVED_PATH,
448 MERGE_RECORD_RESOLVED_PATH,
448 MERGE_RECORD_RESOLVED_PATH,
449 ):
449 ):
450 # Path conflicts. These are stored in 'P' records. The current
450 # Path conflicts. These are stored in 'P' records. The current
451 # resolution state ('pu' or 'pr') is stored within the record.
451 # resolution state ('pu' or 'pr') is stored within the record.
452 records.append(
452 records.append(
453 (RECORD_PATH_CONFLICT, b'\0'.join([filename] + v))
453 (RECORD_PATH_CONFLICT, b'\0'.join([filename] + v))
454 )
454 )
455 elif v[1] == nullhex or v[6] == nullhex:
455 elif v[1] == nullhex or v[6] == nullhex:
456 # Change/Delete or Delete/Change conflicts. These are stored in
456 # Change/Delete or Delete/Change conflicts. These are stored in
457 # 'C' records. v[1] is the local file, and is nullhex when the
457 # 'C' records. v[1] is the local file, and is nullhex when the
458 # file is deleted locally ('dc'). v[6] is the remote file, and
458 # file is deleted locally ('dc'). v[6] is the remote file, and
459 # is nullhex when the file is deleted remotely ('cd').
459 # is nullhex when the file is deleted remotely ('cd').
460 records.append(
460 records.append(
461 (RECORD_CHANGEDELETE_CONFLICT, b'\0'.join([filename] + v))
461 (RECORD_CHANGEDELETE_CONFLICT, b'\0'.join([filename] + v))
462 )
462 )
463 else:
463 else:
464 # Normal files. These are stored in 'F' records.
464 # Normal files. These are stored in 'F' records.
465 records.append((RECORD_MERGED, b'\0'.join([filename] + v)))
465 records.append((RECORD_MERGED, b'\0'.join([filename] + v)))
466 for filename, extras in sorted(pycompat.iteritems(self._stateextras)):
466 for filename, extras in sorted(pycompat.iteritems(self._stateextras)):
467 rawextras = b'\0'.join(
467 rawextras = b'\0'.join(
468 b'%s\0%s' % (k, v) for k, v in pycompat.iteritems(extras)
468 b'%s\0%s' % (k, v) for k, v in pycompat.iteritems(extras)
469 )
469 )
470 records.append(
470 records.append(
471 (RECORD_FILE_VALUES, b'%s\0%s' % (filename, rawextras))
471 (RECORD_FILE_VALUES, b'%s\0%s' % (filename, rawextras))
472 )
472 )
473 if self._labels is not None:
473 if self._labels is not None:
474 labels = b'\0'.join(self._labels)
474 labels = b'\0'.join(self._labels)
475 records.append((RECORD_LABELS, labels))
475 records.append((RECORD_LABELS, labels))
476 return records
476 return records
477
477
478 def _writerecords(self, records):
478 def _writerecords(self, records):
479 """Write current state on disk (both v1 and v2)"""
479 """Write current state on disk (both v1 and v2)"""
480 self._writerecordsv1(records)
480 self._writerecordsv1(records)
481 self._writerecordsv2(records)
481 self._writerecordsv2(records)
482
482
483 def _writerecordsv1(self, records):
483 def _writerecordsv1(self, records):
484 """Write current state on disk in a version 1 file"""
484 """Write current state on disk in a version 1 file"""
485 f = self._repo.vfs(self.statepathv1, b'wb')
485 f = self._repo.vfs(self.statepathv1, b'wb')
486 irecords = iter(records)
486 irecords = iter(records)
487 lrecords = next(irecords)
487 lrecords = next(irecords)
488 assert lrecords[0] == RECORD_LOCAL
488 assert lrecords[0] == RECORD_LOCAL
489 f.write(hex(self._local) + b'\n')
489 f.write(hex(self._local) + b'\n')
490 for rtype, data in irecords:
490 for rtype, data in irecords:
491 if rtype == RECORD_MERGED:
491 if rtype == RECORD_MERGED:
492 f.write(b'%s\n' % _droponode(data))
492 f.write(b'%s\n' % _droponode(data))
493 f.close()
493 f.close()
494
494
495 def _writerecordsv2(self, records):
495 def _writerecordsv2(self, records):
496 """Write current state on disk in a version 2 file
496 """Write current state on disk in a version 2 file
497
497
498 See the docstring for _readrecordsv2 for why we use 't'."""
498 See the docstring for _readrecordsv2 for why we use 't'."""
499 # these are the records that all version 2 clients can read
499 # these are the records that all version 2 clients can read
500 allowlist = (RECORD_LOCAL, RECORD_OTHER, RECORD_MERGED)
500 allowlist = (RECORD_LOCAL, RECORD_OTHER, RECORD_MERGED)
501 f = self._repo.vfs(self.statepathv2, b'wb')
501 f = self._repo.vfs(self.statepathv2, b'wb')
502 for key, data in records:
502 for key, data in records:
503 assert len(key) == 1
503 assert len(key) == 1
504 if key not in allowlist:
504 if key not in allowlist:
505 key, data = RECORD_OVERRIDE, b'%s%s' % (key, data)
505 key, data = RECORD_OVERRIDE, b'%s%s' % (key, data)
506 format = b'>sI%is' % len(data)
506 format = b'>sI%is' % len(data)
507 f.write(_pack(format, key, len(data), data))
507 f.write(_pack(format, key, len(data), data))
508 f.close()
508 f.close()
509
509
510 @staticmethod
510 @staticmethod
511 def getlocalkey(path):
511 def getlocalkey(path):
512 """hash the path of a local file context for storage in the .hg/merge
512 """hash the path of a local file context for storage in the .hg/merge
513 directory."""
513 directory."""
514
514
515 return hex(hashutil.sha1(path).digest())
515 return hex(hashutil.sha1(path).digest())
516
516
517 def add(self, fcl, fco, fca, fd):
517 def add(self, fcl, fco, fca, fd):
518 """add a new (potentially?) conflicting file the merge state
518 """add a new (potentially?) conflicting file the merge state
519 fcl: file context for local,
519 fcl: file context for local,
520 fco: file context for remote,
520 fco: file context for remote,
521 fca: file context for ancestors,
521 fca: file context for ancestors,
522 fd: file path of the resulting merge.
522 fd: file path of the resulting merge.
523
523
524 note: also write the local version to the `.hg/merge` directory.
524 note: also write the local version to the `.hg/merge` directory.
525 """
525 """
526 if fcl.isabsent():
526 if fcl.isabsent():
527 localkey = nullhex
527 localkey = nullhex
528 else:
528 else:
529 localkey = mergestate.getlocalkey(fcl.path())
529 localkey = mergestate.getlocalkey(fcl.path())
530 self._repo.vfs.write(b'merge/' + localkey, fcl.data())
530 self._repo.vfs.write(b'merge/' + localkey, fcl.data())
531 self._state[fd] = [
531 self._state[fd] = [
532 MERGE_RECORD_UNRESOLVED,
532 MERGE_RECORD_UNRESOLVED,
533 localkey,
533 localkey,
534 fcl.path(),
534 fcl.path(),
535 fca.path(),
535 fca.path(),
536 hex(fca.filenode()),
536 hex(fca.filenode()),
537 fco.path(),
537 fco.path(),
538 hex(fco.filenode()),
538 hex(fco.filenode()),
539 fcl.flags(),
539 fcl.flags(),
540 ]
540 ]
541 self._stateextras[fd] = {b'ancestorlinknode': hex(fca.node())}
541 self._stateextras[fd] = {b'ancestorlinknode': hex(fca.node())}
542 self._dirty = True
542 self._dirty = True
543
543
544 def addpath(self, path, frename, forigin):
544 def addpath(self, path, frename, forigin):
545 """add a new conflicting path to the merge state
545 """add a new conflicting path to the merge state
546 path: the path that conflicts
546 path: the path that conflicts
547 frename: the filename the conflicting file was renamed to
547 frename: the filename the conflicting file was renamed to
548 forigin: origin of the file ('l' or 'r' for local/remote)
548 forigin: origin of the file ('l' or 'r' for local/remote)
549 """
549 """
550 self._state[path] = [MERGE_RECORD_UNRESOLVED_PATH, frename, forigin]
550 self._state[path] = [MERGE_RECORD_UNRESOLVED_PATH, frename, forigin]
551 self._dirty = True
551 self._dirty = True
552
552
553 def __contains__(self, dfile):
553 def __contains__(self, dfile):
554 return dfile in self._state
554 return dfile in self._state
555
555
556 def __getitem__(self, dfile):
556 def __getitem__(self, dfile):
557 return self._state[dfile][0]
557 return self._state[dfile][0]
558
558
559 def __iter__(self):
559 def __iter__(self):
560 return iter(sorted(self._state))
560 return iter(sorted(self._state))
561
561
562 def files(self):
562 def files(self):
563 return self._state.keys()
563 return self._state.keys()
564
564
565 def mark(self, dfile, state):
565 def mark(self, dfile, state):
566 self._state[dfile][0] = state
566 self._state[dfile][0] = state
567 self._dirty = True
567 self._dirty = True
568
568
569 def mdstate(self):
569 def mdstate(self):
570 return self._mdstate
570 return self._mdstate
571
571
572 def unresolved(self):
572 def unresolved(self):
573 """Obtain the paths of unresolved files."""
573 """Obtain the paths of unresolved files."""
574
574
575 for f, entry in pycompat.iteritems(self._state):
575 for f, entry in pycompat.iteritems(self._state):
576 if entry[0] in (
576 if entry[0] in (
577 MERGE_RECORD_UNRESOLVED,
577 MERGE_RECORD_UNRESOLVED,
578 MERGE_RECORD_UNRESOLVED_PATH,
578 MERGE_RECORD_UNRESOLVED_PATH,
579 ):
579 ):
580 yield f
580 yield f
581
581
582 def driverresolved(self):
582 def driverresolved(self):
583 """Obtain the paths of driver-resolved files."""
583 """Obtain the paths of driver-resolved files."""
584
584
585 for f, entry in self._state.items():
585 for f, entry in self._state.items():
586 if entry[0] == MERGE_RECORD_DRIVER_RESOLVED:
586 if entry[0] == MERGE_RECORD_DRIVER_RESOLVED:
587 yield f
587 yield f
588
588
589 def extras(self, filename):
589 def extras(self, filename):
590 return self._stateextras.setdefault(filename, {})
590 return self._stateextras.setdefault(filename, {})
591
591
592 def _resolve(self, preresolve, dfile, wctx):
592 def _resolve(self, preresolve, dfile, wctx):
593 """rerun merge process for file path `dfile`"""
593 """rerun merge process for file path `dfile`"""
594 if self[dfile] in (MERGE_RECORD_RESOLVED, MERGE_RECORD_DRIVER_RESOLVED):
594 if self[dfile] in (MERGE_RECORD_RESOLVED, MERGE_RECORD_DRIVER_RESOLVED):
595 return True, 0
595 return True, 0
596 stateentry = self._state[dfile]
596 stateentry = self._state[dfile]
597 state, localkey, lfile, afile, anode, ofile, onode, flags = stateentry
597 state, localkey, lfile, afile, anode, ofile, onode, flags = stateentry
598 octx = self._repo[self._other]
598 octx = self._repo[self._other]
599 extras = self.extras(dfile)
599 extras = self.extras(dfile)
600 anccommitnode = extras.get(b'ancestorlinknode')
600 anccommitnode = extras.get(b'ancestorlinknode')
601 if anccommitnode:
601 if anccommitnode:
602 actx = self._repo[anccommitnode]
602 actx = self._repo[anccommitnode]
603 else:
603 else:
604 actx = None
604 actx = None
605 fcd = self._filectxorabsent(localkey, wctx, dfile)
605 fcd = self._filectxorabsent(localkey, wctx, dfile)
606 fco = self._filectxorabsent(onode, octx, ofile)
606 fco = self._filectxorabsent(onode, octx, ofile)
607 # TODO: move this to filectxorabsent
607 # TODO: move this to filectxorabsent
608 fca = self._repo.filectx(afile, fileid=anode, changectx=actx)
608 fca = self._repo.filectx(afile, fileid=anode, changectx=actx)
609 # "premerge" x flags
609 # "premerge" x flags
610 flo = fco.flags()
610 flo = fco.flags()
611 fla = fca.flags()
611 fla = fca.flags()
612 if b'x' in flags + flo + fla and b'l' not in flags + flo + fla:
612 if b'x' in flags + flo + fla and b'l' not in flags + flo + fla:
613 if fca.node() == nullid and flags != flo:
613 if fca.node() == nullid and flags != flo:
614 if preresolve:
614 if preresolve:
615 self._repo.ui.warn(
615 self._repo.ui.warn(
616 _(
616 _(
617 b'warning: cannot merge flags for %s '
617 b'warning: cannot merge flags for %s '
618 b'without common ancestor - keeping local flags\n'
618 b'without common ancestor - keeping local flags\n'
619 )
619 )
620 % afile
620 % afile
621 )
621 )
622 elif flags == fla:
622 elif flags == fla:
623 flags = flo
623 flags = flo
624 if preresolve:
624 if preresolve:
625 # restore local
625 # restore local
626 if localkey != nullhex:
626 if localkey != nullhex:
627 f = self._repo.vfs(b'merge/' + localkey)
627 f = self._repo.vfs(b'merge/' + localkey)
628 wctx[dfile].write(f.read(), flags)
628 wctx[dfile].write(f.read(), flags)
629 f.close()
629 f.close()
630 else:
630 else:
631 wctx[dfile].remove(ignoremissing=True)
631 wctx[dfile].remove(ignoremissing=True)
632 complete, r, deleted = filemerge.premerge(
632 complete, r, deleted = filemerge.premerge(
633 self._repo,
633 self._repo,
634 wctx,
634 wctx,
635 self._local,
635 self._local,
636 lfile,
636 lfile,
637 fcd,
637 fcd,
638 fco,
638 fco,
639 fca,
639 fca,
640 labels=self._labels,
640 labels=self._labels,
641 )
641 )
642 else:
642 else:
643 complete, r, deleted = filemerge.filemerge(
643 complete, r, deleted = filemerge.filemerge(
644 self._repo,
644 self._repo,
645 wctx,
645 wctx,
646 self._local,
646 self._local,
647 lfile,
647 lfile,
648 fcd,
648 fcd,
649 fco,
649 fco,
650 fca,
650 fca,
651 labels=self._labels,
651 labels=self._labels,
652 )
652 )
653 if r is None:
653 if r is None:
654 # no real conflict
654 # no real conflict
655 del self._state[dfile]
655 del self._state[dfile]
656 self._stateextras.pop(dfile, None)
656 self._stateextras.pop(dfile, None)
657 self._dirty = True
657 self._dirty = True
658 elif not r:
658 elif not r:
659 self.mark(dfile, MERGE_RECORD_RESOLVED)
659 self.mark(dfile, MERGE_RECORD_RESOLVED)
660
660
661 if complete:
661 if complete:
662 action = None
662 action = None
663 if deleted:
663 if deleted:
664 if fcd.isabsent():
664 if fcd.isabsent():
665 # dc: local picked. Need to drop if present, which may
665 # dc: local picked. Need to drop if present, which may
666 # happen on re-resolves.
666 # happen on re-resolves.
667 action = ACTION_FORGET
667 action = ACTION_FORGET
668 else:
668 else:
669 # cd: remote picked (or otherwise deleted)
669 # cd: remote picked (or otherwise deleted)
670 action = ACTION_REMOVE
670 action = ACTION_REMOVE
671 else:
671 else:
672 if fcd.isabsent(): # dc: remote picked
672 if fcd.isabsent(): # dc: remote picked
673 action = ACTION_GET
673 action = ACTION_GET
674 elif fco.isabsent(): # cd: local picked
674 elif fco.isabsent(): # cd: local picked
675 if dfile in self.localctx:
675 if dfile in self.localctx:
676 action = ACTION_ADD_MODIFIED
676 action = ACTION_ADD_MODIFIED
677 else:
677 else:
678 action = ACTION_ADD
678 action = ACTION_ADD
679 # else: regular merges (no action necessary)
679 # else: regular merges (no action necessary)
680 self._results[dfile] = r, action
680 self._results[dfile] = r, action
681
681
682 return complete, r
682 return complete, r
683
683
684 def _filectxorabsent(self, hexnode, ctx, f):
684 def _filectxorabsent(self, hexnode, ctx, f):
685 if hexnode == nullhex:
685 if hexnode == nullhex:
686 return filemerge.absentfilectx(ctx, f)
686 return filemerge.absentfilectx(ctx, f)
687 else:
687 else:
688 return ctx[f]
688 return ctx[f]
689
689
690 def preresolve(self, dfile, wctx):
690 def preresolve(self, dfile, wctx):
691 """run premerge process for dfile
691 """run premerge process for dfile
692
692
693 Returns whether the merge is complete, and the exit code."""
693 Returns whether the merge is complete, and the exit code."""
694 return self._resolve(True, dfile, wctx)
694 return self._resolve(True, dfile, wctx)
695
695
696 def resolve(self, dfile, wctx):
696 def resolve(self, dfile, wctx):
697 """run merge process (assuming premerge was run) for dfile
697 """run merge process (assuming premerge was run) for dfile
698
698
699 Returns the exit code of the merge."""
699 Returns the exit code of the merge."""
700 return self._resolve(False, dfile, wctx)[1]
700 return self._resolve(False, dfile, wctx)[1]
701
701
702 def counts(self):
702 def counts(self):
703 """return counts for updated, merged and removed files in this
703 """return counts for updated, merged and removed files in this
704 session"""
704 session"""
705 updated, merged, removed = 0, 0, 0
705 updated, merged, removed = 0, 0, 0
706 for r, action in pycompat.itervalues(self._results):
706 for r, action in pycompat.itervalues(self._results):
707 if r is None:
707 if r is None:
708 updated += 1
708 updated += 1
709 elif r == 0:
709 elif r == 0:
710 if action == ACTION_REMOVE:
710 if action == ACTION_REMOVE:
711 removed += 1
711 removed += 1
712 else:
712 else:
713 merged += 1
713 merged += 1
714 return updated, merged, removed
714 return updated, merged, removed
715
715
716 def unresolvedcount(self):
716 def unresolvedcount(self):
717 """get unresolved count for this merge (persistent)"""
717 """get unresolved count for this merge (persistent)"""
718 return len(list(self.unresolved()))
718 return len(list(self.unresolved()))
719
719
720 def actions(self):
720 def actions(self):
721 """return lists of actions to perform on the dirstate"""
721 """return lists of actions to perform on the dirstate"""
722 actions = {
722 actions = {
723 ACTION_REMOVE: [],
723 ACTION_REMOVE: [],
724 ACTION_FORGET: [],
724 ACTION_FORGET: [],
725 ACTION_ADD: [],
725 ACTION_ADD: [],
726 ACTION_ADD_MODIFIED: [],
726 ACTION_ADD_MODIFIED: [],
727 ACTION_GET: [],
727 ACTION_GET: [],
728 }
728 }
729 for f, (r, action) in pycompat.iteritems(self._results):
729 for f, (r, action) in pycompat.iteritems(self._results):
730 if action is not None:
730 if action is not None:
731 actions[action].append((f, None, b"merge result"))
731 actions[action].append((f, None, b"merge result"))
732 return actions
732 return actions
733
733
734 def recordactions(self):
734 def recordactions(self):
735 """record remove/add/get actions in the dirstate"""
735 """record remove/add/get actions in the dirstate"""
736 branchmerge = self._repo.dirstate.p2() != nullid
736 branchmerge = self._repo.dirstate.p2() != nullid
737 recordupdates(self._repo, self.actions(), branchmerge, None)
737 recordupdates(self._repo, self.actions(), branchmerge, None)
738
738
739 def queueremove(self, f):
739 def queueremove(self, f):
740 """queues a file to be removed from the dirstate
740 """queues a file to be removed from the dirstate
741
741
742 Meant for use by custom merge drivers."""
742 Meant for use by custom merge drivers."""
743 self._results[f] = 0, ACTION_REMOVE
743 self._results[f] = 0, ACTION_REMOVE
744
744
745 def queueadd(self, f):
745 def queueadd(self, f):
746 """queues a file to be added to the dirstate
746 """queues a file to be added to the dirstate
747
747
748 Meant for use by custom merge drivers."""
748 Meant for use by custom merge drivers."""
749 self._results[f] = 0, ACTION_ADD
749 self._results[f] = 0, ACTION_ADD
750
750
751 def queueget(self, f):
751 def queueget(self, f):
752 """queues a file to be marked modified in the dirstate
752 """queues a file to be marked modified in the dirstate
753
753
754 Meant for use by custom merge drivers."""
754 Meant for use by custom merge drivers."""
755 self._results[f] = 0, ACTION_GET
755 self._results[f] = 0, ACTION_GET
756
756
757
757
758 def _getcheckunknownconfig(repo, section, name):
758 def _getcheckunknownconfig(repo, section, name):
759 config = repo.ui.config(section, name)
759 config = repo.ui.config(section, name)
760 valid = [b'abort', b'ignore', b'warn']
760 valid = [b'abort', b'ignore', b'warn']
761 if config not in valid:
761 if config not in valid:
762 validstr = b', '.join([b"'" + v + b"'" for v in valid])
762 validstr = b', '.join([b"'" + v + b"'" for v in valid])
763 raise error.ConfigError(
763 raise error.ConfigError(
764 _(b"%s.%s not valid ('%s' is none of %s)")
764 _(b"%s.%s not valid ('%s' is none of %s)")
765 % (section, name, config, validstr)
765 % (section, name, config, validstr)
766 )
766 )
767 return config
767 return config
768
768
769
769
770 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
770 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
771 if wctx.isinmemory():
771 if wctx.isinmemory():
772 # Nothing to do in IMM because nothing in the "working copy" can be an
772 # Nothing to do in IMM because nothing in the "working copy" can be an
773 # unknown file.
773 # unknown file.
774 #
774 #
775 # Note that we should bail out here, not in ``_checkunknownfiles()``,
775 # Note that we should bail out here, not in ``_checkunknownfiles()``,
776 # because that function does other useful work.
776 # because that function does other useful work.
777 return False
777 return False
778
778
779 if f2 is None:
779 if f2 is None:
780 f2 = f
780 f2 = f
781 return (
781 return (
782 repo.wvfs.audit.check(f)
782 repo.wvfs.audit.check(f)
783 and repo.wvfs.isfileorlink(f)
783 and repo.wvfs.isfileorlink(f)
784 and repo.dirstate.normalize(f) not in repo.dirstate
784 and repo.dirstate.normalize(f) not in repo.dirstate
785 and mctx[f2].cmp(wctx[f])
785 and mctx[f2].cmp(wctx[f])
786 )
786 )
787
787
788
788
789 class _unknowndirschecker(object):
789 class _unknowndirschecker(object):
790 """
790 """
791 Look for any unknown files or directories that may have a path conflict
791 Look for any unknown files or directories that may have a path conflict
792 with a file. If any path prefix of the file exists as a file or link,
792 with a file. If any path prefix of the file exists as a file or link,
793 then it conflicts. If the file itself is a directory that contains any
793 then it conflicts. If the file itself is a directory that contains any
794 file that is not tracked, then it conflicts.
794 file that is not tracked, then it conflicts.
795
795
796 Returns the shortest path at which a conflict occurs, or None if there is
796 Returns the shortest path at which a conflict occurs, or None if there is
797 no conflict.
797 no conflict.
798 """
798 """
799
799
800 def __init__(self):
800 def __init__(self):
801 # A set of paths known to be good. This prevents repeated checking of
801 # A set of paths known to be good. This prevents repeated checking of
802 # dirs. It will be updated with any new dirs that are checked and found
802 # dirs. It will be updated with any new dirs that are checked and found
803 # to be safe.
803 # to be safe.
804 self._unknowndircache = set()
804 self._unknowndircache = set()
805
805
806 # A set of paths that are known to be absent. This prevents repeated
806 # A set of paths that are known to be absent. This prevents repeated
807 # checking of subdirectories that are known not to exist. It will be
807 # checking of subdirectories that are known not to exist. It will be
808 # updated with any new dirs that are checked and found to be absent.
808 # updated with any new dirs that are checked and found to be absent.
809 self._missingdircache = set()
809 self._missingdircache = set()
810
810
811 def __call__(self, repo, wctx, f):
811 def __call__(self, repo, wctx, f):
812 if wctx.isinmemory():
812 if wctx.isinmemory():
813 # Nothing to do in IMM for the same reason as ``_checkunknownfile``.
813 # Nothing to do in IMM for the same reason as ``_checkunknownfile``.
814 return False
814 return False
815
815
816 # Check for path prefixes that exist as unknown files.
816 # Check for path prefixes that exist as unknown files.
817 for p in reversed(list(pathutil.finddirs(f))):
817 for p in reversed(list(pathutil.finddirs(f))):
818 if p in self._missingdircache:
818 if p in self._missingdircache:
819 return
819 return
820 if p in self._unknowndircache:
820 if p in self._unknowndircache:
821 continue
821 continue
822 if repo.wvfs.audit.check(p):
822 if repo.wvfs.audit.check(p):
823 if (
823 if (
824 repo.wvfs.isfileorlink(p)
824 repo.wvfs.isfileorlink(p)
825 and repo.dirstate.normalize(p) not in repo.dirstate
825 and repo.dirstate.normalize(p) not in repo.dirstate
826 ):
826 ):
827 return p
827 return p
828 if not repo.wvfs.lexists(p):
828 if not repo.wvfs.lexists(p):
829 self._missingdircache.add(p)
829 self._missingdircache.add(p)
830 return
830 return
831 self._unknowndircache.add(p)
831 self._unknowndircache.add(p)
832
832
833 # Check if the file conflicts with a directory containing unknown files.
833 # Check if the file conflicts with a directory containing unknown files.
834 if repo.wvfs.audit.check(f) and repo.wvfs.isdir(f):
834 if repo.wvfs.audit.check(f) and repo.wvfs.isdir(f):
835 # Does the directory contain any files that are not in the dirstate?
835 # Does the directory contain any files that are not in the dirstate?
836 for p, dirs, files in repo.wvfs.walk(f):
836 for p, dirs, files in repo.wvfs.walk(f):
837 for fn in files:
837 for fn in files:
838 relf = util.pconvert(repo.wvfs.reljoin(p, fn))
838 relf = util.pconvert(repo.wvfs.reljoin(p, fn))
839 relf = repo.dirstate.normalize(relf, isknown=True)
839 relf = repo.dirstate.normalize(relf, isknown=True)
840 if relf not in repo.dirstate:
840 if relf not in repo.dirstate:
841 return f
841 return f
842 return None
842 return None
843
843
844
844
845 def _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce):
845 def _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce):
846 """
846 """
847 Considers any actions that care about the presence of conflicting unknown
847 Considers any actions that care about the presence of conflicting unknown
848 files. For some actions, the result is to abort; for others, it is to
848 files. For some actions, the result is to abort; for others, it is to
849 choose a different action.
849 choose a different action.
850 """
850 """
851 fileconflicts = set()
851 fileconflicts = set()
852 pathconflicts = set()
852 pathconflicts = set()
853 warnconflicts = set()
853 warnconflicts = set()
854 abortconflicts = set()
854 abortconflicts = set()
855 unknownconfig = _getcheckunknownconfig(repo, b'merge', b'checkunknown')
855 unknownconfig = _getcheckunknownconfig(repo, b'merge', b'checkunknown')
856 ignoredconfig = _getcheckunknownconfig(repo, b'merge', b'checkignored')
856 ignoredconfig = _getcheckunknownconfig(repo, b'merge', b'checkignored')
857 pathconfig = repo.ui.configbool(
857 pathconfig = repo.ui.configbool(
858 b'experimental', b'merge.checkpathconflicts'
858 b'experimental', b'merge.checkpathconflicts'
859 )
859 )
860 if not force:
860 if not force:
861
861
862 def collectconflicts(conflicts, config):
862 def collectconflicts(conflicts, config):
863 if config == b'abort':
863 if config == b'abort':
864 abortconflicts.update(conflicts)
864 abortconflicts.update(conflicts)
865 elif config == b'warn':
865 elif config == b'warn':
866 warnconflicts.update(conflicts)
866 warnconflicts.update(conflicts)
867
867
868 checkunknowndirs = _unknowndirschecker()
868 checkunknowndirs = _unknowndirschecker()
869 for f, (m, args, msg) in pycompat.iteritems(actions):
869 for f, (m, args, msg) in pycompat.iteritems(actions):
870 if m in (ACTION_CREATED, ACTION_DELETED_CHANGED):
870 if m in (ACTION_CREATED, ACTION_DELETED_CHANGED):
871 if _checkunknownfile(repo, wctx, mctx, f):
871 if _checkunknownfile(repo, wctx, mctx, f):
872 fileconflicts.add(f)
872 fileconflicts.add(f)
873 elif pathconfig and f not in wctx:
873 elif pathconfig and f not in wctx:
874 path = checkunknowndirs(repo, wctx, f)
874 path = checkunknowndirs(repo, wctx, f)
875 if path is not None:
875 if path is not None:
876 pathconflicts.add(path)
876 pathconflicts.add(path)
877 elif m == ACTION_LOCAL_DIR_RENAME_GET:
877 elif m == ACTION_LOCAL_DIR_RENAME_GET:
878 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
878 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
879 fileconflicts.add(f)
879 fileconflicts.add(f)
880
880
881 allconflicts = fileconflicts | pathconflicts
881 allconflicts = fileconflicts | pathconflicts
882 ignoredconflicts = {c for c in allconflicts if repo.dirstate._ignore(c)}
882 ignoredconflicts = {c for c in allconflicts if repo.dirstate._ignore(c)}
883 unknownconflicts = allconflicts - ignoredconflicts
883 unknownconflicts = allconflicts - ignoredconflicts
884 collectconflicts(ignoredconflicts, ignoredconfig)
884 collectconflicts(ignoredconflicts, ignoredconfig)
885 collectconflicts(unknownconflicts, unknownconfig)
885 collectconflicts(unknownconflicts, unknownconfig)
886 else:
886 else:
887 for f, (m, args, msg) in pycompat.iteritems(actions):
887 for f, (m, args, msg) in pycompat.iteritems(actions):
888 if m == ACTION_CREATED_MERGE:
888 if m == ACTION_CREATED_MERGE:
889 fl2, anc = args
889 fl2, anc = args
890 different = _checkunknownfile(repo, wctx, mctx, f)
890 different = _checkunknownfile(repo, wctx, mctx, f)
891 if repo.dirstate._ignore(f):
891 if repo.dirstate._ignore(f):
892 config = ignoredconfig
892 config = ignoredconfig
893 else:
893 else:
894 config = unknownconfig
894 config = unknownconfig
895
895
896 # The behavior when force is True is described by this table:
896 # The behavior when force is True is described by this table:
897 # config different mergeforce | action backup
897 # config different mergeforce | action backup
898 # * n * | get n
898 # * n * | get n
899 # * y y | merge -
899 # * y y | merge -
900 # abort y n | merge - (1)
900 # abort y n | merge - (1)
901 # warn y n | warn + get y
901 # warn y n | warn + get y
902 # ignore y n | get y
902 # ignore y n | get y
903 #
903 #
904 # (1) this is probably the wrong behavior here -- we should
904 # (1) this is probably the wrong behavior here -- we should
905 # probably abort, but some actions like rebases currently
905 # probably abort, but some actions like rebases currently
906 # don't like an abort happening in the middle of
906 # don't like an abort happening in the middle of
907 # merge.update.
907 # merge.update.
908 if not different:
908 if not different:
909 actions[f] = (ACTION_GET, (fl2, False), b'remote created')
909 actions[f] = (ACTION_GET, (fl2, False), b'remote created')
910 elif mergeforce or config == b'abort':
910 elif mergeforce or config == b'abort':
911 actions[f] = (
911 actions[f] = (
912 ACTION_MERGE,
912 ACTION_MERGE,
913 (f, f, None, False, anc),
913 (f, f, None, False, anc),
914 b'remote differs from untracked local',
914 b'remote differs from untracked local',
915 )
915 )
916 elif config == b'abort':
916 elif config == b'abort':
917 abortconflicts.add(f)
917 abortconflicts.add(f)
918 else:
918 else:
919 if config == b'warn':
919 if config == b'warn':
920 warnconflicts.add(f)
920 warnconflicts.add(f)
921 actions[f] = (ACTION_GET, (fl2, True), b'remote created')
921 actions[f] = (ACTION_GET, (fl2, True), b'remote created')
922
922
923 for f in sorted(abortconflicts):
923 for f in sorted(abortconflicts):
924 warn = repo.ui.warn
924 warn = repo.ui.warn
925 if f in pathconflicts:
925 if f in pathconflicts:
926 if repo.wvfs.isfileorlink(f):
926 if repo.wvfs.isfileorlink(f):
927 warn(_(b"%s: untracked file conflicts with directory\n") % f)
927 warn(_(b"%s: untracked file conflicts with directory\n") % f)
928 else:
928 else:
929 warn(_(b"%s: untracked directory conflicts with file\n") % f)
929 warn(_(b"%s: untracked directory conflicts with file\n") % f)
930 else:
930 else:
931 warn(_(b"%s: untracked file differs\n") % f)
931 warn(_(b"%s: untracked file differs\n") % f)
932 if abortconflicts:
932 if abortconflicts:
933 raise error.Abort(
933 raise error.Abort(
934 _(
934 _(
935 b"untracked files in working directory "
935 b"untracked files in working directory "
936 b"differ from files in requested revision"
936 b"differ from files in requested revision"
937 )
937 )
938 )
938 )
939
939
940 for f in sorted(warnconflicts):
940 for f in sorted(warnconflicts):
941 if repo.wvfs.isfileorlink(f):
941 if repo.wvfs.isfileorlink(f):
942 repo.ui.warn(_(b"%s: replacing untracked file\n") % f)
942 repo.ui.warn(_(b"%s: replacing untracked file\n") % f)
943 else:
943 else:
944 repo.ui.warn(_(b"%s: replacing untracked files in directory\n") % f)
944 repo.ui.warn(_(b"%s: replacing untracked files in directory\n") % f)
945
945
946 for f, (m, args, msg) in pycompat.iteritems(actions):
946 for f, (m, args, msg) in pycompat.iteritems(actions):
947 if m == ACTION_CREATED:
947 if m == ACTION_CREATED:
948 backup = (
948 backup = (
949 f in fileconflicts
949 f in fileconflicts
950 or f in pathconflicts
950 or f in pathconflicts
951 or any(p in pathconflicts for p in pathutil.finddirs(f))
951 or any(p in pathconflicts for p in pathutil.finddirs(f))
952 )
952 )
953 (flags,) = args
953 (flags,) = args
954 actions[f] = (ACTION_GET, (flags, backup), msg)
954 actions[f] = (ACTION_GET, (flags, backup), msg)
955
955
956
956
957 def _forgetremoved(wctx, mctx, branchmerge):
957 def _forgetremoved(wctx, mctx, branchmerge):
958 """
958 """
959 Forget removed files
959 Forget removed files
960
960
961 If we're jumping between revisions (as opposed to merging), and if
961 If we're jumping between revisions (as opposed to merging), and if
962 neither the working directory nor the target rev has the file,
962 neither the working directory nor the target rev has the file,
963 then we need to remove it from the dirstate, to prevent the
963 then we need to remove it from the dirstate, to prevent the
964 dirstate from listing the file when it is no longer in the
964 dirstate from listing the file when it is no longer in the
965 manifest.
965 manifest.
966
966
967 If we're merging, and the other revision has removed a file
967 If we're merging, and the other revision has removed a file
968 that is not present in the working directory, we need to mark it
968 that is not present in the working directory, we need to mark it
969 as removed.
969 as removed.
970 """
970 """
971
971
972 actions = {}
972 actions = {}
973 m = ACTION_FORGET
973 m = ACTION_FORGET
974 if branchmerge:
974 if branchmerge:
975 m = ACTION_REMOVE
975 m = ACTION_REMOVE
976 for f in wctx.deleted():
976 for f in wctx.deleted():
977 if f not in mctx:
977 if f not in mctx:
978 actions[f] = m, None, b"forget deleted"
978 actions[f] = m, None, b"forget deleted"
979
979
980 if not branchmerge:
980 if not branchmerge:
981 for f in wctx.removed():
981 for f in wctx.removed():
982 if f not in mctx:
982 if f not in mctx:
983 actions[f] = ACTION_FORGET, None, b"forget removed"
983 actions[f] = ACTION_FORGET, None, b"forget removed"
984
984
985 return actions
985 return actions
986
986
987
987
988 def _checkcollision(repo, wmf, actions):
988 def _checkcollision(repo, wmf, actions):
989 """
989 """
990 Check for case-folding collisions.
990 Check for case-folding collisions.
991 """
991 """
992
992
993 # If the repo is narrowed, filter out files outside the narrowspec.
993 # If the repo is narrowed, filter out files outside the narrowspec.
994 narrowmatch = repo.narrowmatch()
994 narrowmatch = repo.narrowmatch()
995 if not narrowmatch.always():
995 if not narrowmatch.always():
996 wmf = wmf.matches(narrowmatch)
996 wmf = wmf.matches(narrowmatch)
997 if actions:
997 if actions:
998 narrowactions = {}
998 narrowactions = {}
999 for m, actionsfortype in pycompat.iteritems(actions):
999 for m, actionsfortype in pycompat.iteritems(actions):
1000 narrowactions[m] = []
1000 narrowactions[m] = []
1001 for (f, args, msg) in actionsfortype:
1001 for (f, args, msg) in actionsfortype:
1002 if narrowmatch(f):
1002 if narrowmatch(f):
1003 narrowactions[m].append((f, args, msg))
1003 narrowactions[m].append((f, args, msg))
1004 actions = narrowactions
1004 actions = narrowactions
1005
1005
1006 # build provisional merged manifest up
1006 # build provisional merged manifest up
1007 pmmf = set(wmf)
1007 pmmf = set(wmf)
1008
1008
1009 if actions:
1009 if actions:
1010 # KEEP and EXEC are no-op
1010 # KEEP and EXEC are no-op
1011 for m in (
1011 for m in (
1012 ACTION_ADD,
1012 ACTION_ADD,
1013 ACTION_ADD_MODIFIED,
1013 ACTION_ADD_MODIFIED,
1014 ACTION_FORGET,
1014 ACTION_FORGET,
1015 ACTION_GET,
1015 ACTION_GET,
1016 ACTION_CHANGED_DELETED,
1016 ACTION_CHANGED_DELETED,
1017 ACTION_DELETED_CHANGED,
1017 ACTION_DELETED_CHANGED,
1018 ):
1018 ):
1019 for f, args, msg in actions[m]:
1019 for f, args, msg in actions[m]:
1020 pmmf.add(f)
1020 pmmf.add(f)
1021 for f, args, msg in actions[ACTION_REMOVE]:
1021 for f, args, msg in actions[ACTION_REMOVE]:
1022 pmmf.discard(f)
1022 pmmf.discard(f)
1023 for f, args, msg in actions[ACTION_DIR_RENAME_MOVE_LOCAL]:
1023 for f, args, msg in actions[ACTION_DIR_RENAME_MOVE_LOCAL]:
1024 f2, flags = args
1024 f2, flags = args
1025 pmmf.discard(f2)
1025 pmmf.discard(f2)
1026 pmmf.add(f)
1026 pmmf.add(f)
1027 for f, args, msg in actions[ACTION_LOCAL_DIR_RENAME_GET]:
1027 for f, args, msg in actions[ACTION_LOCAL_DIR_RENAME_GET]:
1028 pmmf.add(f)
1028 pmmf.add(f)
1029 for f, args, msg in actions[ACTION_MERGE]:
1029 for f, args, msg in actions[ACTION_MERGE]:
1030 f1, f2, fa, move, anc = args
1030 f1, f2, fa, move, anc = args
1031 if move:
1031 if move:
1032 pmmf.discard(f1)
1032 pmmf.discard(f1)
1033 pmmf.add(f)
1033 pmmf.add(f)
1034
1034
1035 # check case-folding collision in provisional merged manifest
1035 # check case-folding collision in provisional merged manifest
1036 foldmap = {}
1036 foldmap = {}
1037 for f in pmmf:
1037 for f in pmmf:
1038 fold = util.normcase(f)
1038 fold = util.normcase(f)
1039 if fold in foldmap:
1039 if fold in foldmap:
1040 raise error.Abort(
1040 raise error.Abort(
1041 _(b"case-folding collision between %s and %s")
1041 _(b"case-folding collision between %s and %s")
1042 % (f, foldmap[fold])
1042 % (f, foldmap[fold])
1043 )
1043 )
1044 foldmap[fold] = f
1044 foldmap[fold] = f
1045
1045
1046 # check case-folding of directories
1046 # check case-folding of directories
1047 foldprefix = unfoldprefix = lastfull = b''
1047 foldprefix = unfoldprefix = lastfull = b''
1048 for fold, f in sorted(foldmap.items()):
1048 for fold, f in sorted(foldmap.items()):
1049 if fold.startswith(foldprefix) and not f.startswith(unfoldprefix):
1049 if fold.startswith(foldprefix) and not f.startswith(unfoldprefix):
1050 # the folded prefix matches but actual casing is different
1050 # the folded prefix matches but actual casing is different
1051 raise error.Abort(
1051 raise error.Abort(
1052 _(b"case-folding collision between %s and directory of %s")
1052 _(b"case-folding collision between %s and directory of %s")
1053 % (lastfull, f)
1053 % (lastfull, f)
1054 )
1054 )
1055 foldprefix = fold + b'/'
1055 foldprefix = fold + b'/'
1056 unfoldprefix = f + b'/'
1056 unfoldprefix = f + b'/'
1057 lastfull = f
1057 lastfull = f
1058
1058
1059
1059
1060 def driverpreprocess(repo, ms, wctx, labels=None):
1060 def driverpreprocess(repo, ms, wctx, labels=None):
1061 """run the preprocess step of the merge driver, if any
1061 """run the preprocess step of the merge driver, if any
1062
1062
1063 This is currently not implemented -- it's an extension point."""
1063 This is currently not implemented -- it's an extension point."""
1064 return True
1064 return True
1065
1065
1066
1066
1067 def driverconclude(repo, ms, wctx, labels=None):
1067 def driverconclude(repo, ms, wctx, labels=None):
1068 """run the conclude step of the merge driver, if any
1068 """run the conclude step of the merge driver, if any
1069
1069
1070 This is currently not implemented -- it's an extension point."""
1070 This is currently not implemented -- it's an extension point."""
1071 return True
1071 return True
1072
1072
1073
1073
1074 def _filesindirs(repo, manifest, dirs):
1074 def _filesindirs(repo, manifest, dirs):
1075 """
1075 """
1076 Generator that yields pairs of all the files in the manifest that are found
1076 Generator that yields pairs of all the files in the manifest that are found
1077 inside the directories listed in dirs, and which directory they are found
1077 inside the directories listed in dirs, and which directory they are found
1078 in.
1078 in.
1079 """
1079 """
1080 for f in manifest:
1080 for f in manifest:
1081 for p in pathutil.finddirs(f):
1081 for p in pathutil.finddirs(f):
1082 if p in dirs:
1082 if p in dirs:
1083 yield f, p
1083 yield f, p
1084 break
1084 break
1085
1085
1086
1086
1087 def checkpathconflicts(repo, wctx, mctx, actions):
1087 def checkpathconflicts(repo, wctx, mctx, actions):
1088 """
1088 """
1089 Check if any actions introduce path conflicts in the repository, updating
1089 Check if any actions introduce path conflicts in the repository, updating
1090 actions to record or handle the path conflict accordingly.
1090 actions to record or handle the path conflict accordingly.
1091 """
1091 """
1092 mf = wctx.manifest()
1092 mf = wctx.manifest()
1093
1093
1094 # The set of local files that conflict with a remote directory.
1094 # The set of local files that conflict with a remote directory.
1095 localconflicts = set()
1095 localconflicts = set()
1096
1096
1097 # The set of directories that conflict with a remote file, and so may cause
1097 # The set of directories that conflict with a remote file, and so may cause
1098 # conflicts if they still contain any files after the merge.
1098 # conflicts if they still contain any files after the merge.
1099 remoteconflicts = set()
1099 remoteconflicts = set()
1100
1100
1101 # The set of directories that appear as both a file and a directory in the
1101 # The set of directories that appear as both a file and a directory in the
1102 # remote manifest. These indicate an invalid remote manifest, which
1102 # remote manifest. These indicate an invalid remote manifest, which
1103 # can't be updated to cleanly.
1103 # can't be updated to cleanly.
1104 invalidconflicts = set()
1104 invalidconflicts = set()
1105
1105
1106 # The set of directories that contain files that are being created.
1106 # The set of directories that contain files that are being created.
1107 createdfiledirs = set()
1107 createdfiledirs = set()
1108
1108
1109 # The set of files deleted by all the actions.
1109 # The set of files deleted by all the actions.
1110 deletedfiles = set()
1110 deletedfiles = set()
1111
1111
1112 for f, (m, args, msg) in actions.items():
1112 for f, (m, args, msg) in actions.items():
1113 if m in (
1113 if m in (
1114 ACTION_CREATED,
1114 ACTION_CREATED,
1115 ACTION_DELETED_CHANGED,
1115 ACTION_DELETED_CHANGED,
1116 ACTION_MERGE,
1116 ACTION_MERGE,
1117 ACTION_CREATED_MERGE,
1117 ACTION_CREATED_MERGE,
1118 ):
1118 ):
1119 # This action may create a new local file.
1119 # This action may create a new local file.
1120 createdfiledirs.update(pathutil.finddirs(f))
1120 createdfiledirs.update(pathutil.finddirs(f))
1121 if mf.hasdir(f):
1121 if mf.hasdir(f):
1122 # The file aliases a local directory. This might be ok if all
1122 # The file aliases a local directory. This might be ok if all
1123 # the files in the local directory are being deleted. This
1123 # the files in the local directory are being deleted. This
1124 # will be checked once we know what all the deleted files are.
1124 # will be checked once we know what all the deleted files are.
1125 remoteconflicts.add(f)
1125 remoteconflicts.add(f)
1126 # Track the names of all deleted files.
1126 # Track the names of all deleted files.
1127 if m == ACTION_REMOVE:
1127 if m == ACTION_REMOVE:
1128 deletedfiles.add(f)
1128 deletedfiles.add(f)
1129 if m == ACTION_MERGE:
1129 if m == ACTION_MERGE:
1130 f1, f2, fa, move, anc = args
1130 f1, f2, fa, move, anc = args
1131 if move:
1131 if move:
1132 deletedfiles.add(f1)
1132 deletedfiles.add(f1)
1133 if m == ACTION_DIR_RENAME_MOVE_LOCAL:
1133 if m == ACTION_DIR_RENAME_MOVE_LOCAL:
1134 f2, flags = args
1134 f2, flags = args
1135 deletedfiles.add(f2)
1135 deletedfiles.add(f2)
1136
1136
1137 # Check all directories that contain created files for path conflicts.
1137 # Check all directories that contain created files for path conflicts.
1138 for p in createdfiledirs:
1138 for p in createdfiledirs:
1139 if p in mf:
1139 if p in mf:
1140 if p in mctx:
1140 if p in mctx:
1141 # A file is in a directory which aliases both a local
1141 # A file is in a directory which aliases both a local
1142 # and a remote file. This is an internal inconsistency
1142 # and a remote file. This is an internal inconsistency
1143 # within the remote manifest.
1143 # within the remote manifest.
1144 invalidconflicts.add(p)
1144 invalidconflicts.add(p)
1145 else:
1145 else:
1146 # A file is in a directory which aliases a local file.
1146 # A file is in a directory which aliases a local file.
1147 # We will need to rename the local file.
1147 # We will need to rename the local file.
1148 localconflicts.add(p)
1148 localconflicts.add(p)
1149 if p in actions and actions[p][0] in (
1149 if p in actions and actions[p][0] in (
1150 ACTION_CREATED,
1150 ACTION_CREATED,
1151 ACTION_DELETED_CHANGED,
1151 ACTION_DELETED_CHANGED,
1152 ACTION_MERGE,
1152 ACTION_MERGE,
1153 ACTION_CREATED_MERGE,
1153 ACTION_CREATED_MERGE,
1154 ):
1154 ):
1155 # The file is in a directory which aliases a remote file.
1155 # The file is in a directory which aliases a remote file.
1156 # This is an internal inconsistency within the remote
1156 # This is an internal inconsistency within the remote
1157 # manifest.
1157 # manifest.
1158 invalidconflicts.add(p)
1158 invalidconflicts.add(p)
1159
1159
1160 # Rename all local conflicting files that have not been deleted.
1160 # Rename all local conflicting files that have not been deleted.
1161 for p in localconflicts:
1161 for p in localconflicts:
1162 if p not in deletedfiles:
1162 if p not in deletedfiles:
1163 ctxname = bytes(wctx).rstrip(b'+')
1163 ctxname = bytes(wctx).rstrip(b'+')
1164 pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
1164 pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
1165 actions[pnew] = (
1165 actions[pnew] = (
1166 ACTION_PATH_CONFLICT_RESOLVE,
1166 ACTION_PATH_CONFLICT_RESOLVE,
1167 (p,),
1167 (p,),
1168 b'local path conflict',
1168 b'local path conflict',
1169 )
1169 )
1170 actions[p] = (ACTION_PATH_CONFLICT, (pnew, b'l'), b'path conflict')
1170 actions[p] = (ACTION_PATH_CONFLICT, (pnew, b'l'), b'path conflict')
1171
1171
1172 if remoteconflicts:
1172 if remoteconflicts:
1173 # Check if all files in the conflicting directories have been removed.
1173 # Check if all files in the conflicting directories have been removed.
1174 ctxname = bytes(mctx).rstrip(b'+')
1174 ctxname = bytes(mctx).rstrip(b'+')
1175 for f, p in _filesindirs(repo, mf, remoteconflicts):
1175 for f, p in _filesindirs(repo, mf, remoteconflicts):
1176 if f not in deletedfiles:
1176 if f not in deletedfiles:
1177 m, args, msg = actions[p]
1177 m, args, msg = actions[p]
1178 pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
1178 pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
1179 if m in (ACTION_DELETED_CHANGED, ACTION_MERGE):
1179 if m in (ACTION_DELETED_CHANGED, ACTION_MERGE):
1180 # Action was merge, just update target.
1180 # Action was merge, just update target.
1181 actions[pnew] = (m, args, msg)
1181 actions[pnew] = (m, args, msg)
1182 else:
1182 else:
1183 # Action was create, change to renamed get action.
1183 # Action was create, change to renamed get action.
1184 fl = args[0]
1184 fl = args[0]
1185 actions[pnew] = (
1185 actions[pnew] = (
1186 ACTION_LOCAL_DIR_RENAME_GET,
1186 ACTION_LOCAL_DIR_RENAME_GET,
1187 (p, fl),
1187 (p, fl),
1188 b'remote path conflict',
1188 b'remote path conflict',
1189 )
1189 )
1190 actions[p] = (
1190 actions[p] = (
1191 ACTION_PATH_CONFLICT,
1191 ACTION_PATH_CONFLICT,
1192 (pnew, ACTION_REMOVE),
1192 (pnew, ACTION_REMOVE),
1193 b'path conflict',
1193 b'path conflict',
1194 )
1194 )
1195 remoteconflicts.remove(p)
1195 remoteconflicts.remove(p)
1196 break
1196 break
1197
1197
1198 if invalidconflicts:
1198 if invalidconflicts:
1199 for p in invalidconflicts:
1199 for p in invalidconflicts:
1200 repo.ui.warn(_(b"%s: is both a file and a directory\n") % p)
1200 repo.ui.warn(_(b"%s: is both a file and a directory\n") % p)
1201 raise error.Abort(_(b"destination manifest contains path conflicts"))
1201 raise error.Abort(_(b"destination manifest contains path conflicts"))
1202
1202
1203
1203
1204 def _filternarrowactions(narrowmatch, branchmerge, actions):
1204 def _filternarrowactions(narrowmatch, branchmerge, actions):
1205 """
1205 """
1206 Filters out actions that can ignored because the repo is narrowed.
1206 Filters out actions that can ignored because the repo is narrowed.
1207
1207
1208 Raise an exception if the merge cannot be completed because the repo is
1208 Raise an exception if the merge cannot be completed because the repo is
1209 narrowed.
1209 narrowed.
1210 """
1210 """
1211 nooptypes = {b'k'} # TODO: handle with nonconflicttypes
1211 nooptypes = {b'k'} # TODO: handle with nonconflicttypes
1212 nonconflicttypes = set(b'a am c cm f g r e'.split())
1212 nonconflicttypes = set(b'a am c cm f g r e'.split())
1213 # We mutate the items in the dict during iteration, so iterate
1213 # We mutate the items in the dict during iteration, so iterate
1214 # over a copy.
1214 # over a copy.
1215 for f, action in list(actions.items()):
1215 for f, action in list(actions.items()):
1216 if narrowmatch(f):
1216 if narrowmatch(f):
1217 pass
1217 pass
1218 elif not branchmerge:
1218 elif not branchmerge:
1219 del actions[f] # just updating, ignore changes outside clone
1219 del actions[f] # just updating, ignore changes outside clone
1220 elif action[0] in nooptypes:
1220 elif action[0] in nooptypes:
1221 del actions[f] # merge does not affect file
1221 del actions[f] # merge does not affect file
1222 elif action[0] in nonconflicttypes:
1222 elif action[0] in nonconflicttypes:
1223 raise error.Abort(
1223 raise error.Abort(
1224 _(
1224 _(
1225 b'merge affects file \'%s\' outside narrow, '
1225 b'merge affects file \'%s\' outside narrow, '
1226 b'which is not yet supported'
1226 b'which is not yet supported'
1227 )
1227 )
1228 % f,
1228 % f,
1229 hint=_(b'merging in the other direction may work'),
1229 hint=_(b'merging in the other direction may work'),
1230 )
1230 )
1231 else:
1231 else:
1232 raise error.Abort(
1232 raise error.Abort(
1233 _(b'conflict in file \'%s\' is outside narrow clone') % f
1233 _(b'conflict in file \'%s\' is outside narrow clone') % f
1234 )
1234 )
1235
1235
1236
1236
1237 def manifestmerge(
1237 def manifestmerge(
1238 repo,
1238 repo,
1239 wctx,
1239 wctx,
1240 p2,
1240 p2,
1241 pa,
1241 pa,
1242 branchmerge,
1242 branchmerge,
1243 force,
1243 force,
1244 matcher,
1244 matcher,
1245 acceptremote,
1245 acceptremote,
1246 followcopies,
1246 followcopies,
1247 forcefulldiff=False,
1247 forcefulldiff=False,
1248 ):
1248 ):
1249 """
1249 """
1250 Merge wctx and p2 with ancestor pa and generate merge action list
1250 Merge wctx and p2 with ancestor pa and generate merge action list
1251
1251
1252 branchmerge and force are as passed in to update
1252 branchmerge and force are as passed in to update
1253 matcher = matcher to filter file lists
1253 matcher = matcher to filter file lists
1254 acceptremote = accept the incoming changes without prompting
1254 acceptremote = accept the incoming changes without prompting
1255 """
1255 """
1256 if matcher is not None and matcher.always():
1256 if matcher is not None and matcher.always():
1257 matcher = None
1257 matcher = None
1258
1258
1259 # manifests fetched in order are going to be faster, so prime the caches
1259 # manifests fetched in order are going to be faster, so prime the caches
1260 [
1260 [
1261 x.manifest()
1261 x.manifest()
1262 for x in sorted(wctx.parents() + [p2, pa], key=scmutil.intrev)
1262 for x in sorted(wctx.parents() + [p2, pa], key=scmutil.intrev)
1263 ]
1263 ]
1264
1264
1265 branch_copies1 = copies.branch_copies()
1265 branch_copies1 = copies.branch_copies()
1266 branch_copies2 = copies.branch_copies()
1266 branch_copies2 = copies.branch_copies()
1267 diverge = {}
1267 diverge = {}
1268 if followcopies:
1268 if followcopies:
1269 branch_copies1, branch_copies2, diverge = copies.mergecopies(
1269 branch_copies1, branch_copies2, diverge = copies.mergecopies(
1270 repo, wctx, p2, pa
1270 repo, wctx, p2, pa
1271 )
1271 )
1272
1272
1273 boolbm = pycompat.bytestr(bool(branchmerge))
1273 boolbm = pycompat.bytestr(bool(branchmerge))
1274 boolf = pycompat.bytestr(bool(force))
1274 boolf = pycompat.bytestr(bool(force))
1275 boolm = pycompat.bytestr(bool(matcher))
1275 boolm = pycompat.bytestr(bool(matcher))
1276 repo.ui.note(_(b"resolving manifests\n"))
1276 repo.ui.note(_(b"resolving manifests\n"))
1277 repo.ui.debug(
1277 repo.ui.debug(
1278 b" branchmerge: %s, force: %s, partial: %s\n" % (boolbm, boolf, boolm)
1278 b" branchmerge: %s, force: %s, partial: %s\n" % (boolbm, boolf, boolm)
1279 )
1279 )
1280 repo.ui.debug(b" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
1280 repo.ui.debug(b" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
1281
1281
1282 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
1282 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
1283 copied1 = set(branch_copies1.copy.values())
1283 copied1 = set(branch_copies1.copy.values())
1284 copied1.update(branch_copies1.movewithdir.values())
1284 copied1.update(branch_copies1.movewithdir.values())
1285 copied2 = set(branch_copies2.copy.values())
1285 copied2 = set(branch_copies2.copy.values())
1286 copied2.update(branch_copies2.movewithdir.values())
1286 copied2.update(branch_copies2.movewithdir.values())
1287
1287
1288 if b'.hgsubstate' in m1 and wctx.rev() is None:
1288 if b'.hgsubstate' in m1 and wctx.rev() is None:
1289 # Check whether sub state is modified, and overwrite the manifest
1289 # Check whether sub state is modified, and overwrite the manifest
1290 # to flag the change. If wctx is a committed revision, we shouldn't
1290 # to flag the change. If wctx is a committed revision, we shouldn't
1291 # care for the dirty state of the working directory.
1291 # care for the dirty state of the working directory.
1292 if any(wctx.sub(s).dirty() for s in wctx.substate):
1292 if any(wctx.sub(s).dirty() for s in wctx.substate):
1293 m1[b'.hgsubstate'] = modifiednodeid
1293 m1[b'.hgsubstate'] = modifiednodeid
1294
1294
1295 # Don't use m2-vs-ma optimization if:
1295 # Don't use m2-vs-ma optimization if:
1296 # - ma is the same as m1 or m2, which we're just going to diff again later
1296 # - ma is the same as m1 or m2, which we're just going to diff again later
1297 # - The caller specifically asks for a full diff, which is useful during bid
1297 # - The caller specifically asks for a full diff, which is useful during bid
1298 # merge.
1298 # merge.
1299 if pa not in ([wctx, p2] + wctx.parents()) and not forcefulldiff:
1299 if pa not in ([wctx, p2] + wctx.parents()) and not forcefulldiff:
1300 # Identify which files are relevant to the merge, so we can limit the
1300 # Identify which files are relevant to the merge, so we can limit the
1301 # total m1-vs-m2 diff to just those files. This has significant
1301 # total m1-vs-m2 diff to just those files. This has significant
1302 # performance benefits in large repositories.
1302 # performance benefits in large repositories.
1303 relevantfiles = set(ma.diff(m2).keys())
1303 relevantfiles = set(ma.diff(m2).keys())
1304
1304
1305 # For copied and moved files, we need to add the source file too.
1305 # For copied and moved files, we need to add the source file too.
1306 for copykey, copyvalue in pycompat.iteritems(branch_copies1.copy):
1306 for copykey, copyvalue in pycompat.iteritems(branch_copies1.copy):
1307 if copyvalue in relevantfiles:
1307 if copyvalue in relevantfiles:
1308 relevantfiles.add(copykey)
1308 relevantfiles.add(copykey)
1309 for movedirkey in branch_copies1.movewithdir:
1309 for movedirkey in branch_copies1.movewithdir:
1310 relevantfiles.add(movedirkey)
1310 relevantfiles.add(movedirkey)
1311 filesmatcher = scmutil.matchfiles(repo, relevantfiles)
1311 filesmatcher = scmutil.matchfiles(repo, relevantfiles)
1312 matcher = matchmod.intersectmatchers(matcher, filesmatcher)
1312 matcher = matchmod.intersectmatchers(matcher, filesmatcher)
1313
1313
1314 diff = m1.diff(m2, match=matcher)
1314 diff = m1.diff(m2, match=matcher)
1315
1315
1316 actions = {}
1316 actions = {}
1317 for f, ((n1, fl1), (n2, fl2)) in pycompat.iteritems(diff):
1317 for f, ((n1, fl1), (n2, fl2)) in pycompat.iteritems(diff):
1318 if n1 and n2: # file exists on both local and remote side
1318 if n1 and n2: # file exists on both local and remote side
1319 if f not in ma:
1319 if f not in ma:
1320 # TODO: what if they're renamed from different sources?
1320 # TODO: what if they're renamed from different sources?
1321 fa = branch_copies1.copy.get(
1321 fa = branch_copies1.copy.get(
1322 f, None
1322 f, None
1323 ) or branch_copies2.copy.get(f, None)
1323 ) or branch_copies2.copy.get(f, None)
1324 if fa is not None:
1324 if fa is not None:
1325 actions[f] = (
1325 actions[f] = (
1326 ACTION_MERGE,
1326 ACTION_MERGE,
1327 (f, f, fa, False, pa.node()),
1327 (f, f, fa, False, pa.node()),
1328 b'both renamed from %s' % fa,
1328 b'both renamed from %s' % fa,
1329 )
1329 )
1330 else:
1330 else:
1331 actions[f] = (
1331 actions[f] = (
1332 ACTION_MERGE,
1332 ACTION_MERGE,
1333 (f, f, None, False, pa.node()),
1333 (f, f, None, False, pa.node()),
1334 b'both created',
1334 b'both created',
1335 )
1335 )
1336 else:
1336 else:
1337 a = ma[f]
1337 a = ma[f]
1338 fla = ma.flags(f)
1338 fla = ma.flags(f)
1339 nol = b'l' not in fl1 + fl2 + fla
1339 nol = b'l' not in fl1 + fl2 + fla
1340 if n2 == a and fl2 == fla:
1340 if n2 == a and fl2 == fla:
1341 actions[f] = (ACTION_KEEP, (), b'remote unchanged')
1341 actions[f] = (ACTION_KEEP, (), b'remote unchanged')
1342 elif n1 == a and fl1 == fla: # local unchanged - use remote
1342 elif n1 == a and fl1 == fla: # local unchanged - use remote
1343 if n1 == n2: # optimization: keep local content
1343 if n1 == n2: # optimization: keep local content
1344 actions[f] = (
1344 actions[f] = (
1345 ACTION_EXEC,
1345 ACTION_EXEC,
1346 (fl2,),
1346 (fl2,),
1347 b'update permissions',
1347 b'update permissions',
1348 )
1348 )
1349 else:
1349 else:
1350 actions[f] = (
1350 actions[f] = (
1351 ACTION_GET,
1351 ACTION_GET,
1352 (fl2, False),
1352 (fl2, False),
1353 b'remote is newer',
1353 b'remote is newer',
1354 )
1354 )
1355 elif nol and n2 == a: # remote only changed 'x'
1355 elif nol and n2 == a: # remote only changed 'x'
1356 actions[f] = (ACTION_EXEC, (fl2,), b'update permissions')
1356 actions[f] = (ACTION_EXEC, (fl2,), b'update permissions')
1357 elif nol and n1 == a: # local only changed 'x'
1357 elif nol and n1 == a: # local only changed 'x'
1358 actions[f] = (ACTION_GET, (fl1, False), b'remote is newer')
1358 actions[f] = (ACTION_GET, (fl1, False), b'remote is newer')
1359 else: # both changed something
1359 else: # both changed something
1360 actions[f] = (
1360 actions[f] = (
1361 ACTION_MERGE,
1361 ACTION_MERGE,
1362 (f, f, f, False, pa.node()),
1362 (f, f, f, False, pa.node()),
1363 b'versions differ',
1363 b'versions differ',
1364 )
1364 )
1365 elif n1: # file exists only on local side
1365 elif n1: # file exists only on local side
1366 if f in copied2:
1366 if f in copied2:
1367 pass # we'll deal with it on m2 side
1367 pass # we'll deal with it on m2 side
1368 elif (
1368 elif (
1369 f in branch_copies1.movewithdir
1369 f in branch_copies1.movewithdir
1370 ): # directory rename, move local
1370 ): # directory rename, move local
1371 f2 = branch_copies1.movewithdir[f]
1371 f2 = branch_copies1.movewithdir[f]
1372 if f2 in m2:
1372 if f2 in m2:
1373 actions[f2] = (
1373 actions[f2] = (
1374 ACTION_MERGE,
1374 ACTION_MERGE,
1375 (f, f2, None, True, pa.node()),
1375 (f, f2, None, True, pa.node()),
1376 b'remote directory rename, both created',
1376 b'remote directory rename, both created',
1377 )
1377 )
1378 else:
1378 else:
1379 actions[f2] = (
1379 actions[f2] = (
1380 ACTION_DIR_RENAME_MOVE_LOCAL,
1380 ACTION_DIR_RENAME_MOVE_LOCAL,
1381 (f, fl1),
1381 (f, fl1),
1382 b'remote directory rename - move from %s' % f,
1382 b'remote directory rename - move from %s' % f,
1383 )
1383 )
1384 elif f in branch_copies1.copy:
1384 elif f in branch_copies1.copy:
1385 f2 = branch_copies1.copy[f]
1385 f2 = branch_copies1.copy[f]
1386 actions[f] = (
1386 actions[f] = (
1387 ACTION_MERGE,
1387 ACTION_MERGE,
1388 (f, f2, f2, False, pa.node()),
1388 (f, f2, f2, False, pa.node()),
1389 b'local copied/moved from %s' % f2,
1389 b'local copied/moved from %s' % f2,
1390 )
1390 )
1391 elif f in ma: # clean, a different, no remote
1391 elif f in ma: # clean, a different, no remote
1392 if n1 != ma[f]:
1392 if n1 != ma[f]:
1393 if acceptremote:
1393 if acceptremote:
1394 actions[f] = (ACTION_REMOVE, None, b'remote delete')
1394 actions[f] = (ACTION_REMOVE, None, b'remote delete')
1395 else:
1395 else:
1396 actions[f] = (
1396 actions[f] = (
1397 ACTION_CHANGED_DELETED,
1397 ACTION_CHANGED_DELETED,
1398 (f, None, f, False, pa.node()),
1398 (f, None, f, False, pa.node()),
1399 b'prompt changed/deleted',
1399 b'prompt changed/deleted',
1400 )
1400 )
1401 elif n1 == addednodeid:
1401 elif n1 == addednodeid:
1402 # This extra 'a' is added by working copy manifest to mark
1402 # This extra 'a' is added by working copy manifest to mark
1403 # the file as locally added. We should forget it instead of
1403 # the file as locally added. We should forget it instead of
1404 # deleting it.
1404 # deleting it.
1405 actions[f] = (ACTION_FORGET, None, b'remote deleted')
1405 actions[f] = (ACTION_FORGET, None, b'remote deleted')
1406 else:
1406 else:
1407 actions[f] = (ACTION_REMOVE, None, b'other deleted')
1407 actions[f] = (ACTION_REMOVE, None, b'other deleted')
1408 elif n2: # file exists only on remote side
1408 elif n2: # file exists only on remote side
1409 if f in copied1:
1409 if f in copied1:
1410 pass # we'll deal with it on m1 side
1410 pass # we'll deal with it on m1 side
1411 elif f in branch_copies2.movewithdir:
1411 elif f in branch_copies2.movewithdir:
1412 f2 = branch_copies2.movewithdir[f]
1412 f2 = branch_copies2.movewithdir[f]
1413 if f2 in m1:
1413 if f2 in m1:
1414 actions[f2] = (
1414 actions[f2] = (
1415 ACTION_MERGE,
1415 ACTION_MERGE,
1416 (f2, f, None, False, pa.node()),
1416 (f2, f, None, False, pa.node()),
1417 b'local directory rename, both created',
1417 b'local directory rename, both created',
1418 )
1418 )
1419 else:
1419 else:
1420 actions[f2] = (
1420 actions[f2] = (
1421 ACTION_LOCAL_DIR_RENAME_GET,
1421 ACTION_LOCAL_DIR_RENAME_GET,
1422 (f, fl2),
1422 (f, fl2),
1423 b'local directory rename - get from %s' % f,
1423 b'local directory rename - get from %s' % f,
1424 )
1424 )
1425 elif f in branch_copies2.copy:
1425 elif f in branch_copies2.copy:
1426 f2 = branch_copies2.copy[f]
1426 f2 = branch_copies2.copy[f]
1427 if f2 in m2:
1427 if f2 in m2:
1428 actions[f] = (
1428 actions[f] = (
1429 ACTION_MERGE,
1429 ACTION_MERGE,
1430 (f2, f, f2, False, pa.node()),
1430 (f2, f, f2, False, pa.node()),
1431 b'remote copied from %s' % f2,
1431 b'remote copied from %s' % f2,
1432 )
1432 )
1433 else:
1433 else:
1434 actions[f] = (
1434 actions[f] = (
1435 ACTION_MERGE,
1435 ACTION_MERGE,
1436 (f2, f, f2, True, pa.node()),
1436 (f2, f, f2, True, pa.node()),
1437 b'remote moved from %s' % f2,
1437 b'remote moved from %s' % f2,
1438 )
1438 )
1439 elif f not in ma:
1439 elif f not in ma:
1440 # local unknown, remote created: the logic is described by the
1440 # local unknown, remote created: the logic is described by the
1441 # following table:
1441 # following table:
1442 #
1442 #
1443 # force branchmerge different | action
1443 # force branchmerge different | action
1444 # n * * | create
1444 # n * * | create
1445 # y n * | create
1445 # y n * | create
1446 # y y n | create
1446 # y y n | create
1447 # y y y | merge
1447 # y y y | merge
1448 #
1448 #
1449 # Checking whether the files are different is expensive, so we
1449 # Checking whether the files are different is expensive, so we
1450 # don't do that when we can avoid it.
1450 # don't do that when we can avoid it.
1451 if not force:
1451 if not force:
1452 actions[f] = (ACTION_CREATED, (fl2,), b'remote created')
1452 actions[f] = (ACTION_CREATED, (fl2,), b'remote created')
1453 elif not branchmerge:
1453 elif not branchmerge:
1454 actions[f] = (ACTION_CREATED, (fl2,), b'remote created')
1454 actions[f] = (ACTION_CREATED, (fl2,), b'remote created')
1455 else:
1455 else:
1456 actions[f] = (
1456 actions[f] = (
1457 ACTION_CREATED_MERGE,
1457 ACTION_CREATED_MERGE,
1458 (fl2, pa.node()),
1458 (fl2, pa.node()),
1459 b'remote created, get or merge',
1459 b'remote created, get or merge',
1460 )
1460 )
1461 elif n2 != ma[f]:
1461 elif n2 != ma[f]:
1462 df = None
1462 df = None
1463 for d in branch_copies1.dirmove:
1463 for d in branch_copies1.dirmove:
1464 if f.startswith(d):
1464 if f.startswith(d):
1465 # new file added in a directory that was moved
1465 # new file added in a directory that was moved
1466 df = branch_copies1.dirmove[d] + f[len(d) :]
1466 df = branch_copies1.dirmove[d] + f[len(d) :]
1467 break
1467 break
1468 if df is not None and df in m1:
1468 if df is not None and df in m1:
1469 actions[df] = (
1469 actions[df] = (
1470 ACTION_MERGE,
1470 ACTION_MERGE,
1471 (df, f, f, False, pa.node()),
1471 (df, f, f, False, pa.node()),
1472 b'local directory rename - respect move '
1472 b'local directory rename - respect move '
1473 b'from %s' % f,
1473 b'from %s' % f,
1474 )
1474 )
1475 elif acceptremote:
1475 elif acceptremote:
1476 actions[f] = (ACTION_CREATED, (fl2,), b'remote recreating')
1476 actions[f] = (ACTION_CREATED, (fl2,), b'remote recreating')
1477 else:
1477 else:
1478 actions[f] = (
1478 actions[f] = (
1479 ACTION_DELETED_CHANGED,
1479 ACTION_DELETED_CHANGED,
1480 (None, f, f, False, pa.node()),
1480 (None, f, f, False, pa.node()),
1481 b'prompt deleted/changed',
1481 b'prompt deleted/changed',
1482 )
1482 )
1483
1483
1484 if repo.ui.configbool(b'experimental', b'merge.checkpathconflicts'):
1484 if repo.ui.configbool(b'experimental', b'merge.checkpathconflicts'):
1485 # If we are merging, look for path conflicts.
1485 # If we are merging, look for path conflicts.
1486 checkpathconflicts(repo, wctx, p2, actions)
1486 checkpathconflicts(repo, wctx, p2, actions)
1487
1487
1488 narrowmatch = repo.narrowmatch()
1488 narrowmatch = repo.narrowmatch()
1489 if not narrowmatch.always():
1489 if not narrowmatch.always():
1490 # Updates "actions" in place
1490 # Updates "actions" in place
1491 _filternarrowactions(narrowmatch, branchmerge, actions)
1491 _filternarrowactions(narrowmatch, branchmerge, actions)
1492
1492
1493 renamedelete = branch_copies1.renamedelete
1493 renamedelete = branch_copies1.renamedelete
1494 renamedelete.update(branch_copies2.renamedelete)
1494 renamedelete.update(branch_copies2.renamedelete)
1495
1495
1496 return actions, diverge, renamedelete
1496 return actions, diverge, renamedelete
1497
1497
1498
1498
1499 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
1499 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
1500 """Resolves false conflicts where the nodeid changed but the content
1500 """Resolves false conflicts where the nodeid changed but the content
1501 remained the same."""
1501 remained the same."""
1502 # We force a copy of actions.items() because we're going to mutate
1502 # We force a copy of actions.items() because we're going to mutate
1503 # actions as we resolve trivial conflicts.
1503 # actions as we resolve trivial conflicts.
1504 for f, (m, args, msg) in list(actions.items()):
1504 for f, (m, args, msg) in list(actions.items()):
1505 if (
1505 if (
1506 m == ACTION_CHANGED_DELETED
1506 m == ACTION_CHANGED_DELETED
1507 and f in ancestor
1507 and f in ancestor
1508 and not wctx[f].cmp(ancestor[f])
1508 and not wctx[f].cmp(ancestor[f])
1509 ):
1509 ):
1510 # local did change but ended up with same content
1510 # local did change but ended up with same content
1511 actions[f] = ACTION_REMOVE, None, b'prompt same'
1511 actions[f] = ACTION_REMOVE, None, b'prompt same'
1512 elif (
1512 elif (
1513 m == ACTION_DELETED_CHANGED
1513 m == ACTION_DELETED_CHANGED
1514 and f in ancestor
1514 and f in ancestor
1515 and not mctx[f].cmp(ancestor[f])
1515 and not mctx[f].cmp(ancestor[f])
1516 ):
1516 ):
1517 # remote did change but ended up with same content
1517 # remote did change but ended up with same content
1518 del actions[f] # don't get = keep local deleted
1518 del actions[f] # don't get = keep local deleted
1519
1519
1520
1520
1521 def calculateupdates(
1521 def calculateupdates(
1522 repo,
1522 repo,
1523 wctx,
1523 wctx,
1524 mctx,
1524 mctx,
1525 ancestors,
1525 ancestors,
1526 branchmerge,
1526 branchmerge,
1527 force,
1527 force,
1528 acceptremote,
1528 acceptremote,
1529 followcopies,
1529 followcopies,
1530 matcher=None,
1530 matcher=None,
1531 mergeforce=False,
1531 mergeforce=False,
1532 ):
1532 ):
1533 """Calculate the actions needed to merge mctx into wctx using ancestors"""
1533 """Calculate the actions needed to merge mctx into wctx using ancestors"""
1534 # Avoid cycle.
1534 # Avoid cycle.
1535 from . import sparse
1535 from . import sparse
1536
1536
1537 if len(ancestors) == 1: # default
1537 if len(ancestors) == 1: # default
1538 actions, diverge, renamedelete = manifestmerge(
1538 actions, diverge, renamedelete = manifestmerge(
1539 repo,
1539 repo,
1540 wctx,
1540 wctx,
1541 mctx,
1541 mctx,
1542 ancestors[0],
1542 ancestors[0],
1543 branchmerge,
1543 branchmerge,
1544 force,
1544 force,
1545 matcher,
1545 matcher,
1546 acceptremote,
1546 acceptremote,
1547 followcopies,
1547 followcopies,
1548 )
1548 )
1549 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
1549 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
1550
1550
1551 else: # only when merge.preferancestor=* - the default
1551 else: # only when merge.preferancestor=* - the default
1552 repo.ui.note(
1552 repo.ui.note(
1553 _(b"note: merging %s and %s using bids from ancestors %s\n")
1553 _(b"note: merging %s and %s using bids from ancestors %s\n")
1554 % (
1554 % (
1555 wctx,
1555 wctx,
1556 mctx,
1556 mctx,
1557 _(b' and ').join(pycompat.bytestr(anc) for anc in ancestors),
1557 _(b' and ').join(pycompat.bytestr(anc) for anc in ancestors),
1558 )
1558 )
1559 )
1559 )
1560
1560
1561 # Call for bids
1561 # Call for bids
1562 fbids = (
1562 fbids = (
1563 {}
1563 {}
1564 ) # mapping filename to bids (action method to list af actions)
1564 ) # mapping filename to bids (action method to list af actions)
1565 diverge, renamedelete = None, None
1565 diverge, renamedelete = None, None
1566 for ancestor in ancestors:
1566 for ancestor in ancestors:
1567 repo.ui.note(_(b'\ncalculating bids for ancestor %s\n') % ancestor)
1567 repo.ui.note(_(b'\ncalculating bids for ancestor %s\n') % ancestor)
1568 actions, diverge1, renamedelete1 = manifestmerge(
1568 actions, diverge1, renamedelete1 = manifestmerge(
1569 repo,
1569 repo,
1570 wctx,
1570 wctx,
1571 mctx,
1571 mctx,
1572 ancestor,
1572 ancestor,
1573 branchmerge,
1573 branchmerge,
1574 force,
1574 force,
1575 matcher,
1575 matcher,
1576 acceptremote,
1576 acceptremote,
1577 followcopies,
1577 followcopies,
1578 forcefulldiff=True,
1578 forcefulldiff=True,
1579 )
1579 )
1580 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
1580 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
1581
1581
1582 # Track the shortest set of warning on the theory that bid
1582 # Track the shortest set of warning on the theory that bid
1583 # merge will correctly incorporate more information
1583 # merge will correctly incorporate more information
1584 if diverge is None or len(diverge1) < len(diverge):
1584 if diverge is None or len(diverge1) < len(diverge):
1585 diverge = diverge1
1585 diverge = diverge1
1586 if renamedelete is None or len(renamedelete) < len(renamedelete1):
1586 if renamedelete is None or len(renamedelete) < len(renamedelete1):
1587 renamedelete = renamedelete1
1587 renamedelete = renamedelete1
1588
1588
1589 for f, a in sorted(pycompat.iteritems(actions)):
1589 for f, a in sorted(pycompat.iteritems(actions)):
1590 m, args, msg = a
1590 m, args, msg = a
1591 repo.ui.debug(b' %s: %s -> %s\n' % (f, msg, m))
1591 repo.ui.debug(b' %s: %s -> %s\n' % (f, msg, m))
1592 if f in fbids:
1592 if f in fbids:
1593 d = fbids[f]
1593 d = fbids[f]
1594 if m in d:
1594 if m in d:
1595 d[m].append(a)
1595 d[m].append(a)
1596 else:
1596 else:
1597 d[m] = [a]
1597 d[m] = [a]
1598 else:
1598 else:
1599 fbids[f] = {m: [a]}
1599 fbids[f] = {m: [a]}
1600
1600
1601 # Pick the best bid for each file
1601 # Pick the best bid for each file
1602 repo.ui.note(_(b'\nauction for merging merge bids\n'))
1602 repo.ui.note(_(b'\nauction for merging merge bids\n'))
1603 actions = {}
1603 actions = {}
1604 for f, bids in sorted(fbids.items()):
1604 for f, bids in sorted(fbids.items()):
1605 # bids is a mapping from action method to list af actions
1605 # bids is a mapping from action method to list af actions
1606 # Consensus?
1606 # Consensus?
1607 if len(bids) == 1: # all bids are the same kind of method
1607 if len(bids) == 1: # all bids are the same kind of method
1608 m, l = list(bids.items())[0]
1608 m, l = list(bids.items())[0]
1609 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
1609 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
1610 repo.ui.note(_(b" %s: consensus for %s\n") % (f, m))
1610 repo.ui.note(_(b" %s: consensus for %s\n") % (f, m))
1611 actions[f] = l[0]
1611 actions[f] = l[0]
1612 continue
1612 continue
1613 # If keep is an option, just do it.
1613 # If keep is an option, just do it.
1614 if ACTION_KEEP in bids:
1614 if ACTION_KEEP in bids:
1615 repo.ui.note(_(b" %s: picking 'keep' action\n") % f)
1615 repo.ui.note(_(b" %s: picking 'keep' action\n") % f)
1616 actions[f] = bids[ACTION_KEEP][0]
1616 actions[f] = bids[ACTION_KEEP][0]
1617 continue
1617 continue
1618 # If there are gets and they all agree [how could they not?], do it.
1618 # If there are gets and they all agree [how could they not?], do it.
1619 if ACTION_GET in bids:
1619 if ACTION_GET in bids:
1620 ga0 = bids[ACTION_GET][0]
1620 ga0 = bids[ACTION_GET][0]
1621 if all(a == ga0 for a in bids[ACTION_GET][1:]):
1621 if all(a == ga0 for a in bids[ACTION_GET][1:]):
1622 repo.ui.note(_(b" %s: picking 'get' action\n") % f)
1622 repo.ui.note(_(b" %s: picking 'get' action\n") % f)
1623 actions[f] = ga0
1623 actions[f] = ga0
1624 continue
1624 continue
1625 # TODO: Consider other simple actions such as mode changes
1625 # TODO: Consider other simple actions such as mode changes
1626 # Handle inefficient democrazy.
1626 # Handle inefficient democrazy.
1627 repo.ui.note(_(b' %s: multiple bids for merge action:\n') % f)
1627 repo.ui.note(_(b' %s: multiple bids for merge action:\n') % f)
1628 for m, l in sorted(bids.items()):
1628 for m, l in sorted(bids.items()):
1629 for _f, args, msg in l:
1629 for _f, args, msg in l:
1630 repo.ui.note(b' %s -> %s\n' % (msg, m))
1630 repo.ui.note(b' %s -> %s\n' % (msg, m))
1631 # Pick random action. TODO: Instead, prompt user when resolving
1631 # Pick random action. TODO: Instead, prompt user when resolving
1632 m, l = list(bids.items())[0]
1632 m, l = list(bids.items())[0]
1633 repo.ui.warn(
1633 repo.ui.warn(
1634 _(b' %s: ambiguous merge - picked %s action\n') % (f, m)
1634 _(b' %s: ambiguous merge - picked %s action\n') % (f, m)
1635 )
1635 )
1636 actions[f] = l[0]
1636 actions[f] = l[0]
1637 continue
1637 continue
1638 repo.ui.note(_(b'end of auction\n\n'))
1638 repo.ui.note(_(b'end of auction\n\n'))
1639
1639
1640 if wctx.rev() is None:
1640 if wctx.rev() is None:
1641 fractions = _forgetremoved(wctx, mctx, branchmerge)
1641 fractions = _forgetremoved(wctx, mctx, branchmerge)
1642 actions.update(fractions)
1642 actions.update(fractions)
1643
1643
1644 prunedactions = sparse.filterupdatesactions(
1644 prunedactions = sparse.filterupdatesactions(
1645 repo, wctx, mctx, branchmerge, actions
1645 repo, wctx, mctx, branchmerge, actions
1646 )
1646 )
1647 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
1647 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
1648
1648
1649 return prunedactions, diverge, renamedelete
1649 return prunedactions, diverge, renamedelete
1650
1650
1651
1651
1652 def _getcwd():
1652 def _getcwd():
1653 try:
1653 try:
1654 return encoding.getcwd()
1654 return encoding.getcwd()
1655 except OSError as err:
1655 except OSError as err:
1656 if err.errno == errno.ENOENT:
1656 if err.errno == errno.ENOENT:
1657 return None
1657 return None
1658 raise
1658 raise
1659
1659
1660
1660
1661 def batchremove(repo, wctx, actions):
1661 def batchremove(repo, wctx, actions):
1662 """apply removes to the working directory
1662 """apply removes to the working directory
1663
1663
1664 yields tuples for progress updates
1664 yields tuples for progress updates
1665 """
1665 """
1666 verbose = repo.ui.verbose
1666 verbose = repo.ui.verbose
1667 cwd = _getcwd()
1667 cwd = _getcwd()
1668 i = 0
1668 i = 0
1669 for f, args, msg in actions:
1669 for f, args, msg in actions:
1670 repo.ui.debug(b" %s: %s -> r\n" % (f, msg))
1670 repo.ui.debug(b" %s: %s -> r\n" % (f, msg))
1671 if verbose:
1671 if verbose:
1672 repo.ui.note(_(b"removing %s\n") % f)
1672 repo.ui.note(_(b"removing %s\n") % f)
1673 wctx[f].audit()
1673 wctx[f].audit()
1674 try:
1674 try:
1675 wctx[f].remove(ignoremissing=True)
1675 wctx[f].remove(ignoremissing=True)
1676 except OSError as inst:
1676 except OSError as inst:
1677 repo.ui.warn(
1677 repo.ui.warn(
1678 _(b"update failed to remove %s: %s!\n") % (f, inst.strerror)
1678 _(b"update failed to remove %s: %s!\n") % (f, inst.strerror)
1679 )
1679 )
1680 if i == 100:
1680 if i == 100:
1681 yield i, f
1681 yield i, f
1682 i = 0
1682 i = 0
1683 i += 1
1683 i += 1
1684 if i > 0:
1684 if i > 0:
1685 yield i, f
1685 yield i, f
1686
1686
1687 if cwd and not _getcwd():
1687 if cwd and not _getcwd():
1688 # cwd was removed in the course of removing files; print a helpful
1688 # cwd was removed in the course of removing files; print a helpful
1689 # warning.
1689 # warning.
1690 repo.ui.warn(
1690 repo.ui.warn(
1691 _(
1691 _(
1692 b"current directory was removed\n"
1692 b"current directory was removed\n"
1693 b"(consider changing to repo root: %s)\n"
1693 b"(consider changing to repo root: %s)\n"
1694 )
1694 )
1695 % repo.root
1695 % repo.root
1696 )
1696 )
1697
1697
1698
1698
1699 def batchget(repo, mctx, wctx, wantfiledata, actions):
1699 def batchget(repo, mctx, wctx, wantfiledata, actions):
1700 """apply gets to the working directory
1700 """apply gets to the working directory
1701
1701
1702 mctx is the context to get from
1702 mctx is the context to get from
1703
1703
1704 Yields arbitrarily many (False, tuple) for progress updates, followed by
1704 Yields arbitrarily many (False, tuple) for progress updates, followed by
1705 exactly one (True, filedata). When wantfiledata is false, filedata is an
1705 exactly one (True, filedata). When wantfiledata is false, filedata is an
1706 empty dict. When wantfiledata is true, filedata[f] is a triple (mode, size,
1706 empty dict. When wantfiledata is true, filedata[f] is a triple (mode, size,
1707 mtime) of the file f written for each action.
1707 mtime) of the file f written for each action.
1708 """
1708 """
1709 filedata = {}
1709 filedata = {}
1710 verbose = repo.ui.verbose
1710 verbose = repo.ui.verbose
1711 fctx = mctx.filectx
1711 fctx = mctx.filectx
1712 ui = repo.ui
1712 ui = repo.ui
1713 i = 0
1713 i = 0
1714 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1714 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1715 for f, (flags, backup), msg in actions:
1715 for f, (flags, backup), msg in actions:
1716 repo.ui.debug(b" %s: %s -> g\n" % (f, msg))
1716 repo.ui.debug(b" %s: %s -> g\n" % (f, msg))
1717 if verbose:
1717 if verbose:
1718 repo.ui.note(_(b"getting %s\n") % f)
1718 repo.ui.note(_(b"getting %s\n") % f)
1719
1719
1720 if backup:
1720 if backup:
1721 # If a file or directory exists with the same name, back that
1721 # If a file or directory exists with the same name, back that
1722 # up. Otherwise, look to see if there is a file that conflicts
1722 # up. Otherwise, look to see if there is a file that conflicts
1723 # with a directory this file is in, and if so, back that up.
1723 # with a directory this file is in, and if so, back that up.
1724 conflicting = f
1724 conflicting = f
1725 if not repo.wvfs.lexists(f):
1725 if not repo.wvfs.lexists(f):
1726 for p in pathutil.finddirs(f):
1726 for p in pathutil.finddirs(f):
1727 if repo.wvfs.isfileorlink(p):
1727 if repo.wvfs.isfileorlink(p):
1728 conflicting = p
1728 conflicting = p
1729 break
1729 break
1730 if repo.wvfs.lexists(conflicting):
1730 if repo.wvfs.lexists(conflicting):
1731 orig = scmutil.backuppath(ui, repo, conflicting)
1731 orig = scmutil.backuppath(ui, repo, conflicting)
1732 util.rename(repo.wjoin(conflicting), orig)
1732 util.rename(repo.wjoin(conflicting), orig)
1733 wfctx = wctx[f]
1733 wfctx = wctx[f]
1734 wfctx.clearunknown()
1734 wfctx.clearunknown()
1735 atomictemp = ui.configbool(b"experimental", b"update.atomic-file")
1735 atomictemp = ui.configbool(b"experimental", b"update.atomic-file")
1736 size = wfctx.write(
1736 size = wfctx.write(
1737 fctx(f).data(),
1737 fctx(f).data(),
1738 flags,
1738 flags,
1739 backgroundclose=True,
1739 backgroundclose=True,
1740 atomictemp=atomictemp,
1740 atomictemp=atomictemp,
1741 )
1741 )
1742 if wantfiledata:
1742 if wantfiledata:
1743 s = wfctx.lstat()
1743 s = wfctx.lstat()
1744 mode = s.st_mode
1744 mode = s.st_mode
1745 mtime = s[stat.ST_MTIME]
1745 mtime = s[stat.ST_MTIME]
1746 filedata[f] = (mode, size, mtime) # for dirstate.normal
1746 filedata[f] = (mode, size, mtime) # for dirstate.normal
1747 if i == 100:
1747 if i == 100:
1748 yield False, (i, f)
1748 yield False, (i, f)
1749 i = 0
1749 i = 0
1750 i += 1
1750 i += 1
1751 if i > 0:
1751 if i > 0:
1752 yield False, (i, f)
1752 yield False, (i, f)
1753 yield True, filedata
1753 yield True, filedata
1754
1754
1755
1755
1756 def _prefetchfiles(repo, ctx, actions):
1756 def _prefetchfiles(repo, ctx, actions):
1757 """Invoke ``scmutil.prefetchfiles()`` for the files relevant to the dict
1757 """Invoke ``scmutil.prefetchfiles()`` for the files relevant to the dict
1758 of merge actions. ``ctx`` is the context being merged in."""
1758 of merge actions. ``ctx`` is the context being merged in."""
1759
1759
1760 # Skipping 'a', 'am', 'f', 'r', 'dm', 'e', 'k', 'p' and 'pr', because they
1760 # Skipping 'a', 'am', 'f', 'r', 'dm', 'e', 'k', 'p' and 'pr', because they
1761 # don't touch the context to be merged in. 'cd' is skipped, because
1761 # don't touch the context to be merged in. 'cd' is skipped, because
1762 # changed/deleted never resolves to something from the remote side.
1762 # changed/deleted never resolves to something from the remote side.
1763 oplist = [
1763 oplist = [
1764 actions[a]
1764 actions[a]
1765 for a in (
1765 for a in (
1766 ACTION_GET,
1766 ACTION_GET,
1767 ACTION_DELETED_CHANGED,
1767 ACTION_DELETED_CHANGED,
1768 ACTION_LOCAL_DIR_RENAME_GET,
1768 ACTION_LOCAL_DIR_RENAME_GET,
1769 ACTION_MERGE,
1769 ACTION_MERGE,
1770 )
1770 )
1771 ]
1771 ]
1772 prefetch = scmutil.prefetchfiles
1772 prefetch = scmutil.prefetchfiles
1773 matchfiles = scmutil.matchfiles
1773 matchfiles = scmutil.matchfiles
1774 prefetch(
1774 prefetch(
1775 repo,
1775 repo,
1776 [ctx.rev()],
1776 [ctx.rev()],
1777 matchfiles(repo, [f for sublist in oplist for f, args, msg in sublist]),
1777 matchfiles(repo, [f for sublist in oplist for f, args, msg in sublist]),
1778 )
1778 )
1779
1779
1780
1780
1781 @attr.s(frozen=True)
1781 @attr.s(frozen=True)
1782 class updateresult(object):
1782 class updateresult(object):
1783 updatedcount = attr.ib()
1783 updatedcount = attr.ib()
1784 mergedcount = attr.ib()
1784 mergedcount = attr.ib()
1785 removedcount = attr.ib()
1785 removedcount = attr.ib()
1786 unresolvedcount = attr.ib()
1786 unresolvedcount = attr.ib()
1787
1787
1788 def isempty(self):
1788 def isempty(self):
1789 return not (
1789 return not (
1790 self.updatedcount
1790 self.updatedcount
1791 or self.mergedcount
1791 or self.mergedcount
1792 or self.removedcount
1792 or self.removedcount
1793 or self.unresolvedcount
1793 or self.unresolvedcount
1794 )
1794 )
1795
1795
1796
1796
1797 def emptyactions():
1797 def emptyactions():
1798 """create an actions dict, to be populated and passed to applyupdates()"""
1798 """create an actions dict, to be populated and passed to applyupdates()"""
1799 return dict(
1799 return dict(
1800 (m, [])
1800 (m, [])
1801 for m in (
1801 for m in (
1802 ACTION_ADD,
1802 ACTION_ADD,
1803 ACTION_ADD_MODIFIED,
1803 ACTION_ADD_MODIFIED,
1804 ACTION_FORGET,
1804 ACTION_FORGET,
1805 ACTION_GET,
1805 ACTION_GET,
1806 ACTION_CHANGED_DELETED,
1806 ACTION_CHANGED_DELETED,
1807 ACTION_DELETED_CHANGED,
1807 ACTION_DELETED_CHANGED,
1808 ACTION_REMOVE,
1808 ACTION_REMOVE,
1809 ACTION_DIR_RENAME_MOVE_LOCAL,
1809 ACTION_DIR_RENAME_MOVE_LOCAL,
1810 ACTION_LOCAL_DIR_RENAME_GET,
1810 ACTION_LOCAL_DIR_RENAME_GET,
1811 ACTION_MERGE,
1811 ACTION_MERGE,
1812 ACTION_EXEC,
1812 ACTION_EXEC,
1813 ACTION_KEEP,
1813 ACTION_KEEP,
1814 ACTION_PATH_CONFLICT,
1814 ACTION_PATH_CONFLICT,
1815 ACTION_PATH_CONFLICT_RESOLVE,
1815 ACTION_PATH_CONFLICT_RESOLVE,
1816 )
1816 )
1817 )
1817 )
1818
1818
1819
1819
1820 def applyupdates(
1820 def applyupdates(
1821 repo, actions, wctx, mctx, overwrite, wantfiledata, labels=None
1821 repo, actions, wctx, mctx, overwrite, wantfiledata, labels=None
1822 ):
1822 ):
1823 """apply the merge action list to the working directory
1823 """apply the merge action list to the working directory
1824
1824
1825 wctx is the working copy context
1825 wctx is the working copy context
1826 mctx is the context to be merged into the working copy
1826 mctx is the context to be merged into the working copy
1827
1827
1828 Return a tuple of (counts, filedata), where counts is a tuple
1828 Return a tuple of (counts, filedata), where counts is a tuple
1829 (updated, merged, removed, unresolved) that describes how many
1829 (updated, merged, removed, unresolved) that describes how many
1830 files were affected by the update, and filedata is as described in
1830 files were affected by the update, and filedata is as described in
1831 batchget.
1831 batchget.
1832 """
1832 """
1833
1833
1834 _prefetchfiles(repo, mctx, actions)
1834 _prefetchfiles(repo, mctx, actions)
1835
1835
1836 updated, merged, removed = 0, 0, 0
1836 updated, merged, removed = 0, 0, 0
1837 ms = mergestate.clean(repo, wctx.p1().node(), mctx.node(), labels)
1837 ms = mergestate.clean(repo, wctx.p1().node(), mctx.node(), labels)
1838 moves = []
1838 moves = []
1839 for m, l in actions.items():
1839 for m, l in actions.items():
1840 l.sort()
1840 l.sort()
1841
1841
1842 # 'cd' and 'dc' actions are treated like other merge conflicts
1842 # 'cd' and 'dc' actions are treated like other merge conflicts
1843 mergeactions = sorted(actions[ACTION_CHANGED_DELETED])
1843 mergeactions = sorted(actions[ACTION_CHANGED_DELETED])
1844 mergeactions.extend(sorted(actions[ACTION_DELETED_CHANGED]))
1844 mergeactions.extend(sorted(actions[ACTION_DELETED_CHANGED]))
1845 mergeactions.extend(actions[ACTION_MERGE])
1845 mergeactions.extend(actions[ACTION_MERGE])
1846 for f, args, msg in mergeactions:
1846 for f, args, msg in mergeactions:
1847 f1, f2, fa, move, anc = args
1847 f1, f2, fa, move, anc = args
1848 if f == b'.hgsubstate': # merged internally
1848 if f == b'.hgsubstate': # merged internally
1849 continue
1849 continue
1850 if f1 is None:
1850 if f1 is None:
1851 fcl = filemerge.absentfilectx(wctx, fa)
1851 fcl = filemerge.absentfilectx(wctx, fa)
1852 else:
1852 else:
1853 repo.ui.debug(b" preserving %s for resolve of %s\n" % (f1, f))
1853 repo.ui.debug(b" preserving %s for resolve of %s\n" % (f1, f))
1854 fcl = wctx[f1]
1854 fcl = wctx[f1]
1855 if f2 is None:
1855 if f2 is None:
1856 fco = filemerge.absentfilectx(mctx, fa)
1856 fco = filemerge.absentfilectx(mctx, fa)
1857 else:
1857 else:
1858 fco = mctx[f2]
1858 fco = mctx[f2]
1859 actx = repo[anc]
1859 actx = repo[anc]
1860 if fa in actx:
1860 if fa in actx:
1861 fca = actx[fa]
1861 fca = actx[fa]
1862 else:
1862 else:
1863 # TODO: move to absentfilectx
1863 # TODO: move to absentfilectx
1864 fca = repo.filectx(f1, fileid=nullrev)
1864 fca = repo.filectx(f1, fileid=nullrev)
1865 ms.add(fcl, fco, fca, f)
1865 ms.add(fcl, fco, fca, f)
1866 if f1 != f and move:
1866 if f1 != f and move:
1867 moves.append(f1)
1867 moves.append(f1)
1868
1868
1869 # remove renamed files after safely stored
1869 # remove renamed files after safely stored
1870 for f in moves:
1870 for f in moves:
1871 if wctx[f].lexists():
1871 if wctx[f].lexists():
1872 repo.ui.debug(b"removing %s\n" % f)
1872 repo.ui.debug(b"removing %s\n" % f)
1873 wctx[f].audit()
1873 wctx[f].audit()
1874 wctx[f].remove()
1874 wctx[f].remove()
1875
1875
1876 numupdates = sum(len(l) for m, l in actions.items() if m != ACTION_KEEP)
1876 numupdates = sum(len(l) for m, l in actions.items() if m != ACTION_KEEP)
1877 progress = repo.ui.makeprogress(
1877 progress = repo.ui.makeprogress(
1878 _(b'updating'), unit=_(b'files'), total=numupdates
1878 _(b'updating'), unit=_(b'files'), total=numupdates
1879 )
1879 )
1880
1880
1881 if [a for a in actions[ACTION_REMOVE] if a[0] == b'.hgsubstate']:
1881 if [a for a in actions[ACTION_REMOVE] if a[0] == b'.hgsubstate']:
1882 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1882 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1883
1883
1884 # record path conflicts
1884 # record path conflicts
1885 for f, args, msg in actions[ACTION_PATH_CONFLICT]:
1885 for f, args, msg in actions[ACTION_PATH_CONFLICT]:
1886 f1, fo = args
1886 f1, fo = args
1887 s = repo.ui.status
1887 s = repo.ui.status
1888 s(
1888 s(
1889 _(
1889 _(
1890 b"%s: path conflict - a file or link has the same name as a "
1890 b"%s: path conflict - a file or link has the same name as a "
1891 b"directory\n"
1891 b"directory\n"
1892 )
1892 )
1893 % f
1893 % f
1894 )
1894 )
1895 if fo == b'l':
1895 if fo == b'l':
1896 s(_(b"the local file has been renamed to %s\n") % f1)
1896 s(_(b"the local file has been renamed to %s\n") % f1)
1897 else:
1897 else:
1898 s(_(b"the remote file has been renamed to %s\n") % f1)
1898 s(_(b"the remote file has been renamed to %s\n") % f1)
1899 s(_(b"resolve manually then use 'hg resolve --mark %s'\n") % f)
1899 s(_(b"resolve manually then use 'hg resolve --mark %s'\n") % f)
1900 ms.addpath(f, f1, fo)
1900 ms.addpath(f, f1, fo)
1901 progress.increment(item=f)
1901 progress.increment(item=f)
1902
1902
1903 # When merging in-memory, we can't support worker processes, so set the
1903 # When merging in-memory, we can't support worker processes, so set the
1904 # per-item cost at 0 in that case.
1904 # per-item cost at 0 in that case.
1905 cost = 0 if wctx.isinmemory() else 0.001
1905 cost = 0 if wctx.isinmemory() else 0.001
1906
1906
1907 # remove in parallel (must come before resolving path conflicts and getting)
1907 # remove in parallel (must come before resolving path conflicts and getting)
1908 prog = worker.worker(
1908 prog = worker.worker(
1909 repo.ui, cost, batchremove, (repo, wctx), actions[ACTION_REMOVE]
1909 repo.ui, cost, batchremove, (repo, wctx), actions[ACTION_REMOVE]
1910 )
1910 )
1911 for i, item in prog:
1911 for i, item in prog:
1912 progress.increment(step=i, item=item)
1912 progress.increment(step=i, item=item)
1913 removed = len(actions[ACTION_REMOVE])
1913 removed = len(actions[ACTION_REMOVE])
1914
1914
1915 # resolve path conflicts (must come before getting)
1915 # resolve path conflicts (must come before getting)
1916 for f, args, msg in actions[ACTION_PATH_CONFLICT_RESOLVE]:
1916 for f, args, msg in actions[ACTION_PATH_CONFLICT_RESOLVE]:
1917 repo.ui.debug(b" %s: %s -> pr\n" % (f, msg))
1917 repo.ui.debug(b" %s: %s -> pr\n" % (f, msg))
1918 (f0,) = args
1918 (f0,) = args
1919 if wctx[f0].lexists():
1919 if wctx[f0].lexists():
1920 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1920 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1921 wctx[f].audit()
1921 wctx[f].audit()
1922 wctx[f].write(wctx.filectx(f0).data(), wctx.filectx(f0).flags())
1922 wctx[f].write(wctx.filectx(f0).data(), wctx.filectx(f0).flags())
1923 wctx[f0].remove()
1923 wctx[f0].remove()
1924 progress.increment(item=f)
1924 progress.increment(item=f)
1925
1925
1926 # get in parallel.
1926 # get in parallel.
1927 threadsafe = repo.ui.configbool(
1927 threadsafe = repo.ui.configbool(
1928 b'experimental', b'worker.wdir-get-thread-safe'
1928 b'experimental', b'worker.wdir-get-thread-safe'
1929 )
1929 )
1930 prog = worker.worker(
1930 prog = worker.worker(
1931 repo.ui,
1931 repo.ui,
1932 cost,
1932 cost,
1933 batchget,
1933 batchget,
1934 (repo, mctx, wctx, wantfiledata),
1934 (repo, mctx, wctx, wantfiledata),
1935 actions[ACTION_GET],
1935 actions[ACTION_GET],
1936 threadsafe=threadsafe,
1936 threadsafe=threadsafe,
1937 hasretval=True,
1937 hasretval=True,
1938 )
1938 )
1939 getfiledata = {}
1939 getfiledata = {}
1940 for final, res in prog:
1940 for final, res in prog:
1941 if final:
1941 if final:
1942 getfiledata = res
1942 getfiledata = res
1943 else:
1943 else:
1944 i, item = res
1944 i, item = res
1945 progress.increment(step=i, item=item)
1945 progress.increment(step=i, item=item)
1946 updated = len(actions[ACTION_GET])
1946 updated = len(actions[ACTION_GET])
1947
1947
1948 if [a for a in actions[ACTION_GET] if a[0] == b'.hgsubstate']:
1948 if [a for a in actions[ACTION_GET] if a[0] == b'.hgsubstate']:
1949 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1949 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1950
1950
1951 # forget (manifest only, just log it) (must come first)
1951 # forget (manifest only, just log it) (must come first)
1952 for f, args, msg in actions[ACTION_FORGET]:
1952 for f, args, msg in actions[ACTION_FORGET]:
1953 repo.ui.debug(b" %s: %s -> f\n" % (f, msg))
1953 repo.ui.debug(b" %s: %s -> f\n" % (f, msg))
1954 progress.increment(item=f)
1954 progress.increment(item=f)
1955
1955
1956 # re-add (manifest only, just log it)
1956 # re-add (manifest only, just log it)
1957 for f, args, msg in actions[ACTION_ADD]:
1957 for f, args, msg in actions[ACTION_ADD]:
1958 repo.ui.debug(b" %s: %s -> a\n" % (f, msg))
1958 repo.ui.debug(b" %s: %s -> a\n" % (f, msg))
1959 progress.increment(item=f)
1959 progress.increment(item=f)
1960
1960
1961 # re-add/mark as modified (manifest only, just log it)
1961 # re-add/mark as modified (manifest only, just log it)
1962 for f, args, msg in actions[ACTION_ADD_MODIFIED]:
1962 for f, args, msg in actions[ACTION_ADD_MODIFIED]:
1963 repo.ui.debug(b" %s: %s -> am\n" % (f, msg))
1963 repo.ui.debug(b" %s: %s -> am\n" % (f, msg))
1964 progress.increment(item=f)
1964 progress.increment(item=f)
1965
1965
1966 # keep (noop, just log it)
1966 # keep (noop, just log it)
1967 for f, args, msg in actions[ACTION_KEEP]:
1967 for f, args, msg in actions[ACTION_KEEP]:
1968 repo.ui.debug(b" %s: %s -> k\n" % (f, msg))
1968 repo.ui.debug(b" %s: %s -> k\n" % (f, msg))
1969 # no progress
1969 # no progress
1970
1970
1971 # directory rename, move local
1971 # directory rename, move local
1972 for f, args, msg in actions[ACTION_DIR_RENAME_MOVE_LOCAL]:
1972 for f, args, msg in actions[ACTION_DIR_RENAME_MOVE_LOCAL]:
1973 repo.ui.debug(b" %s: %s -> dm\n" % (f, msg))
1973 repo.ui.debug(b" %s: %s -> dm\n" % (f, msg))
1974 progress.increment(item=f)
1974 progress.increment(item=f)
1975 f0, flags = args
1975 f0, flags = args
1976 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1976 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1977 wctx[f].audit()
1977 wctx[f].audit()
1978 wctx[f].write(wctx.filectx(f0).data(), flags)
1978 wctx[f].write(wctx.filectx(f0).data(), flags)
1979 wctx[f0].remove()
1979 wctx[f0].remove()
1980 updated += 1
1980 updated += 1
1981
1981
1982 # local directory rename, get
1982 # local directory rename, get
1983 for f, args, msg in actions[ACTION_LOCAL_DIR_RENAME_GET]:
1983 for f, args, msg in actions[ACTION_LOCAL_DIR_RENAME_GET]:
1984 repo.ui.debug(b" %s: %s -> dg\n" % (f, msg))
1984 repo.ui.debug(b" %s: %s -> dg\n" % (f, msg))
1985 progress.increment(item=f)
1985 progress.increment(item=f)
1986 f0, flags = args
1986 f0, flags = args
1987 repo.ui.note(_(b"getting %s to %s\n") % (f0, f))
1987 repo.ui.note(_(b"getting %s to %s\n") % (f0, f))
1988 wctx[f].write(mctx.filectx(f0).data(), flags)
1988 wctx[f].write(mctx.filectx(f0).data(), flags)
1989 updated += 1
1989 updated += 1
1990
1990
1991 # exec
1991 # exec
1992 for f, args, msg in actions[ACTION_EXEC]:
1992 for f, args, msg in actions[ACTION_EXEC]:
1993 repo.ui.debug(b" %s: %s -> e\n" % (f, msg))
1993 repo.ui.debug(b" %s: %s -> e\n" % (f, msg))
1994 progress.increment(item=f)
1994 progress.increment(item=f)
1995 (flags,) = args
1995 (flags,) = args
1996 wctx[f].audit()
1996 wctx[f].audit()
1997 wctx[f].setflags(b'l' in flags, b'x' in flags)
1997 wctx[f].setflags(b'l' in flags, b'x' in flags)
1998 updated += 1
1998 updated += 1
1999
1999
2000 # the ordering is important here -- ms.mergedriver will raise if the merge
2000 # the ordering is important here -- ms.mergedriver will raise if the merge
2001 # driver has changed, and we want to be able to bypass it when overwrite is
2001 # driver has changed, and we want to be able to bypass it when overwrite is
2002 # True
2002 # True
2003 usemergedriver = not overwrite and mergeactions and ms.mergedriver
2003 usemergedriver = not overwrite and mergeactions and ms.mergedriver
2004
2004
2005 if usemergedriver:
2005 if usemergedriver:
2006 if wctx.isinmemory():
2006 if wctx.isinmemory():
2007 raise error.InMemoryMergeConflictsError(
2007 raise error.InMemoryMergeConflictsError(
2008 b"in-memory merge does not support mergedriver"
2008 b"in-memory merge does not support mergedriver"
2009 )
2009 )
2010 ms.commit()
2010 ms.commit()
2011 proceed = driverpreprocess(repo, ms, wctx, labels=labels)
2011 proceed = driverpreprocess(repo, ms, wctx, labels=labels)
2012 # the driver might leave some files unresolved
2012 # the driver might leave some files unresolved
2013 unresolvedf = set(ms.unresolved())
2013 unresolvedf = set(ms.unresolved())
2014 if not proceed:
2014 if not proceed:
2015 # XXX setting unresolved to at least 1 is a hack to make sure we
2015 # XXX setting unresolved to at least 1 is a hack to make sure we
2016 # error out
2016 # error out
2017 return updateresult(
2017 return updateresult(
2018 updated, merged, removed, max(len(unresolvedf), 1)
2018 updated, merged, removed, max(len(unresolvedf), 1)
2019 )
2019 )
2020 newactions = []
2020 newactions = []
2021 for f, args, msg in mergeactions:
2021 for f, args, msg in mergeactions:
2022 if f in unresolvedf:
2022 if f in unresolvedf:
2023 newactions.append((f, args, msg))
2023 newactions.append((f, args, msg))
2024 mergeactions = newactions
2024 mergeactions = newactions
2025
2025
2026 try:
2026 try:
2027 # premerge
2027 # premerge
2028 tocomplete = []
2028 tocomplete = []
2029 for f, args, msg in mergeactions:
2029 for f, args, msg in mergeactions:
2030 repo.ui.debug(b" %s: %s -> m (premerge)\n" % (f, msg))
2030 repo.ui.debug(b" %s: %s -> m (premerge)\n" % (f, msg))
2031 progress.increment(item=f)
2031 progress.increment(item=f)
2032 if f == b'.hgsubstate': # subrepo states need updating
2032 if f == b'.hgsubstate': # subrepo states need updating
2033 subrepoutil.submerge(
2033 subrepoutil.submerge(
2034 repo, wctx, mctx, wctx.ancestor(mctx), overwrite, labels
2034 repo, wctx, mctx, wctx.ancestor(mctx), overwrite, labels
2035 )
2035 )
2036 continue
2036 continue
2037 wctx[f].audit()
2037 wctx[f].audit()
2038 complete, r = ms.preresolve(f, wctx)
2038 complete, r = ms.preresolve(f, wctx)
2039 if not complete:
2039 if not complete:
2040 numupdates += 1
2040 numupdates += 1
2041 tocomplete.append((f, args, msg))
2041 tocomplete.append((f, args, msg))
2042
2042
2043 # merge
2043 # merge
2044 for f, args, msg in tocomplete:
2044 for f, args, msg in tocomplete:
2045 repo.ui.debug(b" %s: %s -> m (merge)\n" % (f, msg))
2045 repo.ui.debug(b" %s: %s -> m (merge)\n" % (f, msg))
2046 progress.increment(item=f, total=numupdates)
2046 progress.increment(item=f, total=numupdates)
2047 ms.resolve(f, wctx)
2047 ms.resolve(f, wctx)
2048
2048
2049 finally:
2049 finally:
2050 ms.commit()
2050 ms.commit()
2051
2051
2052 unresolved = ms.unresolvedcount()
2052 unresolved = ms.unresolvedcount()
2053
2053
2054 if (
2054 if (
2055 usemergedriver
2055 usemergedriver
2056 and not unresolved
2056 and not unresolved
2057 and ms.mdstate() != MERGE_DRIVER_STATE_SUCCESS
2057 and ms.mdstate() != MERGE_DRIVER_STATE_SUCCESS
2058 ):
2058 ):
2059 if not driverconclude(repo, ms, wctx, labels=labels):
2059 if not driverconclude(repo, ms, wctx, labels=labels):
2060 # XXX setting unresolved to at least 1 is a hack to make sure we
2060 # XXX setting unresolved to at least 1 is a hack to make sure we
2061 # error out
2061 # error out
2062 unresolved = max(unresolved, 1)
2062 unresolved = max(unresolved, 1)
2063
2063
2064 ms.commit()
2064 ms.commit()
2065
2065
2066 msupdated, msmerged, msremoved = ms.counts()
2066 msupdated, msmerged, msremoved = ms.counts()
2067 updated += msupdated
2067 updated += msupdated
2068 merged += msmerged
2068 merged += msmerged
2069 removed += msremoved
2069 removed += msremoved
2070
2070
2071 extraactions = ms.actions()
2071 extraactions = ms.actions()
2072 if extraactions:
2072 if extraactions:
2073 mfiles = set(a[0] for a in actions[ACTION_MERGE])
2073 mfiles = set(a[0] for a in actions[ACTION_MERGE])
2074 for k, acts in pycompat.iteritems(extraactions):
2074 for k, acts in pycompat.iteritems(extraactions):
2075 actions[k].extend(acts)
2075 actions[k].extend(acts)
2076 if k == ACTION_GET and wantfiledata:
2076 if k == ACTION_GET and wantfiledata:
2077 # no filedata until mergestate is updated to provide it
2077 # no filedata until mergestate is updated to provide it
2078 for a in acts:
2078 for a in acts:
2079 getfiledata[a[0]] = None
2079 getfiledata[a[0]] = None
2080 # Remove these files from actions[ACTION_MERGE] as well. This is
2080 # Remove these files from actions[ACTION_MERGE] as well. This is
2081 # important because in recordupdates, files in actions[ACTION_MERGE]
2081 # important because in recordupdates, files in actions[ACTION_MERGE]
2082 # are processed after files in other actions, and the merge driver
2082 # are processed after files in other actions, and the merge driver
2083 # might add files to those actions via extraactions above. This can
2083 # might add files to those actions via extraactions above. This can
2084 # lead to a file being recorded twice, with poor results. This is
2084 # lead to a file being recorded twice, with poor results. This is
2085 # especially problematic for actions[ACTION_REMOVE] (currently only
2085 # especially problematic for actions[ACTION_REMOVE] (currently only
2086 # possible with the merge driver in the initial merge process;
2086 # possible with the merge driver in the initial merge process;
2087 # interrupted merges don't go through this flow).
2087 # interrupted merges don't go through this flow).
2088 #
2088 #
2089 # The real fix here is to have indexes by both file and action so
2089 # The real fix here is to have indexes by both file and action so
2090 # that when the action for a file is changed it is automatically
2090 # that when the action for a file is changed it is automatically
2091 # reflected in the other action lists. But that involves a more
2091 # reflected in the other action lists. But that involves a more
2092 # complex data structure, so this will do for now.
2092 # complex data structure, so this will do for now.
2093 #
2093 #
2094 # We don't need to do the same operation for 'dc' and 'cd' because
2094 # We don't need to do the same operation for 'dc' and 'cd' because
2095 # those lists aren't consulted again.
2095 # those lists aren't consulted again.
2096 mfiles.difference_update(a[0] for a in acts)
2096 mfiles.difference_update(a[0] for a in acts)
2097
2097
2098 actions[ACTION_MERGE] = [
2098 actions[ACTION_MERGE] = [
2099 a for a in actions[ACTION_MERGE] if a[0] in mfiles
2099 a for a in actions[ACTION_MERGE] if a[0] in mfiles
2100 ]
2100 ]
2101
2101
2102 progress.complete()
2102 progress.complete()
2103 assert len(getfiledata) == (len(actions[ACTION_GET]) if wantfiledata else 0)
2103 assert len(getfiledata) == (len(actions[ACTION_GET]) if wantfiledata else 0)
2104 return updateresult(updated, merged, removed, unresolved), getfiledata
2104 return updateresult(updated, merged, removed, unresolved), getfiledata
2105
2105
2106
2106
2107 def recordupdates(repo, actions, branchmerge, getfiledata):
2107 def recordupdates(repo, actions, branchmerge, getfiledata):
2108 """record merge actions to the dirstate"""
2108 """record merge actions to the dirstate"""
2109 # remove (must come first)
2109 # remove (must come first)
2110 for f, args, msg in actions.get(ACTION_REMOVE, []):
2110 for f, args, msg in actions.get(ACTION_REMOVE, []):
2111 if branchmerge:
2111 if branchmerge:
2112 repo.dirstate.remove(f)
2112 repo.dirstate.remove(f)
2113 else:
2113 else:
2114 repo.dirstate.drop(f)
2114 repo.dirstate.drop(f)
2115
2115
2116 # forget (must come first)
2116 # forget (must come first)
2117 for f, args, msg in actions.get(ACTION_FORGET, []):
2117 for f, args, msg in actions.get(ACTION_FORGET, []):
2118 repo.dirstate.drop(f)
2118 repo.dirstate.drop(f)
2119
2119
2120 # resolve path conflicts
2120 # resolve path conflicts
2121 for f, args, msg in actions.get(ACTION_PATH_CONFLICT_RESOLVE, []):
2121 for f, args, msg in actions.get(ACTION_PATH_CONFLICT_RESOLVE, []):
2122 (f0,) = args
2122 (f0,) = args
2123 origf0 = repo.dirstate.copied(f0) or f0
2123 origf0 = repo.dirstate.copied(f0) or f0
2124 repo.dirstate.add(f)
2124 repo.dirstate.add(f)
2125 repo.dirstate.copy(origf0, f)
2125 repo.dirstate.copy(origf0, f)
2126 if f0 == origf0:
2126 if f0 == origf0:
2127 repo.dirstate.remove(f0)
2127 repo.dirstate.remove(f0)
2128 else:
2128 else:
2129 repo.dirstate.drop(f0)
2129 repo.dirstate.drop(f0)
2130
2130
2131 # re-add
2131 # re-add
2132 for f, args, msg in actions.get(ACTION_ADD, []):
2132 for f, args, msg in actions.get(ACTION_ADD, []):
2133 repo.dirstate.add(f)
2133 repo.dirstate.add(f)
2134
2134
2135 # re-add/mark as modified
2135 # re-add/mark as modified
2136 for f, args, msg in actions.get(ACTION_ADD_MODIFIED, []):
2136 for f, args, msg in actions.get(ACTION_ADD_MODIFIED, []):
2137 if branchmerge:
2137 if branchmerge:
2138 repo.dirstate.normallookup(f)
2138 repo.dirstate.normallookup(f)
2139 else:
2139 else:
2140 repo.dirstate.add(f)
2140 repo.dirstate.add(f)
2141
2141
2142 # exec change
2142 # exec change
2143 for f, args, msg in actions.get(ACTION_EXEC, []):
2143 for f, args, msg in actions.get(ACTION_EXEC, []):
2144 repo.dirstate.normallookup(f)
2144 repo.dirstate.normallookup(f)
2145
2145
2146 # keep
2146 # keep
2147 for f, args, msg in actions.get(ACTION_KEEP, []):
2147 for f, args, msg in actions.get(ACTION_KEEP, []):
2148 pass
2148 pass
2149
2149
2150 # get
2150 # get
2151 for f, args, msg in actions.get(ACTION_GET, []):
2151 for f, args, msg in actions.get(ACTION_GET, []):
2152 if branchmerge:
2152 if branchmerge:
2153 repo.dirstate.otherparent(f)
2153 repo.dirstate.otherparent(f)
2154 else:
2154 else:
2155 parentfiledata = getfiledata[f] if getfiledata else None
2155 parentfiledata = getfiledata[f] if getfiledata else None
2156 repo.dirstate.normal(f, parentfiledata=parentfiledata)
2156 repo.dirstate.normal(f, parentfiledata=parentfiledata)
2157
2157
2158 # merge
2158 # merge
2159 for f, args, msg in actions.get(ACTION_MERGE, []):
2159 for f, args, msg in actions.get(ACTION_MERGE, []):
2160 f1, f2, fa, move, anc = args
2160 f1, f2, fa, move, anc = args
2161 if branchmerge:
2161 if branchmerge:
2162 # We've done a branch merge, mark this file as merged
2162 # We've done a branch merge, mark this file as merged
2163 # so that we properly record the merger later
2163 # so that we properly record the merger later
2164 repo.dirstate.merge(f)
2164 repo.dirstate.merge(f)
2165 if f1 != f2: # copy/rename
2165 if f1 != f2: # copy/rename
2166 if move:
2166 if move:
2167 repo.dirstate.remove(f1)
2167 repo.dirstate.remove(f1)
2168 if f1 != f:
2168 if f1 != f:
2169 repo.dirstate.copy(f1, f)
2169 repo.dirstate.copy(f1, f)
2170 else:
2170 else:
2171 repo.dirstate.copy(f2, f)
2171 repo.dirstate.copy(f2, f)
2172 else:
2172 else:
2173 # We've update-merged a locally modified file, so
2173 # We've update-merged a locally modified file, so
2174 # we set the dirstate to emulate a normal checkout
2174 # we set the dirstate to emulate a normal checkout
2175 # of that file some time in the past. Thus our
2175 # of that file some time in the past. Thus our
2176 # merge will appear as a normal local file
2176 # merge will appear as a normal local file
2177 # modification.
2177 # modification.
2178 if f2 == f: # file not locally copied/moved
2178 if f2 == f: # file not locally copied/moved
2179 repo.dirstate.normallookup(f)
2179 repo.dirstate.normallookup(f)
2180 if move:
2180 if move:
2181 repo.dirstate.drop(f1)
2181 repo.dirstate.drop(f1)
2182
2182
2183 # directory rename, move local
2183 # directory rename, move local
2184 for f, args, msg in actions.get(ACTION_DIR_RENAME_MOVE_LOCAL, []):
2184 for f, args, msg in actions.get(ACTION_DIR_RENAME_MOVE_LOCAL, []):
2185 f0, flag = args
2185 f0, flag = args
2186 if branchmerge:
2186 if branchmerge:
2187 repo.dirstate.add(f)
2187 repo.dirstate.add(f)
2188 repo.dirstate.remove(f0)
2188 repo.dirstate.remove(f0)
2189 repo.dirstate.copy(f0, f)
2189 repo.dirstate.copy(f0, f)
2190 else:
2190 else:
2191 repo.dirstate.normal(f)
2191 repo.dirstate.normal(f)
2192 repo.dirstate.drop(f0)
2192 repo.dirstate.drop(f0)
2193
2193
2194 # directory rename, get
2194 # directory rename, get
2195 for f, args, msg in actions.get(ACTION_LOCAL_DIR_RENAME_GET, []):
2195 for f, args, msg in actions.get(ACTION_LOCAL_DIR_RENAME_GET, []):
2196 f0, flag = args
2196 f0, flag = args
2197 if branchmerge:
2197 if branchmerge:
2198 repo.dirstate.add(f)
2198 repo.dirstate.add(f)
2199 repo.dirstate.copy(f0, f)
2199 repo.dirstate.copy(f0, f)
2200 else:
2200 else:
2201 repo.dirstate.normal(f)
2201 repo.dirstate.normal(f)
2202
2202
2203
2203
2204 UPDATECHECK_ABORT = b'abort' # handled at higher layers
2204 UPDATECHECK_ABORT = b'abort' # handled at higher layers
2205 UPDATECHECK_NONE = b'none'
2205 UPDATECHECK_NONE = b'none'
2206 UPDATECHECK_LINEAR = b'linear'
2206 UPDATECHECK_LINEAR = b'linear'
2207 UPDATECHECK_NO_CONFLICT = b'noconflict'
2207 UPDATECHECK_NO_CONFLICT = b'noconflict'
2208
2208
2209
2209
2210 def update(
2210 def update(
2211 repo,
2211 repo,
2212 node,
2212 node,
2213 branchmerge,
2213 branchmerge,
2214 force,
2214 force,
2215 ancestor=None,
2215 ancestor=None,
2216 mergeancestor=False,
2216 mergeancestor=False,
2217 labels=None,
2217 labels=None,
2218 matcher=None,
2218 matcher=None,
2219 mergeforce=False,
2219 mergeforce=False,
2220 updatecheck=None,
2220 updatecheck=None,
2221 wc=None,
2221 wc=None,
2222 ):
2222 ):
2223 """
2223 """
2224 Perform a merge between the working directory and the given node
2224 Perform a merge between the working directory and the given node
2225
2225
2226 node = the node to update to
2226 node = the node to update to
2227 branchmerge = whether to merge between branches
2227 branchmerge = whether to merge between branches
2228 force = whether to force branch merging or file overwriting
2228 force = whether to force branch merging or file overwriting
2229 matcher = a matcher to filter file lists (dirstate not updated)
2229 matcher = a matcher to filter file lists (dirstate not updated)
2230 mergeancestor = whether it is merging with an ancestor. If true,
2230 mergeancestor = whether it is merging with an ancestor. If true,
2231 we should accept the incoming changes for any prompts that occur.
2231 we should accept the incoming changes for any prompts that occur.
2232 If false, merging with an ancestor (fast-forward) is only allowed
2232 If false, merging with an ancestor (fast-forward) is only allowed
2233 between different named branches. This flag is used by rebase extension
2233 between different named branches. This flag is used by rebase extension
2234 as a temporary fix and should be avoided in general.
2234 as a temporary fix and should be avoided in general.
2235 labels = labels to use for base, local and other
2235 labels = labels to use for base, local and other
2236 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
2236 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
2237 this is True, then 'force' should be True as well.
2237 this is True, then 'force' should be True as well.
2238
2238
2239 The table below shows all the behaviors of the update command given the
2239 The table below shows all the behaviors of the update command given the
2240 -c/--check and -C/--clean or no options, whether the working directory is
2240 -c/--check and -C/--clean or no options, whether the working directory is
2241 dirty, whether a revision is specified, and the relationship of the parent
2241 dirty, whether a revision is specified, and the relationship of the parent
2242 rev to the target rev (linear or not). Match from top first. The -n
2242 rev to the target rev (linear or not). Match from top first. The -n
2243 option doesn't exist on the command line, but represents the
2243 option doesn't exist on the command line, but represents the
2244 experimental.updatecheck=noconflict option.
2244 experimental.updatecheck=noconflict option.
2245
2245
2246 This logic is tested by test-update-branches.t.
2246 This logic is tested by test-update-branches.t.
2247
2247
2248 -c -C -n -m dirty rev linear | result
2248 -c -C -n -m dirty rev linear | result
2249 y y * * * * * | (1)
2249 y y * * * * * | (1)
2250 y * y * * * * | (1)
2250 y * y * * * * | (1)
2251 y * * y * * * | (1)
2251 y * * y * * * | (1)
2252 * y y * * * * | (1)
2252 * y y * * * * | (1)
2253 * y * y * * * | (1)
2253 * y * y * * * | (1)
2254 * * y y * * * | (1)
2254 * * y y * * * | (1)
2255 * * * * * n n | x
2255 * * * * * n n | x
2256 * * * * n * * | ok
2256 * * * * n * * | ok
2257 n n n n y * y | merge
2257 n n n n y * y | merge
2258 n n n n y y n | (2)
2258 n n n n y y n | (2)
2259 n n n y y * * | merge
2259 n n n y y * * | merge
2260 n n y n y * * | merge if no conflict
2260 n n y n y * * | merge if no conflict
2261 n y n n y * * | discard
2261 n y n n y * * | discard
2262 y n n n y * * | (3)
2262 y n n n y * * | (3)
2263
2263
2264 x = can't happen
2264 x = can't happen
2265 * = don't-care
2265 * = don't-care
2266 1 = incompatible options (checked in commands.py)
2266 1 = incompatible options (checked in commands.py)
2267 2 = abort: uncommitted changes (commit or update --clean to discard changes)
2267 2 = abort: uncommitted changes (commit or update --clean to discard changes)
2268 3 = abort: uncommitted changes (checked in commands.py)
2268 3 = abort: uncommitted changes (checked in commands.py)
2269
2269
2270 The merge is performed inside ``wc``, a workingctx-like objects. It defaults
2270 The merge is performed inside ``wc``, a workingctx-like objects. It defaults
2271 to repo[None] if None is passed.
2271 to repo[None] if None is passed.
2272
2272
2273 Return the same tuple as applyupdates().
2273 Return the same tuple as applyupdates().
2274 """
2274 """
2275 # Avoid cycle.
2275 # Avoid cycle.
2276 from . import sparse
2276 from . import sparse
2277
2277
2278 # This function used to find the default destination if node was None, but
2278 # This function used to find the default destination if node was None, but
2279 # that's now in destutil.py.
2279 # that's now in destutil.py.
2280 assert node is not None
2280 assert node is not None
2281 if not branchmerge and not force:
2281 if not branchmerge and not force:
2282 # TODO: remove the default once all callers that pass branchmerge=False
2282 # TODO: remove the default once all callers that pass branchmerge=False
2283 # and force=False pass a value for updatecheck. We may want to allow
2283 # and force=False pass a value for updatecheck. We may want to allow
2284 # updatecheck='abort' to better suppport some of these callers.
2284 # updatecheck='abort' to better suppport some of these callers.
2285 if updatecheck is None:
2285 if updatecheck is None:
2286 updatecheck = UPDATECHECK_LINEAR
2286 updatecheck = UPDATECHECK_LINEAR
2287 if updatecheck not in (
2287 if updatecheck not in (
2288 UPDATECHECK_NONE,
2288 UPDATECHECK_NONE,
2289 UPDATECHECK_LINEAR,
2289 UPDATECHECK_LINEAR,
2290 UPDATECHECK_NO_CONFLICT,
2290 UPDATECHECK_NO_CONFLICT,
2291 ):
2291 ):
2292 raise ValueError(
2292 raise ValueError(
2293 r'Invalid updatecheck %r (can accept %r)'
2293 r'Invalid updatecheck %r (can accept %r)'
2294 % (
2294 % (
2295 updatecheck,
2295 updatecheck,
2296 (
2296 (
2297 UPDATECHECK_NONE,
2297 UPDATECHECK_NONE,
2298 UPDATECHECK_LINEAR,
2298 UPDATECHECK_LINEAR,
2299 UPDATECHECK_NO_CONFLICT,
2299 UPDATECHECK_NO_CONFLICT,
2300 ),
2300 ),
2301 )
2301 )
2302 )
2302 )
2303 with repo.wlock():
2303 with repo.wlock():
2304 if wc is None:
2304 if wc is None:
2305 wc = repo[None]
2305 wc = repo[None]
2306 pl = wc.parents()
2306 pl = wc.parents()
2307 p1 = pl[0]
2307 p1 = pl[0]
2308 p2 = repo[node]
2308 p2 = repo[node]
2309 if ancestor is not None:
2309 if ancestor is not None:
2310 pas = [repo[ancestor]]
2310 pas = [repo[ancestor]]
2311 else:
2311 else:
2312 if repo.ui.configlist(b'merge', b'preferancestor') == [b'*']:
2312 if repo.ui.configlist(b'merge', b'preferancestor') == [b'*']:
2313 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
2313 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
2314 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
2314 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
2315 else:
2315 else:
2316 pas = [p1.ancestor(p2, warn=branchmerge)]
2316 pas = [p1.ancestor(p2, warn=branchmerge)]
2317
2317
2318 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), bytes(p1), bytes(p2)
2318 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), bytes(p1), bytes(p2)
2319
2319
2320 overwrite = force and not branchmerge
2320 overwrite = force and not branchmerge
2321 ### check phase
2321 ### check phase
2322 if not overwrite:
2322 if not overwrite:
2323 if len(pl) > 1:
2323 if len(pl) > 1:
2324 raise error.Abort(_(b"outstanding uncommitted merge"))
2324 raise error.Abort(_(b"outstanding uncommitted merge"))
2325 ms = mergestate.read(repo)
2325 ms = mergestate.read(repo)
2326 if list(ms.unresolved()):
2326 if list(ms.unresolved()):
2327 raise error.Abort(
2327 raise error.Abort(
2328 _(b"outstanding merge conflicts"),
2328 _(b"outstanding merge conflicts"),
2329 hint=_(b"use 'hg resolve' to resolve"),
2329 hint=_(b"use 'hg resolve' to resolve"),
2330 )
2330 )
2331 if branchmerge:
2331 if branchmerge:
2332 if pas == [p2]:
2332 if pas == [p2]:
2333 raise error.Abort(
2333 raise error.Abort(
2334 _(
2334 _(
2335 b"merging with a working directory ancestor"
2335 b"merging with a working directory ancestor"
2336 b" has no effect"
2336 b" has no effect"
2337 )
2337 )
2338 )
2338 )
2339 elif pas == [p1]:
2339 elif pas == [p1]:
2340 if not mergeancestor and wc.branch() == p2.branch():
2340 if not mergeancestor and wc.branch() == p2.branch():
2341 raise error.Abort(
2341 raise error.Abort(
2342 _(b"nothing to merge"),
2342 _(b"nothing to merge"),
2343 hint=_(b"use 'hg update' or check 'hg heads'"),
2343 hint=_(b"use 'hg update' or check 'hg heads'"),
2344 )
2344 )
2345 if not force and (wc.files() or wc.deleted()):
2345 if not force and (wc.files() or wc.deleted()):
2346 raise error.Abort(
2346 raise error.Abort(
2347 _(b"uncommitted changes"),
2347 _(b"uncommitted changes"),
2348 hint=_(b"use 'hg status' to list changes"),
2348 hint=_(b"use 'hg status' to list changes"),
2349 )
2349 )
2350 if not wc.isinmemory():
2350 if not wc.isinmemory():
2351 for s in sorted(wc.substate):
2351 for s in sorted(wc.substate):
2352 wc.sub(s).bailifchanged()
2352 wc.sub(s).bailifchanged()
2353
2353
2354 elif not overwrite:
2354 elif not overwrite:
2355 if p1 == p2: # no-op update
2355 if p1 == p2: # no-op update
2356 # call the hooks and exit early
2356 # call the hooks and exit early
2357 repo.hook(b'preupdate', throw=True, parent1=xp2, parent2=b'')
2357 repo.hook(b'preupdate', throw=True, parent1=xp2, parent2=b'')
2358 repo.hook(b'update', parent1=xp2, parent2=b'', error=0)
2358 repo.hook(b'update', parent1=xp2, parent2=b'', error=0)
2359 return updateresult(0, 0, 0, 0)
2359 return updateresult(0, 0, 0, 0)
2360
2360
2361 if updatecheck == UPDATECHECK_LINEAR and pas not in (
2361 if updatecheck == UPDATECHECK_LINEAR and pas not in (
2362 [p1],
2362 [p1],
2363 [p2],
2363 [p2],
2364 ): # nonlinear
2364 ): # nonlinear
2365 dirty = wc.dirty(missing=True)
2365 dirty = wc.dirty(missing=True)
2366 if dirty:
2366 if dirty:
2367 # Branching is a bit strange to ensure we do the minimal
2367 # Branching is a bit strange to ensure we do the minimal
2368 # amount of call to obsutil.foreground.
2368 # amount of call to obsutil.foreground.
2369 foreground = obsutil.foreground(repo, [p1.node()])
2369 foreground = obsutil.foreground(repo, [p1.node()])
2370 # note: the <node> variable contains a random identifier
2370 # note: the <node> variable contains a random identifier
2371 if repo[node].node() in foreground:
2371 if repo[node].node() in foreground:
2372 pass # allow updating to successors
2372 pass # allow updating to successors
2373 else:
2373 else:
2374 msg = _(b"uncommitted changes")
2374 msg = _(b"uncommitted changes")
2375 hint = _(b"commit or update --clean to discard changes")
2375 hint = _(b"commit or update --clean to discard changes")
2376 raise error.UpdateAbort(msg, hint=hint)
2376 raise error.UpdateAbort(msg, hint=hint)
2377 else:
2377 else:
2378 # Allow jumping branches if clean and specific rev given
2378 # Allow jumping branches if clean and specific rev given
2379 pass
2379 pass
2380
2380
2381 if overwrite:
2381 if overwrite:
2382 pas = [wc]
2382 pas = [wc]
2383 elif not branchmerge:
2383 elif not branchmerge:
2384 pas = [p1]
2384 pas = [p1]
2385
2385
2386 # deprecated config: merge.followcopies
2386 # deprecated config: merge.followcopies
2387 followcopies = repo.ui.configbool(b'merge', b'followcopies')
2387 followcopies = repo.ui.configbool(b'merge', b'followcopies')
2388 if overwrite:
2388 if overwrite:
2389 followcopies = False
2389 followcopies = False
2390 elif not pas[0]:
2390 elif not pas[0]:
2391 followcopies = False
2391 followcopies = False
2392 if not branchmerge and not wc.dirty(missing=True):
2392 if not branchmerge and not wc.dirty(missing=True):
2393 followcopies = False
2393 followcopies = False
2394
2394
2395 ### calculate phase
2395 ### calculate phase
2396 actionbyfile, diverge, renamedelete = calculateupdates(
2396 actionbyfile, diverge, renamedelete = calculateupdates(
2397 repo,
2397 repo,
2398 wc,
2398 wc,
2399 p2,
2399 p2,
2400 pas,
2400 pas,
2401 branchmerge,
2401 branchmerge,
2402 force,
2402 force,
2403 mergeancestor,
2403 mergeancestor,
2404 followcopies,
2404 followcopies,
2405 matcher=matcher,
2405 matcher=matcher,
2406 mergeforce=mergeforce,
2406 mergeforce=mergeforce,
2407 )
2407 )
2408
2408
2409 if updatecheck == UPDATECHECK_NO_CONFLICT:
2409 if updatecheck == UPDATECHECK_NO_CONFLICT:
2410 for f, (m, args, msg) in pycompat.iteritems(actionbyfile):
2410 for f, (m, args, msg) in pycompat.iteritems(actionbyfile):
2411 if m not in (
2411 if m not in (
2412 ACTION_GET,
2412 ACTION_GET,
2413 ACTION_KEEP,
2413 ACTION_KEEP,
2414 ACTION_EXEC,
2414 ACTION_EXEC,
2415 ACTION_REMOVE,
2415 ACTION_REMOVE,
2416 ACTION_PATH_CONFLICT_RESOLVE,
2416 ACTION_PATH_CONFLICT_RESOLVE,
2417 ):
2417 ):
2418 msg = _(b"conflicting changes")
2418 msg = _(b"conflicting changes")
2419 hint = _(b"commit or update --clean to discard changes")
2419 hint = _(b"commit or update --clean to discard changes")
2420 raise error.Abort(msg, hint=hint)
2420 raise error.Abort(msg, hint=hint)
2421
2421
2422 # Prompt and create actions. Most of this is in the resolve phase
2422 # Prompt and create actions. Most of this is in the resolve phase
2423 # already, but we can't handle .hgsubstate in filemerge or
2423 # already, but we can't handle .hgsubstate in filemerge or
2424 # subrepoutil.submerge yet so we have to keep prompting for it.
2424 # subrepoutil.submerge yet so we have to keep prompting for it.
2425 if b'.hgsubstate' in actionbyfile:
2425 if b'.hgsubstate' in actionbyfile:
2426 f = b'.hgsubstate'
2426 f = b'.hgsubstate'
2427 m, args, msg = actionbyfile[f]
2427 m, args, msg = actionbyfile[f]
2428 prompts = filemerge.partextras(labels)
2428 prompts = filemerge.partextras(labels)
2429 prompts[b'f'] = f
2429 prompts[b'f'] = f
2430 if m == ACTION_CHANGED_DELETED:
2430 if m == ACTION_CHANGED_DELETED:
2431 if repo.ui.promptchoice(
2431 if repo.ui.promptchoice(
2432 _(
2432 _(
2433 b"local%(l)s changed %(f)s which other%(o)s deleted\n"
2433 b"local%(l)s changed %(f)s which other%(o)s deleted\n"
2434 b"use (c)hanged version or (d)elete?"
2434 b"use (c)hanged version or (d)elete?"
2435 b"$$ &Changed $$ &Delete"
2435 b"$$ &Changed $$ &Delete"
2436 )
2436 )
2437 % prompts,
2437 % prompts,
2438 0,
2438 0,
2439 ):
2439 ):
2440 actionbyfile[f] = (ACTION_REMOVE, None, b'prompt delete')
2440 actionbyfile[f] = (ACTION_REMOVE, None, b'prompt delete')
2441 elif f in p1:
2441 elif f in p1:
2442 actionbyfile[f] = (
2442 actionbyfile[f] = (
2443 ACTION_ADD_MODIFIED,
2443 ACTION_ADD_MODIFIED,
2444 None,
2444 None,
2445 b'prompt keep',
2445 b'prompt keep',
2446 )
2446 )
2447 else:
2447 else:
2448 actionbyfile[f] = (ACTION_ADD, None, b'prompt keep')
2448 actionbyfile[f] = (ACTION_ADD, None, b'prompt keep')
2449 elif m == ACTION_DELETED_CHANGED:
2449 elif m == ACTION_DELETED_CHANGED:
2450 f1, f2, fa, move, anc = args
2450 f1, f2, fa, move, anc = args
2451 flags = p2[f2].flags()
2451 flags = p2[f2].flags()
2452 if (
2452 if (
2453 repo.ui.promptchoice(
2453 repo.ui.promptchoice(
2454 _(
2454 _(
2455 b"other%(o)s changed %(f)s which local%(l)s deleted\n"
2455 b"other%(o)s changed %(f)s which local%(l)s deleted\n"
2456 b"use (c)hanged version or leave (d)eleted?"
2456 b"use (c)hanged version or leave (d)eleted?"
2457 b"$$ &Changed $$ &Deleted"
2457 b"$$ &Changed $$ &Deleted"
2458 )
2458 )
2459 % prompts,
2459 % prompts,
2460 0,
2460 0,
2461 )
2461 )
2462 == 0
2462 == 0
2463 ):
2463 ):
2464 actionbyfile[f] = (
2464 actionbyfile[f] = (
2465 ACTION_GET,
2465 ACTION_GET,
2466 (flags, False),
2466 (flags, False),
2467 b'prompt recreating',
2467 b'prompt recreating',
2468 )
2468 )
2469 else:
2469 else:
2470 del actionbyfile[f]
2470 del actionbyfile[f]
2471
2471
2472 # Convert to dictionary-of-lists format
2472 # Convert to dictionary-of-lists format
2473 actions = emptyactions()
2473 actions = emptyactions()
2474 for f, (m, args, msg) in pycompat.iteritems(actionbyfile):
2474 for f, (m, args, msg) in pycompat.iteritems(actionbyfile):
2475 if m not in actions:
2475 if m not in actions:
2476 actions[m] = []
2476 actions[m] = []
2477 actions[m].append((f, args, msg))
2477 actions[m].append((f, args, msg))
2478
2478
2479 if not util.fscasesensitive(repo.path):
2479 if not util.fscasesensitive(repo.path):
2480 # check collision between files only in p2 for clean update
2480 # check collision between files only in p2 for clean update
2481 if not branchmerge and (
2481 if not branchmerge and (
2482 force or not wc.dirty(missing=True, branch=False)
2482 force or not wc.dirty(missing=True, branch=False)
2483 ):
2483 ):
2484 _checkcollision(repo, p2.manifest(), None)
2484 _checkcollision(repo, p2.manifest(), None)
2485 else:
2485 else:
2486 _checkcollision(repo, wc.manifest(), actions)
2486 _checkcollision(repo, wc.manifest(), actions)
2487
2487
2488 # divergent renames
2488 # divergent renames
2489 for f, fl in sorted(pycompat.iteritems(diverge)):
2489 for f, fl in sorted(pycompat.iteritems(diverge)):
2490 repo.ui.warn(
2490 repo.ui.warn(
2491 _(
2491 _(
2492 b"note: possible conflict - %s was renamed "
2492 b"note: possible conflict - %s was renamed "
2493 b"multiple times to:\n"
2493 b"multiple times to:\n"
2494 )
2494 )
2495 % f
2495 % f
2496 )
2496 )
2497 for nf in sorted(fl):
2497 for nf in sorted(fl):
2498 repo.ui.warn(b" %s\n" % nf)
2498 repo.ui.warn(b" %s\n" % nf)
2499
2499
2500 # rename and delete
2500 # rename and delete
2501 for f, fl in sorted(pycompat.iteritems(renamedelete)):
2501 for f, fl in sorted(pycompat.iteritems(renamedelete)):
2502 repo.ui.warn(
2502 repo.ui.warn(
2503 _(
2503 _(
2504 b"note: possible conflict - %s was deleted "
2504 b"note: possible conflict - %s was deleted "
2505 b"and renamed to:\n"
2505 b"and renamed to:\n"
2506 )
2506 )
2507 % f
2507 % f
2508 )
2508 )
2509 for nf in sorted(fl):
2509 for nf in sorted(fl):
2510 repo.ui.warn(b" %s\n" % nf)
2510 repo.ui.warn(b" %s\n" % nf)
2511
2511
2512 ### apply phase
2512 ### apply phase
2513 if not branchmerge: # just jump to the new rev
2513 if not branchmerge: # just jump to the new rev
2514 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, b''
2514 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, b''
2515 # If we're doing a partial update, we need to skip updating
2515 # If we're doing a partial update, we need to skip updating
2516 # the dirstate.
2516 # the dirstate.
2517 always = matcher is None or matcher.always()
2517 always = matcher is None or matcher.always()
2518 updatedirstate = always and not wc.isinmemory()
2518 updatedirstate = always and not wc.isinmemory()
2519 if updatedirstate:
2519 if updatedirstate:
2520 repo.hook(b'preupdate', throw=True, parent1=xp1, parent2=xp2)
2520 repo.hook(b'preupdate', throw=True, parent1=xp1, parent2=xp2)
2521 # note that we're in the middle of an update
2521 # note that we're in the middle of an update
2522 repo.vfs.write(b'updatestate', p2.hex())
2522 repo.vfs.write(b'updatestate', p2.hex())
2523
2523
2524 # Advertise fsmonitor when its presence could be useful.
2524 # Advertise fsmonitor when its presence could be useful.
2525 #
2525 #
2526 # We only advertise when performing an update from an empty working
2526 # We only advertise when performing an update from an empty working
2527 # directory. This typically only occurs during initial clone.
2527 # directory. This typically only occurs during initial clone.
2528 #
2528 #
2529 # We give users a mechanism to disable the warning in case it is
2529 # We give users a mechanism to disable the warning in case it is
2530 # annoying.
2530 # annoying.
2531 #
2531 #
2532 # We only allow on Linux and MacOS because that's where fsmonitor is
2532 # We only allow on Linux and MacOS because that's where fsmonitor is
2533 # considered stable.
2533 # considered stable.
2534 fsmonitorwarning = repo.ui.configbool(b'fsmonitor', b'warn_when_unused')
2534 fsmonitorwarning = repo.ui.configbool(b'fsmonitor', b'warn_when_unused')
2535 fsmonitorthreshold = repo.ui.configint(
2535 fsmonitorthreshold = repo.ui.configint(
2536 b'fsmonitor', b'warn_update_file_count'
2536 b'fsmonitor', b'warn_update_file_count'
2537 )
2537 )
2538 try:
2538 try:
2539 # avoid cycle: extensions -> cmdutil -> merge
2539 # avoid cycle: extensions -> cmdutil -> merge
2540 from . import extensions
2540 from . import extensions
2541
2541
2542 extensions.find(b'fsmonitor')
2542 extensions.find(b'fsmonitor')
2543 fsmonitorenabled = repo.ui.config(b'fsmonitor', b'mode') != b'off'
2543 fsmonitorenabled = repo.ui.config(b'fsmonitor', b'mode') != b'off'
2544 # We intentionally don't look at whether fsmonitor has disabled
2544 # We intentionally don't look at whether fsmonitor has disabled
2545 # itself because a) fsmonitor may have already printed a warning
2545 # itself because a) fsmonitor may have already printed a warning
2546 # b) we only care about the config state here.
2546 # b) we only care about the config state here.
2547 except KeyError:
2547 except KeyError:
2548 fsmonitorenabled = False
2548 fsmonitorenabled = False
2549
2549
2550 if (
2550 if (
2551 fsmonitorwarning
2551 fsmonitorwarning
2552 and not fsmonitorenabled
2552 and not fsmonitorenabled
2553 and p1.node() == nullid
2553 and p1.node() == nullid
2554 and len(actions[ACTION_GET]) >= fsmonitorthreshold
2554 and len(actions[ACTION_GET]) >= fsmonitorthreshold
2555 and pycompat.sysplatform.startswith((b'linux', b'darwin'))
2555 and pycompat.sysplatform.startswith((b'linux', b'darwin'))
2556 ):
2556 ):
2557 repo.ui.warn(
2557 repo.ui.warn(
2558 _(
2558 _(
2559 b'(warning: large working directory being used without '
2559 b'(warning: large working directory being used without '
2560 b'fsmonitor enabled; enable fsmonitor to improve performance; '
2560 b'fsmonitor enabled; enable fsmonitor to improve performance; '
2561 b'see "hg help -e fsmonitor")\n'
2561 b'see "hg help -e fsmonitor")\n'
2562 )
2562 )
2563 )
2563 )
2564
2564
2565 wantfiledata = updatedirstate and not branchmerge
2565 wantfiledata = updatedirstate and not branchmerge
2566 stats, getfiledata = applyupdates(
2566 stats, getfiledata = applyupdates(
2567 repo, actions, wc, p2, overwrite, wantfiledata, labels=labels
2567 repo, actions, wc, p2, overwrite, wantfiledata, labels=labels
2568 )
2568 )
2569
2569
2570 if updatedirstate:
2570 if updatedirstate:
2571 with repo.dirstate.parentchange():
2571 with repo.dirstate.parentchange():
2572 repo.setparents(fp1, fp2)
2572 repo.setparents(fp1, fp2)
2573 recordupdates(repo, actions, branchmerge, getfiledata)
2573 recordupdates(repo, actions, branchmerge, getfiledata)
2574 # update completed, clear state
2574 # update completed, clear state
2575 util.unlink(repo.vfs.join(b'updatestate'))
2575 util.unlink(repo.vfs.join(b'updatestate'))
2576
2576
2577 if not branchmerge:
2577 if not branchmerge:
2578 repo.dirstate.setbranch(p2.branch())
2578 repo.dirstate.setbranch(p2.branch())
2579
2579
2580 # If we're updating to a location, clean up any stale temporary includes
2580 # If we're updating to a location, clean up any stale temporary includes
2581 # (ex: this happens during hg rebase --abort).
2581 # (ex: this happens during hg rebase --abort).
2582 if not branchmerge:
2582 if not branchmerge:
2583 sparse.prunetemporaryincludes(repo)
2583 sparse.prunetemporaryincludes(repo)
2584
2584
2585 if updatedirstate:
2585 if updatedirstate:
2586 repo.hook(
2586 repo.hook(
2587 b'update', parent1=xp1, parent2=xp2, error=stats.unresolvedcount
2587 b'update', parent1=xp1, parent2=xp2, error=stats.unresolvedcount
2588 )
2588 )
2589 return stats
2589 return stats
2590
2590
2591
2591
2592 def graft(
2592 def graft(
2593 repo, ctx, base, labels=None, keepparent=False, keepconflictparent=False
2593 repo,
2594 ctx,
2595 base,
2596 labels=None,
2597 keepparent=False,
2598 keepconflictparent=False,
2599 wctx=None,
2594 ):
2600 ):
2595 """Do a graft-like merge.
2601 """Do a graft-like merge.
2596
2602
2597 This is a merge where the merge ancestor is chosen such that one
2603 This is a merge where the merge ancestor is chosen such that one
2598 or more changesets are grafted onto the current changeset. In
2604 or more changesets are grafted onto the current changeset. In
2599 addition to the merge, this fixes up the dirstate to include only
2605 addition to the merge, this fixes up the dirstate to include only
2600 a single parent (if keepparent is False) and tries to duplicate any
2606 a single parent (if keepparent is False) and tries to duplicate any
2601 renames/copies appropriately.
2607 renames/copies appropriately.
2602
2608
2603 ctx - changeset to rebase
2609 ctx - changeset to rebase
2604 base - merge base, usually ctx.p1()
2610 base - merge base, usually ctx.p1()
2605 labels - merge labels eg ['local', 'graft']
2611 labels - merge labels eg ['local', 'graft']
2606 keepparent - keep second parent if any
2612 keepparent - keep second parent if any
2607 keepconflictparent - if unresolved, keep parent used for the merge
2613 keepconflictparent - if unresolved, keep parent used for the merge
2608
2614
2609 """
2615 """
2610 # If we're grafting a descendant onto an ancestor, be sure to pass
2616 # If we're grafting a descendant onto an ancestor, be sure to pass
2611 # mergeancestor=True to update. This does two things: 1) allows the merge if
2617 # mergeancestor=True to update. This does two things: 1) allows the merge if
2612 # the destination is the same as the parent of the ctx (so we can use graft
2618 # the destination is the same as the parent of the ctx (so we can use graft
2613 # to copy commits), and 2) informs update that the incoming changes are
2619 # to copy commits), and 2) informs update that the incoming changes are
2614 # newer than the destination so it doesn't prompt about "remote changed foo
2620 # newer than the destination so it doesn't prompt about "remote changed foo
2615 # which local deleted".
2621 # which local deleted".
2616 wctx = repo[None]
2622 wctx = wctx or repo[None]
2617 pctx = wctx.p1()
2623 pctx = wctx.p1()
2618 mergeancestor = repo.changelog.isancestor(pctx.node(), ctx.node())
2624 mergeancestor = repo.changelog.isancestor(pctx.node(), ctx.node())
2619
2625
2620 stats = update(
2626 stats = update(
2621 repo,
2627 repo,
2622 ctx.node(),
2628 ctx.node(),
2623 True,
2629 True,
2624 True,
2630 True,
2625 base.node(),
2631 base.node(),
2626 mergeancestor=mergeancestor,
2632 mergeancestor=mergeancestor,
2627 labels=labels,
2633 labels=labels,
2634 wc=wctx,
2628 )
2635 )
2629
2636
2630 if keepconflictparent and stats.unresolvedcount:
2637 if keepconflictparent and stats.unresolvedcount:
2631 pother = ctx.node()
2638 pother = ctx.node()
2632 else:
2639 else:
2633 pother = nullid
2640 pother = nullid
2634 parents = ctx.parents()
2641 parents = ctx.parents()
2635 if keepparent and len(parents) == 2 and base in parents:
2642 if keepparent and len(parents) == 2 and base in parents:
2636 parents.remove(base)
2643 parents.remove(base)
2637 pother = parents[0].node()
2644 pother = parents[0].node()
2638 # Never set both parents equal to each other
2645 # Never set both parents equal to each other
2639 if pother == pctx.node():
2646 if pother == pctx.node():
2640 pother = nullid
2647 pother = nullid
2641
2648
2642 with repo.dirstate.parentchange():
2649 if wctx.isinmemory():
2643 repo.setparents(pctx.node(), pother)
2650 wctx.setparents(pctx.node(), pother)
2644 repo.dirstate.write(repo.currenttransaction())
2645 # fix up dirstate for copies and renames
2651 # fix up dirstate for copies and renames
2646 copies.graftcopies(wctx, ctx, base)
2652 copies.graftcopies(wctx, ctx, base)
2653 else:
2654 with repo.dirstate.parentchange():
2655 repo.setparents(pctx.node(), pother)
2656 repo.dirstate.write(repo.currenttransaction())
2657 # fix up dirstate for copies and renames
2658 copies.graftcopies(wctx, ctx, base)
2647 return stats
2659 return stats
2648
2660
2649
2661
2650 def purge(
2662 def purge(
2651 repo,
2663 repo,
2652 matcher,
2664 matcher,
2653 ignored=False,
2665 ignored=False,
2654 removeemptydirs=True,
2666 removeemptydirs=True,
2655 removefiles=True,
2667 removefiles=True,
2656 abortonerror=False,
2668 abortonerror=False,
2657 noop=False,
2669 noop=False,
2658 ):
2670 ):
2659 """Purge the working directory of untracked files.
2671 """Purge the working directory of untracked files.
2660
2672
2661 ``matcher`` is a matcher configured to scan the working directory -
2673 ``matcher`` is a matcher configured to scan the working directory -
2662 potentially a subset.
2674 potentially a subset.
2663
2675
2664 ``ignored`` controls whether ignored files should also be purged.
2676 ``ignored`` controls whether ignored files should also be purged.
2665
2677
2666 ``removeemptydirs`` controls whether empty directories should be removed.
2678 ``removeemptydirs`` controls whether empty directories should be removed.
2667
2679
2668 ``removefiles`` controls whether files are removed.
2680 ``removefiles`` controls whether files are removed.
2669
2681
2670 ``abortonerror`` causes an exception to be raised if an error occurs
2682 ``abortonerror`` causes an exception to be raised if an error occurs
2671 deleting a file or directory.
2683 deleting a file or directory.
2672
2684
2673 ``noop`` controls whether to actually remove files. If not defined, actions
2685 ``noop`` controls whether to actually remove files. If not defined, actions
2674 will be taken.
2686 will be taken.
2675
2687
2676 Returns an iterable of relative paths in the working directory that were
2688 Returns an iterable of relative paths in the working directory that were
2677 or would be removed.
2689 or would be removed.
2678 """
2690 """
2679
2691
2680 def remove(removefn, path):
2692 def remove(removefn, path):
2681 try:
2693 try:
2682 removefn(path)
2694 removefn(path)
2683 except OSError:
2695 except OSError:
2684 m = _(b'%s cannot be removed') % path
2696 m = _(b'%s cannot be removed') % path
2685 if abortonerror:
2697 if abortonerror:
2686 raise error.Abort(m)
2698 raise error.Abort(m)
2687 else:
2699 else:
2688 repo.ui.warn(_(b'warning: %s\n') % m)
2700 repo.ui.warn(_(b'warning: %s\n') % m)
2689
2701
2690 # There's no API to copy a matcher. So mutate the passed matcher and
2702 # There's no API to copy a matcher. So mutate the passed matcher and
2691 # restore it when we're done.
2703 # restore it when we're done.
2692 oldtraversedir = matcher.traversedir
2704 oldtraversedir = matcher.traversedir
2693
2705
2694 res = []
2706 res = []
2695
2707
2696 try:
2708 try:
2697 if removeemptydirs:
2709 if removeemptydirs:
2698 directories = []
2710 directories = []
2699 matcher.traversedir = directories.append
2711 matcher.traversedir = directories.append
2700
2712
2701 status = repo.status(match=matcher, ignored=ignored, unknown=True)
2713 status = repo.status(match=matcher, ignored=ignored, unknown=True)
2702
2714
2703 if removefiles:
2715 if removefiles:
2704 for f in sorted(status.unknown + status.ignored):
2716 for f in sorted(status.unknown + status.ignored):
2705 if not noop:
2717 if not noop:
2706 repo.ui.note(_(b'removing file %s\n') % f)
2718 repo.ui.note(_(b'removing file %s\n') % f)
2707 remove(repo.wvfs.unlink, f)
2719 remove(repo.wvfs.unlink, f)
2708 res.append(f)
2720 res.append(f)
2709
2721
2710 if removeemptydirs:
2722 if removeemptydirs:
2711 for f in sorted(directories, reverse=True):
2723 for f in sorted(directories, reverse=True):
2712 if matcher(f) and not repo.wvfs.listdir(f):
2724 if matcher(f) and not repo.wvfs.listdir(f):
2713 if not noop:
2725 if not noop:
2714 repo.ui.note(_(b'removing directory %s\n') % f)
2726 repo.ui.note(_(b'removing directory %s\n') % f)
2715 remove(repo.wvfs.rmdir, f)
2727 remove(repo.wvfs.rmdir, f)
2716 res.append(f)
2728 res.append(f)
2717
2729
2718 return res
2730 return res
2719
2731
2720 finally:
2732 finally:
2721 matcher.traversedir = oldtraversedir
2733 matcher.traversedir = oldtraversedir
General Comments 0
You need to be logged in to leave comments. Login now