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