Re: [PATCH RFC v3 2/2] Move libgit.a sources into separate "lib/" directory

Michael Montalbo <[email protected]>
Newsgroups org.kernel.vger.git
Message-ID <CAC2QwmKs-TV2f=2BhBNQGtVA4vzciN-Vkh95RkGcVKe=6zwwRQ@mail.gmail.com>
On Thu, Aug 13, 2026 at 7:27 AM Junio C Hamano <[email protected]> wrote:
>
> Johannes Schindelin <[email protected]> writes:
>
> > Of course, it would be even nicer if `lib/` was split up further, but
> > then:
> >
> > 1) You've got to start _somewhere_. As we saw with so many things on this
> >    list, they never materialized because reviewers asked for too much and
> >    weren't happy to get incremental improvements first.
>
> That is why moving everything to 'lib/' and thinking about the rest
> later will not work.  Instead, moving a specific component to a
> specific subdirectory (not 'lib/') would be a reasonably
> self-contained first step.  Consider 'builtin/' as an example:  it
> is focused, and anyone can easily tell what the criterion is.  If
> it is the top-level cmd_foo() implementation, it goes there;
> otherwise, it does not.  Then, you can proceed to the second step,
> and then the third.  Iterate enough times, and the top level will
> become thin enough that you can either make your final step a
> no-op and leave the remaining files there, or create one last
> group to house the hodgepodge of leftover bits and move them there.
>
> > 2) Naming is hard. As we saw with _many_ refactorings (I am thinking about
> >    the low-level merge stuff as well as the ODB stuff, for example), it is
> >    unlikely to get the origanization right the first time. So I'd think
> >    that first moving the bulk of the library code to lib/ is a good start,
> >    and worth merging, leaving later contributions to chop off further
> >    parts into subdirectories of lib/.
>
> Again, this is because you are trying to do everything at once.
> Instead, come up with one clear concept, name it well, move the
> related files there, and then iterate.
>

Reading the discussion initially, I was inclined to agree with the
"move to lib/" idea. As a newer contributor, I do think the number of
files at the top level are a lot to navigate. Taking a step back to
roughly frame things, points of tension I see are:

  - the level of granularity for a conceptual grouping.
  - the level of conceptual cohesiveness before a grouping should be
    created, including what "cohesiveness" even means.
  - the need to create conceptual groups in a timely, deliberate
    manner without being too quick to create half-baked ones.

After considering these points, I ended up being in favor of coming up
with more granular, clear conceptual groupings before creating a
coarser, general grouping like "lib/". However, I do think this
grouping process can happen in a more timely and deliberate way. The
main appeal of moving stuff to "lib/" is to make progress on an effort
that otherwise has no concrete roadmap and allows us to "not let
perfect be the enemy of good."

There are already conceptual grouping efforts, both ongoing and past,
that might provide useful signals for how to make further progress.
A good set of signals would:

  - correctly reflect the conceptual groups that were already created
    successfully.
  - indicate candidate group areas as well as areas not "settled."
  - be generated by an ongoing, mechanical process that encodes
    the project's conceptual grouping standards.

I'm not necessarily sure if generating such a set is feasible or a
well-formed plan, but I wanted to provide a motivating example to
criticize.

One mechanical signal is how tightly a set of files shares the same
internal headers: if two .c files include many of the same project
headers (dropping the plumbing everything includes), they lean on the
same machinery. Averaged over every pair in the group, that gives one
cohesion number.

The already-carved directories have the following scores: refs/ scores
3.1, trace2/ 1.2, odb/ 1.0, while the whole root is 0.3. The scores
also seem predictive: run at the end of 2020, before odb/ existed, the
object-database files then at the root already scored 1.2, as cohesive
as trace2/ or negotiator/ were and 6x the root. odb/ is being carved
out of those exact files now.

