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