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