Hacker News new | comments | show | ask | jobs | submit login
Little Things I Like to Do with Git (csswizardry.com)
656 points by csswizardry 202 days ago | hide | past | web | favorite | 103 comments

Since git 2.9 there is another experimental, opt-in, improvement to git diff that results in more readable diffs: Heuristics that try to capture the logical changes better. By default git tries to delay the start of diffs as long as possible leading to bogus highlighting of e.g. doc comments (slash star star).

See https://github.com/blog/2188-git-2-9-has-been-released (section Beautiful diffs) for an example and http://blog.deveo.com/whats-new-in-git-2-11/#experimentalheu... for another similar option that I did not try yet.

Thanks for the tip!

For readers in a hurry, careful: `compactionHeuristic` ended up deprecated in Git 2.11 . The experimental heuristic to add to a >=2.11 .gitconfig is now `indentHeuristic`, see OP's second link :)

I don't see anything about it being deprecated, just that there's a new one. (It's also somewhat unclear what happens if you enable both)

The compactionHeuristic was removed entirely in the 2.12.0 release. You can't enable both since only the indentHeuristic exists now, and serves pretty much the same purpose.

And here's the release note for that: https://github.com/git/git/blob/master/Documentation/RelNote...


Things in my dot repo [0]:

git-overview: Short report about top committers, files with most commits and most authors. Nice when you checkout a non-trivial repo for the first time.

git-onNotify: Do something (like `make`) whenever a tracked file changes. Usually used to build LaTeX and static websites.

git-randomline: Chooses a random file and a random line number there. The game is explain that single line to some fellow. Do this repeatedly to spread knowledge about a codebase.

git-tarball: Pack the repo into a tar.bz2 file.

[0] https://github.com/qznc/dot/tree/master/bin

Big fan of the git-randomline command. So much that I'm stealing it!

I have not used it in a while, but I actually have a good occasion soon. I just updated it to only return interesting lines, which for now means at least 10 letters. That filters lines like "}".

I have his "git recent" alias, and then in .bashrc

    alias co='select br in $(git recent); do git co $br; break; done'
so when I type "co" at the command prompt I get a numbered menu of branches in the order I last checked them out, and can just type a branch's number and hit return to check it out again.

I use a different version of git-recent that Paul Irish wrote (https://github.com/paulirish/git-recent) but thats pretty slick - thanks for sharing. Might have to switch - although sometimes its nice to also have the context.

I also have aliases in my shell instead of git aliases.

    st    git status ...
    gl    git log ...
    gd    git diff ...
    gg    gitg
    gup   git pull --rebase
    gb    git branch
The dots mean there are more arguments. The point is, every once in a while I analyse my shell history and add aliases for the most used commands. Looking at it now, it seems I should add aliases for `git push` and `vi Makefile`.

I also have the aliases g for git, m for make, and v for vim.

Yes, I have similar:

    gs    git status
    gp    git push
    gac   git commit -am 
    gc    git commit
    gcm   git commit -m
    gA    git add -A
    gC    git checkout
    ga    git add
    gd    git diff
    gm    git merge
    gmc   git merge --continue
    gpu   git pull
    grc   git rebase --continue
The only problem is that I cannot use git on anybody else's machine, the muscle memory is too strongly ingrained now. This is a common message:

    The program 'gs' is currently not installed. You can install it by typing:
    sudo apt install ghostscript

Which is better than if they have the same aliases as you, but they mean something else. I had an interesting time a while ago until I understood that my coworker uses 'st' for stash instead of status.

It also means that you can't use any common tools that use the same letters as your shortcuts.

True, if I ever need to use ghostscript (whatever it is) in my day-to-day life, it'll be a trainwreck.

It's a ps/pdf toolkit type program. It is used by imagemagick as a backend for pdf conversion type stuff - so if you every need imagemagick you might find yourself in for some annoyance as well.

Aliases are only expanded in interactive shells so ImageMagick or any program that calls an executable from within a process will not have any issues.

You can, you just have to prefix them with a hyphen, e.g.:

  $ -gs

I'm actually using them as git aliases, not bash command aliases.

How much time does that actually save? I find that for any given task, I spend far more time reading and thinking than I do typing, and of the time spent typing, most of it is spent typing arguments.

I use `git diff` and `git log --graph --decorate --all --oneline` and `git status` so often, that it totally saves heaps of time. Especially the second one ― I usually type my `gl` somewhat subconsciously already; only afterwards I realize what I'm looking at, and start reading. That's possibly related to the fact that I commit very often, use many working branches, and then rebase -i (usually squashing and composing a good readable commit message) before publishing.

But anyway, if someone asks for help with "how to fix my mess in git", the absolutely first thing I do is exactly `git log --graph --decorate --all --oneline`, to start finding out visually what the mess actually is.

The shorter a command is, the fewer typos can be made. Many times have I typed `git stauts` or `git ocmmit`

Of course, my personal solution instead of aliases was to switch to using SourceTree.

I discovered https://github.com/nvbn/thefuck a while ago and now I kind of enjoy making typos like this.

For me, at least a day in 5 years: https://xkcd.com/1205/

I have the exact same alias for git status and I'm very familiar with that error message!

A few more I live and die by:

    gf  git fetch --all; git fetch --tags
    gh  git log --oneline --abbrev-commit --all --graph --decorate

IIUC, the --abbrev-commit is unnecessary there, --oneline already does it. Makes the incantation possible to remember and type on a foreign console: "git log --graph --oneline --decorate --all"

edit: yep, per git help log:

    This is a shorthand for "--pretty=oneline --abbrev-commit"
    used together.


I have similar aliases and have a similar concern. I usually think "git status" when I type gs. This way I hope I would be able to work without my aliases somewhere else (even though it would take longer since I have to remember to type the whole command).

A few more I find useful:

    gdc      git diff --cached
    gg       git grep -n
    gss      git status -s
    glo      git log --oneline
    gamend   git commit --amend --no-edit

I find this reassuring as I sometimes feel behind the times for using command line git instead of some fancy WYSIWYG interface. Am I in a minority or not? Is command line the preferred way and if so is it more productive? I just do commits to master, I don't do fancy hotfixes, feature branches squashed to master or fancy bisects. Maybe a merge conflict in my colleague's CSS is as hard as it gets for me.

Seriously, the more complex your use case is, the better the command line gets.

Personally, I use mostly command line git and on occasion a little bit of gitk if I get lost between all the branches. I die a little (read: a lot) inside when I have to help my team members with git and they adamantly stick to TortoiseGit. It's a prime example of the UI improving discoverability at the expense of performance (and even then the individual actions tend to get lost between all the numerous possible actions).

This discussion got me thinking about my push habits. While I only have to type `g psh`, the actual process has more commands. I check that I'm on the correct branch, check which commits will get pushed, and maybe even the diffs of the commits. Sometimes I have to rebase, fixup, and squash stuff.

It is similar for committing, where checking diffs and partial adding is part of the process.

Using an IDE or GUI might help to streamline this. I do not use any, so I don't know if they do this.

Maybe I should analyse my shell history for sequences?

Maybe check this out? I use it all the time (the zsh integration). https://github.com/powerline/powerline

SCM Breeze does a nice job of this: https://github.com/scmbreeze/scm_breeze

Here is my own DSL. Interesting that it turns out we have similar aliases...

    alias ga='git add'
    alias gb='git branch'
    alias gbD='gb -D '
    alias gbc='gc -b '
    alias gbd='gb -d '
    alias gc='git checkout'
    alias gcm='git checkout master'
    alias gd='git diff '
    alias gdd='gd --cached'
    alias gdm='gd master...'
    alias ge='git grep -InE '
    alias gec='git branch --remotes --contains '
    alias geh='git log --all --oneline --decorate -G '
    alias gei='ge -i'
    alias gff='gh && gpr && gr && ghp'
    alias gfm='b=$(gw) && [[ "$b" != master ]] && gcm && gpr && gfu && gr && gc $b && unset b'
    alias gfr='git fetch && git rebase origin/master'
    alias gfu='git fetch upstream && git merge upstream/$(gw)'
    alias gh='git stash'
    alias gha='gh -k'
    alias ghp='gh pop'
    alias ghs='gh show -p '
    alias gl='git log --graph --decorate --all'
    alias gll='gl --pretty=format:"%C(yellow)%h %Cred%ad %Cblue%an%Cgreen%d %Creset%s" --relative-date'
    alias glll='gl --color --graph --pretty=format:'\''%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr)%C(bold blue)<%an>%Creset'\'' --abbrev-commit'
    alias gp='git pull '
    alias gpr='git pull origin $(gw)'
    alias gps='git push'
    alias gpt='gps --tags'
    alias gr='git push origin $(gw)'
    alias grr='git rebase -i HEAD~$(grrr | wc -l)'
    alias grrr='git rev-list master..'
    alias grt='gr --tags'
    alias gs='git status'
    alias gss='git show'
    alias gu='git config --get remote.origin.url'
    alias gv='git describe --abbrev --dirty --always --tags'
    alias gw='git rev-parse --abbrev-ref HEAD 2>/dev/null'
    alias gx='git archive --format tar --remote '

I especially like the "grr" and "grrr" aliases. I have those days too...

You might be interested in this:


It creates the aliases as you do, but also includes tab completion, and only takes effect when you're in a repository path.

If shell aliases don't tab-complete for you, you can get tab completion just by defining git aliases instead. One extra space if you also alias g=git. And it solves namespacing.

I meant that the alias `b` for `git branch` will have the same bash completion as `git branch`, e.g. branch names.

I don't know if it's the same in bash, but at least in zsh, "git branch <TAB>" and "b <TAB>" have the exact same result, if b is aliased to "git branch"; the first tab fills in the name of the branch, another lets me select a commit, and further tabs are command line options.

That would make sense!

Alas, that is not the case with bash.

I also like to alias `gd-` for `git diff --cached`, for checking what I have staged so far in my index.

I like how he creates an alias to blame called praise. He's right, blame is a loaded word. The command I really use to "blame" people is bisect... though sometimes I use that to find something good too

I don't know, "praise" is a really generic word. "Blame" very specifically means, "who did this." And I feel like it's often used in a non-disparaging way: "Who organized this surprise party?" "You can blame me."

Also, truthly, if you're looking to see who/win a line of code was authored, it's almost always because you're tracking down a problem. :)

That is actually an SVNism. SVN had 'annotate', 'ann', and 'praise' all aliased to 'blame'.

OP here.

> That is actually an SVNism.

Yep. From the article:

> Taking the lead from SVN, I alias praise onto blame…


I actually did read the fine article afterwards and noticed that. Nice job, in fact this was one of the few HN submissions that drew me to both the comments and the article!

It seems awfully silly. Are we really that afraid of hurting someone's feelings by "blaming" them for doing something positive?

Hey! OP here.

> Are we really that afraid of hurting someone’s feelings…

I don’t do it to save peoples’ feelings: I do it because there are two separate use cases for determining the author of something—sometimes I really do not want to blame[1] someone.

1. blame — verb: feel or declare that (someone or something) is responsible for a fault or wrong.

To me, the obvious alias has always been "git credit". It's specific, but completely neutral.

Had someone on Twitter suggest `git who` which I think also strikes a great balance.

I always thought of it as blaming a commit for a change, not a person for a mistake. I can see how it could be taken badly though since the output makes a point of putting the author's name next to the commit id.

"praise" is equally as loaded. Should be "last-change" or something.

I use this alias to quickly cd to the root directory of a repo. Useful if you're way deep in a repo and need to back out.

    alias gitroot='cd $(git rev-parse --show-toplevel) && echo "$_"'

You can enforce a portion of that directly in your .gitconfig:

  	# Show top level repository
	root = rev-parse --show-toplevel
I have found that useful over time.

borrowed and my fingers thank you!

You might also be interested in this function from my zshrc: https://github.com/mortie/nixConf/blob/f078de8890461d247af45...

Basically, if you're in the directory `~/something/src/projectname/foo/com/something/bar/baz`, and want to get back up to say `~/something/src/projectname/foo`, `to foo` will do that. (Or `to oo` or `to o`; it matches the end of the name, and goes to the first match; `to ing` would go to `~/something/src/projectname/foo/com/something`).

I like to run gource: http://gource.io/

That's pretty slick, and it really does help you visualize who is doing what. Thanks for that.

Is it just me or does the main page image look like it's moving?

> You’re not limited to just tags: you can use commit hashes.

Since tags, branches and `HEAD` are simply pointers to commits, it's good to know that you can interchange them and commits pretty much anywhere where you can use them (other than creating/deleting tags or branches, of course).

And IIRC other than cloning, i.e. you cannot git clone at a given sha

I'll be adding a lot of these; I especially like the "what was I doing..." ones. To continue with the sharing:

    up = !git pull --prune $@ && git submodule update --recursive
    cm = !git add -A && git commit
    save = !git add -A && git commit -m 'SAVEPOINT'
    undo = reset HEAD~1 --mixed
    amend = commit -a --amend
    # Removes branches deleted from remote repo
    bclean = "!f() { git branch --merged ${1-master} | grep -v " ${1-master}$" | xargs git branch -d; }; f"
    # Leave current branch & do some cleanup
    bdone = "!f() { git checkout ${1-master} && git up && git bclean ${1-master}; }; f"

I'll add a couple of my favorites, as well:

    glog = log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset'
    stash-all = stash save --include-untracked
    update-fork = "!f() { git pull --rebase upstream $1 && git push origin $1; }; f"

Out of curiosity, does anyone here use tig[1]? I've been wondering if it's worth learning.

[1] https://jonas.github.io/tig/

I use it occasionally, mainly just when I want to get a better look at the git graph. I'm sure it has more uses, but that's all I've needed so far.

It's annoying that all the git commands start with 'git' unless you write your own aliases. It means you can't just type !sta to get the git status much less 'emacs !add:*' to edit the files you just added(!) ; you have to do a ^R search and edit. !git is almost never what you want unless it's just !! anyway. RCS had just co and ci and the like[+]

Clearly the guy who came up with this cockamamie scheme was unfamiliar with Linux. :-)

