Oh Shit, Git?
100 comments
·January 16, 2025pitaj
mrshu
Not trying to defend the choice of `git checkout` over `git switch` (and `git restore`) but they were introduced in v2.23 of Git [0], which was released about 5 years ago [1]. If you take a look at their help pages, they still include a warning that says
> THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.
Granted, it has been in there for basically as long as the command(s) existed [2] and after 5 years perhaps it might be time to no longer call it experimental.
Still, it does seem like `git checkout` might be a bit more backwards compatible (and also reflective of the time when this website was originally created).
[0] https://github.com/git/git/blob/757161efcca150a9a96b312d9e78...
[1] https://github.com/git/git/releases/tag/v2.23.0
[2] https://github.com/git/git/commit/4e43b7ff1ea4b6f16b93a432b6...
lalaithion
The disconnect between git's beautiful internal model of blobs, a tree of commits, and pointers to commits, and the command line interface is so wild. All of these recipes are unintuitive even if you have a firm grasp of git's model; you also need to know the quirks of the commands! To just look at the first one... wouldn't it be more intuitive for the command line interface to be:
# this command exists already;
$ git switch -c some-new-branch-name
# is there a command that simply moves a branch from one commit to another without changing anything else? It feels like it should be possible given how git works.
$ git move-branch master HEAD~
neild
The "move a branch from one commit to another without changing anything" command is "git reset".
"git reset --hard" is "...and also change all the files in the working directory to match the new branch commit".
"git reset --soft" is "...but leave the working directory alone".
lalaithion
git reset only works if you're on the branch you want to move, which is why every one of these example flows has you create your new branch, then do the reset, then switch to the new branch, instead of just allowing you to move a branch you're not on.
rav
Actually, "git reset --soft" moves the current branch to another commit, without moving the index (aka staging area) along with it, whereas "git reset" (aka "git reset --mixed") moves the current branch AND the index to another commit. I really couldn't wrap my head around it before I had gone through "Reset demystified" [1] a couple times - it's not a quick read but I can strongly recommend it.
[1] https://git-scm.com/book/en/v2/Git-Tools-Reset-Demystified
Certhas
The real "internal model" of git contains much more data/moving parts.
There isn't one tree of commits, there are typically at least two: local and remote
Branches are not just pointers to commits, but also possibly related to pointers in the other tree via tracking.
Stash and index and the actual contents of the working directory are additional data that live outside the tree of commits. When op says "avoid git reset hard" it's because of how all these interact.
Files can be tracked, untracked and ignored not ignored. All four combinations are possible.
lalaithion
None of these seem to preclude a command to make an arbitrary branch point to an arbitrary commit without changing anything else.
Terr_
> The disconnect between git's beautiful internal model of blobs, a tree of commits, and pointers to commits, and the command line interface is so wild
In some ways, git isn't a version control system as much as a toolkit for making your own.
jimbokun
Are there alternative git command lines that keep the beautiful internals, but implement a more elegant and intuitive set of commands to manage it?
dalia-reds
Check out jujutsu or jj (same thing). It's its own VCS, but it uses git as a backend, so it works with GitHub and other git integrations
pitaj
I prefer just using `git switch` because it's easy to remember the flags (and the position of arguments), but you're right, there is a simpler way:
git switch -c some-new-branch-name
git branch -f master HEAD~
lalaithion
Good to know! Thanks for the tip.
DangitBobby
You should also be able to do
git branch -f master origin/master
rav
For move-branch: Use `git branch -f master HEAD~` if you're currently on another branch, or `git reset --soft HEAD~` if you're currently on master.
CharlieDigital
What's the problem with `reset --hard`?
pitaj
It leaves behind tracked files that were moved or deleted between revisions.
SebastianKra
We should start recommending UIs as the default way to learn Git. It would solve a third of these problems and another third wouldn't even come up.
If you later decide that the CLI is faster, go ahead. But first, people need to see visually how they can interact with the tree.
I like fork.dev, but most clients are pretty similar at this point.
koito17
Agreed that UIs generally provide a better UX for Git.
I use Magit and doing things like "abort cherry-pick" is discoverable in the interface itself and uses the exact same shortcut as the other "abort X" operations. If I had to use the Git CLI, I'd have no idea where to start.
Similarly, I've made mistakes in interactive rebases where I deleted a commit that shouldn't have been deleted. If I recall correctly, the start of every rebase creates a snapshot that is accessible from the reflog, so this is a safe way to revert changes from a rebase gone wrong. Magit's UI for the reflog is exactly the same as the UI for the log, so I was not lost when I saw it for the first time. With the Git CLI, I'd likely have no clue what's going on.
JTyQZSnP3cQGa8B
I agree, I have used git for more than 10 years and it's the only tool that I refuse to learn. The command-line interface is cryptic and infuriating. I'd rather write assembly language again than learn what is essentially a CLI to the internals of git. It's not high-level, it's not intuitive, and it can be destructive if you don't use the right option. I stick to GUIs and simple actions, and I never had any problems compared to all the horror stories of my CLI-loving coworkers.
nuancebydefault
The cli is faster if you know by heart but a real disadvantage is that it is hard to "see" what you did or what happened in the past. Good look finding where/whether an old branch got merged and find out if it is part of a release, using cli.
globular-toast
As a magit user I agree, apart from the fact most GUIs I've seen are horrendously broken and can lead to an even worse mess. For example, I was really confused about how a colleague messing up and got them to show me. Turns out in VS Code if you set the upstream branch correctly (ie. to master), it tries to get you to "sync" the branch. So it assumes the upstream branch is the branch you push to, which makes no sense at all.
phtrivier
This will feel very weird in April 2025, when we celebrate the 20th anniversary of git.
I was there. And at some point I wondered if I should learn git, darcs, or bazaar, to replace SVN or CVS. Or did I try mercurial too ?
I wonder if the "GitHub" effect has basically killed the need for a newcomer in the space of VCS. Maybe at some point, the yak is shaved enough ?
jimbob45
SVN has always worked for me. You don’t have to “teach” people SVN because it’s intuitive and works just fine for the 99% case. I wish we would all stop larping as 1337 hackerz and just admit that git is overkill for the vast majority of people.
tantalor
I'm not a git user, but stuff like this really drives home the idea that "git commit" is meaningless, the only thing that matters is when your commits are pushed or merged.
It's like saving a textfile. Do you write a little message every time you save a file? No that's silly. Just move on.
trashburger
If I'm saving changes that were done because of a arduous debugging journey and other people are likely to have to refer back to it, yes. In fact, forget little; the smaller the change is, the bigger the text. Some of my changes have 2-3 paragraphs for a 2-3 line change because of the impact.
kstrauser
Same here. If it took me a week to figure out why to tweak a couple lines of code, I'm going to be explaining it.
(Although also/mainly in the comments if it's something I worry someone might accidentally change back later.)
shuntress
Well, it is all local until you push so you can do whatever you want.
With that said, it obviously is not meaningless at a technical level because without the commit there is nothing to push or merge. On top of that, at a non-technical level it can be extremely helpful to record some plain-english prose to describe why you are changing something. If you find yourself doing that too often you need to narrow your definition of what constitutes a "change" and/or be more discerning about what you work on simultaneously.
Out of curiosity, if you do not use git, what do you use for version control and change-management?
globular-toast
Commit is what causes git to make a copy of the file(s) internally. It's vitally important. But there is no point typing in silly messages like "more fixes" etc. What I do is make an initial commit with something like "(WIP) too feature", then keep doing amend commits until I'm happy, at which point I remove the "(WIP)" from the message.
mfashby
Related, you can get this as a nice printed zine https://jvns.ca/blog/2018/10/27/new-zine--oh-shit--git-/
bitwrangler
Other fun git hacks...
frakt0x90
I'm not proud of it, but my #1 "Oh shit" git operation is to just delete my local repo, reclone, and reapply the changes. Works really well for me 95% of the time. The rest I ask dev ops guy to help.
spokaneplumb
I've been using Git for almost 15 years, and have twice built programs/products that use Git internally to achieve certain results (that is, the program/product itself uses Git for things, not just using Git to manage the source code for the program/product) and... sometimes before doing something a little gnarly in Git I'll still just do "cp -R .git ../git-backup" or something like that, so I can replace my entire .git dir with an older copy if I screw it up too bad. It's a ton faster than figuring out the right way to un-fuck any particular operation or set of operations.
wruza
This should be a built-in
git unshit
Or git add --unshit -f ~HEAD^^
If you’re using git version <= 2.844.javier_e06
Lately I've been asked to avoid merge-commits. They pollute the logs? If my push is blocked I am too far behind I create a new temp branch of master and do a "merge --squash" to it and then a "reset --hard" from temp branch back to my original branch. Heck sometimes I rather keep my changes in patches to void does darn merge CONFLICTS...specially when rebasing.
nuancebydefault
The thing is if you merge immediately into master and have conflicts, you solve the conflict and only then you can merge. But then the conflict resolution sits at the merge point with a weird default commit message and is hard to decipher.
A nicer way is merge master into your branch, with the rebase option (you can set that option as the default). This will put your changes on top of the master changes that happened during populating your own branch. There you solve any conflicts and those usually immediately show you what happened in the meantime, making it easier to do so. The latest greatest now sits in your branch.
Then as a plus, you can retest that merge and if necessary, fix the merge.
Optionally you can do a pull request for reviewing. The diff the reviewers see is conflict-less and makes sense, has only your changes.
Then simply merge to master, which should be trivial if you don't wait for long.
pitaj
Hard to understand exactly what your issue is here. Typically when people say "avoid merge commits" they mean they want you to almost always rebase instead of merge. Can you give an example or something?
a_t48
From memory...
git merge origin/master
git reset origin/master
git commit -am "squash" # might need some extra fixup if your branch has added files
No need to make a temp branch. I know there's probably a more efficient way of doing this, but this is what's stuck in my head.grokx
I can also recommend git flight rules: https://github.com/k88hudson/git-flight-rules
It saved my work a couple of times.
amelius
This is why I run Git inside Git, as the latter allows me to undo anything I do within the former.
CharlesW
As a Gitginner I'm wondering if this a good joke that went "wooooosh", or if this has something to do with submodules, or…?
shuntress
Git manages pretty much everything by using the `.git` folder created by `git init` and there is (as far as I am aware) nothing stopping you from going into that .git folder and running init again there to start using git to manage the internal state of your repository. At least... that is what I assumed the joke was.
franky47
One of the beauties of Git is that as long as you’ve created an object, it’s impossible to lose that work (short of nuking the .git directory).
Committing often is key. Precommit hooks (that take more than ~100ms) go against that.
ge96
I have had git corruption problems on a raspberry pi sd card, usually I just had to reclone/abandon that folder
divbzero
Previous discussions:
Oh Shit, Git - https://news.ycombinator.com/item?id=31874308 - June 2022 (232 comments)
Oh Shit Git? - https://news.ycombinator.com/item?id=24173238 - Aug 2020 (156 comments)
Oh shit, git (2016) - https://news.ycombinator.com/item?id=19906972 - May 2019 (277 comments)
Oh shit, git: Getting myself out of bad situations - https://news.ycombinator.com/item?id=15951825 - Dec 2017 (509 comments)
Some changes I would make:
1. Always use `git switch` instead of `git checkout`
2. Avoid `reset --hard` at all costs. So for the "accidentally committed something to master that should have been on a brand new branch" issue, I would do this instead:
3. I'd apply the same to the `cherry-pick` version of "accidentally committed to the wrong branch": 4. And also to the "git-approved" way for "Fuck this noise, I give up.":