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