Hacker News new | comments | show | ask | jobs | submit login
Git Tips (alexkras.com)
487 points by akras14 524 days ago | hide | past | web | 98 comments | favorite

Here's my favorite git oneliner:

  git push origin --delete $(git branch --merged origin/master -r | grep -v master | grep origin | cut -d/ -f2-)
It deletes all remote branches that are merged into the remote master. Because people always forget to click the "Delete branch" button after merging their pull requests.

EDIT: And another thing. Turn all your Git aliases into shell aliases (e.g. "git status" is aliased to "git st" is aliased to just "st" on my system) with this one weird trick! https://github.com/majewsky/devenv/blob/2c4252d37597617a493f...

While not an issue in Zsh, one bummer about using shell aliases in Bash is that auto-completion won't work. That doesn't really matter with an alias for `git status`, but it's annoying for plenty of others.

You can get around it doing the following:

    _completion_loader git  # manually load git completion functions

    alias g='git'
    __git_complete g _git

    alias ga='git add'
    __git_complete ga _git_add
For a more exhaustive list, see https://gist.github.com/brbsix/713feaf3034d60bbe4774cb8ee03b....

Why would you want to delete merged branches? i like to keep them. It's not costing you any storage.

Personally because it makes both `git branch -a` and tab-completing a branch virtually useless because there's just too many branches.


Is there a dry-run option for this?

Just run everything that's inside $(), which is this:

git branch --merged origin/master -r | grep -v master | grep origin | cut -d/ -f2-

Beautiful, I've been looking for something like this for a while. My only issue is getting a strange

  fatal: remote part of refspec is not a valid name in :MARKET-446
error which seems resistant to the usual sort of tweaks.

I'm getting errors such as: `error: unable to delete 'feature/cleanup-casts': remote ref does not exist`. But the branch does exist... Any ideas?

Fixed by fetching to prune first.

Love #16 and the associated article. I cringe when I see something like "js fix" as a commit message.

However, not on board with the hate for rebase. If it's your feature branch and you aren't sharing it with others, I'd much rather get a cleaned up PR than one filled with junk commits because the author was afraid to rebase.

Nitpick with #16: I don't like when there is no "why" associated with a style rule. I'm speaking specifically to #3, "Capitalize the subject line". Why is this preferred to not capitalizing a subject line? Is there a reason, or is it an arbitrary decision (which would be fine - sometimes a style decision is, "We need a standard, and we chose this one." But I think the author of the style guide should explain that that's what they're doing).

But in general I liked the article. It's under-appreciated how helpful a good commit log is.

Capitalize the commit message because ...

Sometimes commit message deserves to be moderately long, like a paragraph or more. If one did not reference a code review or issue tracker link, for example, the need would likely be common. Even with an issue tracker there's value in capturing a few sentences of detail and sometimes more in the commit.

By capitalizing the commit message, it is natural to extend it from a single sentence phrase to a paragraph with an introductory sentence. I think of commit messages as analogous to JavaDoc where the first sentence or first line is taken as the subject, or like a document with a heading. It would be silly for short commit messages not to use capitalization while long do. A capitalized message works as a short description of the intro to a paragraph. Generally. once you're writing more than a single standalone sentence, capitalize. In a permanent medium where it can be either one, also capitalize.

A capitalized sentence or sentence fragment works in many contexts, like within a document or email. It would be annoying to convert going back and forth. Lastly, commit messages can sometimes act as names or proper nouns, and capitalization reinforces that role. Capitalization looks better as project or item headers in an issue tracker, and so to the extent that commits follow items, it's nice to consistently capitalize.

Capitalize commit messages because when Git tools generate commit messages, they generate capitalized ones. (Be consistent.)

I grok not capitalizing IM or IRC or other chat conversations, though I do if I start writing messages longer than a sentence. For everything but IM, capitalization constitutes the norm and good professional style.

This rationale might not be entirely satisfying, because why does English have capitalization to begin with? That's a linguistic question I don't know the answer to. However, I do know that the norms that lead to sentence capitalization also apply to commit messages and code documentation Not capitalizing them feels like refusing to capitalize any other writing: a potential distraction from the content.

Most of my commits start with a component name prefix and a colon. I don't capitalize those. I still write literate English commit message bodies.

That sounds reasonable to me.

  foo: Add utf8 support
If the component name is conventionally written in lower case, because it's a technical term, then I think a comment written in this way counts as good style. I might choose to capitalize Foo myself, but I think foo is defensible in that context.

I use this template and find my commit messages are much more useful now:

    # If applied, this commit will...
    # Explain why this change is being made
    # Provide links to any relevant tickets, articles or other resources

I like to write my commits in the Angular conventional-changelog format [0].

In particular, it looks like this:

    <type>(<scope>): <subject>
Where `<type>` is the type of change (e.g. `feat` for a feature or `docs` for doc updates), `<scope>` is the scope of a change, `<subject>` is the subject line, with the first letter uncapitalized, `<body>` being an explanation of the change in imperative, present tense, and the `<footer>` containing information about breaking changes and any issues the commit closes.

I like it because I find that adhering to this style lets me have a nice seat of easy to read commits which explain their purpose and scope clearly.

Also, it lets me use a tool like `clog-cli` [1] to generate pretty changelogs [2] automatically!

The only nitpick I have with this style is the fact that they recommend not capitalizing the first letter of the subject line. This means that, for example, in emails or when creating a PR, I have to capitalize the summary so that it looks correct. Minor, but annoying.

[0]: https://github.com/conventional-changelog/conventional-chang...

[1]: https://github.com/clog-tool/clog-cli

[2]: https://github.com/clog-tool/clog-cli/blob/master/changelog....

Capitalizing the subject line makes commit logs much easier to read in my opinion. There's a reason we capitalize letters at the beginning of sentences

But it's not the beginning of a sentence. It's supposed to be a continuation of the implicit phrase "When applied this commit will ..." In which case capitalizing makes no sense.

> It's supposed to be a continuation of the implicit phrase "When applied this commit will ..."

No: it's supposed to be written in imperative form. For those of us who are not linguists, it's easiest to explain this as a continuation of that phrase. However, it isn't a continuation. It stands on its own.

If you're working alone in a branch, or a personal project it's not as big a deal... especially if you're going to squash before a PR/merge upstream.

I would be cautious about getting into the habit of squashing lots of changes together into one commit before sending upstream. It makes git bisect harder. Squashing is fine, but I suggest preferring several commits of logical units of change over one commit for a whole feature.

Agreed, rebasing also makes it much easier to find the set of commits you've added to your feature branch if the main branch is high volume.

> Love #16 and the associated article.

I can't recall where now, but I remember reading (and agreeing with) an article which proposed a slightly different format - describe what the commit did. "Fixes the js typo on the foo page".

Grammatically, I prefer this method as well, since the commit has already been applied, so it's not "going" to do anything; it's already been done.

Of course, the only difference is in the tense of the leading verb, so it's probably one of those "potato" thing.

I thought git commit messages were supposed to be present tense. "Fix the JS typo on foo page for Chrome."

Well, especially if you take the source article to heart, "Fix" is actually future tense - "When applied this commit will fix the JS typo on foo page for Chrome". "Fixes" would indicate the present tense: "This commit fixes the JS typo [...]"

I read it as using the imperative mood, myself; otherwise the sentence is missing its subject.

I've seen that reasoning as well, but who is doing the telling, and to whom?

Ain't English fun?

If the sentence still makes sense when ", dammit!" is appended to it, it's in the imperative mood and you are the subject.

Fair enough, but how does a command apply to a git commit? If it's telling the git commit what to do, that doesn't make sense, since the commit comment is supposed to be about the commit, not a command to the commit.

If it's telling me what to do, that doesn't make any sense either, since the work's already been done; the results of doing that work is the commit.

I guess it could be considered to be an abstraction of what request initiated the change, but what value does that provide when going back through git logs (when compared to a past or present verb tense that describes the changes)?

I think the actualy reason for the choice of tense is brevity. Fix is three characters fixes or fixed is five, similar for update vs updates/updated, add vs adds/added etc.

Taking this opportunity to promote gitlint, a git commit message linter (and hook) I wrote in Python: https://github.com/jorisroovers/gitlint

I'm an avid supporter of rebasing over merging. I'll refrain from getting into my workflow, but here's what i want to say: For the love of all that is holy, never squash a merge commit nor squash onto a merge commit.

Squashing a merge commit is a perfect way to reintroduce old code back into master.

Squashing onto a merge commit is great way to lose changes. It's been a while since I have tried this, but creating a didactic repo if fairly easy. Create a repo with two feature branches, a file on each of master and the feature branches, merge featureA to featureB, make some changes or delete a file, `commit --amend` on the merge, and merge featureB to master. Then use `git cat-file` to look at those commits and commit trees. I've seen mysterious things such as simple as unreported changes to files mysteriously being deleted from the repo.

I use git often on the command line, especially for non-trivial tasks, but do not quite understand why someone would pick it for routine committing/tree view over a GUI. I find the ability to graphically see the state of the working tree and the index, and being able to stage/unstage individual lines by visual selection in a non-linear way absolutely natural. Likewise, the ability to get a context menu for a commit in a tree view and fire a command (rebase/branch most often) seems like something git was "built" for.