Pointing forward, merging today's root .c files by that measure until
cohesion drops below the carved band proposes these groups, with no
hand-picked list [1]:

  6.9  connect.c fetch-pack.c remote-curl.c send-pack.c transport.c
       upload-pack.c
  6.8  diff-lib.c entry.c read-cache.c unpack-trees.c
  6.6  commit.c log-tree.c ref-filter.c revision.c sequencer.c
  6.3  diff.c diffcore-rename.c merge-ort.c
  6.0  delta-islands.c pack-bitmap-write.c pack-bitmap.c
carved directories, for reference:
  3.1  refs/
  1.0  odb/
  1.0  trace2/

Every proposed group is more cohesive than any directory already
carved: the same measure that would have flagged odb/ in 2020 points
now at a wire, index, revision, diff, and pack group.

I'm not sure these results necessarily provide the best signal, but
I believe it would be possible to mine a signal from Git's history /
existing project structure that would provide a principled roadmap
for deliberately grouping concepts.

[1] the (Claude generated) script:

import subprocess, re
from collections import defaultdict
from itertools import combinations

# shared distinctive #includes for every .c file; "distinctive" = not
# plumbing, i.e. included by <=12% of the root .c files
pat = r'^[[:space:]]*#[[:space:]]*include[[:space:]]+"[^"]+"'
out = subprocess.check_output(
    ["git", "grep", "-E", pat, "--", "*.c"], text=True)
q = re.compile(r'"([^"]+)"')
inc = defaultdict(set)
for line in out.splitlines():
    path, rest = line.split(":", 1)
    m = q.search(rest)
    if m:
        h = m.group(1).split("/")[-1]
        inc[path].add(h[:-2] if h.endswith(".h") else h)
root = [f for f in inc if "/" not in f]
docs = defaultdict(int)
for f in root:
    for h in inc[f]: docs[h] += 1
dist = {h for h, d in docs.items() if d <= len(root) * 0.12}
for f in inc: inc[f] &= dist
files = sorted(f for f in root if inc[f])

def sim(a, b): return len(inc[a] & inc[b])
def coh(m):
    ps = list(combinations(m, 2))
    return sum(sim(a, b) for a, b in ps) / len(ps) if ps else 0.0

# average-linkage agglomeration of the root files until the best merge's
# linkage drops below T; link[i][j] holds the summed pair similarity
T = 5.0
mem = {i: [f] for i, f in enumerate(files)}
size = {i: 1 for i in mem}
link = defaultdict(dict)
for a, b in combinations(range(len(files)), 2):
    s = sim(files[a], files[b])
    if s: link[a][b] = s; link[b][a] = s
act = set(mem); nid = len(files)
while True:
    best, bv = None, T
    for i in act:
        for j, ss in link[i].items():
            if i < j and ss / (size[i] * size[j]) >= bv:
                bv, best = ss / (size[i] * size[j]), (i, j)
    if not best: break
    i, j = best; c = nid; nid += 1
    mem[c] = mem[i] + mem[j]; size[c] = size[i] + size[j]
    for x in (set(link[i]) | set(link[j])) - {i, j}:
        s = link[i].get(x, 0) + link[j].get(x, 0)
        link[c][x] = s; link[x][c] = s
    for x in list(link[i]): link[x].pop(i, None)
    for x in list(link[j]): link[x].pop(j, None)
    link.pop(i, None); link.pop(j, None)
    act.discard(i); act.discard(j); act.add(c)

groups = sorted((mem[c] for c in act if size[c] >= 3),
                key=lambda m: -coh(m))
print("candidate subsystems (ranked by cohesion):")
for m in groups:
    print(f"  {coh(m):.1f}  {' '.join(sorted(m))}")
tree = subprocess.check_output(
    ["git", "ls-files", "--", "*.c"], text=True).split()
print("carved directories, for reference:")
for d in ["refs", "reftable", "odb", "trace2"]:
    c = coh([f for f in tree if f.startswith(d + "/")])
    if c: print(f"  {c:.1f}  {d}/")
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.