What I dislike with git terminal UI is that commands reflect the innards of git, not what the user wants to do. It also forces a deeper understanding of the innards for basic commands than ought to be necessary.
Examples where undoing an operation looks totally different to doing it:
- To stage a change you `add` it, to unstage you `reset HEAD`
- To commit you `commit`, to undo a commit you `reset --hard HEAD^`
The last example is also where you get pointed to an unsafe command for a fairly benign operation. Uncommitting is not particularly dangerous, but anything with `--hard` ought to give people a pause for thought. If you mistype it and nuke more than the last commit, that's also a bit bad - and fairly easily done.
Some other examples include shenanigans around pushing and pulling remote branches. After 10 years of using git, I still need to google it each time.
I think these commands are an accurate and reasonable representation of its inner state, but IMHO users should at least have the option of being somewhat isolated from it. Git is not a tool for algebraic manipulations on directed graphs, it is a version control software.
It has to be said, to git's credit, that although the commands are confusing to some (myself included), the tool works very well. I wanted to like mercurial more, but ended up going back to git, as much as I dislike the UI.
Yes, except... again, this is fighting fire with fire.
If you don’t know git internals, mistype a non-obvious command and lose a bunch of commits, then “look in the reflog” advice is frankly adding insult to injury.
The reflog is awesome! It keeps pointers to commits that have been dropped, rewritten by rebases, stuff like that.
Git is garbage collected, even if a commit is dropped from a branch it won't simply disappear immediately afterwards. The object is still in the git repository and it can still be reached, most easily through the reflog. Only when garbage collection is performed will any unreachable commits be deleted.
> It's really hard to lose changes once they've been committed.
I've still managed to do it (or maybe I used git rm to delete some files that weren't committed?) so let me give one more shout out to 'git fsck' which saved me here.
Yeah, git is certainly a low level tool. That's actually what I like most about it. There's a few fundamental concepts I get to manipulate directly with git commands. I like dealing with innards like this because I can form a mental model of the tool. High level tools are a lot harder to understand.
I think the problem with add, commit and reset is they aren't low level enough. The reset command in particular is juggling several concepts at once: HEAD, the index and the working tree. The add and commit commands work with fewer concepts: working tree to index and index to commit, respectively.
I too google the commands I don't use often. There's no shame in that.
I probably pushed and pulled O(100) remote branches over the past decade (jeepers I'm getting old), so roughly once a month. It's not that rare, yet I still haven't memorised them, because I find them so unintuitive.
Once a month is close to my usage and it's often enough to remember things in principle but not often enough to remember the d#$%#$%^#$n syntax with all its obscure details.
Depends on the result. I do interactive rebases extremely often, I don't have to look up how it works.
Sometimes I have to use some command I'm not familiar with. Sometimes familiar commands gain new options. It's perfectly okay to use some reference for that. Also, "more time" is like 1 minute.
But this in fact reinforces my overall point. I don't want to be messing with `reset`, hard or soft. What is `reset`? [I now know because I had to learn, but I'd really rather not].
What I want is `uncommit`, or something like that.
That’s a valid point. I think there’s a near consensus, even among people who admire Git and use it constantly (like me, although I’m no expert), that the porcelain commands are confusing in some respects.
On the plus side, Git’s man pages are (sometimes) useful and pretty complete. In this case, if you type `man git-commit` you’ll get a thorough rundown of all the options and it will be clear which one you should use to get what you want.
EDIT: Also, since there are three arenas to keep track of, the file tree, the index, and the commit history, even if there were an `uncommit` command it would need all the flags that `reset` accepts. So it would just be `reset` renamed, which you can do yourself. But maybe some of the `reset` options should be broken out into separate commands.
- git revert {rev} to nuke changes and go back to how the world was
- git uncommit to return to the state literally before git commit
- git unstage to unstage a file
The latter arguably should go with git stage but it’s a bit late for that. NB git add and git rm are totally not inverse operations...
So then I can uncommit, and unstage if need be. Reverting is for discarding changes, and should come with an interactive confirmation prompt and other safety and hygiene messages.
> git uncommit to return to the state literally before git commit
This is ambiguous because there are many such states.
1. Clean working tree, before you began hacking on the code.
2. Modified working tree.
3.1. Modified working tree with staged changes.
3.2. Clean working tree with staged changes.
It's impossible to know which state you want to go back to without telling git. That's what hard, mixed and soft resets do: they bring you back to states 1, 2 and 3.2, respectively.
The default is a mixed reset. I suppose state 2 is what most people want when they try to undo a git commit. So it already does the right thing by default, no?
The only remaining problem is the git reset argument. An undo command will always reset to the previous commit, there should be no need for arguments. Stuff like HEAD^ or HEAD~1 is quite obscure to the uninitiated. We could certainly have a git undo command that runs git reset HEAD^ and allows hard and soft resets. Not sure why it doesn't already exist. Has anyone ever attempted to get that merged into mainline git?
This creates a new commit that reverses the changes of a past commit. That’s different from undoing your last commit, as in, you made a commit but then changed your mind, and want, say to make some more changes before you commit. (Explained in man git-revert.)
If git at least had good man pages. The man-page's description is "Given one or more existing commits, revert the changes that the related patches introduce, and record some new commits that record them." [1] I have a hard time parsing that to mean what it's supposed to mean.
It contains a note that's much better at explaining what git revert does, then goes on to explain alternatives for other things you might want to do, but doesn't mention how to undo a commit without creating a reverting commit.
“ I have a hard time parsing that to mean what it's supposed to mean.”
Can’t disagree. That part could certainly be written better. I’m not saying I would want to learn Git from the man pages, but they’re pretty good in general after learning it elsewhere.
They are decent for reference, and some are genuinely great (like git-everyday [1] or git rebase [2]). But just as often as they are helpful they just leave me scratching my head, either because they are too deep in git-lingo or because they forget to clarify some crucial details
> Git history is intended to be immutable when working with others.
That's another pet peeve (though maybe less git's fault): immutable, yes, but then rebase is pushed quite liberally. Arguably not by git itself, but by many online learning resources.
It's convenient, and mostly works, but then occasionally really stings you.
...yet undoing the last commit, arguably the least aggressive of history-rewriting commands, remains awkward.
It's also very wise to make sure you disallow force pushing to master/main/production. If you force push to a topic branch, it's usually ok. If you accidentally overwrite someone's changes, it's because you are working together and you can communicate that you messed up and for them to `pull --rebase` or however you want to resolve it. Usually that is. There's always room for disaster :)
> Usually that is. There's always room for disaster :)
For me, this is where the problem with got lies. I use so many of it's commands infrequently enough that I don't know where the weird disastrous edge cases are, where I should use.
I think git would greatly benefit from a topical man page; a list of common and uncommon situations that shows the recommended commands for solving them, and explains what those commands do. I know that hundreds of random "guides" exist, but are they outdated, are they cannon, do they have bugs? When your UI is esoteric, situational documentation is really important.
Ya, the git UI is not great. It has improved a ton over the years but still not great. Have you ever seen Git Koans? It's good for a laugh. https://stevelosh.com/blog/2013/04/git-koans/
Oh right. I always neglect to do that mostly out of laziness of typing it out on the command line. It's funny, though, I maintain a branch plugin for vim (it's a fugitive extension) and I recently accepted a PR to switch `--force` to `--force-with-lease` yet I still don't do it. I need to start doing it!
How is "git reset HEAD~" "awkward"? It's not obviously named, but as was noted above, if there was git-uncommit, it would need every option that git-reset has, and so it would just be an alias, without the final argument.
"History is immutable" is generally accepted as describing the contents of commits, not their id's or relative ordering.
The default for `git reset`, which is `--mixed`, may do what you want. The difference between `--mixed` and `--soft` is that the former resets the index as well as moving the HEAD pointer; the latter only moves the HEAD, so if you’ve `git add`ed any changes, they will remain staged.
Yeah, keeping track of Git’s three areas (files, staged changes, repository) can get confusing. After some thought I think my advice to you to use `git reset --soft` would be better as just `git reset`, because that’s a neater “starting over”: it’s more of an “undo” of the last commit action, I guess. I often find myself reaching for `git reset --hard` after screwing up and not feeling like tracking down how I broke things.
"- To commit you `commit`, to undo a commit you `reset --hard HEAD^`"
You're not meant to delete commits like that. Git encourages you to stop "lying about the past", and you will run into a fair amount of trouble if you try to do so with shared history. You don't remove a commit "backwards" and change history; you add a new commit that undoes the previous one (git revert)
Couldn't disagree more. To me, one of the massive advantages of Git over other VCS tools is the support and completeness of its ability to edit time. You are the editor not just of the content under control, but over its branched evolution. Accepting this and working with time gets you to a next level of control.
The first step in this process is really internalizing the tree of commits, and seeing branches not as lines of development as much as simple pointers on that tree. In many VCS systems, branching is a big deal. In Git, a branch is just a smart tag that is automatically updated by HEAD. Users new to Git need to get comfortable creating branches at a whim as the tree grows.
The next step is getting comfortable with editing the tree itself. Primarily, I recommend using interactive rebasing liberally. This has multiple positive side effects:
1. It causes the author to think at a higher-level of the primary artifact they're constructing — the tree of incremental changes.
2. Authors begin to think about the repository as more than a dumb file backup system, but instead as a history with true utility, and so begin to construct "stories" in that evolution that are instructive and useful to future readers of that history.
3. It takes the author out of the flow of time, and encourages them to think about ways to change the past (within limits, of course) so that the constructed history is improved.
All of this, of course, with the caveat that shared history changes must be coordinated (and frequently avoided). You can get very far thinking this way just about your un-pushed topic branch.
There's a very real philosophical difference between those who see VCS as a secure audit trail (where changing history is "lying about the past"), and those who see a repository as a whole constructed artifact of intrinsic value, wholly editable as its contents. In this latter view, editing a line of source isn't "lying" about that line's contents, and neither is editing time.
Interactively rebasing your local changes instead of sharing a hodgepodge of brain vomit is great, but don't go around rebasing history that's already out there between a team of developers unless you have a really, really good reason to.
I did specify that rewriting shared history is going to cause you trouble (unless you really know your way around things), but apparently it wasn't clear enough
Reset is a powerful command that happens to work for the “uncommit” use case. I use reset for a completely different use case: my CI system watches a specific branch for changes and when they occur builds it with a specific configuration. If I reset-push a branch, I get a build of that branch with that configuration.
Examples where undoing an operation looks totally different to doing it:
- To stage a change you `add` it, to unstage you `reset HEAD`
- To commit you `commit`, to undo a commit you `reset --hard HEAD^`
The last example is also where you get pointed to an unsafe command for a fairly benign operation. Uncommitting is not particularly dangerous, but anything with `--hard` ought to give people a pause for thought. If you mistype it and nuke more than the last commit, that's also a bit bad - and fairly easily done.
Some other examples include shenanigans around pushing and pulling remote branches. After 10 years of using git, I still need to google it each time.
I think these commands are an accurate and reasonable representation of its inner state, but IMHO users should at least have the option of being somewhat isolated from it. Git is not a tool for algebraic manipulations on directed graphs, it is a version control software.
It has to be said, to git's credit, that although the commands are confusing to some (myself included), the tool works very well. I wanted to like mercurial more, but ended up going back to git, as much as I dislike the UI.