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