Git undo: We can do better
blog.waleedkhan.name
blog.waleedkhan.name
The truth is, while we use git every day, most people really don't understand how it works.
There I said it. And I'm not ashamed.
I don't really know how Git works. And I think I'm not the only one.
What does "git reflog" or "git reset --hard ...." do? What are the implications?
We don't really know.
I feel stupid. But hey, at least I'm honest.
Like, I've had to explain a lot of times why you `git pull origin master` but when you want to interact with that remote branch otherwise it's `origin/master` instead. The lack of clarity is in what commands operate on what levels, with many of them operating on several at once.
There have been some efforts to reform the command set to be more clear, like `git switch`, but the old commands will persist forever along with a lot of other footguns (like `git push --force` really ought to be replaced with `git push --force-with-lease` and moved to `git push --force-I-really-mean-it` so it hardly matters.
As part of a security-related project some years ago, my team and I hacked jgit to use SHA256, which required changing the length of pretty much every on-disk data structure. Sadly, there was (probably still is) no HASH_LEN constant, just a lot of magic offsets strewn throughout the code. I had to compare lengths against the git spec at every step.
And yet I still scramble for stackoverflow every time something goes slightly amiss.
jGit is actually a separate project from core Git, but once it gets adopted into core Git we can expect that jGit will follow suite, given that it's critical to Gerrit and other projects.
[1] https://lore.kernel.org/git/20191223011306.GF163225@camp.cru...
This is the crux for me. Command naming is completely unrelated to and unindicative of state.
It feels like surely there's an opportunity for the basic CRUD operations to be collapsed down into a standard "{action} {source} {target}" style.
There will be nuances, specifically around branching, but the basics should be basic. As opposed to a Swiss Army knife, where you have to pull out the scissors and squeeze them three times before you can unfold and use the blade.
If it's difficult to keep your mental model of some system up to date, I doubt that doing bigger steps at once makes things easier.
So
1. run `git fetch`
2. if the textual output does not tell you what has happened, run `gitk -all`
3. Decide what to do. Rebase, merge, whatever.
Of course if you know exactly what you are doing, pull can be fine. If you changed the repo yourself on another computer that is the case. Otherwise, how can you know your second step, before having even seen the data you are operating on? Well, it can work, but if it doesn't, don't complain.
git init --submodule --recursive
Or is it
git submodule --init --recursive?
God I hate this UX so much I usually have a ./fetch-subrepos.sh that runs a bunch of "git clone" commands.
And if I push without first pulling, must it always punish me with a merge commit? Can't I say "oh shit I don't want to do this, go back and git pull"?
This is a source of probably 50% of my "ah, fuck, time to undo..." moments with git, these days. I hate that shit. Muscle-memory gets ahead of me and I commit on a shared remote branch, which would be fine given our workflow except that I didn't pull first. What a pain in the ass.
I think I know git well, but you got me confused. I've never heard of pushes causing merges. Surely you are talking about pulls, right?
Git submodule runs commands on submodules.
What is hard about this UX?
And it's not punishing you, its doing what you asked, to pull into a non matching head, how does it know you're not using git in the intended and distributed way?
Btw, just quit the editor without saving, it aborts.
For the rest, there's shell autocomplete and muscle memory.
I'm the guy people go to fix Git screw ups at my jobs but I just click a few buttons or drag a few commits...
Maybe you've already read it, but this is what let me grok the underlying data.
Comments like this, which points to a resource intended to help people "grok the underlying data", has the effect of seizing the focus of conversation and implicitly retargeting it to be concerned with with people who don't understand the underlying data model. When you been through this enough times, it just comes off as incredibly annoying and a source of tiresomeness.
It's just that the command-line interface is very opaque regarding what it does to that data.
For instance, say I want to apply the last three commits I made in one branch to another branch. It's a very simple operation conceptually.
Good luck remembering that the command that does it is rebase, and what the arguments for it are.
1. Despite the claim that git never loses data, there are actually some dangerous operations that will irretrievably nuke your work with no warning. Git checkout is the canonical example. This makes me very gun-shy about doing anything that I'm not intimately familiar with.
2. Git's merge is not smart enough to realize that identical changes in two branches are not actually a conflict. I've often ended up in situations where a small bug has been fixed in two branches which then won't merge without manual intervention. This is incredibly annoying. (To be fair, this is not unique to git. But because git encourages branching more than other systems, I encounter it more when using git in idiomatic ways.)
3. This is the biggie: translating the abstract idea of what I want to do into an actual git command is a black art. The underlying model is beautiful, but the UI is atrocious. The plumbing is great. The porcelain is cracked and mildewy. There are mysterious valves and pipes all over the place when all I want is one control for the hot water and another one for the cold.
Contrast that with TFVC which tries its best to nuke everything within it's reach (that is not under version control) in inexplicably stupid ways.
tf reconcile . -r -clean
This command is equivalent to git reset --hard origin/head && git clean -fdx
Because that's sensible. tf reconcile . -r -i clean
One might think -i is a short flag for -ignore. No. It's short for -preview. That's a "feature". Good luck finding the documentation for this idiotic behavior.How about if you pipe a list of files into tf reconcile, to avoid it's idiotic behavior? Say
git ls-tree HEAD -z | xargs -0 tf reconcile -r -clean
You better hope that ls-tree outputs something, otherwise that is the same as calling rm -rf .
I can't say I recognize your experience. You only risk creating a mess of changes to the point where it's hard to recover due to the share amount data it hasn't deleted.I think this is part of a downside of rebase-centric workflows, since it encourages making multiple branches with identical changes, but no shared history. At some point I want to read more pros/cons on different workflows. My current thinking is that rebase-only sacrifices far too much on the altar of a clean-but-inaccurate commit history, but I don't yet know enough to say for certain.
https://www.git-scm.com/book/en/v2/Git-Tools-Rerere
If you have a lot of long-lived branches that don't merge and get deleted you have a whole different world of pain from normal git usage, and need some resources designed as SCM experts to manage this stuff. But really short lived branches that deliver net steps forward are the way to live.
I hate branches because it breaks git-bisect. Linux kernel history has lived without it, so I'm guessing the "our code is so complex we need it" is a false idea.
Also, the actual history of what main/master points to is linear. If the bit-post merge are identical, it doesn't matter if they were rebased or not except having 2 ancestors for HEAD is a lie.
The similar project [Jujube](https://github.com/martinvonz/jj) has experimented with backing up even unstaged changes after every command, and apparently it works well for them, so we could do the same in the above design.
Undoing even untracked changes might be a bit much.
Take branching, which is something you're supposed to be doing all the time. So abstractly, I want to be able to get a list of my current branches, create a new branch, check out an existing branch, delete an existing branch, and maybe rename a branch. I would expect the commands for these operations to be something like:
git branch list
git branch create [name]
git branch delete [name]
git branch rename [old-name] [new-name]
We can argue over whether checking out a branch should be "git branch checkout [name]" or just "git checkout [name]", but in either case, if I have unsaved changes in my working directory, I would expect to at least get a warning about this before that work got clobbered.None of these things are actually the case. Git checkout will clobber unsaved changes in my working directory. "Git branch list" is just "git branch". "git branch create" is "git branch [name]". So creating a branch is the SAME COMMAND as the one you use to produce a list of current branches, just with an argument. Madness. And this is the rule, not the exception. Minor variations on a theme can result in radically different commands with various mysterious arguments. There is no rhyme or reason or regularity. The only way to know is to look it up.
I agree with your other criticisms, but IME git's branching-heavy approach makes this one easier to avoid: it's natural and easy to create a separate branch for that bugfix and then merge it everywhere that it's wanted. Or to just merge your colleague's feature branch that has the fix on into your own feature branch, if you know their branch doesn't contain any dangerous changes / will hit master ahead of yours.
(If the diff is actually bit-for-bit identical I think it doesn't conflict? But obviously that doesn't happen so much in practice. git merge -Xignore-all-space can be helpful in some cases).
You might enjoy using Magit.
I definitely agree. The UI is terrible. But it is learnable.
I would recommend using a GUI initially so you can learn what the operations are and do without having to figure out that it's not `git remote list` it's `git remote -v`; it's not `git clean` it's `git checkout . && git clean -fdx` and a million other paper cuts.
Between my colleagues, there is a strong correlation between using a GUI, and messing up the repository.
I agree that Git's UX is pretty bad (checkout overloading; overlapping between checkout and reset; push overloading... yikes!), however, I believe that in contexts where using Git is a constraint, stop using GUIs is the best strategy one can apply to improve the understanding.
First of all, a well-designed Git GUI (examples below) exposes the git's underlying data model to you; in contrast, the CLI obscures it until something breaks and you're forced into it without context. Git operates on a graph and there is simply no way around it. The more you're exposed to the graph, the better you can mentally model it and ask it to do the right things.
While there are many half-baked Electron-based UIs that only unnecessarily complicates things, there are good ones too:
On Windows (and Linux thru Mono), I use GitExtensions. The visualizations are sane and discover-friendly. It will tell you what to expect.
When I work on C++ projects, the one built into CLion (JetBrains family; as mentioned by a sibling comment) is very good on its own. What impresses me the most is that has good visual support of a patch-oriented workflow. You can work with multiple "changelists" offline, shuffle individual changes around, seamlessly convert between changelists and patch files, and (most importantly) still work nice with the vanilla git model as the "actual history". It also works transparently with conceptually monorepo projects that have multiple physical repos, allowing you to do simultaneous commits.
I feel VSCode, GitHub client, Kraken, and several other Electron-based stuff, are too focused on the "polish" than substance, or are too opinionated to be used across repos I don't own.
All GUIs are not created equal. The JetBRains GUIs are pretty good. Others try and impose git "their way" and just invite creating a mess.
Basically the IDE has its own revision history of your project’s files that it stores and you can recover your files from it. So when you accidentally revert instead of undo or use —-hard when you didn’t really mean to, Local History comes and saves the day.
Git is really weird but useful.
EDIT: Also, I really like being able to look at a diff of every file before I commit, and easily choosing which files to include in a commit. Too often I see people on the CLI accidentally committing changes they didn't mean to, because there is no easy way to check everything at the last minute.
git commit -v
Displays a diff at the bottom of the editor that pops up to write a commit message.There's a user request that's been sitting around for a while to pull it out into a dedicated application, which I could personally get behind since we have one project at work that's pretty difficult to run outside of Eclipse
Then there's 'git add -i' to easily choose which files to add
git diff
git diff --staged
git restore --staged --partial
git commit
you can also add commit hooks to reject changes like conflict markersAnd it's time well-spent in my opinion; git will probably be one of the longer-lasting constants in software development.
It's something I use all day, every day. I'd say it's worthwhile to learn if your daily job involves working under source control
Sure, recloning and cp your changes to a fresh local repo will _work_, but what happens on the day you don't have access to the remote? What happens when you have to work on a repo that takes minutes to clone from the remote? All of those lost minutes because you don't understand how to use your tools means you aren't getting work done.
> memorize an obscure set of incantations like some kind of D&D wizard
If you're using Git then you're probably someone that works on software, isn't our whole job memorizing and reproducing permutations of obscure incantations to produce business value? Knowing how to use your tools provides business value, that's not some hot take but literally how you provide value to whoever pays you.
Same for VSCode on non-Windows.
I only mention this because I came to hate Sourcetree in its newer iterations on Windows so much that I tried out Visual Studio's support and was pleasantly surprised.
I do most of my git through one or another UI (right now, mainly the integrated functionality in VSCode, a gitflow workflow extension for VSCode, and repository history graph extension for VSCode that supports doing operations against branches/tags/commits from the graph.) I feel loke I’m thriving that way.
Its not a substitute for knowing git, though, and I think that people who lean on a UI as a substitute for knowing what is going on underneath rather than as a convenience layer are not likely to thrive.
But why is one popular when the other one is so much better? I guess we will never know
Mercurial is less intimidating if you don't know much about internals. But tbh, when I use Mercurial I still find myself searching which command to do things. As a frequent power user, I find the Git CLI to be more useable.
I once knew how it worked with moderate level of detail, but I simply do not need anything advanced for my day-to-day work.
Usually my response is if you use something daily, and you know you lack the skills to use it effectively, why don't you improve your knowledge and seek out training or education?
Do you treat a new programming language or framework with the same disdain?
I know I'm gonna get some hate for pointing this out but this same disdain is what causes things like the branchless workflow. A workflow that hamstrings yourself to git stash as a poor man's branch. I get it, I used that as crutch for years, then I spent some time learning git properly.
Most people haven't even watched the one hour talk where Linus talks about the design and building of git.
One hour might sound like a lot to understand why branching is so amazing, and why distributed source control is hard but it's a tool I've used for almost a decade and won't stop using for the next decade. I think it was worth it.
I have no memory of any of that beyond the basics now. Git is like that. There is no way I will remember commands I only use once a quarter or so when something goes wrong.
Maybe git isn't the right tool for you?
I have a set of commands I use multiple times a day, for everything else there are manuals and docs to reference.
Git branch, commit, rebase, merge, clone, check out, pull, push, submodule, remote, and maybe a couple more, are there specific commands you don't use daily? Other than remote and submodule I use all of those almost daily.
FYI, the branchless workflow linked to in the post is really the opposite of this. It encourages making commits even more often than you would in the traditional branching workflow, and discourages using the staging area or stashes when commits would work fine.
`git reflog` shows the commits where your current branch pointer has been.
`git reset --hard` moves your current branch pointer and to the given commit, then modifies your working directory to match that commit. `--soft` moves the branch pointer and does not modify your working directory. `--mixed` moves the branch pointer, does not modify your working directory, but does clear staging.
This is supposed to be covered in `man git reflog` and `man git reset --hard`. I admit that it could be more readable though. Currently, it's more of a technically-correct introduction than a layman's. I guess it's really more of a documentation for experts. Some have made fun of this: https://git-man-page-generator.lokaltog.net/
In layman's terms, `git reflog` is the history of the positions you were at: you'll see every commit you've visited recently, so as long as something was committed, you won't lose it. It's here in case you lose some commit identifier (for instance you finished rebasing but are not happy with the result: the branch now points to the sad commit. Grab the reflog, copy the commit identifier and reset the branch to point it to where it was before).
And `git reset --hard`... `git reset` changes the branch "tip" (pointer) to another commit: the commit tree always exists. Branches are "named commits". `git reset` moves these tags around. The `--hard` part "just" replaces the entire content of your working directory (including non-committed changes) to the commit you give as a parameter. With no parameters, it just resets to the latest commit in that branch, so it's like saying "clean my working directory back to what was committed, discard my changes". Perfect for losing work.
I agree that git "porcelain" commands are sometimes ill-named and a bit counter-intuitive to grasp. For me, learning git paid of (sort of: I don't spend time fighting my issues, I spend it helping others fix theirs).
I'm keeping an eye on better-designed alternatives like pijul. mercurial is interesting and has better-named command, but I sunk some time learning git already, and know it better, so hg has very little more to offer to me.
[1]: https://mirrors.edge.kernel.org/pub/software/scm/git/docs/us...
Most people don't know how the Internet works and yet it's widely used.
You don't need to understand the inner workings of git. You just need to know some commands and some basic concepts.
Yeah. I've met many developers who have no idea what a cookie even is, people who have never read a single IETF RFC.
It is a tool. One should not need to understand the inner workings of a tool to use it. How many APIs do we use where knowing how it does what it does is required? Whether it's Stripe or Node or Bundler or ..., the user does not have to know what happens inside. The need for incantations makes some people feel powerful or exclusive. I just want my tools to do their job so I can focus on doing mine.
If you have ever done a "git reset" (which implies --soft), then you should know what "hard" in this case means. I am not sure, I knew that it would most likely get rid of my uncommitted changes. I "git stash", then "git stash pop".
Do not run anything without knowing what it does, you should consult the manual page: "man git reset". Search for "--hard", and you get:
--hard
Resets the index and working tree. Any changes to tracked files in the working tree since <commit> are discarded.
I do not find myself reading the manual pages that often anymore. That said, I do have my own notes which I read sometimes, just to be sure.Slightly off-topic, but I love working with meld! If I do a "git rebase --onto [...]" I do not get "meld" open automatically, you gotta type "git mergetool" to resolve conflicts.
I wonder if it is possible for someone to write a meta-CLI that works on top of git like “git for humans”. While, still leaving the power for expert users
You just don't need to learn unless you really require it. And even if you do learn you will eventually forget if you don't use all the features.
Its not hard to see why people don't even bother.
FYI: `git reflog` saves your ass when you accidentally delete a branch locally, only to then realize there were valuable commits yet to be pushed to master! How it does it is another matter.
Systems enabling high usefulness with little education (i.e. shallow learning curve, at least initially) and then incremental education are ideal. This isn't essential, just desirable. Git can err a little on the steep learning curve/mystery black box side of things a bit. But it is still a damn fine tool.
Between the tone deaf responses here about "using a GUI client is the problem," to the tone deaf responses of "you just have to learn it's internal architecture," it should be obvious what the problem is. The problem is not just being able to undo a mistake (though that's certainly one of the problems). Git is an incredibly user-hostile experience, and someone needs to fix or replace it. Can we just say it out loud and stop pretending that the problem is the literally thousands of users who have problems using it?
`git branch new-branch-name` means create a new branch. Great, but it doesn't check out the branch, which you want like 99.9% of the time. If you want to do that, you use `git checkout -b new-branch-name`. Yes, a third separate use for git checkout.
Why not (e.g.) `git branch -c new-branch-name` with a config option to make `-c` the default if you want it?
If you rebase and push, it tells you to do a `git pull` to "fix" it, when in every workflow I've ever done, you want to add a `-f` and push away, just be aware that you are intentionally overwriting the remote branch.
I do agree with the second example. Pulling and merging with remote after a rebase makes a terrible mess!
It sounds like you are unaware of
git switch branch
and git switch -c new-branchHeh, and I'll do you one better: it's unstaged changes, so it won't put the file back to the HEAD version 100% of the time >:-)
Regardless, what's so bad about deleting the repo and pulling from remote if you can't figure out why you hosed it? It's not like it costs you anything to do `rm ... && git clone ...`. And it's not like this happens daily either.
In the rare case that someone hoses remote, yes you'll have to do some weird git-fu to get everyone working again, but it's really hard to hose remote if you stick to the pull -> commit -> push -> merge pattern, which is what 99% of users are doing anyways. I've used git for 9 years and I've never had to spend longer than an hour troubleshooting git based BS. And if you'll remember, 9 years ago git wasn't the clear winner, it had competition via SVN and Mercurial. Both of them are practically irrelevant at this point so however bad git's UI was, it's clearly better than anything else that existed before.
If it were a human problem, how could alternatives like Mercurial and Darcs get consistent amounts of praise for their intuitive workflows?
> 9 years ago git wasn't the clear winner, it had competition via SVN and Mercurial. Both of them are practically irrelevant at this point so however bad git's UI was, it's clearly better than anything else that existed before.
SVN isn't distributed; it mostly tried to replace CVS, which it managed to do very successfully.
As for DVCS, the fact that system X became more popular than system Y doesn't mean that every aspect of X is better than Y. Many would say git succeeded despite its awful interface. As far as I can tell, the main feature of git was its speed; that made it usable for huge projects like Linux and Xorg, and this endorsement from high-profile projects gave git the edge for DVCS hosting sites like Gitorious. Then GitHub came along, and grew into such a behemoth that git became the de facto standard.
See also: WorseIsBetter
That doesn't mean that Git couldn't have a much better UI for this problem than it does. And while I agree that Git seems to have mostly won over SVN and Hg, it doesn't follow that it's because of its UI. (For example, I think that a lot of Git's success actually comes from the UI of GitHub, not Git itself, but I don't have evidence to back it up.)
Take a look at the Research section at the bottom of https://gitless.com/ for work that's been done on how to accomplish the same operations with a simpler conceptual model.
Well, actually, a lot of people work in large repos, so this can be a pain.
Which of course brings up another failing of the git UI: the difficulty of removing old history from the repo. Last I checked, you pretty much have to use a 3rd party tool for this.
It helps a lot to set:
git config --global merge.conflictstyle diff3
then you can see the before as well as the 2 changes you are trying to resolve.
git mergetool
or git checkout --ours/theirs && git add .Why is this tone deaf? It’s a development tool for programmers and the internal structure is conceptionally pretty straightforward. Understanding your tools is pretty much prerequisite. You don’t have to have written a compiler to use one, but you do have to have a model of what it does. Git is no different.
But git saved me an immeasurable amount of hours in return. I was asked what I worked on some specific week back in may -- git saved me. I had to look up what performance my program had before a specific change -- git saved me. I had to try stuff out without messing up my codebase -- git saved me.
True, I invested quite a few hours and I still do, but I think the investment pays itself off. I'll never ever do a project without git.
* in your opinion.
Mercurial has no staging area, and that can be inconvenient for some scenarios; but I think no-staging model is easier to grasp for beginners. I especially think many beginners would assume that modifying a file after it's been added to staging area would update staging area automatically too.
Mercurial has high-level, scenario-oriented distinct "undo" commands like "revert", "forget" and "backout" instead of overloading a single command ("reset") for everything.
I still like `git reflog` though.
The key thing seems to be the immutable repo editing is hidden by default, but viewable with --hidden flag if you need it.
On top of this, various git commands can delete things you've done in the working directory without any confirmation or warning (e.g. "git checkout [filename]"). And of course, so can various non-git commands (e.g. "rm [filename]"). If that happens, the work is permanently lost if there isn't another backup mechanism in place.
So they're not wrong, but the second sentence's implications are kind of glossed over. It's always confused me that git lets you lose work so easily, so a very long time ago I've always just made sure to put all of my programming projects in a Dropbox folder. I rarely mess anything up that badly with git, but for the few times I have, Dropbox version history has definitely saved me.
- Use a recent version of git. The error messages have improved a lot, have been localized (unless you do `LANG=C git …` to be able to search it on internet), the UI has improved, `git status` is more helpfull, …
- Create an alias for `git log --graph --decorate --oneline --all` or something fancier with `--format`. `--graph` should help you a lot to visualise stuff. - Don't use `git reflog` but `git log --graph --reflog`, it's much easier to visualize.
- Never use `git checkout`, but `git switch` and "git restore` that were introduced in git 2.15 IIRC. They are much less confusing and error prone.
- `--patches` (`-p` for short) can be used with `add`, `restore`, `reset`, `log`. It helps a lot with commit hygiene.
- Most complex rebase are easier to do with `--interactive` (`-i`).
- Activate `rerere` in your git config (it will reduce conflict merges during rebases).
Git has a nice elegant layer underneath though. The DAG of commits is a very nice model, covered in a layer of terrible commands, and a few rather unnatural abstractions like the staging area.
I think to effectively use it you need to work with your brain squarely in that lower layer. Trying to think in terms of sequences of commands rather than a tree of commits and references is hopeless.
The fact that the UX concepts (commands, working copy, reflog staging area, …) and the underlying model (graph) are difficult to reconcile is git’s biggest weakness.
The difficulty with which is models some of the most common use cases (centralized, often including a few large binary files) is another.
I don't always want a branch to descend from a commit - sometimes I want it to descend from another (less featureful) branch.
I want the ability to say that branch 1.1 is equivalent to branch 1.0 + {some set of changes} is something that would be exceptionally useful in a lot of circumstances.
(And I know this would create some new fun and games from a conflicts perspective, but I think it'd be worth it for any use case that involves long term maintenance of different releases of the same piece of software.)
A new repository starts with an empty branch.
A DAG gets layered on top.
I mentioned conflicts, but can you explain where race conditions would arise?
What does this even mean?
> sometimes I want it to descend from another (less featureful) branch.
Yeah, so? Just do that, then. Checkout the branch you want to descend from, create your new branch, and it's descended from the branch you checked out. (That's literally the default for how branching works in git, so... What are you actually asking about?)
Why is this?
At least from my naive point of view I would expect these extremely user-hostile products to have been overtaken but more user-friendly alternatives. I know the best products don't always win, but when they don't there's often at least some theory about what is going on (network effects, business partnership, government regulation, etc.) that I don't have with these two products.
Granted, I almost never use the Git command line and always favor a 3rd party GUI. For users who are a tad confused I think this is the right approach. And to me that's what makes Git a winner: the core software is powerful enough to do everything that needs to be done, and you can pick whatever UI you're comfortable with. No one is forcing you to use Git's built-in UI.
That said I think even the high-level Git concepts have naming that confuses new users. "Pull request" annoyed me for a long time. Yes I technically understood why they chose that term, but practically speaking it just seemed pointlessly misleading.
Fossil and Darcs sure look nice, but having to self-host my repositories or use a smaller company that could go under at any minute? Having to use barely-maintained plugins for my editor, CI, CD, etc.? And most importantly, having to explain a completely new set of tools to anyone who wants to work on my projects? In my opinion, it just isn't worth it.
- It's fast, which we all love. - It mostly does painless merges, again we love. - It's not actually that hard to 'get going' - and I think that a lot of people are totally overconfident in their skills. - It's free - It's open source - Linus Torvalds created the critical mass for Linux - GitHub - Lack of truly efficient alternatives - It technically 'works' i.e. does what it's supposed to do, relatively robustly. - It's designed for open source, which is a novel concept.
Personally, I'm scared of git from an organizational perspective, and I do not feel that we have reconciled the reality of how much pain it can cause to people, newer teams, the less familiar etc..
If people would pay for software I wonder if a great alternative could come to the fore because without it, we have to rely on volunteers and that's an ugly industry problem.
Now a couple of decades later, I just use Word or Pages.
In my experience most beginners use a GUI like Atlassian Sourcetree or the Github desktop client. It's a lot harder to make mistakes using the GUI in my experience.
I still really like this idea though; eventually a subset of the beginners wants to learn the git cli and that sure seems scary at first.
I would like to see a GUI adopt these concepts. There's no reason why we should limit the commit graph display to only the current time, when we have enough information to scroll through its past states as well.
A year later I had probably learned how to force push correctly and took hold of it again.
However I do think the GUI is great for git and that’s why I use GitHub Desktop. It’s super simple. Most other GUIs try to wrap git concepts too closely for comfort.
It's not as easy to use as SourceTree, but since there's no SourceTree for Linux, it works well enough.
JetBrains IDEs also have the best UI I've come across for conflict merges.
I think it would be possible to solve these with git, by doing something like automatically running a `git stash --include-untracked` before an operation that can clobber untracked files.
`git add -p`: prompt which hunks should be added to staging
`git checkout -p`: prompt which unstaged hunks should be thrown away
`git checkout HEAD^ -p`: prompt which hunks from HEAD should be discarded
`git reset -p`: prompt which currently-staged hunks should be unstaged
`git reset HEAD^ -p`: prompt which hunks from the HEAD commit should be unstaged
Nice thing is that you get interactive yes/no prompts with a preview of the change for each hunk, and you can also quit early if you realize it's not the command you want.
Doesn't fully address the potential for lost changes, but slows it down by adding an interactive roadblock for each change.
1. a large piece of something, especially food, cut or broken off a larger piece.
I am the CTO of Tower, a Git client for Mac and Windows.
When I help people get started with git, I tell them several times to commit as often as they can. If they committed it, I can probably save them when they think they lost something.
Mine is as simple as:
- git pull -r (to pull & automatically rebase commits from the common branch)
- git checkout -b (to create my own feature/debug branch from the common branch)
- git add -A/commit -m/push (to add, commit and push… I usually let my IDE handle that part)
- git rebase (to rebase commits from the common branch)
- git reset (to squash all the "WIP" and "fix" commits on my feature/debug branch. I then do 1 or 2 clean commits and push -f)
- finally I do a merge request, or directly git cherry-pick the commits to the original branch if nobody else works on the project
I've been doing this for years and it just works.
- The interactive interface of `git undo` is probably too hard for beginners. Designing simple interfaces is hard, and you can't pplease everyone.
- It seems `git undo` simply does a `git reset--hard` toward a designed commit. The user probably expected to return to the state before the last git action. Instead `git undo` reverted the amend, but also reset the index and remove the local changes. At this point, the beginner could scream "Undo lost my changes! I just wanted a second commit instead of an amend."
Undoing could mean `git reset --hard {hash}`, but in many cases `--hard` would have unwanted effect and the default `--mixed` would be more suitable. But even in this case, it's not a full undo, since the index is not preserved: you don't end up in the same state as before the `commit --amend`.
It runs a `git checkout`, not a `git reset --hard`, so it will stop you if there are incompatible changes. Of course, if `git undo` can be made to lose your staged or unstaged work, then that's a bug.
Fortunately, you can undo the result of any `git undo` with another `git undo` that goes one more step back in time.
* the working copy
* the graph
And it gets better, if any file gets disconnected from the graph, it can be hard to locate. (Yes I know, it can be found in the reflog)
https://github.com/git/git/commit/e83c5163316f89bfbde7d9ab23...
The original idea was "current directory cache" and that still pops up in the syntax.
I always run "git diff --cached" as a last sanity check before a commit.
Empty directories do not contain files or information, it's entirely reasonable for a VCS to omit such a feature. Not sure which use case for git is hindered by the inability to check-in empty directories. I would even suggest such a use-case may be a case of DIW
For instance, it is nice to think about the repo holistically sometimes before I have say an actual unit test to put in the directory, it is nice to create that structure in the repo.
We could easily get into arguments over the specific uses of committing empty directories against the difference it ends up making in terms of stability and extensibility. Instead, consider that: 1) this behavior is very surprising, and 2) it offers no advantages to users, only disadvantages to some. Hence, it is unergonomic. My complaint was precisely that for extremely popular tools (such as git), it is worthwhile in general to sacrifice ease of implementation for ease of use, because the developper effort is amortized over the number of users.
Tower for Mac supports Undo (and also Redo) for very much all Git operations starting with version 3 two years ago. This includes undoing all working tree actions like staging/unstaging/discarding individual chunks or complete files. Just hit CMD-Z.
This feature is coming to our Windows version soon as well.
If you're interested in technical details, you can read it here: https://www.git-tower.com/blog/how-we-built-undo/
The demo for Undo at https://www.git-tower.com/features/productive/Mac seems to be the wrong demo? It's showing a few lines being discarded, and no undo at all. (And the dialog says that discarding the lines can't be undone...)
From what I understand, some of them were more feasible to develop because they implemented their own plumbing (GitUpKit), instead of relying on the official git plumbing.
Nevertheless my main daily driver, along w/ the command line. It's undo capabilities, best-in-class visualization of the timeline, and ability to easily visually edit, reworder, and squash commits remain amazing.
(I was disappointed to see newer git clients like Submlime Merge did not take inspiration from GitUp.)
Ultimately I think the future of VC will be something patch-based (pijul?) w/ a UI along the lines of GitUp.
But yes, I wasn't really worth actively maintaining it as it was feature complete and I didn't intend to build a business out of it.
But I'm not on Mac, does anyone know of a tool that has a similar design idea behind it that works on Windows or Linux?
Unlimited undo / redo is achieved by taking a snapshot of the entire repo before and after any operation (e.g. checking out the repo or creating a branch etc...). The inspiration I had at the time was that is it is trivially cheap to take such snapshots: essentially all you need is a list of all the refs.
Then when you need to undo, you have 3 things: 1 - current state of all the refs in the repo 2 - state of all the refs from the before snapshot 3 - state of all the refs from the after snapshot Compute the delta between 3 -> 2 and apply on top of 1.
The same technique allows to do the Time Machine feature.
I'm a very visual person so I tend to use Git Graph on VSCode. But I still get in trouble. Especially things like Cherry Picking, or reverting.
Another thing that is not so easy is switching branches without having to do a commit. Stashing, yeah, but then figuring out whether stashes have been applied or not, diffing between branches, picking some changes from a diff.
In the end I stick to some basic commands that don't get me in trouble so that I don't lose half a day to recover my work via Dropbox.
is `git reflog` not enough for your use case ?
You can also try `lazygit` it's a TUI written in Go
# fshow - git commit browser
fshow() {
git log --graph --color=always HEAD \
--format="%C(auto)%h%d %s %C(blue)(%aN) %C(black)%C(bold)%cr" "$@" |
fzf --ansi --no-sort --reverse --tiebreak=index --bind=ctrl-s:toggle-sort \
--bind "j:down,k:up,q:abort,ctrl-m:execute:
(grep -o '[a-f0-9]\{7\}' | head -1 |
xargs -I % sh -c 'git show --color=always % | less -R') << 'FZF-EOF'
{}
FZF-EOF"
}If I use github desktop I can roll back a commit whenever I like with a simple menu command.
What am I missing here. I feel like I don't understand what the tool is doing.
Short answer, recreating this tool's features in vanilla git requires using the reflog, which isn't user-friendly:
> Why not git reflog?
> git reflog is a tool to view the previous position of a single reference (like HEAD), which can be used to undo operations. But since it only tracks the position of a single reference, complicated operations like rebases can be tedious to reverse-engineer. git undo operates at a higher level of abstraction: the entire state of your repository.
None of them works. At some point you will be told to fix the problem by hand.
The tools that do work are ones that add a simple extra function (git absorb is really cool, for example), or GUIs that help you visualise the repository and give you buttons that have the same names as the command lines that they replicate. Tools that try to make things "easier" are just confusing for people who know git, and prevent you ever learning git for real. When I first tried it, github desktop was firmly in this category.
We need to get git itself to adopt some changes - git switch is a good start, replacing checkout's branch switching features. I think that every time I do git checkout branch I should get a reminder to use git switch instead.
Agreed. When I was learning to climb the instructor told me I would never reach my full potential until after the first fall. You cannot work at your limit until you trust the rope.
However, I think this is still avoiding the rope. Git is really simple and when you understand it it's hard to go wrong. I really think what git is missing is a better user interface. Magit is the only one I'm aware of.
The hard part is understanding how to get a reference to the thing you want to get back, how to restore it to its proper place in the history, or at least to a reasonable place, and how, if it's been pushed to a shared repo, for other developers to get their local clone properly sync'd up without their local work getting shoved off into some hard-to-reach reference.
I've gotten kind of familiar with the Git CLI. But it took a lot of wasted hours and headaches to do so, and even now it takes headaches and extra time / effort to do certain things like rewrite history and "good" commits. I still prefer CLI to GUI but I wish it was more intuitive.
It doesn't have too many features, but the main thing it aims for - a better repo browser, it does really well in my opinion.
But nobody's going to solve the major problems of Git any time soon, because you can eventually get Git to work. This is a persistent problem I see all over the tech landscape. We live with crappy solutions because trying to make a better one is way more work than just dealing with the bad one. Progress isn't impossible, it's just tedious.
Still, the UX sugar of undo sounds quite nice. I only worry about introducing something heavyweight into the thing. If I use branchless will it force others to use branchless too?
My company has an equivalent to 'git undo' that basically undoes anything you just did to your local repo.
It's just so freeing. Sometimes you need that Ctrl+Z so you can undo and redo things correctly. It gets your mind back to whatever you were doing before without needing to debug wtf just happened.
> How is it so easy to “lose” your data in a system that’s supposed to never lose your data?
I think the author is mistaken; git isn't "a system that’s supposed to never lose your data". One of its features is the ability to "lose" data in a controlled way; i.e., to rewrite history. If you want a version control that is supposed to never lose data, use Fossil.
It’s extremely difficult to lose anything in Git once it’s been committed. You’d have to do something pathological or intentional.
1. Has three competing ways available to complete the 'undo'
2. Every way has a scary caveat that the user must be aware of
It would be awesome to simplify the process for new users.
The undo described here is much more powerful and much closer to what people expect from the word "undo" from its functionality in other tools.
Or how about after pushing to a remote?
Push: sadly, no.
Not that I would include this in my own shell. To the authors two example uses: 1) Undoing an amended commit. But if you're comfortable amending a commit, can't you just amend your amended commit? 2) Accidentally commiting an incorrectly resolved merge conflict. Revert the change if you want to be non-destructive. Reset HEAD~1 otherwise.
2. Yeah, but I think the UX improvement of a single command to say “Go back to where I was” is reasonable.
I haven't tried it, but have been meaning to look into it. Would be curious to hear experiences of anyone who's tried it, and how it compares to the OP.
`git reflog` `git reset --hard <SHA of commit you want >`
Or if you just want to move back 1 commit
`git reset HEAD@{1}`
When recovering from a bad merge conflict, you have a bunch of commits in your reflog which have the same name, and you need to decide which one you want. It's hard to look at the exact diff data and decide which one is appropriate.
It's not listed in the article, but it also enables a workflow where you check out parent commits many times along your branch of development and start new offshoots, and possibly amend commits with many descendants. It becomes nigh impossible to manage the reflog when there are so many commits with the same message but different content. The reflog itself also doesn't make it easy to recover lost descendant commits, only the given root commit.
Other scenarios: the reflog will not help recover where a deleted branch pointed to, and it won't help update multiple branches that pointed to the different positions in the same rebased stack of commits (the reflog for HEAD, anyways).
As a general rule of thumb, command line tools often ignore decades of UX best practice that have grown up. One of those is that operations (particularly destructive ones) should be easily reversible. It recognizes that human error is common.
And having "undo" specifically recognizes that human error puts the user in an emotional state where restoring things via complex operations is inviting risk. A typo to `git undo` is unlikely to be another command. A typo in the SHA (or an @{2} instead of an @{1}) for `git reset` will compound the error.
... that having been said, I'm going to go add `git undo` as an alias for `git reset HEAD@{1}` right now.
Not that I'd trade git for subversion mind you, in the hands of a trained individual git is a godsend, but the training part can be arduous.
Even among DVCSs I'd argue that mercurial for instance is vastly easier to pick up.
Breaks down in uproarious laughter.
One of the worst offenses git is its use of multiple different jargon terms for the same concept; indeed, it's the only VCS I've used where reading help leaves me less sure than when I started if it does what I want it to do.
If I accidentally leave my system in a weird state (say, I'm in the middle of a git rebase and I forgot to git rebase --continue), and I start doing some regular git commits, the resulting mistakes are, while possible to recover from, difficult to do so unless you know git's internals. I have to use the "what's that git command to list the commits I just dropped on the floor by accident?" with surprising frequency.
You mean Mercurial :)
Here's something that happens when people are encouraged to make more mistakes: they make more mistakes.
The piece where people are encouraged to learn from these mistakes is missing from the author's equation. Without that, this tool becomes a crutch at best, and a disaster waiting to happen at worst.
Can we do better?
*Edit*: Let me answer my own question. We can do better - for example - simply by adding the Git commands this program would run, aside from the human readable explanation. Not sure what people replying to this comment were aiming for, since they clearly did not offer up a single suggestion.
Let me go a bit further and add that self-righteous soapboxes are NOT a good thing, and as it stands none of the replies to this comment were relevant. I was hoping to spark a discussion of what else could be done to improve the experience, but I could have worded my comment better.
On the other hand, you can do better.
Or for an entirely different example, are you also opposed to FPGAs? Should people be forced to not simulate circuits before sending them off to be fabricated, in the hopes that by making the mistake more painful they won't make as many?
Besides its not like people need more examples in their lives of situations where a mistake is not easily recoverable from.
So to answer your question; I certainly can't, I am a mistake based learner. If you can do better then please, please, do.
An analogy in my mind is a "hardcore"(where your in-game character permanently dies if you die once) mode in a game. On the surface, even it's name suggests that a really die hard power gamer is going to want to try this mode, the phrase beckons truly a true epic gamer experience that will test the players skill to the utmost. But, you don't get that. You get people getting level 2 by killing only level 1 critters for 10 hours straight (or equivalent), you get a boring, sterile, slow progressing gameplay that is exactly opposite of what one might imagine going into it.
The underlying assumption here is that undo features make people bad at thinking. I suspect that might be true in a lot of cases, but I think we should give users the benefit of the doubt and start by believing that they're undoing something that they tried to reason about and made a mistake with rather than thinking they're just whacking in random commands hoping to hit the right one. The outcome might be the same but one is a little more kind.
However, a reasonable counter argument to that whole idea is that without an escape hatch giving users a sense of safety they'll never even try to use any features that they perceive as 'dangerous'. Not having the ability to roll back something they've tried means they won't try, and that keeps most git users from becoming experts. An undo feature is an immensely useful learning tool (so long as it always works as expected), which is exactly what you want if you think that people should be encouraged to learn.
Trying to learn from mistakes that can be solved by an undo on local repository are mostly in the realm of "be more careful", which is an impossible requirement to achieve in general.