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