comparison mercurial/commit.py @ 45203:ae5c1a3bc339

commitctx: extract the function in a dedicated module the function have few callers (< 15) is quite long a mostly independent from the repository itself. It seems like a good candidate to reduce the bloatness of the localrepository class. Extracting it will help us cleaning the code up and splitting it into more reasonable-size function. We don't use a copy trick because the amount of code extract is quite small (<5%) and the de-indent means every single line change anyway. So this is not deemed valuable to do so. This is part of a larger refactoring/cleanup of the commitctx code to clarify and augment the logic gathering metadata useful for copy tracing. The current code is a tad too long and entangled to make such update easy. Differential Revision: https://phab.mercurial-scm.org/D8709
author Pierre-Yves David <pierre-yves.david@octobus.net>
date Mon, 06 Jul 2020 23:14:52 +0200
parents
children ce9ee81df9ff
comparison
equal deleted inserted replaced
45202:31393ec06cef 45203:ae5c1a3bc339
1 # commit.py - fonction to perform commit
2 #
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.
5
6 from __future__ import absolute_import
7
8 import errno
9 import weakref
10
11 from .i18n import _
12 from .node import (
13 hex,
14 nullrev,
15 )
16
17 from . import (
18 metadata,
19 phases,
20 scmutil,
21 subrepoutil,
22 )
23
24
25 def commitctx(repo, ctx, error=False, origctx=None):
26 """Add a new revision to the target repository.
27 Revision information is passed via the context argument.
28
29 ctx.files() should list all files involved in this commit, i.e.
30 modified/added/removed files. On merge, it may be wider than the
31 ctx.files() to be committed, since any file nodes derived directly
32 from p1 or p2 are excluded from the committed ctx.files().
33
34 origctx is for convert to work around the problem that bug
35 fixes to the files list in changesets change hashes. For
36 convert to be the identity, it can pass an origctx and this
37 function will use the same files list when it makes sense to
38 do so.
39 """
40 repo = repo.unfiltered()
41
42 p1, p2 = ctx.p1(), ctx.p2()
43 user = ctx.user()
44
45 if repo.filecopiesmode == b'changeset-sidedata':
46 writechangesetcopy = True
47 writefilecopymeta = True
48 writecopiesto = None
49 else:
50 writecopiesto = repo.ui.config(b'experimental', b'copies.write-to')
51 writefilecopymeta = writecopiesto != b'changeset-only'
52 writechangesetcopy = writecopiesto in (
53 b'changeset-only',
54 b'compatibility',
55 )
56 p1copies, p2copies = None, None
57 if writechangesetcopy:
58 p1copies = ctx.p1copies()
59 p2copies = ctx.p2copies()
60 filesadded, filesremoved = None, None
61 with repo.lock(), repo.transaction(b"commit") as tr:
62 trp = weakref.proxy(tr)
63
64 if ctx.manifestnode():
65 # reuse an existing manifest revision
66 repo.ui.debug(b'reusing known manifest\n')
67 mn = ctx.manifestnode()
68 files = ctx.files()
69 if writechangesetcopy:
70 filesadded = ctx.filesadded()
71 filesremoved = ctx.filesremoved()
72 elif not ctx.files():
73 repo.ui.debug(b'reusing manifest from p1 (no file change)\n')
74 mn = p1.manifestnode()
75 files = []
76 else:
77 m1ctx = p1.manifestctx()
78 m2ctx = p2.manifestctx()
79 mctx = m1ctx.copy()
80
81 m = mctx.read()
82 m1 = m1ctx.read()
83 m2 = m2ctx.read()
84
85 # check in files
86 added = []
87 filesadded = []
88 removed = list(ctx.removed())
89 touched = []
90 linkrev = len(repo)
91 repo.ui.note(_(b"committing files:\n"))
92 uipathfn = scmutil.getuipathfn(repo)
93 for f in sorted(ctx.modified() + ctx.added()):
94 repo.ui.note(uipathfn(f) + b"\n")
95 try:
96 fctx = ctx[f]
97 if fctx is None:
98 removed.append(f)
99 else:
100 added.append(f)
101 m[f], is_touched = repo._filecommit(
102 fctx, m1, m2, linkrev, trp, writefilecopymeta,
103 )
104 if is_touched:
105 touched.append(f)
106 if writechangesetcopy and is_touched == 'added':
107 filesadded.append(f)
108 m.setflag(f, fctx.flags())
109 except OSError:
110 repo.ui.warn(_(b"trouble committing %s!\n") % uipathfn(f))
111 raise
112 except IOError as inst:
113 errcode = getattr(inst, 'errno', errno.ENOENT)
114 if error or errcode and errcode != errno.ENOENT:
115 repo.ui.warn(
116 _(b"trouble committing %s!\n") % uipathfn(f)
117 )
118 raise
119
120 # update manifest
121 removed = [f for f in removed if f in m1 or f in m2]
122 drop = sorted([f for f in removed if f in m])
123 for f in drop:
124 del m[f]
125 if p2.rev() != nullrev:
126 rf = metadata.get_removal_filter(ctx, (p1, p2, m1, m2))
127 removed = [f for f in removed if not rf(f)]
128
129 touched.extend(removed)
130
131 if writechangesetcopy:
132 filesremoved = removed
133
134 files = touched
135 md = None
136 if not files:
137 # if no "files" actually changed in terms of the changelog,
138 # try hard to detect unmodified manifest entry so that the
139 # exact same commit can be reproduced later on convert.
140 md = m1.diff(m, scmutil.matchfiles(repo, ctx.files()))
141 if not files and md:
142 repo.ui.debug(
143 b'not reusing manifest (no file change in '
144 b'changelog, but manifest differs)\n'
145 )
146 if files or md:
147 repo.ui.note(_(b"committing manifest\n"))
148 # we're using narrowmatch here since it's already applied at
149 # other stages (such as dirstate.walk), so we're already
150 # ignoring things outside of narrowspec in most cases. The
151 # one case where we might have files outside the narrowspec
152 # at this point is merges, and we already error out in the
153 # case where the merge has files outside of the narrowspec,
154 # so this is safe.
155 mn = mctx.write(
156 trp,
157 linkrev,
158 p1.manifestnode(),
159 p2.manifestnode(),
160 added,
161 drop,
162 match=repo.narrowmatch(),
163 )
164 else:
165 repo.ui.debug(
166 b'reusing manifest from p1 (listed files '
167 b'actually unchanged)\n'
168 )
169 mn = p1.manifestnode()
170
171 if writecopiesto == b'changeset-only':
172 # If writing only to changeset extras, use None to indicate that
173 # no entry should be written. If writing to both, write an empty
174 # entry to prevent the reader from falling back to reading
175 # filelogs.
176 p1copies = p1copies or None
177 p2copies = p2copies or None
178 filesadded = filesadded or None
179 filesremoved = filesremoved or None
180
181 if origctx and origctx.manifestnode() == mn:
182 files = origctx.files()
183
184 # update changelog
185 repo.ui.note(_(b"committing changelog\n"))
186 repo.changelog.delayupdate(tr)
187 n = repo.changelog.add(
188 mn,
189 files,
190 ctx.description(),
191 trp,
192 p1.node(),
193 p2.node(),
194 user,
195 ctx.date(),
196 ctx.extra().copy(),
197 p1copies,
198 p2copies,
199 filesadded,
200 filesremoved,
201 )
202 xp1, xp2 = p1.hex(), p2 and p2.hex() or b''
203 repo.hook(
204 b'pretxncommit', throw=True, node=hex(n), parent1=xp1, parent2=xp2,
205 )
206 # set the new commit is proper phase
207 targetphase = subrepoutil.newcommitphase(repo.ui, ctx)
208 if targetphase:
209 # retract boundary do not alter parent changeset.
210 # if a parent have higher the resulting phase will
211 # be compliant anyway
212 #
213 # if minimal phase was 0 we don't need to retract anything
214 phases.registernew(repo, tr, targetphase, [n])
215 return n