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

[PATCH v3 0/2] fetch: write commit-graph using updated refs only

From
Kristofer Karlsson via GitGitGadget <gitgitgadget@gmail.com>
Date
Oct 7, 2026, 14:22 UTC
Message-ID
<pull.2239.v3.git.1791382977.gitgitgadget@gmail.com>
In-Reply-To
<pull.2239.git.1790930019.gitgitgadget@gmail.com>

When fetch.writeCommitGraph is enabled, the commit-graph is rebuilt from all reachable refs after every fetch. This is unnecessarily expensive on repositories with many refs, since add_ref_to_set() validates each ref against the odb.

This series optimizes the commit-graph write by using only the newly updated refs as seeds instead of scanning all refs. Since fetch writes the commit-graph in split mode, the newly fetched history is added as a new layer on top of the existing chain. A three-mode enum (REACHABLE / TIPS / SKIP) makes the policy explicit:

 * No-op fetch: skip the commit-graph write entirely
 * Updated refs + existing graph: write incrementally from updated tips only
 * No existing graph or multi-remote fetch: fall back to full reachable scan

Patch 1 adds a commit-info subcommand to test-tool read-graph for verifying graph contents in tests.

Patch 2 implements the optimization in builtin/fetch.c with tests covering the incremental, unrelated-commit, no-op, fallback, and shallow-rejected cases.

Benchmark on a synthetic setup: git.git with 200K extra packed refs (~206K total), a local file:// remote, an existing split commit-graph and a warm page cache. Times are the median of 9 runs of the trace2 region fetch/write-commit-graph:

scenario before after no-op fetch 380 ms (skipped) 1 ref updated 357 ms 9.3 ms 10 refs updated 359 ms 8.9 ms

Changes since v2:
 * Trim the commit message: inline the commit references, keep the
   explanation of why the incremental write relies on split mode, and drop
   the paragraphs that only restated the diff (collecting the tips,
   auto-followed tags, skipping shallow-rejected refs).
 * Reword the comment on skipping shallow-rejected refs without the
   reference to store_updated_refs().
 * Simplify the t5537 test by using "test_commit -C ... --no-tag" instead of
   subshells.
Changes since v1:
 * Explain in the commit message that fetch writes in split mode, so the new
   tips are added as a new layer on top of the existing chain, and that the
   incremental path relies on this (a non-split write would replace the
   graph with only the closure of the seeds).
 * Extend the incremental test to check that a local-only commit, which was
   in the graph before the fetch but is not reachable from the fetched tips,
   is still in the graph afterwards.
 * Add benchmark numbers to the commit message.
 * Add a comment explaining why shallow-rejected refs are skipped when
   collecting the updated tips (like store_updated_refs(), since their
   history is incomplete), and mention it in the commit message.
 * Add a test in t5537 for a fetch with fetch.writeCommitGraph where a ref
   is rejected because it would require changes to .git/shallow. Without the
   check, the commit-graph write dies on the missing parent.
Kristofer Karlsson (2):
  test-tool read-graph: add commit-info subcommand
  fetch: write commit-graph using updated refs only
 builtin/fetch.c            | 69 +++++++++++++++++++++++++++++++++-----
 commit-graph.c             |  2 +-
 commit-graph.h             |  1 +
 t/helper/test-read-graph.c | 23 ++++++++++++-
 t/t5510-fetch.sh           | 59 ++++++++++++++++++++++++++++++++
 t/t5537-fetch-shallow.sh   | 22 ++++++++++++
 6 files changed, 165 insertions(+), 11 deletions(-)
