##// END OF EJS Templates
graft: extract `repo['.']` to local variable...
Martin von Zweigbergk -
r44236:fb07126d default
parent child Browse files
Show More
@@ -1,2708 +1,2709 b''
1 # merge.py - directory-level update/merge handling for Mercurial
1 # merge.py - directory-level update/merge handling for Mercurial
2 #
2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import hashlib
11 import hashlib
12 import shutil
12 import shutil
13 import stat
13 import stat
14 import struct
14 import struct
15
15
16 from .i18n import _
16 from .i18n import _
17 from .node import (
17 from .node import (
18 addednodeid,
18 addednodeid,
19 bin,
19 bin,
20 hex,
20 hex,
21 modifiednodeid,
21 modifiednodeid,
22 nullhex,
22 nullhex,
23 nullid,
23 nullid,
24 nullrev,
24 nullrev,
25 )
25 )
26 from .pycompat import delattr
26 from .pycompat import delattr
27 from .thirdparty import attr
27 from .thirdparty import attr
28 from . import (
28 from . import (
29 copies,
29 copies,
30 encoding,
30 encoding,
31 error,
31 error,
32 filemerge,
32 filemerge,
33 match as matchmod,
33 match as matchmod,
34 obsutil,
34 obsutil,
35 pathutil,
35 pathutil,
36 pycompat,
36 pycompat,
37 scmutil,
37 scmutil,
38 subrepoutil,
38 subrepoutil,
39 util,
39 util,
40 worker,
40 worker,
41 )
41 )
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(hashlib.sha1(path).digest())
515 return hex(hashlib.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 copy, movewithdir, diverge, renamedelete, dirmove = {}, {}, {}, {}, {}
1259 copy, movewithdir, diverge, renamedelete, dirmove = {}, {}, {}, {}, {}
1260
1260
1261 # manifests fetched in order are going to be faster, so prime the caches
1261 # manifests fetched in order are going to be faster, so prime the caches
1262 [
1262 [
1263 x.manifest()
1263 x.manifest()
1264 for x in sorted(wctx.parents() + [p2, pa], key=scmutil.intrev)
1264 for x in sorted(wctx.parents() + [p2, pa], key=scmutil.intrev)
1265 ]
1265 ]
1266
1266
1267 if followcopies:
1267 if followcopies:
1268 ret = copies.mergecopies(repo, wctx, p2, pa)
1268 ret = copies.mergecopies(repo, wctx, p2, pa)
1269 copy, movewithdir, diverge, renamedelete, dirmove = ret
1269 copy, movewithdir, diverge, renamedelete, dirmove = ret
1270
1270
1271 boolbm = pycompat.bytestr(bool(branchmerge))
1271 boolbm = pycompat.bytestr(bool(branchmerge))
1272 boolf = pycompat.bytestr(bool(force))
1272 boolf = pycompat.bytestr(bool(force))
1273 boolm = pycompat.bytestr(bool(matcher))
1273 boolm = pycompat.bytestr(bool(matcher))
1274 repo.ui.note(_(b"resolving manifests\n"))
1274 repo.ui.note(_(b"resolving manifests\n"))
1275 repo.ui.debug(
1275 repo.ui.debug(
1276 b" branchmerge: %s, force: %s, partial: %s\n" % (boolbm, boolf, boolm)
1276 b" branchmerge: %s, force: %s, partial: %s\n" % (boolbm, boolf, boolm)
1277 )
1277 )
1278 repo.ui.debug(b" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
1278 repo.ui.debug(b" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
1279
1279
1280 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
1280 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
1281 copied = set(copy.values())
1281 copied = set(copy.values())
1282 copied.update(movewithdir.values())
1282 copied.update(movewithdir.values())
1283
1283
1284 if b'.hgsubstate' in m1 and wctx.rev() is None:
1284 if b'.hgsubstate' in m1 and wctx.rev() is None:
1285 # Check whether sub state is modified, and overwrite the manifest
1285 # Check whether sub state is modified, and overwrite the manifest
1286 # to flag the change. If wctx is a committed revision, we shouldn't
1286 # to flag the change. If wctx is a committed revision, we shouldn't
1287 # care for the dirty state of the working directory.
1287 # care for the dirty state of the working directory.
1288 if any(wctx.sub(s).dirty() for s in wctx.substate):
1288 if any(wctx.sub(s).dirty() for s in wctx.substate):
1289 m1[b'.hgsubstate'] = modifiednodeid
1289 m1[b'.hgsubstate'] = modifiednodeid
1290
1290
1291 # Don't use m2-vs-ma optimization if:
1291 # Don't use m2-vs-ma optimization if:
1292 # - ma is the same as m1 or m2, which we're just going to diff again later
1292 # - ma is the same as m1 or m2, which we're just going to diff again later
1293 # - The caller specifically asks for a full diff, which is useful during bid
1293 # - The caller specifically asks for a full diff, which is useful during bid
1294 # merge.
1294 # merge.
1295 if pa not in ([wctx, p2] + wctx.parents()) and not forcefulldiff:
1295 if pa not in ([wctx, p2] + wctx.parents()) and not forcefulldiff:
1296 # Identify which files are relevant to the merge, so we can limit the
1296 # Identify which files are relevant to the merge, so we can limit the
1297 # total m1-vs-m2 diff to just those files. This has significant
1297 # total m1-vs-m2 diff to just those files. This has significant
1298 # performance benefits in large repositories.
1298 # performance benefits in large repositories.
1299 relevantfiles = set(ma.diff(m2).keys())
1299 relevantfiles = set(ma.diff(m2).keys())
1300
1300
1301 # For copied and moved files, we need to add the source file too.
1301 # For copied and moved files, we need to add the source file too.
1302 for copykey, copyvalue in pycompat.iteritems(copy):
1302 for copykey, copyvalue in pycompat.iteritems(copy):
1303 if copyvalue in relevantfiles:
1303 if copyvalue in relevantfiles:
1304 relevantfiles.add(copykey)
1304 relevantfiles.add(copykey)
1305 for movedirkey in movewithdir:
1305 for movedirkey in movewithdir:
1306 relevantfiles.add(movedirkey)
1306 relevantfiles.add(movedirkey)
1307 filesmatcher = scmutil.matchfiles(repo, relevantfiles)
1307 filesmatcher = scmutil.matchfiles(repo, relevantfiles)
1308 matcher = matchmod.intersectmatchers(matcher, filesmatcher)
1308 matcher = matchmod.intersectmatchers(matcher, filesmatcher)
1309
1309
1310 diff = m1.diff(m2, match=matcher)
1310 diff = m1.diff(m2, match=matcher)
1311
1311
1312 actions = {}
1312 actions = {}
1313 for f, ((n1, fl1), (n2, fl2)) in pycompat.iteritems(diff):
1313 for f, ((n1, fl1), (n2, fl2)) in pycompat.iteritems(diff):
1314 if n1 and n2: # file exists on both local and remote side
1314 if n1 and n2: # file exists on both local and remote side
1315 if f not in ma:
1315 if f not in ma:
1316 fa = copy.get(f, None)
1316 fa = copy.get(f, None)
1317 if fa is not None:
1317 if fa is not None:
1318 actions[f] = (
1318 actions[f] = (
1319 ACTION_MERGE,
1319 ACTION_MERGE,
1320 (f, f, fa, False, pa.node()),
1320 (f, f, fa, False, pa.node()),
1321 b'both renamed from %s' % fa,
1321 b'both renamed from %s' % fa,
1322 )
1322 )
1323 else:
1323 else:
1324 actions[f] = (
1324 actions[f] = (
1325 ACTION_MERGE,
1325 ACTION_MERGE,
1326 (f, f, None, False, pa.node()),
1326 (f, f, None, False, pa.node()),
1327 b'both created',
1327 b'both created',
1328 )
1328 )
1329 else:
1329 else:
1330 a = ma[f]
1330 a = ma[f]
1331 fla = ma.flags(f)
1331 fla = ma.flags(f)
1332 nol = b'l' not in fl1 + fl2 + fla
1332 nol = b'l' not in fl1 + fl2 + fla
1333 if n2 == a and fl2 == fla:
1333 if n2 == a and fl2 == fla:
1334 actions[f] = (ACTION_KEEP, (), b'remote unchanged')
1334 actions[f] = (ACTION_KEEP, (), b'remote unchanged')
1335 elif n1 == a and fl1 == fla: # local unchanged - use remote
1335 elif n1 == a and fl1 == fla: # local unchanged - use remote
1336 if n1 == n2: # optimization: keep local content
1336 if n1 == n2: # optimization: keep local content
1337 actions[f] = (
1337 actions[f] = (
1338 ACTION_EXEC,
1338 ACTION_EXEC,
1339 (fl2,),
1339 (fl2,),
1340 b'update permissions',
1340 b'update permissions',
1341 )
1341 )
1342 else:
1342 else:
1343 actions[f] = (
1343 actions[f] = (
1344 ACTION_GET,
1344 ACTION_GET,
1345 (fl2, False),
1345 (fl2, False),
1346 b'remote is newer',
1346 b'remote is newer',
1347 )
1347 )
1348 elif nol and n2 == a: # remote only changed 'x'
1348 elif nol and n2 == a: # remote only changed 'x'
1349 actions[f] = (ACTION_EXEC, (fl2,), b'update permissions')
1349 actions[f] = (ACTION_EXEC, (fl2,), b'update permissions')
1350 elif nol and n1 == a: # local only changed 'x'
1350 elif nol and n1 == a: # local only changed 'x'
1351 actions[f] = (ACTION_GET, (fl1, False), b'remote is newer')
1351 actions[f] = (ACTION_GET, (fl1, False), b'remote is newer')
1352 else: # both changed something
1352 else: # both changed something
1353 actions[f] = (
1353 actions[f] = (
1354 ACTION_MERGE,
1354 ACTION_MERGE,
1355 (f, f, f, False, pa.node()),
1355 (f, f, f, False, pa.node()),
1356 b'versions differ',
1356 b'versions differ',
1357 )
1357 )
1358 elif n1: # file exists only on local side
1358 elif n1: # file exists only on local side
1359 if f in copied:
1359 if f in copied:
1360 pass # we'll deal with it on m2 side
1360 pass # we'll deal with it on m2 side
1361 elif f in movewithdir: # directory rename, move local
1361 elif f in movewithdir: # directory rename, move local
1362 f2 = movewithdir[f]
1362 f2 = movewithdir[f]
1363 if f2 in m2:
1363 if f2 in m2:
1364 actions[f2] = (
1364 actions[f2] = (
1365 ACTION_MERGE,
1365 ACTION_MERGE,
1366 (f, f2, None, True, pa.node()),
1366 (f, f2, None, True, pa.node()),
1367 b'remote directory rename, both created',
1367 b'remote directory rename, both created',
1368 )
1368 )
1369 else:
1369 else:
1370 actions[f2] = (
1370 actions[f2] = (
1371 ACTION_DIR_RENAME_MOVE_LOCAL,
1371 ACTION_DIR_RENAME_MOVE_LOCAL,
1372 (f, fl1),
1372 (f, fl1),
1373 b'remote directory rename - move from %s' % f,
1373 b'remote directory rename - move from %s' % f,
1374 )
1374 )
1375 elif f in copy:
1375 elif f in copy:
1376 f2 = copy[f]
1376 f2 = copy[f]
1377 actions[f] = (
1377 actions[f] = (
1378 ACTION_MERGE,
1378 ACTION_MERGE,
1379 (f, f2, f2, False, pa.node()),
1379 (f, f2, f2, False, pa.node()),
1380 b'local copied/moved from %s' % f2,
1380 b'local copied/moved from %s' % f2,
1381 )
1381 )
1382 elif f in ma: # clean, a different, no remote
1382 elif f in ma: # clean, a different, no remote
1383 if n1 != ma[f]:
1383 if n1 != ma[f]:
1384 if acceptremote:
1384 if acceptremote:
1385 actions[f] = (ACTION_REMOVE, None, b'remote delete')
1385 actions[f] = (ACTION_REMOVE, None, b'remote delete')
1386 else:
1386 else:
1387 actions[f] = (
1387 actions[f] = (
1388 ACTION_CHANGED_DELETED,
1388 ACTION_CHANGED_DELETED,
1389 (f, None, f, False, pa.node()),
1389 (f, None, f, False, pa.node()),
1390 b'prompt changed/deleted',
1390 b'prompt changed/deleted',
1391 )
1391 )
1392 elif n1 == addednodeid:
1392 elif n1 == addednodeid:
1393 # This extra 'a' is added by working copy manifest to mark
1393 # This extra 'a' is added by working copy manifest to mark
1394 # the file as locally added. We should forget it instead of
1394 # the file as locally added. We should forget it instead of
1395 # deleting it.
1395 # deleting it.
1396 actions[f] = (ACTION_FORGET, None, b'remote deleted')
1396 actions[f] = (ACTION_FORGET, None, b'remote deleted')
1397 else:
1397 else:
1398 actions[f] = (ACTION_REMOVE, None, b'other deleted')
1398 actions[f] = (ACTION_REMOVE, None, b'other deleted')
1399 elif n2: # file exists only on remote side
1399 elif n2: # file exists only on remote side
1400 if f in copied:
1400 if f in copied:
1401 pass # we'll deal with it on m1 side
1401 pass # we'll deal with it on m1 side
1402 elif f in movewithdir:
1402 elif f in movewithdir:
1403 f2 = movewithdir[f]
1403 f2 = movewithdir[f]
1404 if f2 in m1:
1404 if f2 in m1:
1405 actions[f2] = (
1405 actions[f2] = (
1406 ACTION_MERGE,
1406 ACTION_MERGE,
1407 (f2, f, None, False, pa.node()),
1407 (f2, f, None, False, pa.node()),
1408 b'local directory rename, both created',
1408 b'local directory rename, both created',
1409 )
1409 )
1410 else:
1410 else:
1411 actions[f2] = (
1411 actions[f2] = (
1412 ACTION_LOCAL_DIR_RENAME_GET,
1412 ACTION_LOCAL_DIR_RENAME_GET,
1413 (f, fl2),
1413 (f, fl2),
1414 b'local directory rename - get from %s' % f,
1414 b'local directory rename - get from %s' % f,
1415 )
1415 )
1416 elif f in copy:
1416 elif f in copy:
1417 f2 = copy[f]
1417 f2 = copy[f]
1418 if f2 in m2:
1418 if f2 in m2:
1419 actions[f] = (
1419 actions[f] = (
1420 ACTION_MERGE,
1420 ACTION_MERGE,
1421 (f2, f, f2, False, pa.node()),
1421 (f2, f, f2, False, pa.node()),
1422 b'remote copied from %s' % f2,
1422 b'remote copied from %s' % f2,
1423 )
1423 )
1424 else:
1424 else:
1425 actions[f] = (
1425 actions[f] = (
1426 ACTION_MERGE,
1426 ACTION_MERGE,
1427 (f2, f, f2, True, pa.node()),
1427 (f2, f, f2, True, pa.node()),
1428 b'remote moved from %s' % f2,
1428 b'remote moved from %s' % f2,
1429 )
1429 )
1430 elif f not in ma:
1430 elif f not in ma:
1431 # local unknown, remote created: the logic is described by the
1431 # local unknown, remote created: the logic is described by the
1432 # following table:
1432 # following table:
1433 #
1433 #
1434 # force branchmerge different | action
1434 # force branchmerge different | action
1435 # n * * | create
1435 # n * * | create
1436 # y n * | create
1436 # y n * | create
1437 # y y n | create
1437 # y y n | create
1438 # y y y | merge
1438 # y y y | merge
1439 #
1439 #
1440 # Checking whether the files are different is expensive, so we
1440 # Checking whether the files are different is expensive, so we
1441 # don't do that when we can avoid it.
1441 # don't do that when we can avoid it.
1442 if not force:
1442 if not force:
1443 actions[f] = (ACTION_CREATED, (fl2,), b'remote created')
1443 actions[f] = (ACTION_CREATED, (fl2,), b'remote created')
1444 elif not branchmerge:
1444 elif not branchmerge:
1445 actions[f] = (ACTION_CREATED, (fl2,), b'remote created')
1445 actions[f] = (ACTION_CREATED, (fl2,), b'remote created')
1446 else:
1446 else:
1447 actions[f] = (
1447 actions[f] = (
1448 ACTION_CREATED_MERGE,
1448 ACTION_CREATED_MERGE,
1449 (fl2, pa.node()),
1449 (fl2, pa.node()),
1450 b'remote created, get or merge',
1450 b'remote created, get or merge',
1451 )
1451 )
1452 elif n2 != ma[f]:
1452 elif n2 != ma[f]:
1453 df = None
1453 df = None
1454 for d in dirmove:
1454 for d in dirmove:
1455 if f.startswith(d):
1455 if f.startswith(d):
1456 # new file added in a directory that was moved
1456 # new file added in a directory that was moved
1457 df = dirmove[d] + f[len(d) :]
1457 df = dirmove[d] + f[len(d) :]
1458 break
1458 break
1459 if df is not None and df in m1:
1459 if df is not None and df in m1:
1460 actions[df] = (
1460 actions[df] = (
1461 ACTION_MERGE,
1461 ACTION_MERGE,
1462 (df, f, f, False, pa.node()),
1462 (df, f, f, False, pa.node()),
1463 b'local directory rename - respect move '
1463 b'local directory rename - respect move '
1464 b'from %s' % f,
1464 b'from %s' % f,
1465 )
1465 )
1466 elif acceptremote:
1466 elif acceptremote:
1467 actions[f] = (ACTION_CREATED, (fl2,), b'remote recreating')
1467 actions[f] = (ACTION_CREATED, (fl2,), b'remote recreating')
1468 else:
1468 else:
1469 actions[f] = (
1469 actions[f] = (
1470 ACTION_DELETED_CHANGED,
1470 ACTION_DELETED_CHANGED,
1471 (None, f, f, False, pa.node()),
1471 (None, f, f, False, pa.node()),
1472 b'prompt deleted/changed',
1472 b'prompt deleted/changed',
1473 )
1473 )
1474
1474
1475 if repo.ui.configbool(b'experimental', b'merge.checkpathconflicts'):
1475 if repo.ui.configbool(b'experimental', b'merge.checkpathconflicts'):
1476 # If we are merging, look for path conflicts.
1476 # If we are merging, look for path conflicts.
1477 checkpathconflicts(repo, wctx, p2, actions)
1477 checkpathconflicts(repo, wctx, p2, actions)
1478
1478
1479 narrowmatch = repo.narrowmatch()
1479 narrowmatch = repo.narrowmatch()
1480 if not narrowmatch.always():
1480 if not narrowmatch.always():
1481 # Updates "actions" in place
1481 # Updates "actions" in place
1482 _filternarrowactions(narrowmatch, branchmerge, actions)
1482 _filternarrowactions(narrowmatch, branchmerge, actions)
1483
1483
1484 return actions, diverge, renamedelete
1484 return actions, diverge, renamedelete
1485
1485
1486
1486
1487 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
1487 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
1488 """Resolves false conflicts where the nodeid changed but the content
1488 """Resolves false conflicts where the nodeid changed but the content
1489 remained the same."""
1489 remained the same."""
1490 # We force a copy of actions.items() because we're going to mutate
1490 # We force a copy of actions.items() because we're going to mutate
1491 # actions as we resolve trivial conflicts.
1491 # actions as we resolve trivial conflicts.
1492 for f, (m, args, msg) in list(actions.items()):
1492 for f, (m, args, msg) in list(actions.items()):
1493 if (
1493 if (
1494 m == ACTION_CHANGED_DELETED
1494 m == ACTION_CHANGED_DELETED
1495 and f in ancestor
1495 and f in ancestor
1496 and not wctx[f].cmp(ancestor[f])
1496 and not wctx[f].cmp(ancestor[f])
1497 ):
1497 ):
1498 # local did change but ended up with same content
1498 # local did change but ended up with same content
1499 actions[f] = ACTION_REMOVE, None, b'prompt same'
1499 actions[f] = ACTION_REMOVE, None, b'prompt same'
1500 elif (
1500 elif (
1501 m == ACTION_DELETED_CHANGED
1501 m == ACTION_DELETED_CHANGED
1502 and f in ancestor
1502 and f in ancestor
1503 and not mctx[f].cmp(ancestor[f])
1503 and not mctx[f].cmp(ancestor[f])
1504 ):
1504 ):
1505 # remote did change but ended up with same content
1505 # remote did change but ended up with same content
1506 del actions[f] # don't get = keep local deleted
1506 del actions[f] # don't get = keep local deleted
1507
1507
1508
1508
1509 def calculateupdates(
1509 def calculateupdates(
1510 repo,
1510 repo,
1511 wctx,
1511 wctx,
1512 mctx,
1512 mctx,
1513 ancestors,
1513 ancestors,
1514 branchmerge,
1514 branchmerge,
1515 force,
1515 force,
1516 acceptremote,
1516 acceptremote,
1517 followcopies,
1517 followcopies,
1518 matcher=None,
1518 matcher=None,
1519 mergeforce=False,
1519 mergeforce=False,
1520 ):
1520 ):
1521 """Calculate the actions needed to merge mctx into wctx using ancestors"""
1521 """Calculate the actions needed to merge mctx into wctx using ancestors"""
1522 # Avoid cycle.
1522 # Avoid cycle.
1523 from . import sparse
1523 from . import sparse
1524
1524
1525 if len(ancestors) == 1: # default
1525 if len(ancestors) == 1: # default
1526 actions, diverge, renamedelete = manifestmerge(
1526 actions, diverge, renamedelete = manifestmerge(
1527 repo,
1527 repo,
1528 wctx,
1528 wctx,
1529 mctx,
1529 mctx,
1530 ancestors[0],
1530 ancestors[0],
1531 branchmerge,
1531 branchmerge,
1532 force,
1532 force,
1533 matcher,
1533 matcher,
1534 acceptremote,
1534 acceptremote,
1535 followcopies,
1535 followcopies,
1536 )
1536 )
1537 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
1537 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
1538
1538
1539 else: # only when merge.preferancestor=* - the default
1539 else: # only when merge.preferancestor=* - the default
1540 repo.ui.note(
1540 repo.ui.note(
1541 _(b"note: merging %s and %s using bids from ancestors %s\n")
1541 _(b"note: merging %s and %s using bids from ancestors %s\n")
1542 % (
1542 % (
1543 wctx,
1543 wctx,
1544 mctx,
1544 mctx,
1545 _(b' and ').join(pycompat.bytestr(anc) for anc in ancestors),
1545 _(b' and ').join(pycompat.bytestr(anc) for anc in ancestors),
1546 )
1546 )
1547 )
1547 )
1548
1548
1549 # Call for bids
1549 # Call for bids
1550 fbids = (
1550 fbids = (
1551 {}
1551 {}
1552 ) # mapping filename to bids (action method to list af actions)
1552 ) # mapping filename to bids (action method to list af actions)
1553 diverge, renamedelete = None, None
1553 diverge, renamedelete = None, None
1554 for ancestor in ancestors:
1554 for ancestor in ancestors:
1555 repo.ui.note(_(b'\ncalculating bids for ancestor %s\n') % ancestor)
1555 repo.ui.note(_(b'\ncalculating bids for ancestor %s\n') % ancestor)
1556 actions, diverge1, renamedelete1 = manifestmerge(
1556 actions, diverge1, renamedelete1 = manifestmerge(
1557 repo,
1557 repo,
1558 wctx,
1558 wctx,
1559 mctx,
1559 mctx,
1560 ancestor,
1560 ancestor,
1561 branchmerge,
1561 branchmerge,
1562 force,
1562 force,
1563 matcher,
1563 matcher,
1564 acceptremote,
1564 acceptremote,
1565 followcopies,
1565 followcopies,
1566 forcefulldiff=True,
1566 forcefulldiff=True,
1567 )
1567 )
1568 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
1568 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
1569
1569
1570 # Track the shortest set of warning on the theory that bid
1570 # Track the shortest set of warning on the theory that bid
1571 # merge will correctly incorporate more information
1571 # merge will correctly incorporate more information
1572 if diverge is None or len(diverge1) < len(diverge):
1572 if diverge is None or len(diverge1) < len(diverge):
1573 diverge = diverge1
1573 diverge = diverge1
1574 if renamedelete is None or len(renamedelete) < len(renamedelete1):
1574 if renamedelete is None or len(renamedelete) < len(renamedelete1):
1575 renamedelete = renamedelete1
1575 renamedelete = renamedelete1
1576
1576
1577 for f, a in sorted(pycompat.iteritems(actions)):
1577 for f, a in sorted(pycompat.iteritems(actions)):
1578 m, args, msg = a
1578 m, args, msg = a
1579 repo.ui.debug(b' %s: %s -> %s\n' % (f, msg, m))
1579 repo.ui.debug(b' %s: %s -> %s\n' % (f, msg, m))
1580 if f in fbids:
1580 if f in fbids:
1581 d = fbids[f]
1581 d = fbids[f]
1582 if m in d:
1582 if m in d:
1583 d[m].append(a)
1583 d[m].append(a)
1584 else:
1584 else:
1585 d[m] = [a]
1585 d[m] = [a]
1586 else:
1586 else:
1587 fbids[f] = {m: [a]}
1587 fbids[f] = {m: [a]}
1588
1588
1589 # Pick the best bid for each file
1589 # Pick the best bid for each file
1590 repo.ui.note(_(b'\nauction for merging merge bids\n'))
1590 repo.ui.note(_(b'\nauction for merging merge bids\n'))
1591 actions = {}
1591 actions = {}
1592 for f, bids in sorted(fbids.items()):
1592 for f, bids in sorted(fbids.items()):
1593 # bids is a mapping from action method to list af actions
1593 # bids is a mapping from action method to list af actions
1594 # Consensus?
1594 # Consensus?
1595 if len(bids) == 1: # all bids are the same kind of method
1595 if len(bids) == 1: # all bids are the same kind of method
1596 m, l = list(bids.items())[0]
1596 m, l = list(bids.items())[0]
1597 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
1597 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
1598 repo.ui.note(_(b" %s: consensus for %s\n") % (f, m))
1598 repo.ui.note(_(b" %s: consensus for %s\n") % (f, m))
1599 actions[f] = l[0]
1599 actions[f] = l[0]
1600 continue
1600 continue
1601 # If keep is an option, just do it.
1601 # If keep is an option, just do it.
1602 if ACTION_KEEP in bids:
1602 if ACTION_KEEP in bids:
1603 repo.ui.note(_(b" %s: picking 'keep' action\n") % f)
1603 repo.ui.note(_(b" %s: picking 'keep' action\n") % f)
1604 actions[f] = bids[ACTION_KEEP][0]
1604 actions[f] = bids[ACTION_KEEP][0]
1605 continue
1605 continue
1606 # If there are gets and they all agree [how could they not?], do it.
1606 # If there are gets and they all agree [how could they not?], do it.
1607 if ACTION_GET in bids:
1607 if ACTION_GET in bids:
1608 ga0 = bids[ACTION_GET][0]
1608 ga0 = bids[ACTION_GET][0]
1609 if all(a == ga0 for a in bids[ACTION_GET][1:]):
1609 if all(a == ga0 for a in bids[ACTION_GET][1:]):
1610 repo.ui.note(_(b" %s: picking 'get' action\n") % f)
1610 repo.ui.note(_(b" %s: picking 'get' action\n") % f)
1611 actions[f] = ga0
1611 actions[f] = ga0
1612 continue
1612 continue
1613 # TODO: Consider other simple actions such as mode changes
1613 # TODO: Consider other simple actions such as mode changes
1614 # Handle inefficient democrazy.
1614 # Handle inefficient democrazy.
1615 repo.ui.note(_(b' %s: multiple bids for merge action:\n') % f)
1615 repo.ui.note(_(b' %s: multiple bids for merge action:\n') % f)
1616 for m, l in sorted(bids.items()):
1616 for m, l in sorted(bids.items()):
1617 for _f, args, msg in l:
1617 for _f, args, msg in l:
1618 repo.ui.note(b' %s -> %s\n' % (msg, m))
1618 repo.ui.note(b' %s -> %s\n' % (msg, m))
1619 # Pick random action. TODO: Instead, prompt user when resolving
1619 # Pick random action. TODO: Instead, prompt user when resolving
1620 m, l = list(bids.items())[0]
1620 m, l = list(bids.items())[0]
1621 repo.ui.warn(
1621 repo.ui.warn(
1622 _(b' %s: ambiguous merge - picked %s action\n') % (f, m)
1622 _(b' %s: ambiguous merge - picked %s action\n') % (f, m)
1623 )
1623 )
1624 actions[f] = l[0]
1624 actions[f] = l[0]
1625 continue
1625 continue
1626 repo.ui.note(_(b'end of auction\n\n'))
1626 repo.ui.note(_(b'end of auction\n\n'))
1627
1627
1628 if wctx.rev() is None:
1628 if wctx.rev() is None:
1629 fractions = _forgetremoved(wctx, mctx, branchmerge)
1629 fractions = _forgetremoved(wctx, mctx, branchmerge)
1630 actions.update(fractions)
1630 actions.update(fractions)
1631
1631
1632 prunedactions = sparse.filterupdatesactions(
1632 prunedactions = sparse.filterupdatesactions(
1633 repo, wctx, mctx, branchmerge, actions
1633 repo, wctx, mctx, branchmerge, actions
1634 )
1634 )
1635 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
1635 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
1636
1636
1637 return prunedactions, diverge, renamedelete
1637 return prunedactions, diverge, renamedelete
1638
1638
1639
1639
1640 def _getcwd():
1640 def _getcwd():
1641 try:
1641 try:
1642 return encoding.getcwd()
1642 return encoding.getcwd()
1643 except OSError as err:
1643 except OSError as err:
1644 if err.errno == errno.ENOENT:
1644 if err.errno == errno.ENOENT:
1645 return None
1645 return None
1646 raise
1646 raise
1647
1647
1648
1648
1649 def batchremove(repo, wctx, actions):
1649 def batchremove(repo, wctx, actions):
1650 """apply removes to the working directory
1650 """apply removes to the working directory
1651
1651
1652 yields tuples for progress updates
1652 yields tuples for progress updates
1653 """
1653 """
1654 verbose = repo.ui.verbose
1654 verbose = repo.ui.verbose
1655 cwd = _getcwd()
1655 cwd = _getcwd()
1656 i = 0
1656 i = 0
1657 for f, args, msg in actions:
1657 for f, args, msg in actions:
1658 repo.ui.debug(b" %s: %s -> r\n" % (f, msg))
1658 repo.ui.debug(b" %s: %s -> r\n" % (f, msg))
1659 if verbose:
1659 if verbose:
1660 repo.ui.note(_(b"removing %s\n") % f)
1660 repo.ui.note(_(b"removing %s\n") % f)
1661 wctx[f].audit()
1661 wctx[f].audit()
1662 try:
1662 try:
1663 wctx[f].remove(ignoremissing=True)
1663 wctx[f].remove(ignoremissing=True)
1664 except OSError as inst:
1664 except OSError as inst:
1665 repo.ui.warn(
1665 repo.ui.warn(
1666 _(b"update failed to remove %s: %s!\n") % (f, inst.strerror)
1666 _(b"update failed to remove %s: %s!\n") % (f, inst.strerror)
1667 )
1667 )
1668 if i == 100:
1668 if i == 100:
1669 yield i, f
1669 yield i, f
1670 i = 0
1670 i = 0
1671 i += 1
1671 i += 1
1672 if i > 0:
1672 if i > 0:
1673 yield i, f
1673 yield i, f
1674
1674
1675 if cwd and not _getcwd():
1675 if cwd and not _getcwd():
1676 # cwd was removed in the course of removing files; print a helpful
1676 # cwd was removed in the course of removing files; print a helpful
1677 # warning.
1677 # warning.
1678 repo.ui.warn(
1678 repo.ui.warn(
1679 _(
1679 _(
1680 b"current directory was removed\n"
1680 b"current directory was removed\n"
1681 b"(consider changing to repo root: %s)\n"
1681 b"(consider changing to repo root: %s)\n"
1682 )
1682 )
1683 % repo.root
1683 % repo.root
1684 )
1684 )
1685
1685
1686
1686
1687 def batchget(repo, mctx, wctx, wantfiledata, actions):
1687 def batchget(repo, mctx, wctx, wantfiledata, actions):
1688 """apply gets to the working directory
1688 """apply gets to the working directory
1689
1689
1690 mctx is the context to get from
1690 mctx is the context to get from
1691
1691
1692 Yields arbitrarily many (False, tuple) for progress updates, followed by
1692 Yields arbitrarily many (False, tuple) for progress updates, followed by
1693 exactly one (True, filedata). When wantfiledata is false, filedata is an
1693 exactly one (True, filedata). When wantfiledata is false, filedata is an
1694 empty dict. When wantfiledata is true, filedata[f] is a triple (mode, size,
1694 empty dict. When wantfiledata is true, filedata[f] is a triple (mode, size,
1695 mtime) of the file f written for each action.
1695 mtime) of the file f written for each action.
1696 """
1696 """
1697 filedata = {}
1697 filedata = {}
1698 verbose = repo.ui.verbose
1698 verbose = repo.ui.verbose
1699 fctx = mctx.filectx
1699 fctx = mctx.filectx
1700 ui = repo.ui
1700 ui = repo.ui
1701 i = 0
1701 i = 0
1702 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1702 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1703 for f, (flags, backup), msg in actions:
1703 for f, (flags, backup), msg in actions:
1704 repo.ui.debug(b" %s: %s -> g\n" % (f, msg))
1704 repo.ui.debug(b" %s: %s -> g\n" % (f, msg))
1705 if verbose:
1705 if verbose:
1706 repo.ui.note(_(b"getting %s\n") % f)
1706 repo.ui.note(_(b"getting %s\n") % f)
1707
1707
1708 if backup:
1708 if backup:
1709 # If a file or directory exists with the same name, back that
1709 # If a file or directory exists with the same name, back that
1710 # up. Otherwise, look to see if there is a file that conflicts
1710 # up. Otherwise, look to see if there is a file that conflicts
1711 # with a directory this file is in, and if so, back that up.
1711 # with a directory this file is in, and if so, back that up.
1712 conflicting = f
1712 conflicting = f
1713 if not repo.wvfs.lexists(f):
1713 if not repo.wvfs.lexists(f):
1714 for p in pathutil.finddirs(f):
1714 for p in pathutil.finddirs(f):
1715 if repo.wvfs.isfileorlink(p):
1715 if repo.wvfs.isfileorlink(p):
1716 conflicting = p
1716 conflicting = p
1717 break
1717 break
1718 if repo.wvfs.lexists(conflicting):
1718 if repo.wvfs.lexists(conflicting):
1719 orig = scmutil.backuppath(ui, repo, conflicting)
1719 orig = scmutil.backuppath(ui, repo, conflicting)
1720 util.rename(repo.wjoin(conflicting), orig)
1720 util.rename(repo.wjoin(conflicting), orig)
1721 wfctx = wctx[f]
1721 wfctx = wctx[f]
1722 wfctx.clearunknown()
1722 wfctx.clearunknown()
1723 atomictemp = ui.configbool(b"experimental", b"update.atomic-file")
1723 atomictemp = ui.configbool(b"experimental", b"update.atomic-file")
1724 size = wfctx.write(
1724 size = wfctx.write(
1725 fctx(f).data(),
1725 fctx(f).data(),
1726 flags,
1726 flags,
1727 backgroundclose=True,
1727 backgroundclose=True,
1728 atomictemp=atomictemp,
1728 atomictemp=atomictemp,
1729 )
1729 )
1730 if wantfiledata:
1730 if wantfiledata:
1731 s = wfctx.lstat()
1731 s = wfctx.lstat()
1732 mode = s.st_mode
1732 mode = s.st_mode
1733 mtime = s[stat.ST_MTIME]
1733 mtime = s[stat.ST_MTIME]
1734 filedata[f] = (mode, size, mtime) # for dirstate.normal
1734 filedata[f] = (mode, size, mtime) # for dirstate.normal
1735 if i == 100:
1735 if i == 100:
1736 yield False, (i, f)
1736 yield False, (i, f)
1737 i = 0
1737 i = 0
1738 i += 1
1738 i += 1
1739 if i > 0:
1739 if i > 0:
1740 yield False, (i, f)
1740 yield False, (i, f)
1741 yield True, filedata
1741 yield True, filedata
1742
1742
1743
1743
1744 def _prefetchfiles(repo, ctx, actions):
1744 def _prefetchfiles(repo, ctx, actions):
1745 """Invoke ``scmutil.prefetchfiles()`` for the files relevant to the dict
1745 """Invoke ``scmutil.prefetchfiles()`` for the files relevant to the dict
1746 of merge actions. ``ctx`` is the context being merged in."""
1746 of merge actions. ``ctx`` is the context being merged in."""
1747
1747
1748 # Skipping 'a', 'am', 'f', 'r', 'dm', 'e', 'k', 'p' and 'pr', because they
1748 # Skipping 'a', 'am', 'f', 'r', 'dm', 'e', 'k', 'p' and 'pr', because they
1749 # don't touch the context to be merged in. 'cd' is skipped, because
1749 # don't touch the context to be merged in. 'cd' is skipped, because
1750 # changed/deleted never resolves to something from the remote side.
1750 # changed/deleted never resolves to something from the remote side.
1751 oplist = [
1751 oplist = [
1752 actions[a]
1752 actions[a]
1753 for a in (
1753 for a in (
1754 ACTION_GET,
1754 ACTION_GET,
1755 ACTION_DELETED_CHANGED,
1755 ACTION_DELETED_CHANGED,
1756 ACTION_LOCAL_DIR_RENAME_GET,
1756 ACTION_LOCAL_DIR_RENAME_GET,
1757 ACTION_MERGE,
1757 ACTION_MERGE,
1758 )
1758 )
1759 ]
1759 ]
1760 prefetch = scmutil.prefetchfiles
1760 prefetch = scmutil.prefetchfiles
1761 matchfiles = scmutil.matchfiles
1761 matchfiles = scmutil.matchfiles
1762 prefetch(
1762 prefetch(
1763 repo,
1763 repo,
1764 [ctx.rev()],
1764 [ctx.rev()],
1765 matchfiles(repo, [f for sublist in oplist for f, args, msg in sublist]),
1765 matchfiles(repo, [f for sublist in oplist for f, args, msg in sublist]),
1766 )
1766 )
1767
1767
1768
1768
1769 @attr.s(frozen=True)
1769 @attr.s(frozen=True)
1770 class updateresult(object):
1770 class updateresult(object):
1771 updatedcount = attr.ib()
1771 updatedcount = attr.ib()
1772 mergedcount = attr.ib()
1772 mergedcount = attr.ib()
1773 removedcount = attr.ib()
1773 removedcount = attr.ib()
1774 unresolvedcount = attr.ib()
1774 unresolvedcount = attr.ib()
1775
1775
1776 def isempty(self):
1776 def isempty(self):
1777 return not (
1777 return not (
1778 self.updatedcount
1778 self.updatedcount
1779 or self.mergedcount
1779 or self.mergedcount
1780 or self.removedcount
1780 or self.removedcount
1781 or self.unresolvedcount
1781 or self.unresolvedcount
1782 )
1782 )
1783
1783
1784
1784
1785 def emptyactions():
1785 def emptyactions():
1786 """create an actions dict, to be populated and passed to applyupdates()"""
1786 """create an actions dict, to be populated and passed to applyupdates()"""
1787 return dict(
1787 return dict(
1788 (m, [])
1788 (m, [])
1789 for m in (
1789 for m in (
1790 ACTION_ADD,
1790 ACTION_ADD,
1791 ACTION_ADD_MODIFIED,
1791 ACTION_ADD_MODIFIED,
1792 ACTION_FORGET,
1792 ACTION_FORGET,
1793 ACTION_GET,
1793 ACTION_GET,
1794 ACTION_CHANGED_DELETED,
1794 ACTION_CHANGED_DELETED,
1795 ACTION_DELETED_CHANGED,
1795 ACTION_DELETED_CHANGED,
1796 ACTION_REMOVE,
1796 ACTION_REMOVE,
1797 ACTION_DIR_RENAME_MOVE_LOCAL,
1797 ACTION_DIR_RENAME_MOVE_LOCAL,
1798 ACTION_LOCAL_DIR_RENAME_GET,
1798 ACTION_LOCAL_DIR_RENAME_GET,
1799 ACTION_MERGE,
1799 ACTION_MERGE,
1800 ACTION_EXEC,
1800 ACTION_EXEC,
1801 ACTION_KEEP,
1801 ACTION_KEEP,
1802 ACTION_PATH_CONFLICT,
1802 ACTION_PATH_CONFLICT,
1803 ACTION_PATH_CONFLICT_RESOLVE,
1803 ACTION_PATH_CONFLICT_RESOLVE,
1804 )
1804 )
1805 )
1805 )
1806
1806
1807
1807
1808 def applyupdates(
1808 def applyupdates(
1809 repo, actions, wctx, mctx, overwrite, wantfiledata, labels=None
1809 repo, actions, wctx, mctx, overwrite, wantfiledata, labels=None
1810 ):
1810 ):
1811 """apply the merge action list to the working directory
1811 """apply the merge action list to the working directory
1812
1812
1813 wctx is the working copy context
1813 wctx is the working copy context
1814 mctx is the context to be merged into the working copy
1814 mctx is the context to be merged into the working copy
1815
1815
1816 Return a tuple of (counts, filedata), where counts is a tuple
1816 Return a tuple of (counts, filedata), where counts is a tuple
1817 (updated, merged, removed, unresolved) that describes how many
1817 (updated, merged, removed, unresolved) that describes how many
1818 files were affected by the update, and filedata is as described in
1818 files were affected by the update, and filedata is as described in
1819 batchget.
1819 batchget.
1820 """
1820 """
1821
1821
1822 _prefetchfiles(repo, mctx, actions)
1822 _prefetchfiles(repo, mctx, actions)
1823
1823
1824 updated, merged, removed = 0, 0, 0
1824 updated, merged, removed = 0, 0, 0
1825 ms = mergestate.clean(repo, wctx.p1().node(), mctx.node(), labels)
1825 ms = mergestate.clean(repo, wctx.p1().node(), mctx.node(), labels)
1826 moves = []
1826 moves = []
1827 for m, l in actions.items():
1827 for m, l in actions.items():
1828 l.sort()
1828 l.sort()
1829
1829
1830 # 'cd' and 'dc' actions are treated like other merge conflicts
1830 # 'cd' and 'dc' actions are treated like other merge conflicts
1831 mergeactions = sorted(actions[ACTION_CHANGED_DELETED])
1831 mergeactions = sorted(actions[ACTION_CHANGED_DELETED])
1832 mergeactions.extend(sorted(actions[ACTION_DELETED_CHANGED]))
1832 mergeactions.extend(sorted(actions[ACTION_DELETED_CHANGED]))
1833 mergeactions.extend(actions[ACTION_MERGE])
1833 mergeactions.extend(actions[ACTION_MERGE])
1834 for f, args, msg in mergeactions:
1834 for f, args, msg in mergeactions:
1835 f1, f2, fa, move, anc = args
1835 f1, f2, fa, move, anc = args
1836 if f == b'.hgsubstate': # merged internally
1836 if f == b'.hgsubstate': # merged internally
1837 continue
1837 continue
1838 if f1 is None:
1838 if f1 is None:
1839 fcl = filemerge.absentfilectx(wctx, fa)
1839 fcl = filemerge.absentfilectx(wctx, fa)
1840 else:
1840 else:
1841 repo.ui.debug(b" preserving %s for resolve of %s\n" % (f1, f))
1841 repo.ui.debug(b" preserving %s for resolve of %s\n" % (f1, f))
1842 fcl = wctx[f1]
1842 fcl = wctx[f1]
1843 if f2 is None:
1843 if f2 is None:
1844 fco = filemerge.absentfilectx(mctx, fa)
1844 fco = filemerge.absentfilectx(mctx, fa)
1845 else:
1845 else:
1846 fco = mctx[f2]
1846 fco = mctx[f2]
1847 actx = repo[anc]
1847 actx = repo[anc]
1848 if fa in actx:
1848 if fa in actx:
1849 fca = actx[fa]
1849 fca = actx[fa]
1850 else:
1850 else:
1851 # TODO: move to absentfilectx
1851 # TODO: move to absentfilectx
1852 fca = repo.filectx(f1, fileid=nullrev)
1852 fca = repo.filectx(f1, fileid=nullrev)
1853 ms.add(fcl, fco, fca, f)
1853 ms.add(fcl, fco, fca, f)
1854 if f1 != f and move:
1854 if f1 != f and move:
1855 moves.append(f1)
1855 moves.append(f1)
1856
1856
1857 # remove renamed files after safely stored
1857 # remove renamed files after safely stored
1858 for f in moves:
1858 for f in moves:
1859 if wctx[f].lexists():
1859 if wctx[f].lexists():
1860 repo.ui.debug(b"removing %s\n" % f)
1860 repo.ui.debug(b"removing %s\n" % f)
1861 wctx[f].audit()
1861 wctx[f].audit()
1862 wctx[f].remove()
1862 wctx[f].remove()
1863
1863
1864 numupdates = sum(len(l) for m, l in actions.items() if m != ACTION_KEEP)
1864 numupdates = sum(len(l) for m, l in actions.items() if m != ACTION_KEEP)
1865 progress = repo.ui.makeprogress(
1865 progress = repo.ui.makeprogress(
1866 _(b'updating'), unit=_(b'files'), total=numupdates
1866 _(b'updating'), unit=_(b'files'), total=numupdates
1867 )
1867 )
1868
1868
1869 if [a for a in actions[ACTION_REMOVE] if a[0] == b'.hgsubstate']:
1869 if [a for a in actions[ACTION_REMOVE] if a[0] == b'.hgsubstate']:
1870 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1870 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1871
1871
1872 # record path conflicts
1872 # record path conflicts
1873 for f, args, msg in actions[ACTION_PATH_CONFLICT]:
1873 for f, args, msg in actions[ACTION_PATH_CONFLICT]:
1874 f1, fo = args
1874 f1, fo = args
1875 s = repo.ui.status
1875 s = repo.ui.status
1876 s(
1876 s(
1877 _(
1877 _(
1878 b"%s: path conflict - a file or link has the same name as a "
1878 b"%s: path conflict - a file or link has the same name as a "
1879 b"directory\n"
1879 b"directory\n"
1880 )
1880 )
1881 % f
1881 % f
1882 )
1882 )
1883 if fo == b'l':
1883 if fo == b'l':
1884 s(_(b"the local file has been renamed to %s\n") % f1)
1884 s(_(b"the local file has been renamed to %s\n") % f1)
1885 else:
1885 else:
1886 s(_(b"the remote file has been renamed to %s\n") % f1)
1886 s(_(b"the remote file has been renamed to %s\n") % f1)
1887 s(_(b"resolve manually then use 'hg resolve --mark %s'\n") % f)
1887 s(_(b"resolve manually then use 'hg resolve --mark %s'\n") % f)
1888 ms.addpath(f, f1, fo)
1888 ms.addpath(f, f1, fo)
1889 progress.increment(item=f)
1889 progress.increment(item=f)
1890
1890
1891 # When merging in-memory, we can't support worker processes, so set the
1891 # When merging in-memory, we can't support worker processes, so set the
1892 # per-item cost at 0 in that case.
1892 # per-item cost at 0 in that case.
1893 cost = 0 if wctx.isinmemory() else 0.001
1893 cost = 0 if wctx.isinmemory() else 0.001
1894
1894
1895 # remove in parallel (must come before resolving path conflicts and getting)
1895 # remove in parallel (must come before resolving path conflicts and getting)
1896 prog = worker.worker(
1896 prog = worker.worker(
1897 repo.ui, cost, batchremove, (repo, wctx), actions[ACTION_REMOVE]
1897 repo.ui, cost, batchremove, (repo, wctx), actions[ACTION_REMOVE]
1898 )
1898 )
1899 for i, item in prog:
1899 for i, item in prog:
1900 progress.increment(step=i, item=item)
1900 progress.increment(step=i, item=item)
1901 removed = len(actions[ACTION_REMOVE])
1901 removed = len(actions[ACTION_REMOVE])
1902
1902
1903 # resolve path conflicts (must come before getting)
1903 # resolve path conflicts (must come before getting)
1904 for f, args, msg in actions[ACTION_PATH_CONFLICT_RESOLVE]:
1904 for f, args, msg in actions[ACTION_PATH_CONFLICT_RESOLVE]:
1905 repo.ui.debug(b" %s: %s -> pr\n" % (f, msg))
1905 repo.ui.debug(b" %s: %s -> pr\n" % (f, msg))
1906 (f0,) = args
1906 (f0,) = args
1907 if wctx[f0].lexists():
1907 if wctx[f0].lexists():
1908 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1908 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1909 wctx[f].audit()
1909 wctx[f].audit()
1910 wctx[f].write(wctx.filectx(f0).data(), wctx.filectx(f0).flags())
1910 wctx[f].write(wctx.filectx(f0).data(), wctx.filectx(f0).flags())
1911 wctx[f0].remove()
1911 wctx[f0].remove()
1912 progress.increment(item=f)
1912 progress.increment(item=f)
1913
1913
1914 # get in parallel.
1914 # get in parallel.
1915 threadsafe = repo.ui.configbool(
1915 threadsafe = repo.ui.configbool(
1916 b'experimental', b'worker.wdir-get-thread-safe'
1916 b'experimental', b'worker.wdir-get-thread-safe'
1917 )
1917 )
1918 prog = worker.worker(
1918 prog = worker.worker(
1919 repo.ui,
1919 repo.ui,
1920 cost,
1920 cost,
1921 batchget,
1921 batchget,
1922 (repo, mctx, wctx, wantfiledata),
1922 (repo, mctx, wctx, wantfiledata),
1923 actions[ACTION_GET],
1923 actions[ACTION_GET],
1924 threadsafe=threadsafe,
1924 threadsafe=threadsafe,
1925 hasretval=True,
1925 hasretval=True,
1926 )
1926 )
1927 getfiledata = {}
1927 getfiledata = {}
1928 for final, res in prog:
1928 for final, res in prog:
1929 if final:
1929 if final:
1930 getfiledata = res
1930 getfiledata = res
1931 else:
1931 else:
1932 i, item = res
1932 i, item = res
1933 progress.increment(step=i, item=item)
1933 progress.increment(step=i, item=item)
1934 updated = len(actions[ACTION_GET])
1934 updated = len(actions[ACTION_GET])
1935
1935
1936 if [a for a in actions[ACTION_GET] if a[0] == b'.hgsubstate']:
1936 if [a for a in actions[ACTION_GET] if a[0] == b'.hgsubstate']:
1937 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1937 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1938
1938
1939 # forget (manifest only, just log it) (must come first)
1939 # forget (manifest only, just log it) (must come first)
1940 for f, args, msg in actions[ACTION_FORGET]:
1940 for f, args, msg in actions[ACTION_FORGET]:
1941 repo.ui.debug(b" %s: %s -> f\n" % (f, msg))
1941 repo.ui.debug(b" %s: %s -> f\n" % (f, msg))
1942 progress.increment(item=f)
1942 progress.increment(item=f)
1943
1943
1944 # re-add (manifest only, just log it)
1944 # re-add (manifest only, just log it)
1945 for f, args, msg in actions[ACTION_ADD]:
1945 for f, args, msg in actions[ACTION_ADD]:
1946 repo.ui.debug(b" %s: %s -> a\n" % (f, msg))
1946 repo.ui.debug(b" %s: %s -> a\n" % (f, msg))
1947 progress.increment(item=f)
1947 progress.increment(item=f)
1948
1948
1949 # re-add/mark as modified (manifest only, just log it)
1949 # re-add/mark as modified (manifest only, just log it)
1950 for f, args, msg in actions[ACTION_ADD_MODIFIED]:
1950 for f, args, msg in actions[ACTION_ADD_MODIFIED]:
1951 repo.ui.debug(b" %s: %s -> am\n" % (f, msg))
1951 repo.ui.debug(b" %s: %s -> am\n" % (f, msg))
1952 progress.increment(item=f)
1952 progress.increment(item=f)
1953
1953
1954 # keep (noop, just log it)
1954 # keep (noop, just log it)
1955 for f, args, msg in actions[ACTION_KEEP]:
1955 for f, args, msg in actions[ACTION_KEEP]:
1956 repo.ui.debug(b" %s: %s -> k\n" % (f, msg))
1956 repo.ui.debug(b" %s: %s -> k\n" % (f, msg))
1957 # no progress
1957 # no progress
1958
1958
1959 # directory rename, move local
1959 # directory rename, move local
1960 for f, args, msg in actions[ACTION_DIR_RENAME_MOVE_LOCAL]:
1960 for f, args, msg in actions[ACTION_DIR_RENAME_MOVE_LOCAL]:
1961 repo.ui.debug(b" %s: %s -> dm\n" % (f, msg))
1961 repo.ui.debug(b" %s: %s -> dm\n" % (f, msg))
1962 progress.increment(item=f)
1962 progress.increment(item=f)
1963 f0, flags = args
1963 f0, flags = args
1964 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1964 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1965 wctx[f].audit()
1965 wctx[f].audit()
1966 wctx[f].write(wctx.filectx(f0).data(), flags)
1966 wctx[f].write(wctx.filectx(f0).data(), flags)
1967 wctx[f0].remove()
1967 wctx[f0].remove()
1968 updated += 1
1968 updated += 1
1969
1969
1970 # local directory rename, get
1970 # local directory rename, get
1971 for f, args, msg in actions[ACTION_LOCAL_DIR_RENAME_GET]:
1971 for f, args, msg in actions[ACTION_LOCAL_DIR_RENAME_GET]:
1972 repo.ui.debug(b" %s: %s -> dg\n" % (f, msg))
1972 repo.ui.debug(b" %s: %s -> dg\n" % (f, msg))
1973 progress.increment(item=f)
1973 progress.increment(item=f)
1974 f0, flags = args
1974 f0, flags = args
1975 repo.ui.note(_(b"getting %s to %s\n") % (f0, f))
1975 repo.ui.note(_(b"getting %s to %s\n") % (f0, f))
1976 wctx[f].write(mctx.filectx(f0).data(), flags)
1976 wctx[f].write(mctx.filectx(f0).data(), flags)
1977 updated += 1
1977 updated += 1
1978
1978
1979 # exec
1979 # exec
1980 for f, args, msg in actions[ACTION_EXEC]:
1980 for f, args, msg in actions[ACTION_EXEC]:
1981 repo.ui.debug(b" %s: %s -> e\n" % (f, msg))
1981 repo.ui.debug(b" %s: %s -> e\n" % (f, msg))
1982 progress.increment(item=f)
1982 progress.increment(item=f)
1983 (flags,) = args
1983 (flags,) = args
1984 wctx[f].audit()
1984 wctx[f].audit()
1985 wctx[f].setflags(b'l' in flags, b'x' in flags)
1985 wctx[f].setflags(b'l' in flags, b'x' in flags)
1986 updated += 1
1986 updated += 1
1987
1987
1988 # the ordering is important here -- ms.mergedriver will raise if the merge
1988 # the ordering is important here -- ms.mergedriver will raise if the merge
1989 # driver has changed, and we want to be able to bypass it when overwrite is
1989 # driver has changed, and we want to be able to bypass it when overwrite is
1990 # True
1990 # True
1991 usemergedriver = not overwrite and mergeactions and ms.mergedriver
1991 usemergedriver = not overwrite and mergeactions and ms.mergedriver
1992
1992
1993 if usemergedriver:
1993 if usemergedriver:
1994 if wctx.isinmemory():
1994 if wctx.isinmemory():
1995 raise error.InMemoryMergeConflictsError(
1995 raise error.InMemoryMergeConflictsError(
1996 b"in-memory merge does not support mergedriver"
1996 b"in-memory merge does not support mergedriver"
1997 )
1997 )
1998 ms.commit()
1998 ms.commit()
1999 proceed = driverpreprocess(repo, ms, wctx, labels=labels)
1999 proceed = driverpreprocess(repo, ms, wctx, labels=labels)
2000 # the driver might leave some files unresolved
2000 # the driver might leave some files unresolved
2001 unresolvedf = set(ms.unresolved())
2001 unresolvedf = set(ms.unresolved())
2002 if not proceed:
2002 if not proceed:
2003 # XXX setting unresolved to at least 1 is a hack to make sure we
2003 # XXX setting unresolved to at least 1 is a hack to make sure we
2004 # error out
2004 # error out
2005 return updateresult(
2005 return updateresult(
2006 updated, merged, removed, max(len(unresolvedf), 1)
2006 updated, merged, removed, max(len(unresolvedf), 1)
2007 )
2007 )
2008 newactions = []
2008 newactions = []
2009 for f, args, msg in mergeactions:
2009 for f, args, msg in mergeactions:
2010 if f in unresolvedf:
2010 if f in unresolvedf:
2011 newactions.append((f, args, msg))
2011 newactions.append((f, args, msg))
2012 mergeactions = newactions
2012 mergeactions = newactions
2013
2013
2014 try:
2014 try:
2015 # premerge
2015 # premerge
2016 tocomplete = []
2016 tocomplete = []
2017 for f, args, msg in mergeactions:
2017 for f, args, msg in mergeactions:
2018 repo.ui.debug(b" %s: %s -> m (premerge)\n" % (f, msg))
2018 repo.ui.debug(b" %s: %s -> m (premerge)\n" % (f, msg))
2019 progress.increment(item=f)
2019 progress.increment(item=f)
2020 if f == b'.hgsubstate': # subrepo states need updating
2020 if f == b'.hgsubstate': # subrepo states need updating
2021 subrepoutil.submerge(
2021 subrepoutil.submerge(
2022 repo, wctx, mctx, wctx.ancestor(mctx), overwrite, labels
2022 repo, wctx, mctx, wctx.ancestor(mctx), overwrite, labels
2023 )
2023 )
2024 continue
2024 continue
2025 wctx[f].audit()
2025 wctx[f].audit()
2026 complete, r = ms.preresolve(f, wctx)
2026 complete, r = ms.preresolve(f, wctx)
2027 if not complete:
2027 if not complete:
2028 numupdates += 1
2028 numupdates += 1
2029 tocomplete.append((f, args, msg))
2029 tocomplete.append((f, args, msg))
2030
2030
2031 # merge
2031 # merge
2032 for f, args, msg in tocomplete:
2032 for f, args, msg in tocomplete:
2033 repo.ui.debug(b" %s: %s -> m (merge)\n" % (f, msg))
2033 repo.ui.debug(b" %s: %s -> m (merge)\n" % (f, msg))
2034 progress.increment(item=f, total=numupdates)
2034 progress.increment(item=f, total=numupdates)
2035 ms.resolve(f, wctx)
2035 ms.resolve(f, wctx)
2036
2036
2037 finally:
2037 finally:
2038 ms.commit()
2038 ms.commit()
2039
2039
2040 unresolved = ms.unresolvedcount()
2040 unresolved = ms.unresolvedcount()
2041
2041
2042 if (
2042 if (
2043 usemergedriver
2043 usemergedriver
2044 and not unresolved
2044 and not unresolved
2045 and ms.mdstate() != MERGE_DRIVER_STATE_SUCCESS
2045 and ms.mdstate() != MERGE_DRIVER_STATE_SUCCESS
2046 ):
2046 ):
2047 if not driverconclude(repo, ms, wctx, labels=labels):
2047 if not driverconclude(repo, ms, wctx, labels=labels):
2048 # XXX setting unresolved to at least 1 is a hack to make sure we
2048 # XXX setting unresolved to at least 1 is a hack to make sure we
2049 # error out
2049 # error out
2050 unresolved = max(unresolved, 1)
2050 unresolved = max(unresolved, 1)
2051
2051
2052 ms.commit()
2052 ms.commit()
2053
2053
2054 msupdated, msmerged, msremoved = ms.counts()
2054 msupdated, msmerged, msremoved = ms.counts()
2055 updated += msupdated
2055 updated += msupdated
2056 merged += msmerged
2056 merged += msmerged
2057 removed += msremoved
2057 removed += msremoved
2058
2058
2059 extraactions = ms.actions()
2059 extraactions = ms.actions()
2060 if extraactions:
2060 if extraactions:
2061 mfiles = set(a[0] for a in actions[ACTION_MERGE])
2061 mfiles = set(a[0] for a in actions[ACTION_MERGE])
2062 for k, acts in pycompat.iteritems(extraactions):
2062 for k, acts in pycompat.iteritems(extraactions):
2063 actions[k].extend(acts)
2063 actions[k].extend(acts)
2064 if k == ACTION_GET and wantfiledata:
2064 if k == ACTION_GET and wantfiledata:
2065 # no filedata until mergestate is updated to provide it
2065 # no filedata until mergestate is updated to provide it
2066 for a in acts:
2066 for a in acts:
2067 getfiledata[a[0]] = None
2067 getfiledata[a[0]] = None
2068 # Remove these files from actions[ACTION_MERGE] as well. This is
2068 # Remove these files from actions[ACTION_MERGE] as well. This is
2069 # important because in recordupdates, files in actions[ACTION_MERGE]
2069 # important because in recordupdates, files in actions[ACTION_MERGE]
2070 # are processed after files in other actions, and the merge driver
2070 # are processed after files in other actions, and the merge driver
2071 # might add files to those actions via extraactions above. This can
2071 # might add files to those actions via extraactions above. This can
2072 # lead to a file being recorded twice, with poor results. This is
2072 # lead to a file being recorded twice, with poor results. This is
2073 # especially problematic for actions[ACTION_REMOVE] (currently only
2073 # especially problematic for actions[ACTION_REMOVE] (currently only
2074 # possible with the merge driver in the initial merge process;
2074 # possible with the merge driver in the initial merge process;
2075 # interrupted merges don't go through this flow).
2075 # interrupted merges don't go through this flow).
2076 #
2076 #
2077 # The real fix here is to have indexes by both file and action so
2077 # The real fix here is to have indexes by both file and action so
2078 # that when the action for a file is changed it is automatically
2078 # that when the action for a file is changed it is automatically
2079 # reflected in the other action lists. But that involves a more
2079 # reflected in the other action lists. But that involves a more
2080 # complex data structure, so this will do for now.
2080 # complex data structure, so this will do for now.
2081 #
2081 #
2082 # We don't need to do the same operation for 'dc' and 'cd' because
2082 # We don't need to do the same operation for 'dc' and 'cd' because
2083 # those lists aren't consulted again.
2083 # those lists aren't consulted again.
2084 mfiles.difference_update(a[0] for a in acts)
2084 mfiles.difference_update(a[0] for a in acts)
2085
2085
2086 actions[ACTION_MERGE] = [
2086 actions[ACTION_MERGE] = [
2087 a for a in actions[ACTION_MERGE] if a[0] in mfiles
2087 a for a in actions[ACTION_MERGE] if a[0] in mfiles
2088 ]
2088 ]
2089
2089
2090 progress.complete()
2090 progress.complete()
2091 assert len(getfiledata) == (len(actions[ACTION_GET]) if wantfiledata else 0)
2091 assert len(getfiledata) == (len(actions[ACTION_GET]) if wantfiledata else 0)
2092 return updateresult(updated, merged, removed, unresolved), getfiledata
2092 return updateresult(updated, merged, removed, unresolved), getfiledata
2093
2093
2094
2094
2095 def recordupdates(repo, actions, branchmerge, getfiledata):
2095 def recordupdates(repo, actions, branchmerge, getfiledata):
2096 """record merge actions to the dirstate"""
2096 """record merge actions to the dirstate"""
2097 # remove (must come first)
2097 # remove (must come first)
2098 for f, args, msg in actions.get(ACTION_REMOVE, []):
2098 for f, args, msg in actions.get(ACTION_REMOVE, []):
2099 if branchmerge:
2099 if branchmerge:
2100 repo.dirstate.remove(f)
2100 repo.dirstate.remove(f)
2101 else:
2101 else:
2102 repo.dirstate.drop(f)
2102 repo.dirstate.drop(f)
2103
2103
2104 # forget (must come first)
2104 # forget (must come first)
2105 for f, args, msg in actions.get(ACTION_FORGET, []):
2105 for f, args, msg in actions.get(ACTION_FORGET, []):
2106 repo.dirstate.drop(f)
2106 repo.dirstate.drop(f)
2107
2107
2108 # resolve path conflicts
2108 # resolve path conflicts
2109 for f, args, msg in actions.get(ACTION_PATH_CONFLICT_RESOLVE, []):
2109 for f, args, msg in actions.get(ACTION_PATH_CONFLICT_RESOLVE, []):
2110 (f0,) = args
2110 (f0,) = args
2111 origf0 = repo.dirstate.copied(f0) or f0
2111 origf0 = repo.dirstate.copied(f0) or f0
2112 repo.dirstate.add(f)
2112 repo.dirstate.add(f)
2113 repo.dirstate.copy(origf0, f)
2113 repo.dirstate.copy(origf0, f)
2114 if f0 == origf0:
2114 if f0 == origf0:
2115 repo.dirstate.remove(f0)
2115 repo.dirstate.remove(f0)
2116 else:
2116 else:
2117 repo.dirstate.drop(f0)
2117 repo.dirstate.drop(f0)
2118
2118
2119 # re-add
2119 # re-add
2120 for f, args, msg in actions.get(ACTION_ADD, []):
2120 for f, args, msg in actions.get(ACTION_ADD, []):
2121 repo.dirstate.add(f)
2121 repo.dirstate.add(f)
2122
2122
2123 # re-add/mark as modified
2123 # re-add/mark as modified
2124 for f, args, msg in actions.get(ACTION_ADD_MODIFIED, []):
2124 for f, args, msg in actions.get(ACTION_ADD_MODIFIED, []):
2125 if branchmerge:
2125 if branchmerge:
2126 repo.dirstate.normallookup(f)
2126 repo.dirstate.normallookup(f)
2127 else:
2127 else:
2128 repo.dirstate.add(f)
2128 repo.dirstate.add(f)
2129
2129
2130 # exec change
2130 # exec change
2131 for f, args, msg in actions.get(ACTION_EXEC, []):
2131 for f, args, msg in actions.get(ACTION_EXEC, []):
2132 repo.dirstate.normallookup(f)
2132 repo.dirstate.normallookup(f)
2133
2133
2134 # keep
2134 # keep
2135 for f, args, msg in actions.get(ACTION_KEEP, []):
2135 for f, args, msg in actions.get(ACTION_KEEP, []):
2136 pass
2136 pass
2137
2137
2138 # get
2138 # get
2139 for f, args, msg in actions.get(ACTION_GET, []):
2139 for f, args, msg in actions.get(ACTION_GET, []):
2140 if branchmerge:
2140 if branchmerge:
2141 repo.dirstate.otherparent(f)
2141 repo.dirstate.otherparent(f)
2142 else:
2142 else:
2143 parentfiledata = getfiledata[f] if getfiledata else None
2143 parentfiledata = getfiledata[f] if getfiledata else None
2144 repo.dirstate.normal(f, parentfiledata=parentfiledata)
2144 repo.dirstate.normal(f, parentfiledata=parentfiledata)
2145
2145
2146 # merge
2146 # merge
2147 for f, args, msg in actions.get(ACTION_MERGE, []):
2147 for f, args, msg in actions.get(ACTION_MERGE, []):
2148 f1, f2, fa, move, anc = args
2148 f1, f2, fa, move, anc = args
2149 if branchmerge:
2149 if branchmerge:
2150 # We've done a branch merge, mark this file as merged
2150 # We've done a branch merge, mark this file as merged
2151 # so that we properly record the merger later
2151 # so that we properly record the merger later
2152 repo.dirstate.merge(f)
2152 repo.dirstate.merge(f)
2153 if f1 != f2: # copy/rename
2153 if f1 != f2: # copy/rename
2154 if move:
2154 if move:
2155 repo.dirstate.remove(f1)
2155 repo.dirstate.remove(f1)
2156 if f1 != f:
2156 if f1 != f:
2157 repo.dirstate.copy(f1, f)
2157 repo.dirstate.copy(f1, f)
2158 else:
2158 else:
2159 repo.dirstate.copy(f2, f)
2159 repo.dirstate.copy(f2, f)
2160 else:
2160 else:
2161 # We've update-merged a locally modified file, so
2161 # We've update-merged a locally modified file, so
2162 # we set the dirstate to emulate a normal checkout
2162 # we set the dirstate to emulate a normal checkout
2163 # of that file some time in the past. Thus our
2163 # of that file some time in the past. Thus our
2164 # merge will appear as a normal local file
2164 # merge will appear as a normal local file
2165 # modification.
2165 # modification.
2166 if f2 == f: # file not locally copied/moved
2166 if f2 == f: # file not locally copied/moved
2167 repo.dirstate.normallookup(f)
2167 repo.dirstate.normallookup(f)
2168 if move:
2168 if move:
2169 repo.dirstate.drop(f1)
2169 repo.dirstate.drop(f1)
2170
2170
2171 # directory rename, move local
2171 # directory rename, move local
2172 for f, args, msg in actions.get(ACTION_DIR_RENAME_MOVE_LOCAL, []):
2172 for f, args, msg in actions.get(ACTION_DIR_RENAME_MOVE_LOCAL, []):
2173 f0, flag = args
2173 f0, flag = args
2174 if branchmerge:
2174 if branchmerge:
2175 repo.dirstate.add(f)
2175 repo.dirstate.add(f)
2176 repo.dirstate.remove(f0)
2176 repo.dirstate.remove(f0)
2177 repo.dirstate.copy(f0, f)
2177 repo.dirstate.copy(f0, f)
2178 else:
2178 else:
2179 repo.dirstate.normal(f)
2179 repo.dirstate.normal(f)
2180 repo.dirstate.drop(f0)
2180 repo.dirstate.drop(f0)
2181
2181
2182 # directory rename, get
2182 # directory rename, get
2183 for f, args, msg in actions.get(ACTION_LOCAL_DIR_RENAME_GET, []):
2183 for f, args, msg in actions.get(ACTION_LOCAL_DIR_RENAME_GET, []):
2184 f0, flag = args
2184 f0, flag = args
2185 if branchmerge:
2185 if branchmerge:
2186 repo.dirstate.add(f)
2186 repo.dirstate.add(f)
2187 repo.dirstate.copy(f0, f)
2187 repo.dirstate.copy(f0, f)
2188 else:
2188 else:
2189 repo.dirstate.normal(f)
2189 repo.dirstate.normal(f)
2190
2190
2191
2191
2192 UPDATECHECK_ABORT = b'abort' # handled at higher layers
2192 UPDATECHECK_ABORT = b'abort' # handled at higher layers
2193 UPDATECHECK_NONE = b'none'
2193 UPDATECHECK_NONE = b'none'
2194 UPDATECHECK_LINEAR = b'linear'
2194 UPDATECHECK_LINEAR = b'linear'
2195 UPDATECHECK_NO_CONFLICT = b'noconflict'
2195 UPDATECHECK_NO_CONFLICT = b'noconflict'
2196
2196
2197
2197
2198 def update(
2198 def update(
2199 repo,
2199 repo,
2200 node,
2200 node,
2201 branchmerge,
2201 branchmerge,
2202 force,
2202 force,
2203 ancestor=None,
2203 ancestor=None,
2204 mergeancestor=False,
2204 mergeancestor=False,
2205 labels=None,
2205 labels=None,
2206 matcher=None,
2206 matcher=None,
2207 mergeforce=False,
2207 mergeforce=False,
2208 updatecheck=None,
2208 updatecheck=None,
2209 wc=None,
2209 wc=None,
2210 ):
2210 ):
2211 """
2211 """
2212 Perform a merge between the working directory and the given node
2212 Perform a merge between the working directory and the given node
2213
2213
2214 node = the node to update to
2214 node = the node to update to
2215 branchmerge = whether to merge between branches
2215 branchmerge = whether to merge between branches
2216 force = whether to force branch merging or file overwriting
2216 force = whether to force branch merging or file overwriting
2217 matcher = a matcher to filter file lists (dirstate not updated)
2217 matcher = a matcher to filter file lists (dirstate not updated)
2218 mergeancestor = whether it is merging with an ancestor. If true,
2218 mergeancestor = whether it is merging with an ancestor. If true,
2219 we should accept the incoming changes for any prompts that occur.
2219 we should accept the incoming changes for any prompts that occur.
2220 If false, merging with an ancestor (fast-forward) is only allowed
2220 If false, merging with an ancestor (fast-forward) is only allowed
2221 between different named branches. This flag is used by rebase extension
2221 between different named branches. This flag is used by rebase extension
2222 as a temporary fix and should be avoided in general.
2222 as a temporary fix and should be avoided in general.
2223 labels = labels to use for base, local and other
2223 labels = labels to use for base, local and other
2224 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
2224 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
2225 this is True, then 'force' should be True as well.
2225 this is True, then 'force' should be True as well.
2226
2226
2227 The table below shows all the behaviors of the update command given the
2227 The table below shows all the behaviors of the update command given the
2228 -c/--check and -C/--clean or no options, whether the working directory is
2228 -c/--check and -C/--clean or no options, whether the working directory is
2229 dirty, whether a revision is specified, and the relationship of the parent
2229 dirty, whether a revision is specified, and the relationship of the parent
2230 rev to the target rev (linear or not). Match from top first. The -n
2230 rev to the target rev (linear or not). Match from top first. The -n
2231 option doesn't exist on the command line, but represents the
2231 option doesn't exist on the command line, but represents the
2232 experimental.updatecheck=noconflict option.
2232 experimental.updatecheck=noconflict option.
2233
2233
2234 This logic is tested by test-update-branches.t.
2234 This logic is tested by test-update-branches.t.
2235
2235
2236 -c -C -n -m dirty rev linear | result
2236 -c -C -n -m dirty rev linear | result
2237 y y * * * * * | (1)
2237 y y * * * * * | (1)
2238 y * y * * * * | (1)
2238 y * y * * * * | (1)
2239 y * * y * * * | (1)
2239 y * * y * * * | (1)
2240 * y y * * * * | (1)
2240 * y y * * * * | (1)
2241 * y * y * * * | (1)
2241 * y * y * * * | (1)
2242 * * y y * * * | (1)
2242 * * y y * * * | (1)
2243 * * * * * n n | x
2243 * * * * * n n | x
2244 * * * * n * * | ok
2244 * * * * n * * | ok
2245 n n n n y * y | merge
2245 n n n n y * y | merge
2246 n n n n y y n | (2)
2246 n n n n y y n | (2)
2247 n n n y y * * | merge
2247 n n n y y * * | merge
2248 n n y n y * * | merge if no conflict
2248 n n y n y * * | merge if no conflict
2249 n y n n y * * | discard
2249 n y n n y * * | discard
2250 y n n n y * * | (3)
2250 y n n n y * * | (3)
2251
2251
2252 x = can't happen
2252 x = can't happen
2253 * = don't-care
2253 * = don't-care
2254 1 = incompatible options (checked in commands.py)
2254 1 = incompatible options (checked in commands.py)
2255 2 = abort: uncommitted changes (commit or update --clean to discard changes)
2255 2 = abort: uncommitted changes (commit or update --clean to discard changes)
2256 3 = abort: uncommitted changes (checked in commands.py)
2256 3 = abort: uncommitted changes (checked in commands.py)
2257
2257
2258 The merge is performed inside ``wc``, a workingctx-like objects. It defaults
2258 The merge is performed inside ``wc``, a workingctx-like objects. It defaults
2259 to repo[None] if None is passed.
2259 to repo[None] if None is passed.
2260
2260
2261 Return the same tuple as applyupdates().
2261 Return the same tuple as applyupdates().
2262 """
2262 """
2263 # Avoid cycle.
2263 # Avoid cycle.
2264 from . import sparse
2264 from . import sparse
2265
2265
2266 # This function used to find the default destination if node was None, but
2266 # This function used to find the default destination if node was None, but
2267 # that's now in destutil.py.
2267 # that's now in destutil.py.
2268 assert node is not None
2268 assert node is not None
2269 if not branchmerge and not force:
2269 if not branchmerge and not force:
2270 # TODO: remove the default once all callers that pass branchmerge=False
2270 # TODO: remove the default once all callers that pass branchmerge=False
2271 # and force=False pass a value for updatecheck. We may want to allow
2271 # and force=False pass a value for updatecheck. We may want to allow
2272 # updatecheck='abort' to better suppport some of these callers.
2272 # updatecheck='abort' to better suppport some of these callers.
2273 if updatecheck is None:
2273 if updatecheck is None:
2274 updatecheck = UPDATECHECK_LINEAR
2274 updatecheck = UPDATECHECK_LINEAR
2275 if updatecheck not in (
2275 if updatecheck not in (
2276 UPDATECHECK_NONE,
2276 UPDATECHECK_NONE,
2277 UPDATECHECK_LINEAR,
2277 UPDATECHECK_LINEAR,
2278 UPDATECHECK_NO_CONFLICT,
2278 UPDATECHECK_NO_CONFLICT,
2279 ):
2279 ):
2280 raise ValueError(
2280 raise ValueError(
2281 r'Invalid updatecheck %r (can accept %r)'
2281 r'Invalid updatecheck %r (can accept %r)'
2282 % (
2282 % (
2283 updatecheck,
2283 updatecheck,
2284 (
2284 (
2285 UPDATECHECK_NONE,
2285 UPDATECHECK_NONE,
2286 UPDATECHECK_LINEAR,
2286 UPDATECHECK_LINEAR,
2287 UPDATECHECK_NO_CONFLICT,
2287 UPDATECHECK_NO_CONFLICT,
2288 ),
2288 ),
2289 )
2289 )
2290 )
2290 )
2291 # If we're doing a partial update, we need to skip updating
2291 # If we're doing a partial update, we need to skip updating
2292 # the dirstate, so make a note of any partial-ness to the
2292 # the dirstate, so make a note of any partial-ness to the
2293 # update here.
2293 # update here.
2294 if matcher is None or matcher.always():
2294 if matcher is None or matcher.always():
2295 partial = False
2295 partial = False
2296 else:
2296 else:
2297 partial = True
2297 partial = True
2298 with repo.wlock():
2298 with repo.wlock():
2299 if wc is None:
2299 if wc is None:
2300 wc = repo[None]
2300 wc = repo[None]
2301 pl = wc.parents()
2301 pl = wc.parents()
2302 p1 = pl[0]
2302 p1 = pl[0]
2303 p2 = repo[node]
2303 p2 = repo[node]
2304 if ancestor is not None:
2304 if ancestor is not None:
2305 pas = [repo[ancestor]]
2305 pas = [repo[ancestor]]
2306 else:
2306 else:
2307 if repo.ui.configlist(b'merge', b'preferancestor') == [b'*']:
2307 if repo.ui.configlist(b'merge', b'preferancestor') == [b'*']:
2308 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
2308 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
2309 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
2309 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
2310 else:
2310 else:
2311 pas = [p1.ancestor(p2, warn=branchmerge)]
2311 pas = [p1.ancestor(p2, warn=branchmerge)]
2312
2312
2313 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), bytes(p1), bytes(p2)
2313 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), bytes(p1), bytes(p2)
2314
2314
2315 overwrite = force and not branchmerge
2315 overwrite = force and not branchmerge
2316 ### check phase
2316 ### check phase
2317 if not overwrite:
2317 if not overwrite:
2318 if len(pl) > 1:
2318 if len(pl) > 1:
2319 raise error.Abort(_(b"outstanding uncommitted merge"))
2319 raise error.Abort(_(b"outstanding uncommitted merge"))
2320 ms = mergestate.read(repo)
2320 ms = mergestate.read(repo)
2321 if list(ms.unresolved()):
2321 if list(ms.unresolved()):
2322 raise error.Abort(
2322 raise error.Abort(
2323 _(b"outstanding merge conflicts"),
2323 _(b"outstanding merge conflicts"),
2324 hint=_(b"use 'hg resolve' to resolve"),
2324 hint=_(b"use 'hg resolve' to resolve"),
2325 )
2325 )
2326 if branchmerge:
2326 if branchmerge:
2327 if pas == [p2]:
2327 if pas == [p2]:
2328 raise error.Abort(
2328 raise error.Abort(
2329 _(
2329 _(
2330 b"merging with a working directory ancestor"
2330 b"merging with a working directory ancestor"
2331 b" has no effect"
2331 b" has no effect"
2332 )
2332 )
2333 )
2333 )
2334 elif pas == [p1]:
2334 elif pas == [p1]:
2335 if not mergeancestor and wc.branch() == p2.branch():
2335 if not mergeancestor and wc.branch() == p2.branch():
2336 raise error.Abort(
2336 raise error.Abort(
2337 _(b"nothing to merge"),
2337 _(b"nothing to merge"),
2338 hint=_(b"use 'hg update' or check 'hg heads'"),
2338 hint=_(b"use 'hg update' or check 'hg heads'"),
2339 )
2339 )
2340 if not force and (wc.files() or wc.deleted()):
2340 if not force and (wc.files() or wc.deleted()):
2341 raise error.Abort(
2341 raise error.Abort(
2342 _(b"uncommitted changes"),
2342 _(b"uncommitted changes"),
2343 hint=_(b"use 'hg status' to list changes"),
2343 hint=_(b"use 'hg status' to list changes"),
2344 )
2344 )
2345 if not wc.isinmemory():
2345 if not wc.isinmemory():
2346 for s in sorted(wc.substate):
2346 for s in sorted(wc.substate):
2347 wc.sub(s).bailifchanged()
2347 wc.sub(s).bailifchanged()
2348
2348
2349 elif not overwrite:
2349 elif not overwrite:
2350 if p1 == p2: # no-op update
2350 if p1 == p2: # no-op update
2351 # call the hooks and exit early
2351 # call the hooks and exit early
2352 repo.hook(b'preupdate', throw=True, parent1=xp2, parent2=b'')
2352 repo.hook(b'preupdate', throw=True, parent1=xp2, parent2=b'')
2353 repo.hook(b'update', parent1=xp2, parent2=b'', error=0)
2353 repo.hook(b'update', parent1=xp2, parent2=b'', error=0)
2354 return updateresult(0, 0, 0, 0)
2354 return updateresult(0, 0, 0, 0)
2355
2355
2356 if updatecheck == UPDATECHECK_LINEAR and pas not in (
2356 if updatecheck == UPDATECHECK_LINEAR and pas not in (
2357 [p1],
2357 [p1],
2358 [p2],
2358 [p2],
2359 ): # nonlinear
2359 ): # nonlinear
2360 dirty = wc.dirty(missing=True)
2360 dirty = wc.dirty(missing=True)
2361 if dirty:
2361 if dirty:
2362 # Branching is a bit strange to ensure we do the minimal
2362 # Branching is a bit strange to ensure we do the minimal
2363 # amount of call to obsutil.foreground.
2363 # amount of call to obsutil.foreground.
2364 foreground = obsutil.foreground(repo, [p1.node()])
2364 foreground = obsutil.foreground(repo, [p1.node()])
2365 # note: the <node> variable contains a random identifier
2365 # note: the <node> variable contains a random identifier
2366 if repo[node].node() in foreground:
2366 if repo[node].node() in foreground:
2367 pass # allow updating to successors
2367 pass # allow updating to successors
2368 else:
2368 else:
2369 msg = _(b"uncommitted changes")
2369 msg = _(b"uncommitted changes")
2370 hint = _(b"commit or update --clean to discard changes")
2370 hint = _(b"commit or update --clean to discard changes")
2371 raise error.UpdateAbort(msg, hint=hint)
2371 raise error.UpdateAbort(msg, hint=hint)
2372 else:
2372 else:
2373 # Allow jumping branches if clean and specific rev given
2373 # Allow jumping branches if clean and specific rev given
2374 pass
2374 pass
2375
2375
2376 if overwrite:
2376 if overwrite:
2377 pas = [wc]
2377 pas = [wc]
2378 elif not branchmerge:
2378 elif not branchmerge:
2379 pas = [p1]
2379 pas = [p1]
2380
2380
2381 # deprecated config: merge.followcopies
2381 # deprecated config: merge.followcopies
2382 followcopies = repo.ui.configbool(b'merge', b'followcopies')
2382 followcopies = repo.ui.configbool(b'merge', b'followcopies')
2383 if overwrite:
2383 if overwrite:
2384 followcopies = False
2384 followcopies = False
2385 elif not pas[0]:
2385 elif not pas[0]:
2386 followcopies = False
2386 followcopies = False
2387 if not branchmerge and not wc.dirty(missing=True):
2387 if not branchmerge and not wc.dirty(missing=True):
2388 followcopies = False
2388 followcopies = False
2389
2389
2390 ### calculate phase
2390 ### calculate phase
2391 actionbyfile, diverge, renamedelete = calculateupdates(
2391 actionbyfile, diverge, renamedelete = calculateupdates(
2392 repo,
2392 repo,
2393 wc,
2393 wc,
2394 p2,
2394 p2,
2395 pas,
2395 pas,
2396 branchmerge,
2396 branchmerge,
2397 force,
2397 force,
2398 mergeancestor,
2398 mergeancestor,
2399 followcopies,
2399 followcopies,
2400 matcher=matcher,
2400 matcher=matcher,
2401 mergeforce=mergeforce,
2401 mergeforce=mergeforce,
2402 )
2402 )
2403
2403
2404 if updatecheck == UPDATECHECK_NO_CONFLICT:
2404 if updatecheck == UPDATECHECK_NO_CONFLICT:
2405 for f, (m, args, msg) in pycompat.iteritems(actionbyfile):
2405 for f, (m, args, msg) in pycompat.iteritems(actionbyfile):
2406 if m not in (
2406 if m not in (
2407 ACTION_GET,
2407 ACTION_GET,
2408 ACTION_KEEP,
2408 ACTION_KEEP,
2409 ACTION_EXEC,
2409 ACTION_EXEC,
2410 ACTION_REMOVE,
2410 ACTION_REMOVE,
2411 ACTION_PATH_CONFLICT_RESOLVE,
2411 ACTION_PATH_CONFLICT_RESOLVE,
2412 ):
2412 ):
2413 msg = _(b"conflicting changes")
2413 msg = _(b"conflicting changes")
2414 hint = _(b"commit or update --clean to discard changes")
2414 hint = _(b"commit or update --clean to discard changes")
2415 raise error.Abort(msg, hint=hint)
2415 raise error.Abort(msg, hint=hint)
2416
2416
2417 # Prompt and create actions. Most of this is in the resolve phase
2417 # Prompt and create actions. Most of this is in the resolve phase
2418 # already, but we can't handle .hgsubstate in filemerge or
2418 # already, but we can't handle .hgsubstate in filemerge or
2419 # subrepoutil.submerge yet so we have to keep prompting for it.
2419 # subrepoutil.submerge yet so we have to keep prompting for it.
2420 if b'.hgsubstate' in actionbyfile:
2420 if b'.hgsubstate' in actionbyfile:
2421 f = b'.hgsubstate'
2421 f = b'.hgsubstate'
2422 m, args, msg = actionbyfile[f]
2422 m, args, msg = actionbyfile[f]
2423 prompts = filemerge.partextras(labels)
2423 prompts = filemerge.partextras(labels)
2424 prompts[b'f'] = f
2424 prompts[b'f'] = f
2425 if m == ACTION_CHANGED_DELETED:
2425 if m == ACTION_CHANGED_DELETED:
2426 if repo.ui.promptchoice(
2426 if repo.ui.promptchoice(
2427 _(
2427 _(
2428 b"local%(l)s changed %(f)s which other%(o)s deleted\n"
2428 b"local%(l)s changed %(f)s which other%(o)s deleted\n"
2429 b"use (c)hanged version or (d)elete?"
2429 b"use (c)hanged version or (d)elete?"
2430 b"$$ &Changed $$ &Delete"
2430 b"$$ &Changed $$ &Delete"
2431 )
2431 )
2432 % prompts,
2432 % prompts,
2433 0,
2433 0,
2434 ):
2434 ):
2435 actionbyfile[f] = (ACTION_REMOVE, None, b'prompt delete')
2435 actionbyfile[f] = (ACTION_REMOVE, None, b'prompt delete')
2436 elif f in p1:
2436 elif f in p1:
2437 actionbyfile[f] = (
2437 actionbyfile[f] = (
2438 ACTION_ADD_MODIFIED,
2438 ACTION_ADD_MODIFIED,
2439 None,
2439 None,
2440 b'prompt keep',
2440 b'prompt keep',
2441 )
2441 )
2442 else:
2442 else:
2443 actionbyfile[f] = (ACTION_ADD, None, b'prompt keep')
2443 actionbyfile[f] = (ACTION_ADD, None, b'prompt keep')
2444 elif m == ACTION_DELETED_CHANGED:
2444 elif m == ACTION_DELETED_CHANGED:
2445 f1, f2, fa, move, anc = args
2445 f1, f2, fa, move, anc = args
2446 flags = p2[f2].flags()
2446 flags = p2[f2].flags()
2447 if (
2447 if (
2448 repo.ui.promptchoice(
2448 repo.ui.promptchoice(
2449 _(
2449 _(
2450 b"other%(o)s changed %(f)s which local%(l)s deleted\n"
2450 b"other%(o)s changed %(f)s which local%(l)s deleted\n"
2451 b"use (c)hanged version or leave (d)eleted?"
2451 b"use (c)hanged version or leave (d)eleted?"
2452 b"$$ &Changed $$ &Deleted"
2452 b"$$ &Changed $$ &Deleted"
2453 )
2453 )
2454 % prompts,
2454 % prompts,
2455 0,
2455 0,
2456 )
2456 )
2457 == 0
2457 == 0
2458 ):
2458 ):
2459 actionbyfile[f] = (
2459 actionbyfile[f] = (
2460 ACTION_GET,
2460 ACTION_GET,
2461 (flags, False),
2461 (flags, False),
2462 b'prompt recreating',
2462 b'prompt recreating',
2463 )
2463 )
2464 else:
2464 else:
2465 del actionbyfile[f]
2465 del actionbyfile[f]
2466
2466
2467 # Convert to dictionary-of-lists format
2467 # Convert to dictionary-of-lists format
2468 actions = emptyactions()
2468 actions = emptyactions()
2469 for f, (m, args, msg) in pycompat.iteritems(actionbyfile):
2469 for f, (m, args, msg) in pycompat.iteritems(actionbyfile):
2470 if m not in actions:
2470 if m not in actions:
2471 actions[m] = []
2471 actions[m] = []
2472 actions[m].append((f, args, msg))
2472 actions[m].append((f, args, msg))
2473
2473
2474 if not util.fscasesensitive(repo.path):
2474 if not util.fscasesensitive(repo.path):
2475 # check collision between files only in p2 for clean update
2475 # check collision between files only in p2 for clean update
2476 if not branchmerge and (
2476 if not branchmerge and (
2477 force or not wc.dirty(missing=True, branch=False)
2477 force or not wc.dirty(missing=True, branch=False)
2478 ):
2478 ):
2479 _checkcollision(repo, p2.manifest(), None)
2479 _checkcollision(repo, p2.manifest(), None)
2480 else:
2480 else:
2481 _checkcollision(repo, wc.manifest(), actions)
2481 _checkcollision(repo, wc.manifest(), actions)
2482
2482
2483 # divergent renames
2483 # divergent renames
2484 for f, fl in sorted(pycompat.iteritems(diverge)):
2484 for f, fl in sorted(pycompat.iteritems(diverge)):
2485 repo.ui.warn(
2485 repo.ui.warn(
2486 _(
2486 _(
2487 b"note: possible conflict - %s was renamed "
2487 b"note: possible conflict - %s was renamed "
2488 b"multiple times to:\n"
2488 b"multiple times to:\n"
2489 )
2489 )
2490 % f
2490 % f
2491 )
2491 )
2492 for nf in sorted(fl):
2492 for nf in sorted(fl):
2493 repo.ui.warn(b" %s\n" % nf)
2493 repo.ui.warn(b" %s\n" % nf)
2494
2494
2495 # rename and delete
2495 # rename and delete
2496 for f, fl in sorted(pycompat.iteritems(renamedelete)):
2496 for f, fl in sorted(pycompat.iteritems(renamedelete)):
2497 repo.ui.warn(
2497 repo.ui.warn(
2498 _(
2498 _(
2499 b"note: possible conflict - %s was deleted "
2499 b"note: possible conflict - %s was deleted "
2500 b"and renamed to:\n"
2500 b"and renamed to:\n"
2501 )
2501 )
2502 % f
2502 % f
2503 )
2503 )
2504 for nf in sorted(fl):
2504 for nf in sorted(fl):
2505 repo.ui.warn(b" %s\n" % nf)
2505 repo.ui.warn(b" %s\n" % nf)
2506
2506
2507 ### apply phase
2507 ### apply phase
2508 if not branchmerge: # just jump to the new rev
2508 if not branchmerge: # just jump to the new rev
2509 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, b''
2509 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, b''
2510 if not partial and not wc.isinmemory():
2510 if not partial and not wc.isinmemory():
2511 repo.hook(b'preupdate', throw=True, parent1=xp1, parent2=xp2)
2511 repo.hook(b'preupdate', throw=True, parent1=xp1, parent2=xp2)
2512 # note that we're in the middle of an update
2512 # note that we're in the middle of an update
2513 repo.vfs.write(b'updatestate', p2.hex())
2513 repo.vfs.write(b'updatestate', p2.hex())
2514
2514
2515 # Advertise fsmonitor when its presence could be useful.
2515 # Advertise fsmonitor when its presence could be useful.
2516 #
2516 #
2517 # We only advertise when performing an update from an empty working
2517 # We only advertise when performing an update from an empty working
2518 # directory. This typically only occurs during initial clone.
2518 # directory. This typically only occurs during initial clone.
2519 #
2519 #
2520 # We give users a mechanism to disable the warning in case it is
2520 # We give users a mechanism to disable the warning in case it is
2521 # annoying.
2521 # annoying.
2522 #
2522 #
2523 # We only allow on Linux and MacOS because that's where fsmonitor is
2523 # We only allow on Linux and MacOS because that's where fsmonitor is
2524 # considered stable.
2524 # considered stable.
2525 fsmonitorwarning = repo.ui.configbool(b'fsmonitor', b'warn_when_unused')
2525 fsmonitorwarning = repo.ui.configbool(b'fsmonitor', b'warn_when_unused')
2526 fsmonitorthreshold = repo.ui.configint(
2526 fsmonitorthreshold = repo.ui.configint(
2527 b'fsmonitor', b'warn_update_file_count'
2527 b'fsmonitor', b'warn_update_file_count'
2528 )
2528 )
2529 try:
2529 try:
2530 # avoid cycle: extensions -> cmdutil -> merge
2530 # avoid cycle: extensions -> cmdutil -> merge
2531 from . import extensions
2531 from . import extensions
2532
2532
2533 extensions.find(b'fsmonitor')
2533 extensions.find(b'fsmonitor')
2534 fsmonitorenabled = repo.ui.config(b'fsmonitor', b'mode') != b'off'
2534 fsmonitorenabled = repo.ui.config(b'fsmonitor', b'mode') != b'off'
2535 # We intentionally don't look at whether fsmonitor has disabled
2535 # We intentionally don't look at whether fsmonitor has disabled
2536 # itself because a) fsmonitor may have already printed a warning
2536 # itself because a) fsmonitor may have already printed a warning
2537 # b) we only care about the config state here.
2537 # b) we only care about the config state here.
2538 except KeyError:
2538 except KeyError:
2539 fsmonitorenabled = False
2539 fsmonitorenabled = False
2540
2540
2541 if (
2541 if (
2542 fsmonitorwarning
2542 fsmonitorwarning
2543 and not fsmonitorenabled
2543 and not fsmonitorenabled
2544 and p1.node() == nullid
2544 and p1.node() == nullid
2545 and len(actions[ACTION_GET]) >= fsmonitorthreshold
2545 and len(actions[ACTION_GET]) >= fsmonitorthreshold
2546 and pycompat.sysplatform.startswith((b'linux', b'darwin'))
2546 and pycompat.sysplatform.startswith((b'linux', b'darwin'))
2547 ):
2547 ):
2548 repo.ui.warn(
2548 repo.ui.warn(
2549 _(
2549 _(
2550 b'(warning: large working directory being used without '
2550 b'(warning: large working directory being used without '
2551 b'fsmonitor enabled; enable fsmonitor to improve performance; '
2551 b'fsmonitor enabled; enable fsmonitor to improve performance; '
2552 b'see "hg help -e fsmonitor")\n'
2552 b'see "hg help -e fsmonitor")\n'
2553 )
2553 )
2554 )
2554 )
2555
2555
2556 updatedirstate = not partial and not wc.isinmemory()
2556 updatedirstate = not partial and not wc.isinmemory()
2557 wantfiledata = updatedirstate and not branchmerge
2557 wantfiledata = updatedirstate and not branchmerge
2558 stats, getfiledata = applyupdates(
2558 stats, getfiledata = applyupdates(
2559 repo, actions, wc, p2, overwrite, wantfiledata, labels=labels
2559 repo, actions, wc, p2, overwrite, wantfiledata, labels=labels
2560 )
2560 )
2561
2561
2562 if updatedirstate:
2562 if updatedirstate:
2563 with repo.dirstate.parentchange():
2563 with repo.dirstate.parentchange():
2564 repo.setparents(fp1, fp2)
2564 repo.setparents(fp1, fp2)
2565 recordupdates(repo, actions, branchmerge, getfiledata)
2565 recordupdates(repo, actions, branchmerge, getfiledata)
2566 # update completed, clear state
2566 # update completed, clear state
2567 util.unlink(repo.vfs.join(b'updatestate'))
2567 util.unlink(repo.vfs.join(b'updatestate'))
2568
2568
2569 if not branchmerge:
2569 if not branchmerge:
2570 repo.dirstate.setbranch(p2.branch())
2570 repo.dirstate.setbranch(p2.branch())
2571
2571
2572 # If we're updating to a location, clean up any stale temporary includes
2572 # If we're updating to a location, clean up any stale temporary includes
2573 # (ex: this happens during hg rebase --abort).
2573 # (ex: this happens during hg rebase --abort).
2574 if not branchmerge:
2574 if not branchmerge:
2575 sparse.prunetemporaryincludes(repo)
2575 sparse.prunetemporaryincludes(repo)
2576
2576
2577 if not partial:
2577 if not partial:
2578 repo.hook(
2578 repo.hook(
2579 b'update', parent1=xp1, parent2=xp2, error=stats.unresolvedcount
2579 b'update', parent1=xp1, parent2=xp2, error=stats.unresolvedcount
2580 )
2580 )
2581 return stats
2581 return stats
2582
2582
2583
2583
2584 def graft(
2584 def graft(
2585 repo, ctx, base, labels=None, keepparent=False, keepconflictparent=False
2585 repo, ctx, base, labels=None, keepparent=False, keepconflictparent=False
2586 ):
2586 ):
2587 """Do a graft-like merge.
2587 """Do a graft-like merge.
2588
2588
2589 This is a merge where the merge ancestor is chosen such that one
2589 This is a merge where the merge ancestor is chosen such that one
2590 or more changesets are grafted onto the current changeset. In
2590 or more changesets are grafted onto the current changeset. In
2591 addition to the merge, this fixes up the dirstate to include only
2591 addition to the merge, this fixes up the dirstate to include only
2592 a single parent (if keepparent is False) and tries to duplicate any
2592 a single parent (if keepparent is False) and tries to duplicate any
2593 renames/copies appropriately.
2593 renames/copies appropriately.
2594
2594
2595 ctx - changeset to rebase
2595 ctx - changeset to rebase
2596 base - merge base, usually ctx.p1()
2596 base - merge base, usually ctx.p1()
2597 labels - merge labels eg ['local', 'graft']
2597 labels - merge labels eg ['local', 'graft']
2598 keepparent - keep second parent if any
2598 keepparent - keep second parent if any
2599 keepconflictparent - if unresolved, keep parent used for the merge
2599 keepconflictparent - if unresolved, keep parent used for the merge
2600
2600
2601 """
2601 """
2602 # If we're grafting a descendant onto an ancestor, be sure to pass
2602 # If we're grafting a descendant onto an ancestor, be sure to pass
2603 # mergeancestor=True to update. This does two things: 1) allows the merge if
2603 # mergeancestor=True to update. This does two things: 1) allows the merge if
2604 # the destination is the same as the parent of the ctx (so we can use graft
2604 # the destination is the same as the parent of the ctx (so we can use graft
2605 # to copy commits), and 2) informs update that the incoming changes are
2605 # to copy commits), and 2) informs update that the incoming changes are
2606 # newer than the destination so it doesn't prompt about "remote changed foo
2606 # newer than the destination so it doesn't prompt about "remote changed foo
2607 # which local deleted".
2607 # which local deleted".
2608 mergeancestor = repo.changelog.isancestor(repo[b'.'].node(), ctx.node())
2608 pctx = repo[b'.']
2609 mergeancestor = repo.changelog.isancestor(pctx.node(), ctx.node())
2609
2610
2610 stats = update(
2611 stats = update(
2611 repo,
2612 repo,
2612 ctx.node(),
2613 ctx.node(),
2613 True,
2614 True,
2614 True,
2615 True,
2615 base.node(),
2616 base.node(),
2616 mergeancestor=mergeancestor,
2617 mergeancestor=mergeancestor,
2617 labels=labels,
2618 labels=labels,
2618 )
2619 )
2619
2620
2620 if keepconflictparent and stats.unresolvedcount:
2621 if keepconflictparent and stats.unresolvedcount:
2621 pother = ctx.node()
2622 pother = ctx.node()
2622 else:
2623 else:
2623 pother = nullid
2624 pother = nullid
2624 parents = ctx.parents()
2625 parents = ctx.parents()
2625 if keepparent and len(parents) == 2 and base in parents:
2626 if keepparent and len(parents) == 2 and base in parents:
2626 parents.remove(base)
2627 parents.remove(base)
2627 pother = parents[0].node()
2628 pother = parents[0].node()
2628
2629
2629 with repo.dirstate.parentchange():
2630 with repo.dirstate.parentchange():
2630 repo.setparents(repo[b'.'].node(), pother)
2631 repo.setparents(pctx.node(), pother)
2631 repo.dirstate.write(repo.currenttransaction())
2632 repo.dirstate.write(repo.currenttransaction())
2632 # fix up dirstate for copies and renames
2633 # fix up dirstate for copies and renames
2633 copies.duplicatecopies(repo, repo[None], ctx.rev(), base.rev())
2634 copies.duplicatecopies(repo, repo[None], ctx.rev(), base.rev())
2634 return stats
2635 return stats
2635
2636
2636
2637
2637 def purge(
2638 def purge(
2638 repo,
2639 repo,
2639 matcher,
2640 matcher,
2640 ignored=False,
2641 ignored=False,
2641 removeemptydirs=True,
2642 removeemptydirs=True,
2642 removefiles=True,
2643 removefiles=True,
2643 abortonerror=False,
2644 abortonerror=False,
2644 noop=False,
2645 noop=False,
2645 ):
2646 ):
2646 """Purge the working directory of untracked files.
2647 """Purge the working directory of untracked files.
2647
2648
2648 ``matcher`` is a matcher configured to scan the working directory -
2649 ``matcher`` is a matcher configured to scan the working directory -
2649 potentially a subset.
2650 potentially a subset.
2650
2651
2651 ``ignored`` controls whether ignored files should also be purged.
2652 ``ignored`` controls whether ignored files should also be purged.
2652
2653
2653 ``removeemptydirs`` controls whether empty directories should be removed.
2654 ``removeemptydirs`` controls whether empty directories should be removed.
2654
2655
2655 ``removefiles`` controls whether files are removed.
2656 ``removefiles`` controls whether files are removed.
2656
2657
2657 ``abortonerror`` causes an exception to be raised if an error occurs
2658 ``abortonerror`` causes an exception to be raised if an error occurs
2658 deleting a file or directory.
2659 deleting a file or directory.
2659
2660
2660 ``noop`` controls whether to actually remove files. If not defined, actions
2661 ``noop`` controls whether to actually remove files. If not defined, actions
2661 will be taken.
2662 will be taken.
2662
2663
2663 Returns an iterable of relative paths in the working directory that were
2664 Returns an iterable of relative paths in the working directory that were
2664 or would be removed.
2665 or would be removed.
2665 """
2666 """
2666
2667
2667 def remove(removefn, path):
2668 def remove(removefn, path):
2668 try:
2669 try:
2669 removefn(path)
2670 removefn(path)
2670 except OSError:
2671 except OSError:
2671 m = _(b'%s cannot be removed') % path
2672 m = _(b'%s cannot be removed') % path
2672 if abortonerror:
2673 if abortonerror:
2673 raise error.Abort(m)
2674 raise error.Abort(m)
2674 else:
2675 else:
2675 repo.ui.warn(_(b'warning: %s\n') % m)
2676 repo.ui.warn(_(b'warning: %s\n') % m)
2676
2677
2677 # There's no API to copy a matcher. So mutate the passed matcher and
2678 # There's no API to copy a matcher. So mutate the passed matcher and
2678 # restore it when we're done.
2679 # restore it when we're done.
2679 oldtraversedir = matcher.traversedir
2680 oldtraversedir = matcher.traversedir
2680
2681
2681 res = []
2682 res = []
2682
2683
2683 try:
2684 try:
2684 if removeemptydirs:
2685 if removeemptydirs:
2685 directories = []
2686 directories = []
2686 matcher.traversedir = directories.append
2687 matcher.traversedir = directories.append
2687
2688
2688 status = repo.status(match=matcher, ignored=ignored, unknown=True)
2689 status = repo.status(match=matcher, ignored=ignored, unknown=True)
2689
2690
2690 if removefiles:
2691 if removefiles:
2691 for f in sorted(status.unknown + status.ignored):
2692 for f in sorted(status.unknown + status.ignored):
2692 if not noop:
2693 if not noop:
2693 repo.ui.note(_(b'removing file %s\n') % f)
2694 repo.ui.note(_(b'removing file %s\n') % f)
2694 remove(repo.wvfs.unlink, f)
2695 remove(repo.wvfs.unlink, f)
2695 res.append(f)
2696 res.append(f)
2696
2697
2697 if removeemptydirs:
2698 if removeemptydirs:
2698 for f in sorted(directories, reverse=True):
2699 for f in sorted(directories, reverse=True):
2699 if matcher(f) and not repo.wvfs.listdir(f):
2700 if matcher(f) and not repo.wvfs.listdir(f):
2700 if not noop:
2701 if not noop:
2701 repo.ui.note(_(b'removing directory %s\n') % f)
2702 repo.ui.note(_(b'removing directory %s\n') % f)
2702 remove(repo.wvfs.rmdir, f)
2703 remove(repo.wvfs.rmdir, f)
2703 res.append(f)
2704 res.append(f)
2704
2705
2705 return res
2706 return res
2706
2707
2707 finally:
2708 finally:
2708 matcher.traversedir = oldtraversedir
2709 matcher.traversedir = oldtraversedir
General Comments 0
You need to be logged in to leave comments. Login now