Hacker News new | past | comments | ask | show | jobs | submit login
ToolGit: A collection of scripts that extend Git with various sub-commands (github.com/ahmetsait)
111 points by ahmetsait 1 day ago | hide | past | favorite | 52 comments





These should be aliases in your git config.

    git config --global alias.<cmd> <cmd>
I'm really trying to understand, and not to be too negative. I get coming up with a solution to a problem, and finding it neat locally. But before posting to HN, one might realize that these solutions that git provides existing tools for is better than maintaining this.

I'm also confused by all the comments just blindly commenting in the affirmative or providing suggestions. Are we all bots?


But before posting to HN, one might realize that these solutions that git provides existing tools for is better than maintaining this.

One might. But not everybody knows about those existing tools. Taking myself as an example: I've been using Git since nearly the time it was released and I had no idea that git had aliasing built in. Sometimes things just never come up and people don't find out about them. shrug


Hi! Author here. Some of these can indeed be an alias, others.. not so much. `git-root` for example is more-or-less a one-liner, meanwhile `git-mode-restore` is a ~840 loc python script. I do use git aliases but trying to do anything non-trivial in them seems rather counter-productive so I'm confused about everyone suggesting it :P

Hey. I think my point is that writing and maintaining 900 lines of code is an option, and another option is to figure out how to not write and rely on 900 lines of code. I looked through git-more-restore a bit and I think you're trying to reverse permission changes in your working branch back to what's on HEAD.

I think a way to do this is to not let git track that in the first place, with some variation of:

    git config core.fileMode false
via: https://stackoverflow.com/questions/1580596/how-do-i-make-gi...

or

https://stackoverflow.com/questions/2517339/how-to-restore-t...


It seems to me like a shallow reading and dismissal of your work. Which is sad. For what it's worth I'll be giving it a go as soon as I'm back at work on Tuesday.

What would the alias command be for

  git-delete-gone-branches
?

EDIT: I saw your other comment (incorrectly) claiming that git remote prune origin will do the job. I don't know of any git trickery that can do the job. And believe me I'd love it if there was something.


Thanks for keeping me honest. I just read the first line of the stackoverflow, but the one liner is still in the comments. I just tested it.

https://stackoverflow.com/questions/7726949/remove-tracking-...

    git fetch -p && git branch -vv | awk '/: gone]/{print $1}' | xargs git branch -d

That looks fine and will generally work, except when the current branch is one of the ones to be deleted.

The script in OP handles that edge case by switching to the main branch before deleting. That may or may not be intuitive but I think it's a reasonable response. The script also prints error messages for various other bad inputs. Once you strip all that out it's only a few lines long.


I'm going to assume you have about as many years of experience as me based on your profile, but if you're going to argue that maintaining this script - for the sole convenience of deleting a branch out from underneath you - is worth it, then there's nothing more I can do to help you.

Same as mine. I also pass the `-r` flag to xargs so it doesn't run if there are no inputs.

I have an alias "git prune-branches" that deletes branches that remote has deleted. Is that what you are loking for? Cannot check the code right now but will try to get back tomorrow.

If you're willing to report back I'd be very grateful.

The trouble is that git aliases aren't composable and you can't install them. Each installation has its own aliases. If you are in a new machine you must define the aliases yourself. This also makes git aliases have poor discoverability.

Compare this with another tool that lets people extend its own command, cargo. You can do cargo install <something> and then run cargo <something>.


As a culture we need to move away from “just write these things yourself/copy these aliases and maintain them”. It makes things too much of a rite of passage.

No, I have never used NPM directly. Why do you ask?


If I see a git thread, I tend to semi-spam my git aliases because so few people realize that they can customize git's command line behaviour.

Yeah I am just finding it hard to believe that there are so many people who don't.

Maybe we are 10x engineers w.r.t. git aliases. Congratulations


Most tech folks in IT that I've known tend to just accept defaults, whether they're in vscode, a command line, or joining a new project. They don't seem to think in terms of "this is annoying/inefficient, how can I make it better?"

I should get "10x engineer with my git aliases" put onto a t-shirt :)


You’re suggesting not to use a functionality built into got from early days that naming a script git-something and putting it on your PATH gives you a sub command of “git something”.

Why do you disagree with this early decision from git?


Came here to say this.

I looked at 1/3 of the scripts and not one _adds_ functionality, they only add interfaces for existing functionality.

I hate to say this but I feel like this post got upvoted simply for being hit-adjacent.

I wonder if voting on the front page is a bad setup.


Some of my git aliases might be useful to folks here (I post them from time to time on git threads)

Time to share my gitconfig aliases again :D

  lol = !git --no-pager log --graph --decorate --abbrev-commit --all --date=local -25 --pretty=short
  sw = !git checkout $(git branch -a --format '%(refname:short)' | sed 's~origin/~~' | sort | uniq | fzf)
  lc = !git rev-parse HEAD
  rb = !git for-each-ref --sort=-committerdate refs/heads/ --format='%(refname:short) %(objectname:short) %(committerdate:format:%F)' | column -t
  fza = "!git ls-files -m -o --exclude-standard | fzf -m --print0 | xargs -0 git add"
  gone = "!f() { git fetch --all --prune; git branch -vv | awk '/: gone]/{print $1}' | xargs git branch -D; }; f"
  root = rev-parse --show-toplevel
  oldest-ancestor = !zsh -c 'diff -u <(git rev-list --first-parent "${1:-main}") <(git rev-list --first-parent "${2:-HEAD}") | sed -ne \"s/^ //p\" | head -1' -
  diverges = !sh -c 'git rev-list --boundary $1...$2 | grep "^-" | cut -c2-'
  dlog = "!f() { GIT_EXTERNAL_DIFF=difft git log -p --ext-diff $@; }; f"
Some of these I don't use much, but others I use every day:

"git fza" (aliased to "ga") shows all unstaged files in fzf and you can use space to toggle them, then hitting enter finishes adding/staging them. This is great for selecting some files to stage. I use this one every day, it makes my workflow just a little better :)

"git gone" deletes local branches that don't exist in the remote. I just saw in this thread that git remote prune origin might do the same thing, I need to test that.

"git lol" is a log alias.

"git oldest-ancestor brancha branchb" does what it says.

"git root" is part of an alias "gr" which runs "cd $(git root)". That takes you to the project root, and "cd -" will take you back to your previous location.

"git dlog" shows a detailed commit log.

"git lc" just shows the last commit.

"git rb" shows recent branches. Piping it to "| sort -k3" will sort by date. (I really need to update that!)

"git sw" shows branches in fzf, hit enter on one and you checkout that branch.


If you use oh-my-zsh's git plugin, it defines a huge number of aliases that minimize the characters you have to type in. I use these mostly every day:

  gl = git pull
  gp = git push
  ga = aliased to git fza from my above comment
  gc = "git commit -m " so I type 'gc "chore: commit message"'
  gd = git diff
  gs = git status
  gr = cd's to the top level of the repo, cd - returns to your previous position.
I had to override some of the oh-my-zsh defaults in my .zshrc:

  unalias gcd
  unalias ga
  unalias grep
  unalias gr
  unalias gc

  alias m='mise'
  alias fd='fd -HI'
  alias gfv='git fetch --verbose'
  alias gs='git status'
  alias gr='cd $(git rev-parse --show-toplevel)'
  alias ga='git fza' # depends on gitconfig containing the alias "fza"
  alias gc='git commit -m' # depends on "unalias gc"
  alias commit-types='cat $HOME/.local/dotfiles/commit-types.txt'
  alias ct='cat $HOME/.local/dotfiles/commit-types.txt'
  alias mcdd='mcd $(date "+%Y-%m-%d")'
  alias sp='showpath' # showpath = echo $PATH | sed -e $'s/:/\\\n/g'
  alias uuidgen='uuidgen | tr "[:upper:]" "[:lower:]"'
I hope these are useful to someone at some point!

If you really want to minimize typing, try Emacs + Magit.

I should, I use Emacs a bunch, but I guess it's just muscle memory now to flip to the terminal.

I see that you're using --print0 and -0 for your "git fza" alias, it might be useful to add -z to the git-ls-files invocation and --read0 to fzf.

Ah, good point, I do need to revisit these and tidy them up. The perils of cargo-culting!

Fairly sure git remote prune will do just the remote-tracking refs/remote branches, same as fetch --prune that you're already using inside your alias. It won't remove your checked-out local copies of those.

> "git oldest-ancestor brancha branchb" does what it says.

The oldest (common) ancestor of two revisions would typically be the initial commit. I assume your alias really finds the last (most recent) common ancestor. But are you aware of the `git merge-base` builtin? Your alias looks an awful lot like it.


Oh, yes that's exactly what I really meant. Whoops.

I'll check out merge-base tomorrow, thanks for mentioning it!


