# plugin-git: git aliases for the Fish shell, with staged-change testing

> plugin-git is a Fish shell plugin that gives oh-my-fish and fisher users a set of short git commands, and it is based loosely on the Oh My Zsh git plugin. What it adds beyond a copy of that list is Fish-specific resolution of your default branch name and a command that runs your test suite against staged changes only.

**jhillyerd/plugin-git** — Git aliases plugin for the Fish shell (similar to oh-my-zsh git)

- Repository: https://github.com/jhillyerd/plugin-git
- Stars: 777 · Forks: 96
- Language: Shell
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/jhillyerd-plugin-git

## Three install routes, and the one that matters for reproducibility

The plugin is a git aliases plugin for the Fish shell, and it is described as similar to the oh-my-zsh git plugin, though the README says it is based loosely on the Oh My Zsh Git Plugin rather than being a straight port.

There are three documented install routes. For oh-my-fish:

```bash
omf install https://github.com/jhillyerd/plugin-git
```

For fisher:

```bash
fisher install jhillyerd/plugin-git
```

And for Nix home-manager, where the plugin is declared as a program in the fish configuration:

```yaml
{
  # ...
  programs.fish = {
    enable = true;

    plugins = [
      {
        name = "plugin-git";
        src = pkgs.fishPlugins.plugin-git.src;
      }
    ];
  };
  # ...
}
```

The Nix route is the one worth thinking about if you already manage your shell declaratively, because the plugin is packaged in nixpkgs as fishPlugins.plugin-git, and the badge in the README points at the nixpkgs definition. That means your dotfiles pull a pinned package version instead of a git checkout that can drift.

The two Fish framework routes are not equivalent in behaviour. oh-my-fish is a plugin framework with its own lifecycle and completion system, and fisher is a minimal manager that just clones plugins into a directory. The plugin itself is small, with an init.fish file and conf.d/ and functions/ directories at the root of the repository, so it does not depend on much of either framework, but it is packaged for both.

The badge also records a minimum Fish version of 3.5.0, and the licence is MIT.

## The default branch resolution that no oh-my-zsh clone does

The one piece of real logic in the plugin is how it decides what your main branch is called, and it is worth understanding because most alias sets just hardcode master.

plugin-git respects the init.defaultBranch setting that was introduced in git 2.28. The resolution order is explicit. First, init.defaultBranch if it is set and the branch exists. Second, main if it exists. Third, master as the fallback.

Each of those steps has a reason. The first honours an explicit global preference, but only if a branch of that name actually exists in the repository, which stops the alias from trying to check out something that was never created. The second catches the large number of repositories created after the industry moved away from master, and the third is the historical fallback so that older repositories still work.

That resolution is exposed in the alias tables through function calls rather than literals. The checkout section has gcod mapping to git checkout (__git.develop_branch) and gcom mapping to git checkout (__git.default_branch), so both the develop branch and the default branch resolve dynamically per repository.

This is the part that separates the plugin from a copy-pasted alias list, and it is also the part most likely to surprise you if you use a non-standard branch name, because the fallback chain will quietly pick main when you meant something else.

The repository is on master with its last push on 2026-07-28, and the most recent release is v0.4 from 2026-04-05, after v0.3 in October 2024 and v0.2 in February 2024.

## The alias tables, and the ! suffix that changes the meaning

The bulk of the documentation is tables mapping an abbreviation to a git command, grouped by area: bisect, branch, checkout, commit, diff and more.

The commit table shows the pattern most clearly. gc is git commit -v, gc! is git commit -v --amend, gcn! is git commit --no-edit --amend, gca is git commit -v -a, gca! is git commit -v -a --amend, and gcan! is git commit -a --no-edit --amend. gcv is git commit -v --no-verify and gcav is git commit -a -v --no-verify, with an exclamation variant for the amended form.

So the convention is that a trailing exclamation means amend. That single character is doing a lot of work, and it is the sort of convention that is fine after a week and dangerous if you are tired at the end of a long day, since gc! and gc differ by a rewrite of the last commit.

The branch table shows the same level of detail. gb is git branch -vv, gba is git branch -a -v, gban is git branch -a -v --no-merged, gbd is git branch -d and gbD is git branch -D, preserving git's own distinction between safe delete and force delete in a single letter of case. gbda deletes all branches merged in current HEAD and takes a --gone flag to include branches already deleted on the remote, and gbage lists local branches with their age.

The diff table follows the same shape, with gd, gdca for the cached version, gds and gdsc for the stat variants, gdw and gdwc for word diff, and gdt listing changed files.

The level of granularity is the point. This is not a handful of convenience aliases, it is a fairly complete replacement vocabulary for the everyday git commands, which is what makes it a plugin rather than a snippet.

## gtest is the alias that earns the plugin its keep

Most of the aliases are compressions of git commands you could type. One is not.

gtest runs a test command against staged changes only. The sample workflow shows it plainly:

```
gtest make test  # runs 'make test' against staged changes only
```

The workflow that surrounds it is worth reading as a whole, because it tells you what the plugin is optimising for. You stage only the files you want with ga file1 file2, you run gtest make test, the tests fail, you stage more changes with gapa, you run gtest again, and only when the tests pass do you commit with gcm "tests pass" and then commit the rest with gca.

That is a real workflow, and it is not something a plain alias set gives you. Running your test suite against a staging area rather than a working tree means the test runs against exactly what is about to be committed, which is a different thing from running it against whatever is in your directory. It also means the plugin is implicitly assuming a project with a test command, which is the assumption that makes it useful in a software project and pointless in a scratch repository.

Two other aliases in the same sample are worth calling out. gwip saves a WIP commit of the working directory for later, and gunwip restores that work in progress, which is the standard escape hatch for context switching between tasks. And gp! force pushes changes, which pairs with the amend aliases, so the sample flow of gcan! followed by gp! is a coherent two-step for rewriting the last commit before it reaches anyone else.

The sample also shows gfa to fetch all branches from origin, gloga to view a log graph of recent activity, gco to switch to an existing branch, gwch to inspect what changed recently with diffs, and grbom to fetch origin and rebase on the main or master branch.

## The destructive aliases, and the risk of installing a whole vocabulary at once

There is a design question here that the plugin does not address, and it is worth thinking about before you install it.

The alias set includes gclean!! to get back to a pristine repository, gp! to force push, gbD to force delete a branch, and gcan! to amend a commit with all modified files. The sample workflow is written by someone comfortable with those, but a user who has just installed the plugin and typed gclean!! by muscle memory on a repository with uncommitted work will have a bad afternoon.

Compare that with how git itself has evolved. git branch -d refuses to delete an unmerged branch, and -D is the escape hatch, and the plugin preserves that distinction. But the plugin also adds its own layer of compounding, where an exclamation means amend and a double exclamation means something else entirely, and where a two letter alias can rewrite history and push it.

The mitigations available are modest. The aliases are added by the plugin, so you can inspect or disable them in the functions directory. And nothing here is a wrapper with hidden behaviour: the tables map directly to git commands, so gclean!! is a git command with git semantics, and if you forget an alias you can always fall back to typing git.

The other practical point is that a plugin like this is a shell of aliases you will use for years without re-reading, so the day you adopt it, you should skim the whole table once rather than discovering gcan! in a hurry. The README is the reference for that, and it is short enough to do in a coffee break.

There is also a smaller question of scope. The tables are grouped by git subcommand, which makes them easy to look up, but a plugin that defines a hundred short names in a shared shell namespace is, by construction, more likely to collide with a function you already have.

## Maintenance and the state of the project

The repository is small and the release history is short. The top level holds a LICENSE, the README, an init.fish file, and conf.d/ and functions/ directories, which is the standard layout for a Fish plugin: init.fish runs at load, conf.d holds configuration that is sourced, and functions holds the alias definitions.

The versions are v0.4 from 2026-04-05, v0.3 from 2024-10-05 and v0.2 from 2024-02-09. The last push to the repository was on 2026-07-28, and the repository is not archived, so the code is still being touched even though the tagged releases are infrequent.

The gap between v0.3 in late 2024 and v0.4 in early 2026 is a year and a half, which for a shell alias plugin is not alarming. There is not much in a git alias set that breaks, and the one thing that could break is the git behaviour underneath, which is where the init.defaultBranch handling from git 2.28 comes in. A plugin that hardcoded master would have started misbehaving quietly across the ecosystem, and this one was rewritten to avoid that.

The licence is MIT, the project has no homepage beyond the repository, and the topics list places it squarely in the Fish ecosystem: fish, fisher, git, oh-my-fish and omf.

If you use Fish and want these aliases, the install is one line and the documentation is one page. If you do not use Fish, none of this applies, and the same ideas are available in the plugin ecosystem for your own shell.

## Conclusion

Adopt plugin-git if you use Fish with oh-my-fish or fisher and you keep a muscle memory for the short git aliases from the Oh My Zsh plugin, since the port is complete enough to be worth the switch. Do not adopt it if you use Bash or Zsh, because the plugin is Fish-specific and depends on the Fish plugin framework. Before you install it, check what your default branch resolves to, because the plugin honours init.defaultBranch and falls back through main to master, and read the alias tables carefully before you start typing gc! and gp! in a repository with real history.

## FAQ

### How do I install plugin-git?

With oh-my-fish, run omf install https://github.com/jhillyerd/plugin-git. With fisher, run fisher install jhillyerd/plugin-git. For Nix home-manager, declare it in the fish plugins list using pkgs.fishPlugins.plugin-git.src, since the plugin is packaged in nixpkgs.

### How does plugin-git decide what the default branch is?

It respects init.defaultBranch if it is set and a branch of that name exists, then falls back to main if it exists, then to master. The resolution is exposed through the checkout aliases, where gcom checks out the default branch and gcod checks out the develop branch.

### What does the gtest alias do?

It runs a test command against staged changes only, so gtest make test runs make test against what is staged rather than against the whole working directory. The intended flow is to stage the files you want with ga, run gtest, stage more with gapa if it fails, and commit only once the staged set passes.

### What is the difference between gc and gc! in plugin-git?

gc is git commit -v and gc! is git commit -v --amend, so the trailing exclamation marks an amend across the alias set. There are separate variants for staging all files, for skipping the editor with --no-edit, and for bypassing hooks with --no-verify.

## Sources

- [Issues](https://github.com/jhillyerd/plugin-git/issues)
- [jhillyerd/plugin-git on GitHub](https://github.com/jhillyerd/plugin-git)
- [License: MIT](https://github.com/jhillyerd/plugin-git/blob/master/LICENSE)
- [README](https://github.com/jhillyerd/plugin-git/blob/master/README.md)
- [Releases](https://github.com/jhillyerd/plugin-git/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/jhillyerd-plugin-git
