|
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 |