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