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