##// END OF EJS Templates
commit: look-up new revision once...
Joerg Sonnenberger -
r47066:72f5280e default
parent child Browse files
Show More
@@ -1,493 +1,494
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 nullid,
13 nullid,
14 nullrev,
14 nullrev,
15 )
15 )
16
16
17 from . import (
17 from . import (
18 context,
18 context,
19 mergestate,
19 mergestate,
20 metadata,
20 metadata,
21 phases,
21 phases,
22 scmutil,
22 scmutil,
23 subrepoutil,
23 subrepoutil,
24 )
24 )
25
25
26
26
27 def _write_copy_meta(repo):
27 def _write_copy_meta(repo):
28 """return a (changelog, filelog) boolean tuple
28 """return a (changelog, filelog) boolean tuple
29
29
30 changelog: copy related information should be stored in the changeset
30 changelog: copy related information should be stored in the changeset
31 filelof: copy related information should be written in the file revision
31 filelof: copy related information should be written in the file revision
32 """
32 """
33 if repo.filecopiesmode == b'changeset-sidedata':
33 if repo.filecopiesmode == b'changeset-sidedata':
34 writechangesetcopy = True
34 writechangesetcopy = True
35 writefilecopymeta = True
35 writefilecopymeta = True
36 else:
36 else:
37 writecopiesto = repo.ui.config(b'experimental', b'copies.write-to')
37 writecopiesto = repo.ui.config(b'experimental', b'copies.write-to')
38 writefilecopymeta = writecopiesto != b'changeset-only'
38 writefilecopymeta = writecopiesto != b'changeset-only'
39 writechangesetcopy = writecopiesto in (
39 writechangesetcopy = writecopiesto in (
40 b'changeset-only',
40 b'changeset-only',
41 b'compatibility',
41 b'compatibility',
42 )
42 )
43 return writechangesetcopy, writefilecopymeta
43 return writechangesetcopy, writefilecopymeta
44
44
45
45
46 def commitctx(repo, ctx, error=False, origctx=None):
46 def commitctx(repo, ctx, error=False, origctx=None):
47 """Add a new revision to the target repository.
47 """Add a new revision to the target repository.
48 Revision information is passed via the context argument.
48 Revision information is passed via the context argument.
49
49
50 ctx.files() should list all files involved in this commit, i.e.
50 ctx.files() should list all files involved in this commit, i.e.
51 modified/added/removed files. On merge, it may be wider than the
51 modified/added/removed files. On merge, it may be wider than the
52 ctx.files() to be committed, since any file nodes derived directly
52 ctx.files() to be committed, since any file nodes derived directly
53 from p1 or p2 are excluded from the committed ctx.files().
53 from p1 or p2 are excluded from the committed ctx.files().
54
54
55 origctx is for convert to work around the problem that bug
55 origctx is for convert to work around the problem that bug
56 fixes to the files list in changesets change hashes. For
56 fixes to the files list in changesets change hashes. For
57 convert to be the identity, it can pass an origctx and this
57 convert to be the identity, it can pass an origctx and this
58 function will use the same files list when it makes sense to
58 function will use the same files list when it makes sense to
59 do so.
59 do so.
60 """
60 """
61 repo = repo.unfiltered()
61 repo = repo.unfiltered()
62
62
63 p1, p2 = ctx.p1(), ctx.p2()
63 p1, p2 = ctx.p1(), ctx.p2()
64 user = ctx.user()
64 user = ctx.user()
65
65
66 with repo.lock(), repo.transaction(b"commit") as tr:
66 with repo.lock(), repo.transaction(b"commit") as tr:
67 mn, files = _prepare_files(tr, ctx, error=error, origctx=origctx)
67 mn, files = _prepare_files(tr, ctx, error=error, origctx=origctx)
68
68
69 extra = ctx.extra().copy()
69 extra = ctx.extra().copy()
70
70
71 if extra is not None:
71 if extra is not None:
72 for name in (
72 for name in (
73 b'p1copies',
73 b'p1copies',
74 b'p2copies',
74 b'p2copies',
75 b'filesadded',
75 b'filesadded',
76 b'filesremoved',
76 b'filesremoved',
77 ):
77 ):
78 extra.pop(name, None)
78 extra.pop(name, None)
79 if repo.changelog._copiesstorage == b'extra':
79 if repo.changelog._copiesstorage == b'extra':
80 extra = _extra_with_copies(repo, extra, files)
80 extra = _extra_with_copies(repo, extra, files)
81
81
82 # save the tip to check whether we actually committed anything
82 # save the tip to check whether we actually committed anything
83 oldtip = repo.changelog.tiprev()
83 oldtip = repo.changelog.tiprev()
84
84
85 # update changelog
85 # update changelog
86 repo.ui.note(_(b"committing changelog\n"))
86 repo.ui.note(_(b"committing changelog\n"))
87 repo.changelog.delayupdate(tr)
87 repo.changelog.delayupdate(tr)
88 n = repo.changelog.add(
88 n = repo.changelog.add(
89 mn,
89 mn,
90 files,
90 files,
91 ctx.description(),
91 ctx.description(),
92 tr,
92 tr,
93 p1.node(),
93 p1.node(),
94 p2.node(),
94 p2.node(),
95 user,
95 user,
96 ctx.date(),
96 ctx.date(),
97 extra,
97 extra,
98 )
98 )
99 rev = repo[n].rev()
99 xp1, xp2 = p1.hex(), p2 and p2.hex() or b''
100 xp1, xp2 = p1.hex(), p2 and p2.hex() or b''
100 repo.hook(
101 repo.hook(
101 b'pretxncommit',
102 b'pretxncommit',
102 throw=True,
103 throw=True,
103 node=hex(n),
104 node=hex(n),
104 parent1=xp1,
105 parent1=xp1,
105 parent2=xp2,
106 parent2=xp2,
106 )
107 )
107 # set the new commit is proper phase
108 # set the new commit is proper phase
108 targetphase = subrepoutil.newcommitphase(repo.ui, ctx)
109 targetphase = subrepoutil.newcommitphase(repo.ui, ctx)
109
110
110 # prevent unmarking changesets as public on recommit
111 # prevent unmarking changesets as public on recommit
111 waspublic = oldtip == repo.changelog.tiprev() and not repo[n].phase()
112 waspublic = oldtip == repo.changelog.tiprev() and not repo[rev].phase()
112
113
113 if targetphase and not waspublic:
114 if targetphase and not waspublic:
114 # retract boundary do not alter parent changeset.
115 # retract boundary do not alter parent changeset.
115 # if a parent have higher the resulting phase will
116 # if a parent have higher the resulting phase will
116 # be compliant anyway
117 # be compliant anyway
117 #
118 #
118 # if minimal phase was 0 we don't need to retract anything
119 # if minimal phase was 0 we don't need to retract anything
119 phases.registernew(repo, tr, targetphase, [repo[n].rev()])
120 phases.registernew(repo, tr, targetphase, [rev])
120 return n
121 return n
121
122
122
123
123 def _prepare_files(tr, ctx, error=False, origctx=None):
124 def _prepare_files(tr, ctx, error=False, origctx=None):
124 repo = ctx.repo()
125 repo = ctx.repo()
125 p1 = ctx.p1()
126 p1 = ctx.p1()
126
127
127 writechangesetcopy, writefilecopymeta = _write_copy_meta(repo)
128 writechangesetcopy, writefilecopymeta = _write_copy_meta(repo)
128 files = metadata.ChangingFiles()
129 files = metadata.ChangingFiles()
129 ms = mergestate.mergestate.read(repo)
130 ms = mergestate.mergestate.read(repo)
130 salvaged = _get_salvaged(repo, ms, ctx)
131 salvaged = _get_salvaged(repo, ms, ctx)
131 for s in salvaged:
132 for s in salvaged:
132 files.mark_salvaged(s)
133 files.mark_salvaged(s)
133
134
134 if ctx.manifestnode():
135 if ctx.manifestnode():
135 # reuse an existing manifest revision
136 # reuse an existing manifest revision
136 repo.ui.debug(b'reusing known manifest\n')
137 repo.ui.debug(b'reusing known manifest\n')
137 mn = ctx.manifestnode()
138 mn = ctx.manifestnode()
138 files.update_touched(ctx.files())
139 files.update_touched(ctx.files())
139 if writechangesetcopy:
140 if writechangesetcopy:
140 files.update_added(ctx.filesadded())
141 files.update_added(ctx.filesadded())
141 files.update_removed(ctx.filesremoved())
142 files.update_removed(ctx.filesremoved())
142 elif not ctx.files():
143 elif not ctx.files():
143 repo.ui.debug(b'reusing manifest from p1 (no file change)\n')
144 repo.ui.debug(b'reusing manifest from p1 (no file change)\n')
144 mn = p1.manifestnode()
145 mn = p1.manifestnode()
145 else:
146 else:
146 mn = _process_files(tr, ctx, ms, files, error=error)
147 mn = _process_files(tr, ctx, ms, files, error=error)
147
148
148 if origctx and origctx.manifestnode() == mn:
149 if origctx and origctx.manifestnode() == mn:
149 origfiles = origctx.files()
150 origfiles = origctx.files()
150 assert files.touched.issubset(origfiles)
151 assert files.touched.issubset(origfiles)
151 files.update_touched(origfiles)
152 files.update_touched(origfiles)
152
153
153 if writechangesetcopy:
154 if writechangesetcopy:
154 files.update_copies_from_p1(ctx.p1copies())
155 files.update_copies_from_p1(ctx.p1copies())
155 files.update_copies_from_p2(ctx.p2copies())
156 files.update_copies_from_p2(ctx.p2copies())
156
157
157 return mn, files
158 return mn, files
158
159
159
160
160 def _get_salvaged(repo, ms, ctx):
161 def _get_salvaged(repo, ms, ctx):
161 """returns a list of salvaged files
162 """returns a list of salvaged files
162
163
163 returns empty list if config option which process salvaged files are
164 returns empty list if config option which process salvaged files are
164 not enabled"""
165 not enabled"""
165 salvaged = []
166 salvaged = []
166 copy_sd = repo.filecopiesmode == b'changeset-sidedata'
167 copy_sd = repo.filecopiesmode == b'changeset-sidedata'
167 if copy_sd and len(ctx.parents()) > 1:
168 if copy_sd and len(ctx.parents()) > 1:
168 if ms.active():
169 if ms.active():
169 for fname in sorted(ms.allextras().keys()):
170 for fname in sorted(ms.allextras().keys()):
170 might_removed = ms.extras(fname).get(b'merge-removal-candidate')
171 might_removed = ms.extras(fname).get(b'merge-removal-candidate')
171 if might_removed == b'yes':
172 if might_removed == b'yes':
172 if fname in ctx:
173 if fname in ctx:
173 salvaged.append(fname)
174 salvaged.append(fname)
174 return salvaged
175 return salvaged
175
176
176
177
177 def _process_files(tr, ctx, ms, files, error=False):
178 def _process_files(tr, ctx, ms, files, error=False):
178 repo = ctx.repo()
179 repo = ctx.repo()
179 p1 = ctx.p1()
180 p1 = ctx.p1()
180 p2 = ctx.p2()
181 p2 = ctx.p2()
181
182
182 writechangesetcopy, writefilecopymeta = _write_copy_meta(repo)
183 writechangesetcopy, writefilecopymeta = _write_copy_meta(repo)
183
184
184 m1ctx = p1.manifestctx()
185 m1ctx = p1.manifestctx()
185 m2ctx = p2.manifestctx()
186 m2ctx = p2.manifestctx()
186 mctx = m1ctx.copy()
187 mctx = m1ctx.copy()
187
188
188 m = mctx.read()
189 m = mctx.read()
189 m1 = m1ctx.read()
190 m1 = m1ctx.read()
190 m2 = m2ctx.read()
191 m2 = m2ctx.read()
191
192
192 # check in files
193 # check in files
193 added = []
194 added = []
194 removed = list(ctx.removed())
195 removed = list(ctx.removed())
195 linkrev = len(repo)
196 linkrev = len(repo)
196 repo.ui.note(_(b"committing files:\n"))
197 repo.ui.note(_(b"committing files:\n"))
197 uipathfn = scmutil.getuipathfn(repo)
198 uipathfn = scmutil.getuipathfn(repo)
198 for f in sorted(ctx.modified() + ctx.added()):
199 for f in sorted(ctx.modified() + ctx.added()):
199 repo.ui.note(uipathfn(f) + b"\n")
200 repo.ui.note(uipathfn(f) + b"\n")
200 try:
201 try:
201 fctx = ctx[f]
202 fctx = ctx[f]
202 if fctx is None:
203 if fctx is None:
203 removed.append(f)
204 removed.append(f)
204 else:
205 else:
205 added.append(f)
206 added.append(f)
206 m[f], is_touched = _filecommit(
207 m[f], is_touched = _filecommit(
207 repo, fctx, m1, m2, linkrev, tr, writefilecopymeta, ms
208 repo, fctx, m1, m2, linkrev, tr, writefilecopymeta, ms
208 )
209 )
209 if is_touched:
210 if is_touched:
210 if is_touched == 'added':
211 if is_touched == 'added':
211 files.mark_added(f)
212 files.mark_added(f)
212 elif is_touched == 'merged':
213 elif is_touched == 'merged':
213 files.mark_merged(f)
214 files.mark_merged(f)
214 else:
215 else:
215 files.mark_touched(f)
216 files.mark_touched(f)
216 m.setflag(f, fctx.flags())
217 m.setflag(f, fctx.flags())
217 except OSError:
218 except OSError:
218 repo.ui.warn(_(b"trouble committing %s!\n") % uipathfn(f))
219 repo.ui.warn(_(b"trouble committing %s!\n") % uipathfn(f))
219 raise
220 raise
220 except IOError as inst:
221 except IOError as inst:
221 errcode = getattr(inst, 'errno', errno.ENOENT)
222 errcode = getattr(inst, 'errno', errno.ENOENT)
222 if error or errcode and errcode != errno.ENOENT:
223 if error or errcode and errcode != errno.ENOENT:
223 repo.ui.warn(_(b"trouble committing %s!\n") % uipathfn(f))
224 repo.ui.warn(_(b"trouble committing %s!\n") % uipathfn(f))
224 raise
225 raise
225
226
226 # update manifest
227 # update manifest
227 removed = [f for f in removed if f in m1 or f in m2]
228 removed = [f for f in removed if f in m1 or f in m2]
228 drop = sorted([f for f in removed if f in m])
229 drop = sorted([f for f in removed if f in m])
229 for f in drop:
230 for f in drop:
230 del m[f]
231 del m[f]
231 if p2.rev() == nullrev:
232 if p2.rev() == nullrev:
232 files.update_removed(removed)
233 files.update_removed(removed)
233 else:
234 else:
234 rf = metadata.get_removal_filter(ctx, (p1, p2, m1, m2))
235 rf = metadata.get_removal_filter(ctx, (p1, p2, m1, m2))
235 for f in removed:
236 for f in removed:
236 if not rf(f):
237 if not rf(f):
237 files.mark_removed(f)
238 files.mark_removed(f)
238
239
239 mn = _commit_manifest(tr, linkrev, ctx, mctx, m, files.touched, added, drop)
240 mn = _commit_manifest(tr, linkrev, ctx, mctx, m, files.touched, added, drop)
240
241
241 return mn
242 return mn
242
243
243
244
244 def _filecommit(
245 def _filecommit(
245 repo,
246 repo,
246 fctx,
247 fctx,
247 manifest1,
248 manifest1,
248 manifest2,
249 manifest2,
249 linkrev,
250 linkrev,
250 tr,
251 tr,
251 includecopymeta,
252 includecopymeta,
252 ms,
253 ms,
253 ):
254 ):
254 """
255 """
255 commit an individual file as part of a larger transaction
256 commit an individual file as part of a larger transaction
256
257
257 input:
258 input:
258
259
259 fctx: a file context with the content we are trying to commit
260 fctx: a file context with the content we are trying to commit
260 manifest1: manifest of changeset first parent
261 manifest1: manifest of changeset first parent
261 manifest2: manifest of changeset second parent
262 manifest2: manifest of changeset second parent
262 linkrev: revision number of the changeset being created
263 linkrev: revision number of the changeset being created
263 tr: current transation
264 tr: current transation
264 includecopymeta: boolean, set to False to skip storing the copy data
265 includecopymeta: boolean, set to False to skip storing the copy data
265 (only used by the Google specific feature of using
266 (only used by the Google specific feature of using
266 changeset extra as copy source of truth).
267 changeset extra as copy source of truth).
267 ms: mergestate object
268 ms: mergestate object
268
269
269 output: (filenode, touched)
270 output: (filenode, touched)
270
271
271 filenode: the filenode that should be used by this changeset
272 filenode: the filenode that should be used by this changeset
272 touched: one of: None (mean untouched), 'added' or 'modified'
273 touched: one of: None (mean untouched), 'added' or 'modified'
273 """
274 """
274
275
275 fname = fctx.path()
276 fname = fctx.path()
276 fparent1 = manifest1.get(fname, nullid)
277 fparent1 = manifest1.get(fname, nullid)
277 fparent2 = manifest2.get(fname, nullid)
278 fparent2 = manifest2.get(fname, nullid)
278 touched = None
279 touched = None
279 if fparent1 == fparent2 == nullid:
280 if fparent1 == fparent2 == nullid:
280 touched = 'added'
281 touched = 'added'
281
282
282 if isinstance(fctx, context.filectx):
283 if isinstance(fctx, context.filectx):
283 # This block fast path most comparisons which are usually done. It
284 # This block fast path most comparisons which are usually done. It
284 # assumes that bare filectx is used and no merge happened, hence no
285 # assumes that bare filectx is used and no merge happened, hence no
285 # need to create a new file revision in this case.
286 # need to create a new file revision in this case.
286 node = fctx.filenode()
287 node = fctx.filenode()
287 if node in [fparent1, fparent2]:
288 if node in [fparent1, fparent2]:
288 repo.ui.debug(b'reusing %s filelog entry\n' % fname)
289 repo.ui.debug(b'reusing %s filelog entry\n' % fname)
289 if (
290 if (
290 fparent1 != nullid and manifest1.flags(fname) != fctx.flags()
291 fparent1 != nullid and manifest1.flags(fname) != fctx.flags()
291 ) or (
292 ) or (
292 fparent2 != nullid and manifest2.flags(fname) != fctx.flags()
293 fparent2 != nullid and manifest2.flags(fname) != fctx.flags()
293 ):
294 ):
294 touched = 'modified'
295 touched = 'modified'
295 return node, touched
296 return node, touched
296
297
297 flog = repo.file(fname)
298 flog = repo.file(fname)
298 meta = {}
299 meta = {}
299 cfname = fctx.copysource()
300 cfname = fctx.copysource()
300 fnode = None
301 fnode = None
301
302
302 if cfname and cfname != fname:
303 if cfname and cfname != fname:
303 # Mark the new revision of this file as a copy of another
304 # Mark the new revision of this file as a copy of another
304 # file. This copy data will effectively act as a parent
305 # file. This copy data will effectively act as a parent
305 # of this new revision. If this is a merge, the first
306 # of this new revision. If this is a merge, the first
306 # parent will be the nullid (meaning "look up the copy data")
307 # parent will be the nullid (meaning "look up the copy data")
307 # and the second one will be the other parent. For example:
308 # and the second one will be the other parent. For example:
308 #
309 #
309 # 0 --- 1 --- 3 rev1 changes file foo
310 # 0 --- 1 --- 3 rev1 changes file foo
310 # \ / rev2 renames foo to bar and changes it
311 # \ / rev2 renames foo to bar and changes it
311 # \- 2 -/ rev3 should have bar with all changes and
312 # \- 2 -/ rev3 should have bar with all changes and
312 # should record that bar descends from
313 # should record that bar descends from
313 # bar in rev2 and foo in rev1
314 # bar in rev2 and foo in rev1
314 #
315 #
315 # this allows this merge to succeed:
316 # this allows this merge to succeed:
316 #
317 #
317 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
318 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
318 # \ / merging rev3 and rev4 should use bar@rev2
319 # \ / merging rev3 and rev4 should use bar@rev2
319 # \- 2 --- 4 as the merge base
320 # \- 2 --- 4 as the merge base
320 #
321 #
321
322
322 cnode = manifest1.get(cfname)
323 cnode = manifest1.get(cfname)
323 newfparent = fparent2
324 newfparent = fparent2
324
325
325 if manifest2: # branch merge
326 if manifest2: # branch merge
326 if fparent2 == nullid or cnode is None: # copied on remote side
327 if fparent2 == nullid or cnode is None: # copied on remote side
327 if cfname in manifest2:
328 if cfname in manifest2:
328 cnode = manifest2[cfname]
329 cnode = manifest2[cfname]
329 newfparent = fparent1
330 newfparent = fparent1
330
331
331 # Here, we used to search backwards through history to try to find
332 # Here, we used to search backwards through history to try to find
332 # where the file copy came from if the source of a copy was not in
333 # where the file copy came from if the source of a copy was not in
333 # the parent directory. However, this doesn't actually make sense to
334 # the parent directory. However, this doesn't actually make sense to
334 # do (what does a copy from something not in your working copy even
335 # do (what does a copy from something not in your working copy even
335 # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
336 # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
336 # the user that copy information was dropped, so if they didn't
337 # the user that copy information was dropped, so if they didn't
337 # expect this outcome it can be fixed, but this is the correct
338 # expect this outcome it can be fixed, but this is the correct
338 # behavior in this circumstance.
339 # behavior in this circumstance.
339
340
340 if cnode:
341 if cnode:
341 repo.ui.debug(b" %s: copy %s:%s\n" % (fname, cfname, hex(cnode)))
342 repo.ui.debug(b" %s: copy %s:%s\n" % (fname, cfname, hex(cnode)))
342 if includecopymeta:
343 if includecopymeta:
343 meta[b"copy"] = cfname
344 meta[b"copy"] = cfname
344 meta[b"copyrev"] = hex(cnode)
345 meta[b"copyrev"] = hex(cnode)
345 fparent1, fparent2 = nullid, newfparent
346 fparent1, fparent2 = nullid, newfparent
346 else:
347 else:
347 repo.ui.warn(
348 repo.ui.warn(
348 _(
349 _(
349 b"warning: can't find ancestor for '%s' "
350 b"warning: can't find ancestor for '%s' "
350 b"copied from '%s'!\n"
351 b"copied from '%s'!\n"
351 )
352 )
352 % (fname, cfname)
353 % (fname, cfname)
353 )
354 )
354
355
355 elif fparent1 == nullid:
356 elif fparent1 == nullid:
356 fparent1, fparent2 = fparent2, nullid
357 fparent1, fparent2 = fparent2, nullid
357 elif fparent2 != nullid:
358 elif fparent2 != nullid:
358 # is one parent an ancestor of the other?
359 # is one parent an ancestor of the other?
359 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
360 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
360 if fparent1 in fparentancestors:
361 if fparent1 in fparentancestors:
361 fparent1, fparent2 = fparent2, nullid
362 fparent1, fparent2 = fparent2, nullid
362 elif fparent2 in fparentancestors:
363 elif fparent2 in fparentancestors:
363 fparent2 = nullid
364 fparent2 = nullid
364 elif not fparentancestors:
365 elif not fparentancestors:
365 # TODO: this whole if-else might be simplified much more
366 # TODO: this whole if-else might be simplified much more
366 if (
367 if (
367 ms.active()
368 ms.active()
368 and ms.extras(fname).get(b'filenode-source') == b'other'
369 and ms.extras(fname).get(b'filenode-source') == b'other'
369 ):
370 ):
370 fparent1, fparent2 = fparent2, nullid
371 fparent1, fparent2 = fparent2, nullid
371
372
372 force_new_node = False
373 force_new_node = False
373 # The file might have been deleted by merge code and user explicitly choose
374 # The file might have been deleted by merge code and user explicitly choose
374 # to revert the file and keep it. The other case can be where there is
375 # to revert the file and keep it. The other case can be where there is
375 # change-delete or delete-change conflict and user explicitly choose to keep
376 # change-delete or delete-change conflict and user explicitly choose to keep
376 # the file. The goal is to create a new filenode for users explicit choices
377 # the file. The goal is to create a new filenode for users explicit choices
377 if (
378 if (
378 repo.ui.configbool(b'experimental', b'merge-track-salvaged')
379 repo.ui.configbool(b'experimental', b'merge-track-salvaged')
379 and ms.active()
380 and ms.active()
380 and ms.extras(fname).get(b'merge-removal-candidate') == b'yes'
381 and ms.extras(fname).get(b'merge-removal-candidate') == b'yes'
381 ):
382 ):
382 force_new_node = True
383 force_new_node = True
383 # is the file changed?
384 # is the file changed?
384 text = fctx.data()
385 text = fctx.data()
385 if fparent2 != nullid or meta or flog.cmp(fparent1, text) or force_new_node:
386 if fparent2 != nullid or meta or flog.cmp(fparent1, text) or force_new_node:
386 if touched is None: # do not overwrite added
387 if touched is None: # do not overwrite added
387 if fparent2 == nullid:
388 if fparent2 == nullid:
388 touched = 'modified'
389 touched = 'modified'
389 else:
390 else:
390 touched = 'merged'
391 touched = 'merged'
391 fnode = flog.add(text, meta, tr, linkrev, fparent1, fparent2)
392 fnode = flog.add(text, meta, tr, linkrev, fparent1, fparent2)
392 # are just the flags changed during merge?
393 # are just the flags changed during merge?
393 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
394 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
394 touched = 'modified'
395 touched = 'modified'
395 fnode = fparent1
396 fnode = fparent1
396 else:
397 else:
397 fnode = fparent1
398 fnode = fparent1
398 return fnode, touched
399 return fnode, touched
399
400
400
401
401 def _commit_manifest(tr, linkrev, ctx, mctx, manifest, files, added, drop):
402 def _commit_manifest(tr, linkrev, ctx, mctx, manifest, files, added, drop):
402 """make a new manifest entry (or reuse a new one)
403 """make a new manifest entry (or reuse a new one)
403
404
404 given an initialised manifest context and precomputed list of
405 given an initialised manifest context and precomputed list of
405 - files: files affected by the commit
406 - files: files affected by the commit
406 - added: new entries in the manifest
407 - added: new entries in the manifest
407 - drop: entries present in parents but absent of this one
408 - drop: entries present in parents but absent of this one
408
409
409 Create a new manifest revision, reuse existing ones if possible.
410 Create a new manifest revision, reuse existing ones if possible.
410
411
411 Return the nodeid of the manifest revision.
412 Return the nodeid of the manifest revision.
412 """
413 """
413 repo = ctx.repo()
414 repo = ctx.repo()
414
415
415 md = None
416 md = None
416
417
417 # all this is cached, so it is find to get them all from the ctx.
418 # all this is cached, so it is find to get them all from the ctx.
418 p1 = ctx.p1()
419 p1 = ctx.p1()
419 p2 = ctx.p2()
420 p2 = ctx.p2()
420 m1ctx = p1.manifestctx()
421 m1ctx = p1.manifestctx()
421
422
422 m1 = m1ctx.read()
423 m1 = m1ctx.read()
423
424
424 if not files:
425 if not files:
425 # if no "files" actually changed in terms of the changelog,
426 # if no "files" actually changed in terms of the changelog,
426 # try hard to detect unmodified manifest entry so that the
427 # try hard to detect unmodified manifest entry so that the
427 # exact same commit can be reproduced later on convert.
428 # exact same commit can be reproduced later on convert.
428 md = m1.diff(manifest, scmutil.matchfiles(repo, ctx.files()))
429 md = m1.diff(manifest, scmutil.matchfiles(repo, ctx.files()))
429 if not files and md:
430 if not files and md:
430 repo.ui.debug(
431 repo.ui.debug(
431 b'not reusing manifest (no file change in '
432 b'not reusing manifest (no file change in '
432 b'changelog, but manifest differs)\n'
433 b'changelog, but manifest differs)\n'
433 )
434 )
434 if files or md:
435 if files or md:
435 repo.ui.note(_(b"committing manifest\n"))
436 repo.ui.note(_(b"committing manifest\n"))
436 # we're using narrowmatch here since it's already applied at
437 # we're using narrowmatch here since it's already applied at
437 # other stages (such as dirstate.walk), so we're already
438 # other stages (such as dirstate.walk), so we're already
438 # ignoring things outside of narrowspec in most cases. The
439 # ignoring things outside of narrowspec in most cases. The
439 # one case where we might have files outside the narrowspec
440 # one case where we might have files outside the narrowspec
440 # at this point is merges, and we already error out in the
441 # at this point is merges, and we already error out in the
441 # case where the merge has files outside of the narrowspec,
442 # case where the merge has files outside of the narrowspec,
442 # so this is safe.
443 # so this is safe.
443 mn = mctx.write(
444 mn = mctx.write(
444 tr,
445 tr,
445 linkrev,
446 linkrev,
446 p1.manifestnode(),
447 p1.manifestnode(),
447 p2.manifestnode(),
448 p2.manifestnode(),
448 added,
449 added,
449 drop,
450 drop,
450 match=repo.narrowmatch(),
451 match=repo.narrowmatch(),
451 )
452 )
452 else:
453 else:
453 repo.ui.debug(
454 repo.ui.debug(
454 b'reusing manifest from p1 (listed files ' b'actually unchanged)\n'
455 b'reusing manifest from p1 (listed files ' b'actually unchanged)\n'
455 )
456 )
456 mn = p1.manifestnode()
457 mn = p1.manifestnode()
457
458
458 return mn
459 return mn
459
460
460
461
461 def _extra_with_copies(repo, extra, files):
462 def _extra_with_copies(repo, extra, files):
462 """encode copy information into a `extra` dictionnary"""
463 """encode copy information into a `extra` dictionnary"""
463 p1copies = files.copied_from_p1
464 p1copies = files.copied_from_p1
464 p2copies = files.copied_from_p2
465 p2copies = files.copied_from_p2
465 filesadded = files.added
466 filesadded = files.added
466 filesremoved = files.removed
467 filesremoved = files.removed
467 files = sorted(files.touched)
468 files = sorted(files.touched)
468 if not _write_copy_meta(repo)[1]:
469 if not _write_copy_meta(repo)[1]:
469 # If writing only to changeset extras, use None to indicate that
470 # If writing only to changeset extras, use None to indicate that
470 # no entry should be written. If writing to both, write an empty
471 # no entry should be written. If writing to both, write an empty
471 # entry to prevent the reader from falling back to reading
472 # entry to prevent the reader from falling back to reading
472 # filelogs.
473 # filelogs.
473 p1copies = p1copies or None
474 p1copies = p1copies or None
474 p2copies = p2copies or None
475 p2copies = p2copies or None
475 filesadded = filesadded or None
476 filesadded = filesadded or None
476 filesremoved = filesremoved or None
477 filesremoved = filesremoved or None
477
478
478 extrasentries = p1copies, p2copies, filesadded, filesremoved
479 extrasentries = p1copies, p2copies, filesadded, filesremoved
479 if extra is None and any(x is not None for x in extrasentries):
480 if extra is None and any(x is not None for x in extrasentries):
480 extra = {}
481 extra = {}
481 if p1copies is not None:
482 if p1copies is not None:
482 p1copies = metadata.encodecopies(files, p1copies)
483 p1copies = metadata.encodecopies(files, p1copies)
483 extra[b'p1copies'] = p1copies
484 extra[b'p1copies'] = p1copies
484 if p2copies is not None:
485 if p2copies is not None:
485 p2copies = metadata.encodecopies(files, p2copies)
486 p2copies = metadata.encodecopies(files, p2copies)
486 extra[b'p2copies'] = p2copies
487 extra[b'p2copies'] = p2copies
487 if filesadded is not None:
488 if filesadded is not None:
488 filesadded = metadata.encodefileindices(files, filesadded)
489 filesadded = metadata.encodefileindices(files, filesadded)
489 extra[b'filesadded'] = filesadded
490 extra[b'filesadded'] = filesadded
490 if filesremoved is not None:
491 if filesremoved is not None:
491 filesremoved = metadata.encodefileindices(files, filesremoved)
492 filesremoved = metadata.encodefileindices(files, filesremoved)
492 extra[b'filesremoved'] = filesremoved
493 extra[b'filesremoved'] = filesremoved
493 return extra
494 return extra
General Comments 0
You need to be logged in to leave comments. Login now