
Oh shit, git (2016) - IvarsIndriks
https://ohshitgit.com/
======
dnprock
Reading this article, I realize that I'm old now. I still remember wrestling
with cvs, svn. Merge, branch were slow and even more challenging. It was much
easier to mess up and so difficult to rewind.

When I first learned git, I thought it's pretty neat. It solves merge, branch,
rewind problems. Git is one of the things in life that doesn't work like the
way we think. But it turns out to be a better way.

~~~
jchw
I’m torn. Practically Mercurial feels like it should be the winner. The
commands are more uniform and predictable.

That’s not all it has going for it either. Mercurial has a concept of commit
stages to make history rewriting safer. It has a commit model that enables you
to work on and manipulate branches of commits seamlessly, without needing
named branches. It has not just a tree of commits but also each commit tracks
its own history through rewrites. You get cool commands like absorb and
evolve. It’s easier to extend than Git.

The only downside to modern Mercurial I can think of is it’s still slower than
Git, by at least a bit. But it can scale incredibly far with its
extensibility. For example, what Facebook did:

[https://code.fb.com/core-data/scaling-mercurial-at-
facebook/](https://code.fb.com/core-data/scaling-mercurial-at-facebook/)

So why does it never seem to get consideration? I guess it’s because of the
insane proliferation of Github and Linux, which is definitely more a blessing
than a curse. But it’s weird. Back in the CVS and SVN days, it didn’t seem
like there was ever going to be a ‘network effect’ for source control like
there is today.

I kind of wish Gitlab would implement Mercurial support. I bet it would help
Mercurial gain more adoption within teams working on closed source projects. I
know Bitbucket does, but to be honest that doesn’t really appeal to me much.

~~~
andrewla
> I’m torn. Practically Mercurial feels like it should be the winner. The
> commands are more uniform and predictable.

I think that's true if you're coming from something like Perforce or
Subversion and have some notion of what the expectations are around those
environments. It's much easier to translate that model into Mercurial
commands.

But in the end it's deceptive; git, ugly as it is, reflects the underlying
data model. Once you understand that data model, and how the basic commands
manipulate that model, then it becomes a very natural language for
manipulating the commit chain.

If there's a downside, it's that git doesn't let you get away for too long
without learning the data model, while Mercurial lets you live in blessed
ignorance until the first time it doesn't work like svn does, and then you're
stuck because its data model is not as cleanly exposed, so you need to gently
massage it into a good state rather than just surgically moving it to where
you want it to be.

~~~
rjzzleep
I don't get that argument at all. I came from svn, used cvs and ss and I think
I even tried to use Perforce at some point.

I don't see how people consider mercurial "easier", "the winner" or whatever.

I think it's like languages: if you're a french speaker and you translate
everything into your native tongue while learning, then of course you won't
ever get past roman languages. It's just different.

`git add -p` is my absolute favorite command ever. `tig` is just awesome. The
gui clients are so beautiful and take away all the so called uglyness
nowadays, though I just can't find myself using anything over git add -p.
Nothing in mercurials commandline ever came close to these two. But yeah tig
isn't really part of git but at the same time it's been around for so long it
might as well be.

~~~
jcranmer
The man page for git reset states in its first paragraph:

> In the first and second form, copy entries from <tree-ish> to the index. In
> the third form, set the current branch head (HEAD) to <commit>, optionally
> modifying index and working tree to match.

When your documentation starts by saying (in effect) "this command does
several different things", that should be a very strong sign that your command
UX has some issues.

------
dwaltrip
Git is pretty nice, but I'm sure there is something much better waiting to he
invented. The CLI in particular could use a ton of improvements.

And I feel it in my bones that there is a revolutionary GUI waiting to be
invented. Why can't I drag a commit or set of commits from one branch to
another? With safe, easy undo (reflog doesn't count) and super smooth conflict
resolution? Etc etc.

And of course there is the interesting rabbit hole of semanitc / language
aware diff. Line diffs suck in many ways.

It's one of the hundreds of of problems that I'd love to work on one day, but
probably won't get a chance to. Sigh... :)

~~~
batisteo
> Git is pretty nice, but I'm sure there is something much better waiting to
> he invented.

It is indeed being invented. It’s called Pijul:
[http://pijul.org/](http://pijul.org/)

This tool is based on strong mathematical theory of patches, instead of
snapshot/commit-based. It seems simpler to reason with, but we’d have to
unlearn a lot from Git.

It’s not suitable for big projects yet, but it’s already used by Pijul itself
and other Rust components. And it already have its own „Github” called the
Nest (because pijul is a bird). Pretty promising imho.

~~~
dahart
> This tool is based on strong mathematical theory of patches

Is this a good thing? What practical problems does a strong mathematical
theory of patches solve that git doesn’t? And what’s the difference between a
commit and a patch? Aren’t git commits stored as patches?

I’m a math lover, but my gut reaction to that idea is that it sounds off-
putting. I don’t mean that as a judgement or insult; I’m admitting my own
assumption and bias here, jumping to unwarranted conclusion, not saying
anything is wrong with pijul. But when the elevator sales pitch is “strong
math”, it immediately makes me assume it’s too technical for a normal
programmer and focused on academic ideals rather than getting practical work
done as easily as possible.

The FAQ even says, “Pijul is trivial for whoever knows category theory.” Is
that question really asked frequently? Words like that might convince me to
never try it. ;)

~~~
cesarb
> Aren’t git commits stored as patches?

No, they aren't, git commits are stored as snapshots. Each git commit has one
tree, which is a snapshot of the state after the commit; zero or more parent
commits; a pair of authors with corresponding timestamps; a commit message;
and nothing more. Any patch you see in git is an illusion, made by comparing
the commit's tree with the parent commit's tree.

~~~
dahart
Thank you. Sheesh I feel like I should have known this about git. That would
make diffing very far apart changes super fast.

So what's the advantage to explicit storage in patches over snapshots? Are
diffs between snapshots not able to capture the same information that explicit
patches have?

~~~
erik_seaberg
The trouble with patches is that you can't get engineers to reliably declare
whenever they copy or move code and where it came from. This is part of why
svn merging was such a trainwreck, undeclared copies and moves (which look
like delete+insert) almost always conflicted.

------
dzdt
Am I the only one who reads "reflog" as "re-flog", that is, to be painfully
whipped again?

~~~
jrochkind1
I knew about it for at least a year thinking it _was_ "re-flog", and just
considering it mysterious why they would have called it that. (You do often
use it for "re"-doing things, maybe it's got something to do with that?) Hey,
it was (and is) hardly the only mystery to me in git UI.

Then like a year on I suddenly realized Ohhhhh it's ref-log, that makes a
_lot_ more sense!

~~~
jsilence
.oO( the beatings continue until morale improves! )

------
gpvos
One thing that I had been looking for for a long time, but never could find,
was a description of the several syntaxes you can use to refer to specific
commits. Lots of git tutorials use these magic incantations, but none point
you to this crucial bit of explanation.

Recently I discovered that it is found under "git help revisions".

~~~
xelxebar
Or just gitrevisions(7). The manpage for git(1) mentions this under the
section SYMBOLIC IDENTIFIERS. Not that that's the most obvious place to look,
but if you rtfm the obvious stuff, you should stumble upon these gems
naturally.

It's worth scrounging through git(1) anyway; it mentions several other
manpages for things like recommended workflows, the basic structure of the
.git/ directory, not to mention gittutorial(7).

~~~
gpvos
In git(1) it's below LOW-LEVEL COMMANDS (PLUMBING). You cannot expect a novice
to read thoroughly past something like that.

And it's remarkably hard to find using web searches, since most git
documentation uses the term "commit" for these things, not "revision".[0] I
found it after I discovered "git help -g", which lists "some concept guides"
according to "git help".

Of course, n=1 and stuff, but there's a lot about git that is not obviously
documented. Something like this should be linked to in multiple places, so
even if you skim over one you'll catch it relatively early.

[0] The name of the man page was chosen to avoid conflict with the "git
commit" command, I guess?

------
saagarjha
> I committed and immediately realized I need to make one small change!

I think it might be nice to add a disclaimer saying that this is not advisable
if you've already pushed the code. Suck it up and make a new commit–don't
rewrite public Git history.

~~~
colatkinson
I think changing history is fine on feature branches that only you are working
on, though. IMO the benefits of keeping commit history clean outweigh the cost
of having to push with "\--force".

~~~
fulafel
This then discourages ad hoc teamwork because you can't touch feature branches
other than the ones you own. And because the results of getting it wrong are
hairy, people will tend to stay away just in case. It's a chilling effect.

Or, someone might have created another branch off your feature branch, because
they depend on your work. Now you've creaed a time bomb for them when they try
to merge their work after you've merged your alternative-history version of
it. (and the failure mode is just weird, it takes experience to identify that
all those seemingly nonsensical merge conflicts are result of this situation)

Etc. It just breaks a lot of things. The Git model and bad UI are already
taxing enough to work with in your head, concurrently with your actual
programming and domain cognitive load, that adding the uncertainty and
multiplied complexity from having history rewritten around you is just a bad
tradeoff.

(This may be different if the scenario is not a team, of course...)

~~~
serpi
you just rebase on top of their changes. No biggie here. It does not matter
one bit if their branch rewrites itself underneath.

~~~
fulafel
But you can't safely rebase that branch! Having rebased it, you would have now
broken it for others working on it. So this is a great example how the damage
spreads and taints other branches around the history rewrite.

(And even arriving at the "ok i could fix this with rebase" diagnosis will
have been painful and frustrating and eaten time & energy, and you can't be
sure you got away with it before actually doing it and waiting if your
teammates will come kick you in the nuts. or worse, silently spend a day
untangling their work.).

It's just fundamentally unsound.

~~~
mehrdadn
> But you can't safely rebase that branch!

Huh? You _don 't_ rebase their branch. You rebase your _own_ changes on top of
their branch, which they happened to recently rewrite. Just like you might
rebase your changes on top of master after master has undergone changes. I
think the parent's point was that it doesn't matter if the branch was
rewritten or just extended; either way you rebase the same commits on it the
same way. (If you're one of those people who's against the notion of rebasing
_entirely_ then that's a separate debate we can have another time, but you
need to separate that from the force-push issue.)

~~~
fulafel
The "own" branch is also public here and maybe collaboratively worked on

~~~
mehrdadn
So then anyone building on it would rebase the same way you just did? Which
was the same way they would have done so if you had just pushed a new commit?

~~~
falsedan
yeah exactly, all the FUD about rebase and shared branches is baffling to me.
are people regularly getting stuck on 'git pull --rebase' and slamming into
merge conflicts?

------
spenrose
Same idea, but much more comprehensive: [https://github.com/k88hudson/git-
flight-rules](https://github.com/k88hudson/git-flight-rules)

------
sadness2
I prefer this, because it has a flow-chart
[http://sethrobertson.github.io/GitFixUm/fixup.html](http://sethrobertson.github.io/GitFixUm/fixup.html)

------
dang
2017:
[https://news.ycombinator.com/item?id=15951825](https://news.ycombinator.com/item?id=15951825)

2016:
[https://news.ycombinator.com/item?id=12459755](https://news.ycombinator.com/item?id=12459755)

------
zoomablemind
Git is nice tool, very versatile in able hands. Thanks to its promotion
(plugins and github included), the pragmatic practice of source versioning
gained wider adoption and lost that beg-your-IT-dept-to-set-it-up flair.

In the mean time, 'thanks' to Git, the source change history became a
maintenance line-item. The expectations of a clean history were raised almost
to the level of expectations for bug-free code.

I can see a utility of clean feature history, but asking developers to craft
the history is shifting their focus away from the actual code. As long as the
source state has been saved, the source control has done its main job.

So for the most of the listed 'shits', the developer should just be able to
revert, cherry-pick, and re-commit, and keep going. Nothing esoteric and hard
to remember, also fairly common commands across different VCS tools. Shit
happens and will happen again, no biggie, no need to blame and shame, source
annotation will show the right change/comment anyway.

------
systemBuilder
Git performs like what it is : a piece of code created by debugging a blank
sheet of paper. I can detect almost no philosophy and no simplifying
assumptions. The thought that you need 60+ commands (the current size of my
git cheat sheet, including all the bizarre argument incantations which seem
customized for all 100+ possible mistakes in git) to get through the day is an
abomination. I prefer perforce which requires less than 20 commands. The only
reason people use git is because, Linus.

Like no good program, ever, to use git you have to understand all the
compromises and all the internals of its data structures. What a joke.

------
luxcem
> I use reflog A LOT

If you need to reset with reflog a lot you're probably using git wrong.

Sure it can be useful but I don't see why it should be in a workflow.

------
jancsika
> git diff --staged

The way it worked well was that the "\--staged" flag would be implied if you
had already staged some files to be committed.

But on this day I noticed that nothing bad happened from that behavior. So I
time traveled back and whispered to the git devs that the interface should be
made more pedantic to keep users from relying too much on git to do the right
thing for them.

Now it's great because users suffer and I have plausible deniability from this
now being on par with the rest of git's interface.

------
vijaybritto
This has been immensely useful every now and then

------
ghiculescu
> git reset HEAD~ --hard

Seems fairly magical compared to other stuff here. To me at least. Can anyone
briefly explain what it does?

~~~
q3k
HEAD~ means 'the second to last commit in the current branch' (ie. the second
to last when you `git log`).

git reset means 'point the current branch to this commit instead of wherver
it's pointing now' (branches in git are just pointers to commits)

git reset --hard means 'also reset the state of the checkout and staging area
to be in sync with the commit'

Thus, the entire spell means 'reset the current branch to make it point the
the seecond-to-last-commit, also ensure my current checkout and staging area
are in sync to that', or, in other words, fully forget and drop the latest
commit.

~~~
gwright
[Edit: It seems that I was mistaken but I'm leaving this up to illustrate the
confusion. In my experience "second to last" isn't common and "next to last"
is much more common, which I think is why I was confused because I thought
they were different, but apparently they are the same. I wonder if there is a
regional usage pattern to these phrases. ]

This seems like mistake or a non-standard usage of the english phrase "second
to last". Given a git log of:

    
    
        $ git log --oneline
        15e0437 - (HEAD -> master) this is the third commit
        f82d1fd - this is the second commit
        9180c17 - initial commit
    
    

I would call "f82d1fd" the "next to last commit" and it can be referred to as
HEAD~ I would call "9180c17" the "second to last commit" and it can be
referred to as HEAD~2

~~~
q3k
I don't know (definitely not a native English speaker), but Wikitionary seems
to agree with me:
[https://en.wiktionary.org/wiki/second_to_last#English](https://en.wiktionary.org/wiki/second_to_last#English)
.

~~~
gwright
Well, I think you may be right. See my edit above. I suspect this is a
regional usage pattern and I've just always been in places where "next to
last" would be used and not "second to last" so I was thinking they were
different, but they apparently are the same!

Learn something new every day.

~~~
gwright
Here is an ngram analysis with a British corpus

[https://books.google.com/ngrams/graph?content=second+last%2C...](https://books.google.com/ngrams/graph?content=second+last%2Csecond+to+last%2Cnext+to+last&year_start=1800&year_end=2000&corpus=18&smoothing=3&share=&direct_url=t1%3B%2Csecond%20last%3B%2Cc0%3B.t1%3B%2Csecond%20to%20last%3B%2Cc0%3B.t1%3B%2Cnext%20to%20last%3B%2Cc0#t1%3B%2Csecond%20last%3B%2Cc0%3B.t1%3B%2Csecond%20to%20last%3B%2Cc0%3B.t1%3B%2Cnext%20to%20last%3B%2Cc0)

And here is one with an American corpus:

[https://books.google.com/ngrams/graph?content=second+last%2C...](https://books.google.com/ngrams/graph?content=second+last%2Csecond+to+last%2Cnext+to+last&year_start=1800&year_end=2000&corpus=17&smoothing=3&share=&direct_url=t1%3B%2Csecond%20last%3B%2Cc0%3B.t1%3B%2Csecond%20to%20last%3B%2Cc0%3B.t1%3B%2Cnext%20to%20last%3B%2Cc0)

~~~
q3k
Nice tool. You should expand the search range to include 2018/2019\. Quite a
significant change happened since then.

------
ddtaylor
Many of these problems can be avoided by using a pull-request style workflow.

~~~
aflag
I don't see how prs would help solving any of those. It's common for me to use
a few of these commands before I open the PR.

~~~
Sammi
They stop you from pushing anything broken to master. So you only have to
worry about being in a broken state locally on your machine, not about
accidentally breaking master for everybody.

Ideally your code review system should be the only one to have merge rights to
master. Then nobody can singularly break master at least.

------
reacweb
Oh shit, this commit message buried by new commits must be fixed before it is
propagated to other repositories.

Oh shit someone has fixed an old commit message that was already pushed to
other repositories.

Oh shit, this commit ough to be a merge commit. The tree is good, but not the
parents.

------
js4ever
Thanks for this excellent cheat shit

------
BuildTheRobots
The last example [0] really should reference the obligatory XKCD [1]

[0] [https://ohshitgit.com/#fuck-this-noise](https://ohshitgit.com/#fuck-this-
noise)

[1] [https://xkcd.com/1597/](https://xkcd.com/1597/)

------
rich-tea
Git is not hard. It's very simple. But people learn it the wrong way. You have
to learn it from the DAG up. If you cannot grasp how the DAG works you'll
forever be reading and writing articles like this one which do not help you to
learn.

This is a horrible article. You should not bookmark it or use it. If you're
not a programmer, you shouldn't use git. If you are a programmer, do yourself
a favour and spend a day going through something like this:
[https://wyag.thb.lt/](https://wyag.thb.lt/)

It will make you better at git and better at programming. Git is a powerful
tool and you need to _learn_ how to use it. Imagine if people read articles
like this one instead of learning how to drive.

~~~
wishinghand
What's a DAG?

~~~
yaseer
[https://medium.com/girl-writes-code/git-is-a-directed-
acycli...](https://medium.com/girl-writes-code/git-is-a-directed-acyclic-
graph-and-what-the-heck-does-that-mean-b6c8dec65059)

~~~
rich-tea
Unfortunately this article, like almost all others, is still wrong because it
looks like commits get mutated when you rebase and the old commits disappear.

It is very important to understand that commits (in fact, all blobs) are
immutable in git. You can only make new things. You can't modify old things.
Git doesn't delete anything for a while either.

------
_pmf_
I don't get this "afraid of losing something" mindset at all. In fifteen
years, I've "lost" some minor changes maybe 3 or 4 times, and this was mostly
with SVN, which does not have the safeguards that Git has. The only thing that
I am moderately afraid of is pushing to the wrong remote branch.

~~~
mehrdadn
> I don't get this "afraid of losing something" mindset at all. In fifteen
> years, I've "lost" some minor changes maybe 3 or 4 times, and this was
> mostly with SVN, which does not have the safeguards that Git has. The only
> thing that I am moderately afraid of is pushing to the wrong remote branch.

I can lose something for you in 2 seconds in git. Have fun e.g. recovering
from this:

    
    
      $ git init
      $ mkdir -p widget && echo Introduction > widget/readme.txt
      $ git add widget
      $ git commit -m "Initial commit"
      $ echo Conclusion > widget/readme.txt
      $ git checkout widget
      $ cat widget/readme.txt  # No "Conclusion"??

~~~
fileeditview
Wow just tried this.. is this considered a bug or a feature and where can you
read about this if it is intended behavior?

~~~
chappi42
`git checkout --help` gives

    
    
           git checkout [<tree-ish>] [--] <pathspec>...
               Overwrite paths in the working tree by replacing with the contents
               in the index or in the <tree-ish> (most often a commit). When a
               <tree-ish> is given, the paths that match the <pathspec> are
               updated both in the index and in the working tree.
    

As 'widget' is a _path_ (and not a branchname as most often), local changes
will be overwritten. Together with `git reset --hard` this is a bit a
dangerous operation. I must say, that I don't fully understand the help text
and just remember that `git checkout <path>` throws away my local uncommitted
changes.

~~~
mehrdadn
Also note that 'widget' could've also easily been a typo for a branch (maybe
they meant 'widgets' or something). Meaning that even if you only stick to
uses of git checkout for branches, you're not safe (unless you're infallible).

------
scarejunba
Honest to god, I don't know how people who find `git` hard to use manage to
write code. Everyone on the Internet acts like the concepts are impossible to
grasp and it's like really easy to grok.

Honestly, it faded into the background of code from the beginning. I mean, I
know "Forward-port local commits to the updated upstream head" means nothing
to anyone not already familiar with `git rebase` but a practical mastery of
the tool is very easy to achieve.

I honestly think this is a pedagogical lack. We tell everyone it's this
complex thing and that they should be scared of rebase and the reflog and they
believe it. Maybe if we didn't, it'd be easier.

~~~
tluyben2
I was thinking if I would post what you did it would get downvoted but yeah,
if you find git hard how or why are you writing code? That is surely a lot
harder. Not sure why it is downvoted as sure it might not be a popular opinion
but it seems to hold...

~~~
collyw
I want to focus on code and not be wrestling with a version control system.