I have only one that I use all the time "rcheckout". Since I use gitlab and it allows the creation of branches with a title like "ISSUE_NR-ISSUE_TITLE" I can do git rcheckout ISSUE_NR and checkout that branch without having to type the full title. I don't know where I got this from originally.

#!/bin/bash git fetch

[ ${#} -ne 1 ] && { echo -e "Please provide one search string" ; exit 1 ; } MATCHES=( $(git branch -a --color=never | sed -E 's|^[* ] (remotes/origin/)?||' | sort -u | grep -E "^((feature|bugfix|release|hotfix)/)?([A-Z]+-[1-9][0-9]*-)?${1}") ) case ${#MATCHES[@]} in ( 0 ) echo "No branches matched '${1}'" ; exit 1 ;; ( 1 ) git checkout "${MATCHES[0]}" ; exit $? ;; esac echo "Ambiguous search '${1}'; returned ${#MATCHES[@]} matches:"

for ITEM in "${MATCHES[@]}" ; do echo -e " ${ITEM}" done exit 1


Do you still use this now that 'git checkout' tab completes?

Here's one I like that shows all the branches sorted by date, most recent commit hash, branch name, time ago, and user who committed, in color.

  alias git.branches="git for-each-ref --color=always --sort=-committerdate refs/heads refs/remotes --format='%(authordate:short) %(color:red)%(objectname:short) %(color:yellow)%(refname:short)%(color:reset) (%(color:green)%(committerdate:relative)%(color:reset)) %(authorname)'"
  
Bonus one for contributing to open source projects outside of work hours:

  alias git-future-commit='git commit --date "$(date -v +7H)"'\n

Since we are at a git customisation thread, I remember an older HN post that did the opposite. It had a set of like 10 git aliases that were aligned with the simplest of workflows for newbies. I have lost track of this does anyone recall this or knows anything similar?

https://gist.github.com/romainl/a3ddb1d08764b93183260f8cdf0f...

This script in your path means that after you say pull code and have merge conflicts, "git jump" will open vim with the quickfix list populated with the locations of the conflicts.


> NOTE: The original git-jump now supports the flag --stdout, which makes this hack redundant, but I will keep it for posterity

This update is from May 2023


Jump is distributed under /usr/share/git-core/contrib or something.

How’s this different to ‘git mergetool’?

This needs a “git-track”. Make the current branch track the one in origin with the same name, if it exists

  [push]
    autoSetupRemote = true

In oh-my-zsh you have `ggsup` which is an alias for `git branch --set-upstream-to=origin/$(git_current_branch)`. `git_current_branch` is also a function that is bundled with oh my zsh.

Obligatory mention of https://github.com/tj/git-extras/blob/main/Commands.md for more/similar commands.

Especially useful to me is `git bulk` to apply a command to all the git repos under the directory, and `git ignore "bin/"` to quickly add something to the `.gitignore` file.


I like these a lot! It seems like git-extras is pure bash allowing it to work out of the box without any dependencies but I would probably go mad if I had to implement¹ reading/writing git index² in bash.

¹ https://github.com/ahmetsait/toolgit/blob/58713ead5abc060510...

² https://git-scm.com/docs/index-format


> `git bulk` to apply a command to all the git repos under the directory

There is also: gitup . -e "arbitraryShellCommand withArg"


"git-delete-gone-branches" is exactly what I've been needing, will give it a try soon!

"git delete gone branches" is a one liner that you can google. first stack overflow result gives you the answer:

    git remote prune origin
Why would you use this 50 line script to do the same?

This only removes references to remote branches. See e.g. [0]. The command in OP operates on local branches.

[0] https://stackoverflow.com/questions/20106712/what-are-the-di...


Just for anyone else following along..you're right. Here it is.

    git fetch -p && git branch -vv | awk '/: gone]/{print $1}' | xargs git branch -d

Drupal core developers likely will appreciate git-forward quite a bit. I personally like git force-pull, I often do git remote update; git branch -D foo; git checkout -b foo, that looks like a good way to collapse those.

Let me advertise git blameall from https://github.com/gnddev/git-blameall here's a part of Drupal index.php: https://i.imgur.com/Xw4OAEC.png

Also, my own creation which provides a safety net against git reset --hard https://gist.github.com/chx/85db0ebed1e02ab14b1a65b6024dea29 AFAIK this trick is the only way to override a built in command.


save this to a file named 'git-moo' in your path

    #!/bin/sh
    echo '
    < Have you mooed today? >
      ----------------------
             \   ^__^
              \  (oo)\_______
                 (__)\       )\/\
                     ||----w |
                     ||     ||
    '
then type

    git moo
Enjoy

Most of these would be better as aliases in your global hit config file.

Several are 30+ lines of boilerplate to run a git one-liner.


This was my first thought as well. This might be an indictment of how obscure/confusing some git invocations are...hence the alias to human readable names.

It is kind of interesting that there is much interest, work and maintenance on these over-complications...There seems to be a related repo - git-extras - that does the same as this repo, and has 17k stars.


It struck me the same, many seem like aliases such as `gc!` from oh-my-zsh.

Uhm... These scripts seem to be over-engineered if used in scripting, but let me review it. ':)

`git-amend`: A simple `git-commit` alias would support autocomplete and handle all `git-commit` options. Additionally, I would not add the `--no-edit` option by default as editing the commit message would be fine and quickly aborted (say in `nano`), but would introduce another `amend-no-edit` alias as it would be supported in autocomplete anyway.

`git-delete-gone-branches`: This seems to be handy for me. Might be a neat picking, but I would avoid using `awk` in this case in favor of `while IFS=$'\t' read -r ref_name marker ... < <(git for-each-ref --format='%(refname)%09%(upstream:track,nobracket)' ...)`. Additionally, it's a dangerous/destructive operation, it should have the `--force` option. Especially if it deletes a branch if it has commits that are not yet merged.

`git-dir`: The same: an alias is just fine. If used as a user command, sure.

`git-force-pull`: Seems to be fine. I would probably parse the list of tracked branches remotes to be passed to `git-fetch` and then process each with `git-for-each-ref` in a single loop.

`git-forward`: The script seems to execute excessive `git-pull` and `git-fetch` (I would prefer the latter unifying the commands in use), in terms of interacting with the remote repository that would probably need more visible progress verbosity to stderr, and then merely `git-merge` the current branch using the `--ff-only` option, but `git pull --ff-only` is okay too. Since bash supports arrays the script may construct multiple arguments to pass to `git-fetch` so it may fetch more in a single go (but, to be honest, I'm not sure if it would not cause the whole `git-fetch` run to fail if any of its refspec fails for whatever reason).

`git-gc-all`: I would go with an alias as it's clearly a user command, but yes, adding the `--force` would be more tricky perhaps requiring an environment variable like `FORCE` to handle the force flag (i.e. `FORCE= git-gc-all`). Not sure why the script checks whether the command runs in the git repository or a working directory. (Also, it would need the exact path for the script, otherwise it may run in as situation where it would trying to find the `git-in-repo` script in user's current directory.)

`git-in-repo`: Not sure if it's supposed to be used as a user command at all, not a scripting one, but if it's the latter case, git checks the repository directory itself if the given command works with the repository. (N.B. git quirk: deleting a remote repository ref, at least using `git pull -d ...`, requires any local git repo even if it's unrelated to the remote or does not have a remote repository registered in its remotes list...)

`git-is-branch-remote`: I can't pick of a scenario this would be handy for.

`git-is-head-detached`: Not sure if it's supposed to be used in scripts only. From the user perspective, `git-status` or configured `PS1` indicating if `HEAD` is detached would work.

`git-is-worktree-clean`: Another alias candidate if it's supposed to be a user command?

`git-legacy`: To be honest, I didn't figure out how it works and what it's supposed to do. ':)

`git-main-branch`: The script has currently the `origin` remote hardcoded. But I'm not sure if the concept of the main branch exists in git at all.

`git-mode-restore`: This script is crazy. ':) If I understand its purpose, can this be implemented using `git-diff-tree` and `git-update-index`?

`git-root`: Another alias candidate? Is Cygwin pain for certain commands? I also don't know how `cygpath` affects what the Cygwin users do.

`git-xlog`: This seems to be a good candidate as a `git-reflog`/`git-log` builtin, I guess. I have a script, similar to this one, that finds `TODO`-marks introduced (i.e. added lines only) at a specific revision using `git-diff-tree`, but yes it must be combined with `git-rev-list` to work like this one.

General stuff:

* The `USAGE` variable is unnecessarily evaluated everytime any script gets run and does not require to exist: the usage might be defined as a function to be invoked on demand and run external commands like `expand` on demand.

* The scripts may also construct arbitrary options in an array and inject the array to command, hence not requiring command duplicates with sightly another set of options.

* Some of commands from the toolset don't work when launched from the repository directory because they require to be installed first.

* Some of variables are not quoted and may cause unexpected results.




Consider applying for YC's W25 batch! Applications are open till Nov 12.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: