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