Show More
@@ -1,3 +1,4 b'' | |||
|
1 | # coding: utf8 | |
|
1 | 2 | # metadata.py -- code related to various metadata computation and access. |
|
2 | 3 | # |
|
3 | 4 | # Copyright 2019 Google, Inc <martinvonz@google.com> |
@@ -238,18 +239,8 b' def compute_all_files_changes(ctx):' | |||
|
238 | 239 | elif p1.rev() == p2.rev(): |
|
239 | 240 | # In the wild, one can encounter such "non-merge" |
|
240 | 241 | return _process_linear(p1, ctx) |
|
241 | filescopies = computechangesetcopies(ctx) | |
|
242 | filesadded = computechangesetfilesadded(ctx) | |
|
243 | filesremoved = computechangesetfilesremoved(ctx) | |
|
244 | filesmerged = computechangesetfilesmerged(ctx) | |
|
245 | files = ChangingFiles() | |
|
246 | files.update_touched(ctx.files()) | |
|
247 | files.update_added(filesadded) | |
|
248 | files.update_removed(filesremoved) | |
|
249 | files.update_merged(filesmerged) | |
|
250 | files.update_copies_from_p1(filescopies[0]) | |
|
251 | files.update_copies_from_p2(filescopies[1]) | |
|
252 | return files | |
|
242 | else: | |
|
243 | return _process_merge(p1, p2, ctx) | |
|
253 | 244 | |
|
254 | 245 | |
|
255 | 246 | def _process_root(ctx): |
@@ -301,6 +292,218 b' def _process_linear(parent_ctx, children' | |||
|
301 | 292 | return md |
|
302 | 293 | |
|
303 | 294 | |
|
295 | def _process_merge(p1_ctx, p2_ctx, ctx): | |
|
296 | """compute the appropriate changed files for a changeset with two parents | |
|
297 | ||
|
298 | This is a more advance case. The information we need to record is summarise | |
|
299 | in the following table: | |
|
300 | ||
|
301 | ββββββββββββββββ¬βββββββββββββββ¬βββββββββββββββ¬βββββββββββββββ¬βββββββββββββββ | |
|
302 | β diff β² diff β ΓΈ β (Some, None) β (None, Some) β (Some, Some) β | |
|
303 | β p2 β² p1 β β β β β | |
|
304 | ββββββββββββββββΌβββββββββββββββΌβββββββββββββββΌβββββββββββββββΌβββββββββββββββ€ | |
|
305 | β β βπ± No Changes βπ³ No Changes β β | |
|
306 | β ΓΈ βπ° No Changes β OR β OR βπ΅ No Changes β | |
|
307 | β β βπ² Deleted[1] βπ΄ Salvaged[2]β [3] β | |
|
308 | ββββββββββββββββΌβββββββββββββββΌβββββββββββββββΌβββββββββββββββΌβββββββββββββββ€ | |
|
309 | β βπΆ No Changes β β β β | |
|
310 | β (Some, None) β OR βπ» Deleted β ΓΈ β ΓΈ β | |
|
311 | β βπ· Deleted[1] β β β β | |
|
312 | ββββββββββββββββΌβββββββββββββββΌβββββββββββββββΌβββββββββββββββΌβββββββββββββββ€ | |
|
313 | β βπΈ No Changes β β β β | |
|
314 | β (None, Some) β OR β ΓΈ βπΌ Added βπ½ Merged β | |
|
315 | β βπΉ Salvaged[2]β β (copied?) β (copied?) β | |
|
316 | ββββββββββββββββΌβββββββββββββββΌβββββββββββββββΌβββββββββββββββΌβββββββββββββββ€ | |
|
317 | β β β β β β | |
|
318 | β (Some, Some) βπΊ No Changes β ΓΈ βπΎ Merged βπΏ Merged β | |
|
319 | β β [3] β β (copied?) β (copied?) β | |
|
320 | ββββββββββββββββ΄βββββββββββββββ΄βββββββββββββββ΄βββββββββββββββ΄βββββββββββββββ | |
|
321 | ||
|
322 | Special case [1]: | |
|
323 | ||
|
324 | The situation is: | |
|
325 | - parent-A: file exists, | |
|
326 | - parent-B: no file, | |
|
327 | - working-copy: no file. | |
|
328 | ||
|
329 | Detecting a "deletion" will depend on the presence of actual change on | |
|
330 | the "parent-A" branch: | |
|
331 | ||
|
332 | Subcase π± or πΆ : if the state of the file in "parent-A" is unchanged | |
|
333 | compared to the merge ancestors, then parent-A branch left the file | |
|
334 | untouched while parent-B deleted it. We simply apply the change from | |
|
335 | "parent-B" branch the file was automatically dropped. | |
|
336 | The result is: | |
|
337 | - file is not recorded as touched by the merge. | |
|
338 | ||
|
339 | Subcase π² or π· : otherwise, the change from parent-A branch were explicitly dropped and | |
|
340 | the file was "deleted again". From a user perspective, the message | |
|
341 | about "locally changed" while "remotely deleted" (or the other way | |
|
342 | around) was issued and the user chose to deleted the file. | |
|
343 | The result: | |
|
344 | - file is recorded as touched by the merge. | |
|
345 | ||
|
346 | ||
|
347 | Special case [2]: | |
|
348 | ||
|
349 | The situation is: | |
|
350 | - parent-A: no file, | |
|
351 | - parent-B: file, | |
|
352 | - working-copy: file (same content as parent-B). | |
|
353 | ||
|
354 | There are three subcases depending on the ancestors contents: | |
|
355 | ||
|
356 | - A) the file is missing in all ancestors, | |
|
357 | - B) at least one ancestor has the file with filenode β from parent-B, | |
|
358 | - C) all ancestors use the same filenode as parent-B, | |
|
359 | ||
|
360 | Subcase (A) is the simpler, nothing happend on parent-A side while | |
|
361 | parent-B added it. | |
|
362 | ||
|
363 | The result: | |
|
364 | - the file is not marked as touched by the merge. | |
|
365 | ||
|
366 | Subcase (B) is the counter part of "Special case [1]", the file was | |
|
367 | modified on parent-B side, while parent-A side deleted it. However this | |
|
368 | time, the conflict was solved by keeping the file (and its | |
|
369 | modification). We consider the file as "salvaged". | |
|
370 | ||
|
371 | The result: | |
|
372 | - the file is marked as "salvaged" by the merge. | |
|
373 | ||
|
374 | Subcase (C) is subtle variation of the case above. In this case, the | |
|
375 | file in unchanged on the parent-B side and actively removed on the | |
|
376 | parent-A side. So the merge machinery correctly decide it should be | |
|
377 | removed. However, the file was explicitly restored to its parent-B | |
|
378 | content before the merge was commited. The file is be marked | |
|
379 | as salvaged too. From the merge result perspective, this is similar to | |
|
380 | Subcase (B), however from the merge resolution perspective they differ | |
|
381 | since in (C), there was some conflict not obvious solution to the | |
|
382 | merge (That got reversed) | |
|
383 | ||
|
384 | Special case [3]: | |
|
385 | ||
|
386 | The situation is: | |
|
387 | - parent-A: file, | |
|
388 | - parent-B: file (different filenode as parent-A), | |
|
389 | - working-copy: file (same filenode as parent-B). | |
|
390 | ||
|
391 | This case is in theory much simple, for this to happens, this mean the | |
|
392 | filenode in parent-A is purely replacing the one in parent-B (either a | |
|
393 | descendant, or a full new file history, see changeset). So the merge | |
|
394 | introduce no changes, and the file is not affected by the merge... | |
|
395 | ||
|
396 | However, in the wild it is possible to find commit with the above is not | |
|
397 | True. For example repository have some commit where the *new* node is an | |
|
398 | ancestor of the node in parent-A, or where parent-A and parent-B are two | |
|
399 | branches of the same file history, yet not merge-filenode were created | |
|
400 | (while the "merge" should have led to a "modification"). | |
|
401 | ||
|
402 | Detecting such cases (and not recording the file as modified) would be a | |
|
403 | nice bonus. However do not any of this yet. | |
|
404 | """ | |
|
405 | ||
|
406 | md = ChangingFiles() | |
|
407 | ||
|
408 | m = ctx.manifest() | |
|
409 | p1m = p1_ctx.manifest() | |
|
410 | p2m = p2_ctx.manifest() | |
|
411 | diff_p1 = p1m.diff(m) | |
|
412 | diff_p2 = p2m.diff(m) | |
|
413 | ||
|
414 | cahs = ctx.repo().changelog.commonancestorsheads( | |
|
415 | p1_ctx.node(), p2_ctx.node() | |
|
416 | ) | |
|
417 | if not cahs: | |
|
418 | cahs = [node.nullrev] | |
|
419 | mas = [ctx.repo()[r].manifest() for r in cahs] | |
|
420 | ||
|
421 | copy_candidates = [] | |
|
422 | ||
|
423 | # Dealing with case π° happens automatically. Since there are no entry in | |
|
424 | # d1 nor d2, we won't iterate on it ever. | |
|
425 | ||
|
426 | # Iteration over d1 content will deal with all cases, but the one in the | |
|
427 | # first column of the table. | |
|
428 | for filename, d1 in diff_p1.items(): | |
|
429 | ||
|
430 | d2 = diff_p2.pop(filename, None) | |
|
431 | ||
|
432 | if d2 is None: | |
|
433 | # this deal with the first line of the table. | |
|
434 | _process_other_unchanged(md, mas, filename, d1) | |
|
435 | else: | |
|
436 | ||
|
437 | if d1[0][0] is None and d2[0][0] is None: | |
|
438 | # case πΌ β both deleted the file. | |
|
439 | md.mark_added(filename) | |
|
440 | copy_candidates.append(filename) | |
|
441 | elif d1[1][0] is None and d2[1][0] is None: | |
|
442 | # case π» β both deleted the file. | |
|
443 | md.mark_removed(filename) | |
|
444 | elif d1[1][0] is not None and d2[1][0] is not None: | |
|
445 | # case π½ πΎ πΏ | |
|
446 | md.mark_merged(filename) | |
|
447 | copy_candidates.append(filename) | |
|
448 | else: | |
|
449 | # Impossible case, the post-merge file status cannot be None on | |
|
450 | # one side and Something on the other side. | |
|
451 | assert False, "unreachable" | |
|
452 | ||
|
453 | # Iteration over remaining d2 content deal with the first column of the | |
|
454 | # table. | |
|
455 | for filename, d2 in diff_p2.items(): | |
|
456 | _process_other_unchanged(md, mas, filename, d2) | |
|
457 | ||
|
458 | for filename in copy_candidates: | |
|
459 | copy_info = ctx[filename].renamed() | |
|
460 | if copy_info: | |
|
461 | source, srcnode = copy_info | |
|
462 | if source in p1_ctx and p1_ctx[source].filenode() == srcnode: | |
|
463 | md.mark_copied_from_p1(source, filename) | |
|
464 | elif source in p2_ctx and p2_ctx[source].filenode() == srcnode: | |
|
465 | md.mark_copied_from_p2(source, filename) | |
|
466 | return md | |
|
467 | ||
|
468 | ||
|
469 | def _find(manifest, filename): | |
|
470 | """return the associate filenode or None""" | |
|
471 | if filename not in manifest: | |
|
472 | return None | |
|
473 | return manifest.find(filename)[0] | |
|
474 | ||
|
475 | ||
|
476 | def _process_other_unchanged(md, mas, filename, diff): | |
|
477 | source_node = diff[0][0] | |
|
478 | target_node = diff[1][0] | |
|
479 | ||
|
480 | if source_node is not None and target_node is None: | |
|
481 | if any(not _find(ma, filename) == source_node for ma in mas): | |
|
482 | # case π² of π· | |
|
483 | md.mark_removed(filename) | |
|
484 | # else, we have case π± or πΆ : no change need to be recorded | |
|
485 | elif source_node is None and target_node is not None: | |
|
486 | if any(_find(ma, filename) is not None for ma in mas): | |
|
487 | # case π΄ or πΉ | |
|
488 | md.mark_salvaged(filename) | |
|
489 | # else, we have case π³ or πΈ : simple merge without intervention | |
|
490 | elif source_node is not None and target_node is not None: | |
|
491 | # case π΅ or πΊ : simple merge without intervention | |
|
492 | # | |
|
493 | # In buggy case where source_node is not an ancestors of target_node. | |
|
494 | # There should have a been a new filenode created, recording this as | |
|
495 | # "modified". We do not deal with them yet. | |
|
496 | pass | |
|
497 | else: | |
|
498 | # An impossible case, the diff algorithm should not return entry if the | |
|
499 | # file is missing on both side. | |
|
500 | assert False, "unreachable" | |
|
501 | ||
|
502 | ||
|
503 | def _missing_from_all_ancestors(mas, filename): | |
|
504 | return all(_find(ma, filename) is None for ma in mas) | |
|
505 | ||
|
506 | ||
|
304 | 507 | def computechangesetfilesadded(ctx): |
|
305 | 508 | """return the list of files added in a changeset |
|
306 | 509 | """ |
General Comments 0
You need to be logged in to leave comments.
Login now