I, on the other hand, am very uncomfortable using a GUI for such things, and find it extremely distracting to switch back and forth between the CLI and a GUI. Luckily, we both have options that do what we want and work for our particular tasks/skills/aptitudes/learning-style/whatever you want to call it.

I spend a lot of time in the commmandline, so it makes sense for me to not use a GUI. This alias (in ~/.gitcofig) is gold for most of the "graphical" stuff I'd use something like SourceTree for.

	lg = log --all --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr)%C(bold blue)<%an>%Creset' --abbrev-commit

I have these alias, plus :

    lg2 = log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold cyan)%aD%C(reset) %C(bold green)(%ar)%C(reset)%C(bold yellow)%d%C(reset)%n'' %C(white)%s%C(reset) %C(dim white)- %an%C(reset)' --all

these are really good! I have this one..

  lg = log --graph --decorate --pretty=oneline --abbrev-commit

The "--decorate" is key!

In Git's early history the vast majority of its users were people who significantly preferred the command line interfaces over GUI tools because they can be much quicker (if that is how you work normally at least) and easier to automate. In fact at first there were no GUI tools at all. There is an amount of momentum in tutorials being written for the command line, so people learn from the command line, so people write their procedures/tutorials/other from that perspective.

And it makes sense to learn the basics in its native form and then learn the useful trinkets that are added on top - then your initial learning is restricted to the core so you are learning that which is transferable to all Git front-ends rather than learning specific to a particular GUI.

