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