+ RCS was pretty bad too; I'm using it just for illustrative purposes

Something I love to do with git: make temporary commits at the tip of a branch so I can checkout other branches without losing anything. I much prefer this to stashes, generally. Stashes can still be useful to move changes from one branch to another, but generally I find changes make sense on the branch they were being written for.

> git commit -am "TMP COMMIT" > git checkout ... Once you switch back: > git reset HEAD^

and you'll undo the temporary commit and be back to where you were.


This doesn't exactly fit what you're asking for, but is an alternative workflow where you'll never need to do that.

Interesting! Seems a bit heavyweight for most of what I use temporary commits for, but could be interesting if there were better tooling

Part of my workflow as well.

I do git reset --soft HEAD^ so that the changes from the previous session are still staged, which is occassionally useful.

And the question is... why all these nice features are not enabled by default? Especially word-level and whitespace-aware diffs.

Word-level diffs are very situational. In my experience they make the diff less readable more often than not, because they find the smallest common parts, which do not necessarily reflect the logical changes.

So how do largish binaries work in git nowadays? When I last looked (several years ago), the answer was essentially 'dont do it' or 'use this other tool to sort of make it work'. Can I store a few gigs of data (alongside my source code) in git?

Git-annex might be of interest to you.


I don't know how the situation has changed in regards to binaries, or if this is a good suggestion, I just remember reading about git-annex when it came out a few years ago.

Or git lfs, which is even supported by / integrated into github and other git hosting services.

If you're willing to use github you can use lfs.

Some binaries can be serialized so they at least function as text files for vanilla git's benefit. I did a project where we used LibreOffice or OpenOffice and we had a hook than unzipped the contents and flattened them into text for checking in and reversed that when checking out. It worked, technically, but we lost most of the benefits of git (tiny changes would lead to changes to several binary tables which lead to incomprehensible diffs). On the other hand the same project used gnuCAD which saves its files as text -- which was awesome: we made some edits (id changes and the like mostly) with sed and awk!

The big binaries problem is the sole reason anybody buys Perforce. It's horrible, but it does handle this case, and for folks like game developers there's little alternative.

I really do wish I could use git with solid works and Cadence files. Solidworks is the worst: their files are build with some Microsoft structured file library, and contain hard-coded paths; their dreadful proprietary SCM tool understands this and edits the file while checking it out to fit it to your local machine. Aaagh!

And it's all centralized. Recently we had an internet outage for a repo hosted on github: people still got work done and collaborated, pushing to and pulling from each others' machines until we had external access restored the next day. With FB down it was a pretty productive day!

You do need to use something centralized, but LFS is not specific to GitHub. Bitbucket, GitLab, and Gitea also support it.

