From: Patrick Steinhardt <ps@pks.im>
To: Kristofer Karlsson via GitGitGadget <gitgitgadget@gmail.com>
Cc: git@vger.kernel.org, Derrick Stolee <stolee@gmail.com>,
Taylor Blau <me@ttaylorr.com>, Jeff King <peff@peff.net>,
Kristofer Karlsson <krka@spotify.com>
Subject: Re: [PATCH 2/2] fetch: write commit-graph using updated refs only
Date: Fri, 2 Oct 2026 13:22:03 +0200 [thread overview]
Message-ID: <ar-T2y54X1uDQ4mX@pks.im> (raw)
In-Reply-To: <fee92f3c2009f8f282fe98e6b16d403704db9ad9.1790930019.git.gitgitgadget@gmail.com>
On Fri, Oct 02, 2026 at 08:33:38AM +0000, Kristofer Karlsson via GitGitGadget wrote:
> From: Kristofer Karlsson <krka@spotify.com>
>
> 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.
Hm. The big question here is whether these additional seeds are additive
or exclusive. That is, if I have an existing commit graph already, would
it basically just extend the commit graph with the additional object IDs
or would it replace the commit graph with a new one that only considers
the passe object IDs as input?
I would hope that it's additive, because otherwise you may now lose
commit graph coverage for stuff that was covered before the patch.
> 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
> for correctness. On a repository with many refs, this makes the
> full reachable scan unnecessarily costly for a targeted fetch.
I was wondering whether incremental commit graphs would also be part of
the reasoning. Because in theory, now that we have those, we could even
extend the commit graph on a fetch by just writing another layer.
> Optimize the commit-graph write by using only the newly updated refs
> as seeds instead of scanning all refs after every fetch. To keep
> this change small, skip the optimization for multi-remote fetches
> (since that would require propagating the set of refs across process
> boundaries).
Yeah, the way we perform fetches can be a bit annoying at times, as all
these subprocesses make it very hard to exchange information.
> Since do_fetch() already knows which refs were updated, collect them
> into an oidset and then pass them directly to write_commit_graph().
> In split mode, close_reachable() walks from the updated tips and
> stops at commits already present in the graph, efficiently adding
> the newly fetched history. This reachability closure also covers
> auto-followed tags, since their targets are reachable from the
> fetched tips that caused them to be auto-followed.
Aha! So I wasn't that far off :) Now there's a follow-up question
though: what happens in non-split mode?
> After fetch_one() returns, call prepare_commit_graph() (which is
> made non-static by this commit) to determine the graph-write mode:
>
> - If no commit-graph exists yet, fall back to the full reachable
> scan so the first graph creation covers all refs.
>
> - If a commit-graph exists and the fetch updated at least one ref,
> write incrementally using only the new refs as seeds.
>
> - If a commit-graph exists but the fetch is a no-op, skip the
> commit-graph write entirely.
>
> - For the multi-remote path (fetch --all), where child processes
> do the actual fetching, fall back to the full reachable scan.
All of these make sense, but the above question is not answered yet.
> Full commit-graph coverage of all refs remains the responsibility
> of "git maintenance", "git gc" and "git commit-graph write".
> Regular Git operations may trigger "git maintenance run --auto",
> which periodically rebuilds the commit-graph from all reachable
> refs.
Curiously, you mention performance as motivating factor for this change
but don't provide a benchmark demonstrating the benefit.
> diff --git a/builtin/fetch.c b/builtin/fetch.c
> index 533fdfe7d8..8ad7331640 100644
> --- a/builtin/fetch.c
> +++ b/builtin/fetch.c
> @@ -1903,10 +1903,30 @@ out:
> return retcode;
> }
>
> +static void collect_updated_tips(struct oidset *tips, struct ref *ref_map)
> +{
> + struct ref *rm;
> + for (rm = ref_map; rm; rm = rm->next) {
> + struct commit *commit;
> + if (rm->status == REF_STATUS_REJECT_SHALLOW)
> + continue;
Hm. Shouldn't we also refuse almost all of the other values here? I'd
expect that we only want to consider a tip when it has REF_STATUS_OK.
> + if (is_null_oid(&rm->old_oid))
> + continue;
> + if (rm->peer_ref &&
> + oideq(&rm->old_oid, &rm->peer_ref->old_oid))
> + continue;
> + commit = lookup_commit_reference_gently(the_repository,
> + &rm->old_oid, 1);
> + if (commit)
> + oidset_insert(tips, &commit->object.oid);
This is something that always trips me with `struct ref`, that I'm never
quite sure what's what. So please forgive my ignorance, but why do we
look up `rm->old_oid` here?
> @@ -2535,6 +2559,12 @@ int cmd_fetch(int argc,
> int negotiate_only = 0;
> int porcelain = 0;
> int i;
> + enum {
> + GRAPH_WRITE_REACHABLE,
> + GRAPH_WRITE_TIPS,
> + GRAPH_WRITE_SKIP,
> + } graph_write_mode = GRAPH_WRITE_REACHABLE;
> + struct oidset updated_tips = OIDSET_INIT;
>
> struct option builtin_fetch_options[] = {
> OPT__VERBOSITY(&verbosity),
> @@ -2822,7 +2852,13 @@ int cmd_fetch(int argc,
> }
> trace2_region_enter("fetch", "fetch-one", the_repository);
> result = fetch_one(remote, argc, argv, prune_tags_ok, stdin_refspecs,
> - &config, &filter_options);
> + &config, &filter_options, &updated_tips);
> + if (prepare_commit_graph(the_repository)) {
> + if (oidset_size(&updated_tips))
> + graph_write_mode = GRAPH_WRITE_TIPS;
> + else
> + graph_write_mode = GRAPH_WRITE_SKIP;
> + }
> trace2_region_leave("fetch", "fetch-one", the_repository);
> } else {
> int max_children = max_jobs;
It's a bit curious that we have `GRAPH_WRITE_SKIP` as an explicit value
here as it can be trivially derived from `oidset_size()` anyway. But
other than that this is the safeguard that you were talking about: when
we have a commit graph already then we only update with new tips,
otherwise we use a full reachability walk.
Thanks!
Patrick
next prev parent reply other threads:[~2026-10-02 11:22 UTC|newest]
Thread overview: 17+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-02 8:33 [PATCH 0/2] fetch: write commit-graph using updated refs only Kristofer Karlsson via GitGitGadget
2026-10-02 8:33 ` [PATCH 1/2] test-tool read-graph: add commit-info subcommand Kristofer Karlsson via GitGitGadget
2026-10-02 8:33 ` [PATCH 2/2] fetch: write commit-graph using updated refs only Kristofer Karlsson via GitGitGadget
2026-10-02 11:22 ` Patrick Steinhardt [this message]
2026-10-02 12:40 ` Kristofer Karlsson
2026-10-05 6:27 ` Patrick Steinhardt
2026-10-05 14:47 ` Kristofer Karlsson
2026-10-06 9:46 ` [PATCH v2 0/2] " Kristofer Karlsson via GitGitGadget
2026-10-06 9:46 ` [PATCH v2 1/2] test-tool read-graph: add commit-info subcommand Kristofer Karlsson via GitGitGadget
2026-10-06 9:46 ` [PATCH v2 2/2] fetch: write commit-graph using updated refs only Kristofer Karlsson via GitGitGadget
2026-10-07 6:39 ` Patrick Steinhardt
2026-10-07 7:33 ` Kristofer Karlsson
2026-10-07 14:22 ` [PATCH v3 0/2] " Kristofer Karlsson via GitGitGadget
2026-10-07 14:22 ` [PATCH v3 1/2] test-tool read-graph: add commit-info subcommand Kristofer Karlsson via GitGitGadget
2026-10-07 14:22 ` [PATCH v3 2/2] fetch: write commit-graph using updated refs only Kristofer Karlsson via GitGitGadget
2026-10-08 6:01 ` Patrick Steinhardt
2026-10-08 6:49 ` Kristofer Karlsson
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=ar-T2y54X1uDQ4mX@pks.im \
--to=ps@pks.im \
--cc=git@vger.kernel.org \
--cc=gitgitgadget@gmail.com \
--cc=krka@spotify.com \
--cc=me@ttaylorr.com \
--cc=peff@peff.net \
--cc=stolee@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox