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