The way advanced users tend to work in my limited experience (I'm am very much not an advanced user) is to use the command line for the basic day-to-day working and then the GUI tools when anything more complicated (large and/or partial merges) is required.

I'm so often doing stuff on remote servers that I don't want to rely on GUI tools for my daily work.

Also, GUI tools almost always expect you to use the mouse, which I prefer to avoid.

But I do often use Emacs Magit which gives me most of the benefits you mention over the CLI.

Because userskill is directly proportional to how many things you can do from a command line instead of a GUI. /s

I agree. It doesn't help that the git CLI is so awful. SourceTree is a much nicer interface (although it is too damn slow).

Have you looked at QGit? Yes it is very old - but is also still works fine.

My tip #20: tig [1]

[1]: http://jonas.nitro.dk/tig/

gitk is great if you are outside a terminal

Since interactive rebase was mentioned I'd recommend taking a look at its --autosquash flag and the --fixup flag of commit. With autosquash the interactive rebase will move fixup commits to where they belong:

  git commit --fixup=<commitref>
  git rebase -i --autosquash HEAD~5

Also enable --autostash (git config --global --add rebase.autostash true) for that workflow so that dirty files do not get in the way.

My recommendation: configure the shell prompt (add __git_ps1 to $PS1), this saves a ton of time and makes it much easier to understand what is going on. Especially recommended for beginners (but obviously not limited to).

Use fish shell and setup it to use the git prompt. Far better that bash + __git_ps1

Mandatory xkcd reference: https://xkcd.com/1597/

So true

For vimmers: https://github.com/tpope/vim-fugitive is awesome, especially for only adding only parts of a file to a commit.

"fu git i've" is yoda speek for the common scenario "f-ed up git I have"

Site is non-responsive for me. Cached link:


    git checkout -
Switches to the previously checked out branch.

More generally "-" is an alias for the previous branch, so it can be used in other commands as well (ex: git merge -)

I hope I'm not the only one who thinks that "Update README file" is a poor commit message. It's overly nondescript. A better commit message would be "Update README install instructions" or "Fix README links to dependencies" or anything else that better describes what about the README file was updated.

My #1 git tip to anyone who will listen: use magit. Having the options for each command within easy reach makes getting git to do the right thing so much easier, and the UI feels very emacs.


A golang+termbox clone of this would be great...

(Of course, parsing text is a joy with Emacs/Lisp and a PITA in Go).

For git commit messages. I've began liking to initiate or end a statement with stuff like these:


( ͡° ͜ʖ ͡°)





How do you guys feel about these in git commit messages?

If they're relevant to the code change ("Not sure what I did, but the tests are passing now ¯\_(ツ)_/¯", "Removing production usernames and passwords from front-end config files, MIKE ಠ_ಠ"), it's fine.

If I see a commit that's just "¯\_(ツ)_/¯", I will force-push over it, don't you even think about it.

Anyone who writes these needs to have themselves, not the changes, committed.

At my old job I had to do a major cleanup of a git repo with dozens of old branches. I got really good with the `git log` command. Here my alias form my ~/.gitconfig that shows author, branches, and remotes in a colorful manner:

        ll = log --graph --oneline  --decorate --date=short --all --pretty=format:'%ad %h %Cgreen%an %Cred%d %Creset%s'
With this you have a pretty good picture of what's going on accross all branches.

Can you provide an example of the output?

Sorry for the delay, the main idea is to show all the branches on the different remotes:

    * 2016-04-11 86f68c3 Ivan Savov  (HEAD, origin/bugfix/math_valign, bugfix/math_valign) Better solution to 
    | * 2016-01-20 df77345 Ivan Savov  (origin/miniref, miniref) Customizations necessary for no BS math render
    * 2016-04-07 364619f Michael Hartl  (upstream/stable, upstream/master, origin/master, origin/HEAD, master) Change to bigger line height for EPUB/MOBI
    * 2016-04-07 92952da Michael Hartl  Bump version
    *   2016-04-07 6a56c24 Michael Hartl  Merge pull request #152 from jackkinsella/patch-2
    | * 2016-03-23 216d410 Jack Kinsella  softcover check compatible with brew
    * |   2016-04-07 d792d4b Michael Hartl  Merge branch 'minireference-feature/math_valign'
    |\ \
    | * \   2016-04-07 5fa560e Michael Hartl  Merge branch 'feature/math_valign' of https://github.com/minireference/softcover into minireference-feature/math_valign
    | |\ \
    | | |/
    | |/|
    | | * 2016-01-31 77a4e5e Ivan Savov  (origin/feature/math_valign, feature/math_valign) Set veritcal-align for better looking inline math
    * | | 2016-04-07 a369139 Michael Hartl  Update README
    |/ /
    * | 2016-03-21 0e94095 Michael Hartl  Bump version number
    * | 2016-03-21 a5fbc82 Michael Hartl  Reuse menu headings in MOBI ToC
    * |   2016-03-21 fadd558 Michael Hartl  Merge branch 'master' of https://github.com/softcover/softcover
    |\ \
    | |/
    | * 2016-03-16 60dc7c9 Nick Merwin  (tag: v1.2.5) added exercise generator for course chapters
    * | 2016-03-12 fe2f844 Michael Hartl  Lower the EPUB/MOBI line height
    * 2016-03-10 bba610b Michael Hartl  Make anal changes
    * 2016-03-10 c9bad17 Michael Hartl  (tag: v1.2.4) Bump version number
    * 2016-03-10 8edf40a Michael Hartl  Add symbols for euros and pounds

Great set of tips. Unfortunately I find that so many post that suggest rebase recommend against force pushing. This takes a lot of the power away from rebase/amend. So much does this happen that is wrote a blog post titled "You should force push more"[0].

0: https://hugotunius.se/2014/09/08/you-should-force-push-more....

My current understanding is that... "it depends". I like that you identified ground rules/preconditions for aggressive force-pushing.

Imagine a scenario for a large team (think kernel). Chat logs of a team developing some feature look something like:

A: Ok, I have finished with issue1, check out commit abcd1 B: Hm, does not work for me. Looks like race condition in foo.c, fixed in abce2. Going to sleep. C: You broke init_foo(), reverted in qwer3 A: Ok, modified init_bar() works on my machine, check dvor4 C: LGTM

Yet release branch contains all those commits squashed into abcd1. Now person B (or anyone else) has no idea whether which versions of init_foo() and init_bar() work on their machine and what to fix.

We sometimes tend to forget (and tools like github/gitlab help with that) that not only git, but the whole workflow might be distributed and changing history in one place might desync it with other places. I think that unless all that history is absolutely unnecessary, e.g. done by a single developer, is extremely well tested, contains loads of "oops" commits, history should not be messed with. That's why all the warnings: git cannot manage all the references to history

My favourite tip for commit messages: write them as if you would explain someone how to do what was done in the commit. So, instead of "fixed a bug" use "replace datetime object with date". It can be more abstract for larger commits (e.g. "refactor payments"), but it should be roughly analogue to what you would write to a ticket title to the same effect.

My must have: pre-commit unit tests. See http://daurnimator.com/post/134519891749/testing-pre-commit-...

I usually rely on travis-ci to do this after I've committed and pushed to a branch (and only merge back when I get a green result, this is shown neatly in the Github UI).

A pre-commit hook probably works fine if your tests run in a few seconds. But in one of my projects, tests run for 10+ minutes and I want to get a test run on debug and release builds with a few different compiler versions. That's about 50 minutes of CPU time (but embarassingly parallelizable, although I typically don't do that). I don't want to have to wait for this to happen before every commit (not all of which even require testing, e.g. README commits).

