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