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