Proposal: modularize `bin/git-forgit` into a stable entrypoint plus `lib/`
Nobody has claimed this yet.
- Dominant language
- Shell
- Stars
- 5.1k
- Forks
- 166
- Avg merge
- 1d 11h
- Merged PRs (30d)
- 5
Description
Background
bin/git-forgit currently acts as the executable entrypoint, the shared helper library, the implementation of every command, the implementation of preview/private commands, and the dispatcher. As forgit keeps growing, this single-file structure increases review cost, makes reuse harder, and raises the chance of accidental coupling between unrelated commands.
I would like to propose a larger refactor that keeps the current user-facing behavior intact, while reorganizing the implementation into a clearer internal structure.
Goals
- Keep
bin/git-forgitas the stable executable entrypoint. - Split shared helpers and command entrypoints into
lib/. - Keep the first phase simple: load everything up front, then dispatch as today.
- Preserve both primary usage modes:
- shell plugin usage in bash/zsh/fish
git forgit <command>usage viagit-forgiton$PATH
- Make internal helpers easier to reuse without copying code or sourcing the entire
bin/git-forgit.
Non-goals
- No command renames.
- No alias or completion behavior changes.
- No lazy-loading in the first phase.
- No promise that
lib/*becomes a stable public API.
Proposed structure
bin/
git-forgit
lib/
bashunit
core.sh
helpers/
ansi.sh
git.sh
parse.sh
pager.sh
path.sh
preview.sh
repo.sh
select.sh
text.sh
forgit.sh
bin/git-forgit- stays as the stable executable entrypoint
- is responsible for setup, loading modules, and dispatch
lib/core.sh- contains paths, constants, environment setup, and the most fundamental shared helpers
lib/helpers/*.sh- contains reusable helpers grouped by capability rather than by command/domain
lib/forgit.sh- contains command entrypoints and private command entrypoints
- would ideally stay thin and orchestration-focused
Why keep a separate lib/forgit.sh?
Keeping a separate lib/forgit.sh makes the boundary clearer:
bin/git-forgitbecomes a small and stable entrypoint- command implementation can continue to evolve without repeatedly reshaping the executable wrapper itself
Why group helpers by capability?
One goal here is to encourage reuse rather than local reimplementation where possible.
For that reason, it may be more useful to group helpers primarily by capability, such as parsing, selection, preview building, text processing, git wrappers, and path handling, instead of grouping them by command/domain from the start.
That makes it easier for a new command to compose existing helpers, instead of adding yet another command-local helper block.
lib/api.sh
It may also be worth reserving room for a future lib/api.sh, without making it part of phase 1.
The idea would be:
lib/api.shcan expose many internal helpers- this is for convenience and reuse, not as an officially supported stable API
- external consumers may use it, but it should be understood as depending on forgit internals
- helpers exposed there may change, move, be renamed, or be removed at any time
So api.sh would be a centralized export surface for internal implementation, not a compatibility promise.
Loading model
One possible way to keep phase 1 simple is:
bin/git-forgitresolvesFORGIT_INSTALL_DIR- it loads
lib/core.sh - it loads the helper modules
- it loads
lib/forgit.sh - it dispatches to
_forgit_"${cmd}"
The main point of this phase would be structural cleanup rather than changing execution semantics.
Compatibility requirements
In particular, it would be important not to break either of the two main ways people use forgit:
1. Shell plugin mode
Ideally these would continue to work without requiring user config changes:
forgit.plugin.zshforgit.plugin.shconf.d/forgit.plugin.fish
That includes:
- existing wrapper functions
- existing aliases / abbreviations
- existing
FORGIT_*configuration behavior - worktree wrappers with
cdbehavior
2. Git subcommand mode
These would ideally keep working as they do today:
git forgit <command>- git aliases that expand to
forgit ...
That suggests git-forgit should remain the executable name and should stay self-contained as an entrypoint.
3. Completions
The existing bash/zsh/fish completion behavior would ideally remain unchanged in phase 1.
Risks
- reviewers may interpret
lib/api.shas a stable API unless we explicitly document otherwise - packaging/install paths may miss newly added
lib/**files
Alternatives considered
Keep everything in one file and only reorganize sections
Pros:
- lowest risk
- minimal packaging/testing impact
Cons:
- does not create real module boundaries
- is less helpful for improving helper reuse
- continues to keep
bin/git-forgitas the main implementation container
Add lazy-loading from the start
Pros:
- could enable finer-grained loading later
Cons:
- adds more shell complexity immediately
- makes the first refactor harder to reason about and review
Open questions
- How coarse- or fine-grained should the initial
lib/helpers/*.shsplit be? - Is it worth splitting
lib/forgit.shfurther at all, and if so, when? - Is it worth preserving existing helper names during the initial move and deferring naming cleanup to a later pass?
- Is it worth eventually consolidating subcommand metadata—such as names, descriptions, and completion behavior—so bash, zsh, and fish completions can share a single definition?
Summary
My current inclination would be to:
- keep
bin/git-forgitas the stable entrypoint - move shared logic into
lib/ - keep phase 1 fully eager-loaded
- organize helpers by capability
- treat any future
lib/api.shas a convenience export layer for internals, not as a stable public API
@cjappl @carlfriedrich @sandr01d would love to hear what you all think too. Does this direction seem reasonable? Especially curious whether the split between bin/git-forgit, lib/forgit.sh, and lib/helpers/* feels right.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading bin/git-forgit and the existing plugin entrypoints forgit.plugin.zsh, forgit.plugin.sh, and conf.d/forgit.plugin.fish, then trace the current loading and dispatch flow. Compare the proposed lib/core.sh, lib/helpers/*.sh, and lib/forgit.sh boundaries against the compatibility requirements; done means the refactor preserves shell plugin behavior, git-forgit subcommands, aliases, configuration, worktree wrappers, and completions.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- bash, fish, git, shell, zsh
- Domain
- cli, developer-experience, tooling
- Issue type
- Refactor
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Needs clarification
- Newbie friendliness
- 35/100