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