Full implementation list https://github.com/git-lfs/git-lfs/wiki/Implementations

I actually have that leaderboard alias in my gitconfig already.

I work on a codebase that's over 20 yrs old. A while back, I was interested to find out who had done the most commits, and what kind impact moving to git, with it's "commit early, commit often" mentality, had pushed newer players up the ranks. Suffice to say, it hadn't had as much impact as I was expecting yet.

I've always thought of git blame as a bit of Linus personality infusion into the git CLI. Aliasing that to "praise" kind of misses that bit of fun.

Using the name 'blame' for that subcommand is at least as old as CVS, and probably older.

Are you sure? I can't find docs for "cvs blame"; just "cvs annotate."

SVN definitely aliased "blame" and "praise" to annotate, though.

Interesting! Thanks!

Git-extras[0] is also quite nice.

[0] https://github.com/tj/git-extras

There's a great comparison with hg here:


Instead of --word-diff, I like --color-words.

Though it can be difficult to notice an addition if it's just one or two chars.

I use meld as my git diff tool. Visual diff is just way easier in my opinion.

My personal favourite:

    git for-each-ref --sort=-committerdate
.. shows progress for each branch .. this makes it surprisingly easy to see which of the developers in our group (with their own branches) is pushing the codebase further ..

Sounds like a great place to work