base-commit: 0f8e75abebff0877cae681a3d5ff31ac47f54220
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2239%2Fspkrka%2Fkrka%2Fincremental-commit-graph-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2239/spkrka/krka/incremental-commit-graph-v3
Pull-Request: https://github.com/gitgitgadget/git/pull/2239
Range-diff vs v2:
 1:  36cc3ff3b3 = 1:  36cc3ff3b3 test-tool read-graph: add commit-info subcommand
 2:  7507354cc9 ! 2:  01da9857bc fetch: write commit-graph using updated refs only
     @@ Metadata
       ## Commit message ##
          fetch: write commit-graph using updated refs only
      
     -    When fetch.writeCommitGraph was introduced in
     -
     -        50f26bd035 (fetch: add fetch.writeCommitGraph config
     -                    setting, 2019-09-02),
     -
     -    the stated goal was to stay updated with the latest commits after
     -    fetching new objects.  The implementation used
     -    write_commit_graph_reachable() because it was the only API available,
     -    but two things have changed since then:
     +    When fetch.writeCommitGraph was introduced in 50f26bd035 (fetch: add
     +    fetch.writeCommitGraph config setting, 2019-09-02), the stated goal
     +    was to stay updated with the latest commits after fetching new
     +    objects.  The implementation used write_commit_graph_reachable()
     +    because it was the only API available, but two things have changed
     +    since then:
      
           1. write_commit_graph() was added, and it accepts an explicit set of
              commits as seeds, enabling more targeted commit-graph updates.
      
           2. The ref-scanning callback add_ref_to_set() became more expensive
     -        in
     -            630cd5194e (commit-graph.c: peel refs in 'add_ref_to_set',
     -                        2020-07-22)
     -        when it started to validate the refs against the odb
     +        in 630cd5194e (commit-graph.c: peel refs in 'add_ref_to_set',
     +        2020-07-22) when it started to validate the refs against the odb
              for correctness.  On a repository with many refs, this makes the
              full reachable scan unnecessarily costly for a targeted fetch.
      
     @@ Commit message
          (since that would require propagating the set of refs across process
          boundaries).
      
     -    Since do_fetch() already knows which refs were updated, collect them
     -    into an oidset and then pass them directly to write_commit_graph().
     -    fetch always writes the commit-graph in split mode, so this adds a
     -    new layer on top of the existing chain rather than replacing it:
     -    close_reachable() walks from the updated tips and stops at commits
     -    already present in the graph, so the new layer only contains the
     -    newly fetched history, and commits covered by the existing layers
     -    remain covered.  This relies on split mode; a non-split write would
     -    replace the graph with just the closure of the seeds.
     -
     -    The reachability closure also covers auto-followed tags, since their
     -    targets are reachable from the fetched tips that caused them to be
     -    auto-followed.
     -
     -    Refs that are rejected because they would require changes to
     -    .git/shallow are skipped, just like store_updated_refs() does.  Their
     -    objects are received but their history is incomplete, so walking from
     -    them would make the commit-graph write fail.
     +    This relies on the commit-graph write being additive, keeping the
     +    commits that are already in the graph.  fetch already operates in
     +    this mode (COMMIT_GRAPH_WRITE_SPLIT) and now that becomes
     +    required for correctness.  Without that mode, the write would
     +    replace the commit-graph and lose other commits.
      
          After fetch_one() returns, call prepare_commit_graph() (which is
          made non-static by this commit) to determine the graph-write mode:
     @@ builtin/fetch.c: out:
      +	for (rm = ref_map; rm; rm = rm->next) {
      +		struct commit *commit;
      +		/*
     -+		 * Like store_updated_refs(), skip shallow-rejected refs:
     -+		 * they are not stored, and their history is incomplete.
     ++		 * Shallow-rejected refs are not stored and their history
     ++		 * is incomplete, so skip them.
      +		 */
      +		if (rm->status == REF_STATUS_REJECT_SHALLOW)
      +			continue;
     @@ t/t5537-fetch-shallow.sh: test_expect_success 'fetch that requires changes in .g
       
      +test_expect_success 'fetch.writeCommitGraph skips refs that require changes in .git/shallow' '
      +	git clone --no-local --depth=2 .git shallow-graph &&
     -+	(
     -+		cd shallow-graph &&
     -+		git checkout --orphan no-shallow &&
     -+		commit no-shallow
     -+	) &&
     ++	git -C shallow-graph checkout --orphan no-shallow &&
     ++	test_commit -C shallow-graph --no-tag no-shallow &&
      +	git init notshallow-graph &&
      +	git -C notshallow-graph -c fetch.writeCommitGraph=true \
      +		fetch ../shallow-graph/.git "refs/heads/*:refs/remotes/shallow/*" &&
     -+	(
     -+		cd shallow-graph &&
     -+		commit no-shallow-2
     -+	) &&
     ++	test_commit -C shallow-graph --no-tag no-shallow-2 &&
      +	rejected=$(git -C shallow-graph rev-parse main) &&
      +	(
      +		cd notshallow-graph &&
-- 
gitgitgadget
Previous: Kristofer KarlssonNext: Kristofer Karlsson via GitGitGadget
Message 13 of 15 in “fetch: write commit-graph using updated refs only”
  1. 0/2 fetch: write commit-graph using updated refs onlyKristofer Karlsson via GitGitGadget, Oct 2, 2026
  2. 1/2 test-tool read-graph: add commit-info subcommandKristofer Karlsson via GitGitGadget, Oct 2, 2026
  3. 2/2 fetch: write commit-graph using updated refs onlyKristofer Karlsson via GitGitGadget, Oct 2, 2026
  4. Patrick SteinhardtOct 2, 2026
  5. Kristofer KarlssonOct 2, 2026
  6. Patrick SteinhardtOct 5, 2026
  7. Kristofer KarlssonOct 5, 2026
  8. 0/2 fetch: write commit-graph using updated refs onlyKristofer Karlsson via GitGitGadget, Oct 6, 2026
  9. 1/2 test-tool read-graph: add commit-info subcommandKristofer Karlsson via GitGitGadget, Oct 6, 2026
  10. 2/2 fetch: write commit-graph using updated refs onlyKristofer Karlsson via GitGitGadget, Oct 6, 2026
  11. Patrick SteinhardtOct 7, 2026
  12. Kristofer KarlssonOct 7, 2026
  13. 0/2 fetch: write commit-graph using updated refs onlyKristofer Karlsson via GitGitGadget, Oct 7, 2026
  14. 1/2 test-tool read-graph: add commit-info subcommandKristofer Karlsson via GitGitGadget, Oct 7, 2026
  15. 2/2 fetch: write commit-graph using updated refs onlyKristofer Karlsson via GitGitGadget, Oct 7, 2026

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.