git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Missing Promisor Objects in Partial Repo Design Doc

From
Calvin Wan <calvinwan@google.com>
Date
Oct 1, 2024, 19:17 UTC
Message-ID
<20241001191811.1934900-1-calvinwan@google.com>
In-Reply-To
<20240802073143.56731-1-hanyang.tony@bytedance.com>

It seems that we're at a standstill for the various possible designs that can solve this problem, so I decided to write up a design document to discuss the ideas we've come up with so far and new ones. Hopefully this will get us closer to a viable implementation we can agree on.

Missing Promisor Objects in Partial Repo Design Doc ===================================================

Basic Reproduction Steps ------------------------

 - Partial clone repository
 - Create local commit and push
 - Fetch new changes
 - Garbage collection

State After Reproduction ------------------------

commit  tree  blob
  C3 ---- T3 -- B3 (fetched from remote, in promisor pack)
  |
  C2b ---- T2b -- B2b (created locally, in non-promisor pack)
  |
  C2a ---- T2a -- B2a (created locally, in non-promisor pack)
  |
  C1 ---- T1 -- B1 (fetched from remote, in promisor pack)

Explanation of the Problem --------------------------

In a partial clone repository, non-promisor commits are locally committed as children of promisor commits and then pushed up to the server. Fetches of new history can result in promisor commits that have non-promisor commits as ancestors. During garbage collection, objects are repacked in 2 steps. In the first step, if there is more than one promisor packfile, all objects in promisor packfiles are repacked into a single promisor packfile. In the second step, a revision walk is made from all refs (and some other things like HEAD and reflog entries) that stops whenever it encounters a promisor object. In the example above, if a ref pointed directly to C2a, it would be returned by the walk (as an object to be packed). But if we only had a ref pointing to C3, the revision walk immediately sees that it is a promisor object, does not return it, and does not iterate through its parents.

(C2b is a bit of a special case. Despite not being in a promisor pack, it is still considered to be a promisor object since C3 directly references it.)

If we think this is a bad state, we should propagate the “promisor-ness” of C3 to its ancestors. Git commands should either prevent this state from occurring or tolerate it and fix it when we can. If we did run into this state unexpectedly, then it would be considered a BUG.

If we think it is a valid state, we should NOT propagate the “promisor-ness” of C3 to its ancestors. Git commands should respect that this is a possible state and be able to work around it. Therefore, this bug would then be strictly caused by garbage collection

Bad State Solutions ===================

Fetch negotiation ----------------- Implemented at https://lore.kernel.org/git/20240919234741.1317946-1-calvinwan@google.com/

During fetch negotiation, if a commit is not in a promisor pack and therefore local, do not declare it as "have" so they can be fetched into a promisor pack.

Cost:
- Creation of set of promisor pack objects (by iterating through every
  .idx of promisor packs)
- Refetch number of local commits
Pros: Implementation is simple, client doesn’t have to repack, prevents
state from ever occurring in the repository.
Cons: Network cost of refetching could be high if many local commits
need to be refetched.
commit  tree  blob
  C3 ---- T3 -- B3 (fetched from remote, in promisor pack)
  |
  C2 ---- T2 -- B2 (created locally, refetched into promisor pack)
  |
  C1 ---- T1 -- B1 (fetched from remote, in promisor pack)

Fetch repack ------------ Not yet implemented.

Enumerate the objects in the freshly fetched promisor packs, checking every outgoing link to see if they reference a non-promisor object that we have, to get a list of tips where local objects are parents of promisor objects ("bad history"). After collecting these "tips of bad history", you then start another traversal from them until you hit an object in a promisor pack and stop traversal there. You have successfully enumerated the local objects to be repacked into a promisor pack.

Cost:
- Traversal through newly fetched promisor trees and commits
- Creation of set of promisor pack objects (for tips of bad history
  traversal to stop at a promisor object)
- Traversal through all local commits and check existence in promisor
  pack set
- Repack all pushed local commits
Pros: Prevents state from ever occurring in the repository, no network
cost.
Cons: Additional cost of repacking is incurred during fetch, more
complex implementation.
commit  tree  blob
  C3 ---- T3 -- B3 (fetched from remote, in promisor pack)
  |
  C2 ---- T2 -- B2 (created locally, packed into promisor pack)
  |
  C1 ---- T1 -- B1 (fetched from remote, in promisor pack)

Garbage Collection repack ------------------------- Not yet implemented.

Same concept at “fetch repack”, but happens during garbage collection instead. The traversal is more expensive since we no longer have access to what was recently fetched so we have to traverse through all promisor packs to collect tips of “bad” history.

Cost:
- Creation of set of promisor pack objects
- Traversal through all promisor commits
- Traversal through all local commits and check existence in promisor
  object set
- Repack all pushed local commits
Pros: Can be run in the background as part of maintenance, no network
cost.
Cons: More expensive than “fetch repack”, state isn’t fixed until
garbage collection, more complex implementation
commit  tree  blob
  C3 ---- T3 -- B3 (fetched from remote, in promisor pack)
  |
  C2 ---- T2 -- B2 (created locally, packed into promisor pack)
  |
  C1 ---- T1 -- B1 (fetched from remote, in promisor pack)

Garbage Collection repack all ----------------------------- Implemented at https://lore.kernel.org/git/20240925072021.77078-1-hanyang.tony@bytedance.com/

Repack all local commits into promisor packs during garbage collection.
Both valid scenarios
commit  tree  blob
  C3 ---- T3 -- B3 (fetched from remote, in promisor pack)
  |
  C2 ---- T2 -- B2 (created locally, packed into promisor pack)
  |
  C1 ---- T1 -- B1 (fetched from remote, in promisor pack)
commit  tree  blob
  C3 ---- T3 -- B3 (created locally, packed into promisor pack)
  |
  C2 ---- T2 -- B2 (created locally, packed into promisor pack)
  |
  C1 ---- T1 -- B1 (fetched from remote, in promisor pack)

Cost: Repack all local commits

Pros: Can be run in the background as part of maintenance, no network
cost, less complex implementation, and less expensive than “garbage
collection repack”.
Cons: Packing local objects into promisor packs means that it is no
longer possible to detect if an object is missing due to repository
corruption or because we need to fetch it from a promisor remote.
Packing local objects into promisor packs means that garbage collection
will no longer remove unreachable local objects.

Valid State Solutions ===================== Garbage Collection check ------------------------ Not yet implemented.

Currently during the garbage collection rev walk, whenever a promisor commit is reached, it is marked UNINTERESTING, and then subsequently all ancestors of the promisor commit are traversed and also marked UNINTERESTING. Therefore, add a check for whether a commit is local or not during promisor commit ancestor traversal and do not mark local commits as UNINTERESTING.

commit  tree  blob
  C3 ---- T3 -- B3 (fetched from remote, in promisor pack)
  |
  C2 ---- T2 -- B2 (created locally, in non-promisor pack, gc does not delete)
  |
  C1 ---- T1 -- B1 (fetched from remote, in promisor pack)
Cost:
- Adds an additional check to every ancestor of a promisor commit.

This is practically the only solution if the state is valid. Fsck would also have to start checking for validity of ancestors of promisor commits instead of ignoring them as it currently does.

Optimizations =============

The “creation of set of promisor pack objects” can be replaced with “creation of set of non-promisor objects” since the latter is almost always cheaper and we can check for non-existence rather than existence. This does not work for “fetch negotiation” since if we have a commit that's in both a promisor pack and a non-promisor pack, the algorithm's correctness relies on the fact that we report it as a promisor object (because we really need the server to re-send it).

Previous: Junio C HamanoNext: Junio C Hamano
Message 31 of 65 in “revision: fix reachable objects being gc'ed in no blob clone repo”
  1. 0/1 revision: fix reachable objects being gc'ed in no blob clone repoHan Young, Aug 2, 2024
  2. 1/1 revision: don't set parents as uninteresting if exclude promisor objectsHan Young, Aug 2, 2024
  3. Junio C HamanoAug 2, 2024
  4. 韩仰Aug 12, 2024
  5. Junio C HamanoAug 12, 2024
  6. 韩仰Aug 22, 2024
  7. Jonathan TanAug 13, 2024
  8. Jonathan TanAug 13, 2024
  9. Junio C HamanoAug 14, 2024
  10. Jonathan TanAug 14, 2024
  11. 0/4 revision: fix reachable objects being gc'ed in no blob clone repoHan Young, Aug 23, 2024
  12. 1/4 packfile: split promisor objects oidset into twoHan Young, Aug 23, 2024
  13. 2/4 revision: add exclude-promisor-pack-objects optionHan Young, Aug 23, 2024
  14. 3/4 revision: don't mark commit as UNINTERESTING if --exclude-promisor-objects is setHan Young, Aug 23, 2024
  15. 4/4 repack: use new exclude promisor pack objects optionHan Young, Aug 23, 2024
  16. 0/2 revision: fix reachable commits being gc'ed in partial repoCalvin Wan, Sep 19, 2024
  17. 1/2 packfile: split promisor objects oidset into twoCalvin Wan, Sep 19, 2024
  18. Junio C HamanoSep 22, 2024
  19. 2/2 fetch-pack.c: do not declare local commits as "have" in partial reposCalvin Wan, Sep 19, 2024
  20. Junio C HamanoSep 22, 2024
  21. Junio C HamanoSep 22, 2024
  22. 韩仰Sep 23, 2024
  23. Junio C HamanoSep 23, 2024
  24. Calvin WanOct 2, 2024
  25. 0/2 repack: pack everything into promisor packfile in partial reposHan Young, Sep 25, 2024
  26. 1/2 repack: pack everything into packfileHan Young, Sep 25, 2024
  27. 2/2 t0410: adapt tests to repack changesHan Young, Sep 25, 2024
  28. Phillip WoodSep 25, 2024
  29. Junio C HamanoSep 25, 2024
  30. Junio C HamanoSep 25, 2024
  31. Missing Promisor Objects in Partial Repo Design DocCalvin Wan, Oct 1, 2024
  32. Junio C HamanoOct 1, 2024
  33. Junio C HamanoOct 2, 2024
  34. Han YoungOct 2, 2024
  35. Calvin WanOct 8, 2024
  36. Han YoungOct 9, 2024
  37. Jonathan TanOct 9, 2024
  38. Jonathan TanOct 12, 2024
  39. Han YoungOct 12, 2024
  40. Jonathan TanOct 14, 2024
  41. Jonathan TanOct 9, 2024
  42. 0/3 repack: pack everything into promisor packfile in partial reposHan Young, Oct 8, 2024
  43. 1/3 repack: pack everything into packfileHan Young, Oct 8, 2024
  44. Calvin WanOct 8, 2024
  45. 2/3 t0410: adapt tests to repack changesHan Young, Oct 8, 2024
  46. 3/3 partial-clone: update docHan Young, Oct 8, 2024
  47. Junio C HamanoOct 8, 2024
  48. Junio C HamanoOct 8, 2024
  49. Han YoungOct 9, 2024
  50. 0/3 repack: pack everything into promisor packfile in partial reposHan Young, Oct 11, 2024
  51. 1/3 repack: pack everything into packfileHan Young, Oct 11, 2024
  52. 2/3 repack: adapt tests to repack changesHan Young, Oct 11, 2024
  53. 3/3 partial-clone: update docHan Young, Oct 11, 2024
  54. Junio C HamanoOct 11, 2024
  55. Junio C HamanoOct 11, 2024
  56. 0/3 repack: pack everything into promisor packfile in partial reposHan Young, Oct 14, 2024
  57. 1/3 repack: pack everything into packfileHan Young, Oct 14, 2024
  58. 2/3 t0410: adapt tests to repack changesHan Young, Oct 14, 2024
  59. 3/3 partial-clone: update docHan Young, Oct 14, 2024
  60. 0/3 Repack on fetchJonathan Tan, Oct 21, 2024
  61. 1/3 move variableJonathan Tan, Oct 21, 2024
  62. 2/3 pack-objectsJonathan Tan, Oct 21, 2024
  63. 3/3 record local links and call pack-objectsJonathan Tan, Oct 21, 2024
  64. Han YoungOct 23, 2024
  65. Jonathan TanOct 23, 2024

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.