That sounds terribly mean spirited. Is that what you meant?

Are you trying to separate the wheat from the chaff from developers that are already in your team?

This has nothing to do with developer assessment - its only for finding out, during the daily review, what branches have had the most activity and what is ready for the daily review.

Sometimes you find a repo and you want to figure out who "owns" it or is the "expert". That's what I'd use it for.

I don't understand how to interpret the data this spits out.

3 columns - 1: git hash, 2: type, 3: branch name

Sorted by 'most recently touched'.

This is why I wish Fossil were less opinionated and supported rebase and exposed branches and tags as the light-weight names that they are underneath the covers (just like git). Fossil uses SQL, which means that all of the things @csswizardry and much more that no git developer has ever thought of.. can be done with a little bit of SQL.

But no, Fossil's UI is like Mercurial's, and it favors merging over cherry-picking and rebasing. Their loss!

Fossil does have a cherry-pick operation, and anyways, one could trivially be constructed. Which means that rebase can also be constructed easily enough. But my impression is that the devs aren't interested in such contributions. And the heavy- vs. light-weight branching model in the UI is a big turn off even if I can deal with it at the SQL level. Fossil's push/pull model ([auto]sync everything) is also not to my liking -- sure, in a corporate environment pushing every branch is a good idea, but in an open source world it's not: I may want to push some branches to one upstream, others to another, and yet others not at all.

This is what I like about git:

- the index

- git exposes the Merkle hash tree concept at the lowest layer

- git branches and tags are just symbolic pointers to commits (see previous point)

- support for many remotes

- git is not opinionated -- if you want to use a merge-based workflow, you can, and if you want to rebase instead, you can, and if you have to use e-mail to exchange commits, you can, and so on.

I'm done with the Mercurial "you do what we say" model. A model they keep half-way reneging on, adding bookmarks (which don't work well), and histedit and rebase (why not both in one command?! "because we don't like git rebase" is the answer I imagine) (they really need to be one command!! what if in the process of rebasing you must drop commits that you know duplicate others in the new base?!).

I wish Fossil's developers saw this. But they're focused on their needs: VCS for SQLite3. Since they seem to have few topic branches, they like merging.

Conversely, since Fossil's devs refuse to be non-opinionated, I wish git's developers saw the power of SQL for VCS. It would save a ton of code C and shell code, and it would make new extensions trivial. It also would make git much more power-failure safe: since it could leave that to something like SQLite3 that does a fantastic job of it (and is very well tested, both in general and as to power failure safety).

Besides this, I wish git has branch history. That is, a single push can push multiple commits by different authors, so it would be nice if one could see who pushed what commits. This would be useful as documentation in and of itself: if you see N>1 commits pushed together and need to revert one of them, you might look at whether you need to revert the rest as well, as they might go together. (Some codebases like to push regression tests first, bug fixes after. This allows one to see that tests detect the bugs they're testing for and that corresponding bug fixes fix those bugs. If one has to revert a bug fix commit, one might have to also revert a corresponding test commit.)

Your mercurial experience is out of date. Mercurial now supports a rebase workflow better than git does (with github and their pull-request workflow dominating the world, nobody seems to rebase with git anymore). See the evolve extension, and for even more mindblowingly cool stuff, see mercurial absorb.

Mercurial seems to give merge tools better information when rebasing too. I've had to resolve some annoying conflicts when rebasing with git, and I've cloned the repository with hg-git and done the same rebase and had it go flawlessly.

Cool. Absorb sounds very nice. But even if bookmarks now work flawlessly such that I need never again deal with Mercurial's awful heavy-weight branching model, for me it's too late. This is what happens when you don't mind the store: someone else takes your business. Mercurial people spent years screaming about the evils of re-writing history only to now aim to provide a better rebase experience -- fantastic, but too late, the world has moved on.

Also, don't forget the index! I adore the index in git.

I hope people learn the value of not being opinionated when it comes to building mind-share. I know, being opinionated works for Apple, and Apple is a fantastically valuable company. But I don't think it works well for everyone else. Git's being not-opinionated may be the thing that is best about it.

rebase works with github pull requests reasonably well, I use it all the time. I think, others do too.

Took github long enough to provide that feature. Speaking of opinionated tools...

There is something strange that I cannot explain. When I use git I am comfortable rebasing quite a lot. When I use fossil I do not miss it at all.

Similarly, when I use go I do not miss generics despite using them quite a lot when they are available in other languages.

So based on this, I both agree and disagree with your complaint about fossil not having rebase.

However, one place where I disagree is that fossil is not just VCS for SQLite3. Its github in a single executable -- much more powerful and potentially much more important for the opensource community. In some dystopian future developers will be using fossil over tor.

It's possible that if I started using Fossil seriously I'd have the same feeling as you. However, when I browse SQLite3's history, I still see a branched model, not clean, linear history.

I can imagine that a VCS might make merging so painless that one might not miss rebasing, but I would still prefer rebasing.

I didn't say that Fossil is just for SQLite3! I said that's what its developers care about.

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | DMCA | Apply to YC | Contact