Yeah I only run the unit tests against my current locally installed packages.

Via travis I then run a full matrix.

Talking of commit message styling, Antirez (of Redis fame) also has a say. http://antirez.com/news/90

love this one :

    git diff --name-only | uniq | xargs $EDITOR
(opens all modified files)

and this one to open files with conflicts

    git diff --name-only --diff-filter=U | uniq  | xargs $EDITOR

List the files with changes

    git diff --stat 
And git merge-tool & git diff-tool So I can use kdiff3 when I have merge conflicts.

Am I the only one who's strugling to see the website? My Phone is having a bad time trying to show it (Windows Phone, Internet Explorer)

Works fine on mine (Windows Phone 10, Edge).

I seriously must be doing something wrong, but when I'm doing an interactive rebase, if it stops due to a merge conflict I sometimes find I need to tweak other files, and to add the files to git I find I do the following:

  git add $(git diff --name-only)
Am I wrong, or is this the right way of doing this?

  git add -u

Awesome :-) I guess I need to re-read the git man pages yet again. There's also git add -A which I can use if I add a new file.

I work with git daily for couple years and I find it quite tiring to follow git branches/commits in command line.

I rather use Git Extensions on windows and on mac Git Kraken. For beginners I think it would be better to use GUI tools.

For experienced users, I do agree wholeheartedly. However, to learn how and why git works, it is much more intuitive to work from the command line for a while. For example, you can browse the .git directory, see how refs work, browse through the logs, etc. etc.

Another reason why I advice learning the command line interface first is that it is the most versatile, so it's helpful to be able to fall back to it when necessary. Every GUI tool I've seen so far lacks a feature or two.

For learning what 'rebase', 'merge', 'sqush', 'git fetch', 'git pull' does it is nice to have a GUI. Command line on the other hand is usefull to restore lost commits with 'git log' and 'git reset' and I find it quite advanced topic. I think all basic commands are all in GUIS so those that I listed in first line.

I'm still not even quite sure how to a) discard edits that I've made but not added/committed yet and b) delete something from a repo that I added accidentally.

    git checkout somefile
will discard edits which you've not added to the index. This command is destructive of course.

    git rm -f somefile
will delete a file from the repo (you then have to commit the change). If you want to permanently delete a file, including from history, eg because it contains a password, that's a bit more difficult, but possible in some circumstances.

a) You can also use `git checkout -p` here that lets you selectively revert patches

I can also recommend these two aliases I have in my gitconfig:

    unstage = reset HEAD --
    undo = reset --soft HEAD^
`unstage` takes an argument and removes changes from the index (but keeps them in the file). `undo` undos the last commit and puts the changes into the index. Unstage you pass a file or `.` for all

git checkout <file> to discard. This will checkout the full file to latest version tracked. Alternatively If you wanna save some of the file you can use git add -p <file> and stage the individual parts you want. After you've committed those you can checkout the file.

git rm <file> to delete something you've added accidentally

I'm collecting my personal notes/tips at: https://gingkoapp.com/git-notes

I never recall the command, but it lives in my bash history somewhere. Running your test suite over a series of commits can be very handy.

Something like

  (set -e; git rev-list --reverse upstream.. | while read r; do git checkout -q $r && make check; done)
(Aped from the very excellent http://blog.extracheese.org/2010/12/rebase-is-safe.html)

git log --format=%ae | sort | uniq -c | sort -nr

"Table of contents"

git reflog is my favorite

I used reflog for the first time a few days ago. I rebased a branch, and somewhere i messed up the conflict resolution, so I wanted to undo the rebase. It was surprisingly easy!

reflog is one of these things where you think "that would have been awesome to have" in hindsight.

In 2009 or so, I was using git-svn on the KDE SVN. I was preparing a major refactoring of Kolf in a local branch (about 60 commits with 10000 lines changed), and pushed it to the SVN once I was satisfied with it.

Unfortunately, the 20th-or-so commit failed because a SVN pre-commit hook rejected it (there were some DOS line-endings in a source file that I imported). At that time, git-svn was (AFAIR) already prepared to handle a failing SVN pre-commit hook on the first commit (e.g. when authorization is missing or stuff like that), but not somewhere in the middle.

The end result was that my working copy, my index, and my master branch was completely trashed. And I didn't have a backup... Luckily, the tip commit of my feature branch had not yet been packed, so a clever grep on .git/objects found it, and I could recover my pre-push state.

Since then, reflog has always been enabled on my systems. Luckily, I haven't needed it since.

Wow, I had no idea git bisect was so easy to use

Brilliant tips

Applications are open for YC Winter 2018

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