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