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