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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.