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