Can we communally deprecate git checkout?
mirawelner.com
mirawelner.com
`git switch` and `git restore` are better alternatives to the `git checkout` command, as they have a lower cognitive overhead and fewer footguns to avoid. Although these commands are marked in the documentation as experimental, they work extremely well in practice. Experimental in this sense means that these may gain or lose behavior over time, not that these commands are unstable for use. As a community, we should use `switch` and `restore` commands rather than `checkout` when documenting / teaching git so that newer users start with a greater ability to reason about how the commands affect their workflow.
For more about the introduction of these commands and the details of the above see https://github.blog/open-source/git/highlights-from-git-2-23...
Honestly I have not bothered to even learn switch or restore because of these warnings. If they keep them in after five years, then the commands might be removed in the next version, because clearly there's no champion behind them pushing them forward.
And the few times I have tried to learn what they do, I have not seen any benefit over my current knowledge. So why learn something that may go away and is not trusted even five years and many many versions after deployment?
gsw git switch
gswc git switch -c
gswd git switch $(git_develop_branch)
gswm git switch $(git_main_branch)It takes no time to add some, and it's not necessary to put them online and synchronize them; it would be a no-brainer for me to add some manually, if I had to work on a new machine for more than a few minutes (and couldn't access my usual ones from it)
They're shorter and let you be more efficient at common tasks.
They also tend to be personal and hard to share. Which can make it harder to explain what you’re doing to a coworker for pairing/debugging/training purposes. And you have to remember both the aliases and the underlying commands/options, at least enough to remember when you don’t want to use the alias.
I don’t find that I use git subcommands with a frequency that makes me want to alias them. It’s likely that different workflows lead to different optimizations.
ie. If you do "checkout master" it will checkout your working tree to the master branch. If you do "checkout HEAD", you get whatever's the current HEAD of your repository (in practice, this discards your changes).
Checkout can also do a partial checkout on a specific part of your working tree; "checkout [filename]" makes it so that you update that specific file to how it is on whatever you point it at.
The only weirdness is "checkout -b [branch]" making a new branch and switching you to it. It's a bit syntactically off/breaks the previous thing, but makes a lot of sense when you realize that your average workflow will combine git branch and git checkout anyway (since you're pretty much always making a branch to them work on it.)
Git switch and restore both feel like syntactic sugar that doesn't really help enough to warrant the mental switch of going from checkout to switch/restore. (Not to mention that checkout and restore do different things; restore also merged some functionality from git reset, a command whose main purpose is to update the index.)
git checkout <commit> requires a clean working tree, and will change HEAD to the specified <commit>.
git checkout [<commit>] <paths> does not require a clean working tree, will replace the specified paths (i.e. files or directories), and does not change HEAD.
These are extremely different behaviors. I agree with the article that git switch vs. git restore are better replacements.
Since you bring up the topic of git reset, I also disagree with what you said; I don't think its main purpose is to update the index; rather, it is to update the current branch's pointer to an arbitrary commit with the possibility of orphaning children (unlike git commit or git merge, which never lose children). Factually speaking, git reset can update just the branch (--soft), the branch and then index (--mixed), or the branch then index then working tree (--hard). Of course, if you perform git reset and stay at the current HEAD commit, then you have the choice of updating just the index, or the index and the working tree.
other than that, contrary to sibling comments, no the rest of checkout is fine. yes it has guardrails that are perhaps not 100% consistent, but i don't think they every could be. but guardrails or not, when you say `git checkout <versionish>` it trys to checkout the <versionish> version to the working tree. it's highly intuitive that `git checkout <versionish> <filename>` it doesn't do the whole tree, but only that file (excellent for reverting a single file).
i don't want git checkout to be called git switch or any other nonsense. git checkout checks out a version and that is terminology common with just about every cm tool ever.
git reset is a hot mess. the blog post should be about that mess, not checkout.
There is a consistency in checkout and reset.
Both checkout and reset can be whole or partial (terms I made up). Partial checkouts and resets specify paths. Whole specify at most a revision.
The checkout operation updates the indexed and working copies of files to be like what is specified in a given revision.
- A whole checkout to another revision, if it succeeds, will update the working files and index to what is in that revision, and move HEAD to it.
- A partial checkout updates the specified files, working and index, to what they look like in the given revision, without moving HEAD.
- A whole reset to to another revision updates just the index to that revision, and moves the current branch and HEAD to it. Working files stay the same.
- A partial reset updates the index entries for the specified files, to the given revision, without moving HEAD or branch. Working files stay the same.
There are obviously more details, due to various options like reset --hard and so on, plus considerations of what happens with a dirty index. But that's the basic paradigm:
(starting with clean tree)
checkout reset
whole moves: HEAD moves: HEAD and branch
updates: working + index updates: index
result: clean result: working diff, nothing staged
partial moves: nothing moves: nothing
updates: working + index updates: index
result: working diff, staged result: working diff, opposite change staged
The basic consistencies are:- the partial operations don't move the HEAD or current branch in both cases; the whole operations do move HEAD or HEAD + branch.
- whole or partial checkout updates working + index.
- whole or partial reset updates index.
I don't think I have used the incomprehensible `checkout` command once since I learned that `switch` and `restore` exist, and the previous years-long state of perpetual low-grade irritation I had felt toward git has diminished accordingly.
In practice you will still need 'git reset' but only with a branch parameter (all 'git reset -- paths' can be replaced with 'git restore'), usually with --keep, --soft, or --mixed.
So switch is just a dumber checkout that has to be told when to detach or not instead of inferring it from the kind of revision.
I don't feel like typing 'git s -d <branch>' has slowed me down in practice but you're technically right of course (or I would add a 'd="switch -d'"' alias).
I mean that’s obvious and true of most software (https://xkcd.com/1168/ though this one is not actually hard).
But think about this case. Would it make sense for git switch to have a delete option? Of course not. But you can't be sure, because, git. The feeling is that they would merge something like that.
This is, and always has been, the primary UX problem with git. You use commands to manipulate the program’s model of the data, rather than to demonstrate your intent. It works only as long as your mental model of git matches git itself, but it doesn’t easily afford abstraction, and abstraction is the essence of efficient manipulation.
Put another way, a good tool allows you to use it well even if you don’t understand how it works. Git OTOH will quickly punish you for not understanding its model of the world.
A good UX for git would maintain the complexity for experts and provide a clear and simple surface area for most people who just want to perform familiar tasks. The fact that each person and team seems to have a unique incantation just for everyday tasks is an awful smell, and a sign of git’s immaturity.
Much later in my career I learnt that people were using git without even knowing anything about what a Merkle tree is and I must say I was extremely surprised. I'm not saying its good or bad, just describing my younger days.
> they had not asked me to explain git, they had asked me to explain GitHub
This is often the case for me as well.
Hey, at least git owns it by calling the shell commands "porcelain" (with git itself being the plumbing -- https://stackoverflow.com/questions/6976473/what-does-the-te... ).
The core value proposition of GitHub, GitLab, etc is to provide a nice GUI atop git. That's huge. I think that user-studies to improve one of the many existing foss git GUIs would be much better use of brain than deprecating git checkout.
Copy my changed files to a temporary directory.
Delete my working directory.
Checkout the latest version from the repo.
Copy my changed files back.
What you're doing sounds like git reset/git switch would be fine. I think I'm in favor of what the author suggests, conflating switch and reset/restore seems bad. It's going to take some effort to fix my habits though.
git stash -- filename git stash push <filename>I'll grant it for an article for wide readership but as a general tool of technical discourse used in places like github issues etc., highly inappropriate.
I think if the "only operate on certain files" mode of checkout was a flag, it would be much less confusing and easier to explain and we never would have gotten into this mess to begin with.
Then again, until today I never knew about "git switch" so I, too, learned something today :)
git checkout, git checkout -b
git pull
git merge (mostly to merge master back into a development branch, or something like that)
git push
git restore (much more rarely)
git reset (even much more rarely)
Barely ever have I had to do anything but these for 99.999% of my workflow, and none of them seem particularly complicated unless you run into merge conflicts, but on every team there seems to be at least one pro "merge conflict" guy. Idk. Checkout is occasionally annoying when it forces you to stash unrelated changes to what you're doing, that's about all I can come up with.
I am fine rewriting my own history on local because I know what I did, but not when its on GitHub because other people don't neccecarily know what I did.
https://docs.kernel.org/maintainer/rebasing-and-merging.html
Rebase = Takes all the commits from one branch and sticks them on top of another branch, like stacking them in front. It rewrites the commit history to make everything look like one straight line.
I'm a rebase stan, but that's just my opinion.
Of course, having a merge based workflow doesn't guarantee that people only commit source that runs, so bisect might be broken anyway, and you get a messy history on top of that, unlike the beautiful clean line of a rebase based workflow, that makes it look like the system was developed by people of superhuman mental clarity.
There are situations that make one or the other clearly preferred within a repo, besides per-repo/org-consistency for more widely shared work. After a few years excavating in a merge-only-salt-mine as well as working orgs where merge commits are banned you get a feeling for what's helpful when (esp considering auditability and history legibility).
Always rebase incoming feature branch on the base branch before merging into a shared repo regardless of approach, though - "merged from main" inside a merge commit on main makes anything an inscrutable tangly mess very quickly.
`git rebase` is for academics and startups where no one gives a fuck about anything and no one bats an eye if you waste a day or two on a rebase gone wrong.
It's easy to undo a merge and a bit nasty if you already pushed the merge commit. But undoing a rebase? Oh god you're in for a lot of pain.
It's actually pretty easy. You either make a checkpoint tag before you start the rebase, and reset to it if you notice something went wrong; or, if you forgot to make the checkpoint, check the reflog and find the commit you started from, and reset to that.
Reflog has a flag which adds timestamps to every entry, which I find is helpful for this:
git reflog --date=isoMy good faith addition here: a major influence in this debate seems to me to be how a team views their repo. Merge heavy stuff makes great sense for a repo whose job is just to hold everything; essentially a big and powerful shared clipboard that has all the myriad work-in-progress. Almost like a "dumb" tool (and trust me when I say dumb with lots of love and respect - it isn't criticism it is about which direction coupling points). On the other hand, the rebase heavy approach makes more sense if you view the repo as a contextual knowledge store, holding an almost idealized version of the point in time, uncluttered by the reality of the path actually taken. To broadly generalize my experience, I've found the merge version is generally faster, has low overhead in the moment, and scales pretty well since it is nearly the lowest common denominator approach. Conversely, I've found the rebase version makes debugging and context switching faster and enables a leaner team to cover more ground because the repo has some context and documentation built in via its organization.
Of course, doing either approach poorly will have negative consequences and being inconsistent in either approach is worse than consistently doing one or the other.
I'm not trying to make an argument for which to choose but I am pushing back hard on rebase only being used by teams who are unprofessional and don't care about wasting their time.
Anecdotally, a lot of my coworkers who use merge are conceptually trying to stack their commits on top of previous ones. My lukewarm take: most of the time, rebase is actually the intended operation and merge is used as a hammer.
Its not really part of the job to deal with 'insane merge issues', or crazy git tricks and things, only once in a while...
Also, rebasing is so confusing at least in the cmdline sense, I don't see the point.
In any case, I think checkout does in fact conflate things that should be (at least) two separate commands. That's not "less complex" it's about having more predictable behavior. And arguably it's more complex than the status quo.
There's really nothing simple, quick or logical about Git when you need to do things like:
copy some files from another repo into yours while preserving file history
copying files within a repo while preserving file history (if you just copy+paste you lose, if you edit the files after moving with git mv, but before comitting the move first, you lose all history)
deleting 1 file from all commits in a repo (almost impossible without using an external non-git tool)
dealing with a rebase that was pushed that shouldn't have happened
I could go on and on with examples of things that should be trivial but aren't.
Yes, I currently manage my team's merge process and review all code that gets merged into our main repository. I tend to only do trunk-based repositories, work mostly in infrastructure, so I imagine my process is a little different than some traditional "dev" ones, but it's largely uncomplicated on my 5 person team.
> copy some files from another repo into yours while preserving file history
I'm not really sure what this means - I don't often have to do this, can't imagine why I would have to do this. If by "file history" you mean the commits on the file from the other repo? I can't imagine why I'd do that and it sounds like an anti pattern to me, and I avoid doing things like that at all costs, or would just create a git submodule if I really, really had to have that history and file in my repo for whatever reason.
> copying files within a repo while preserving file history (if you just copy+paste you lose, if you edit the files after moving with git mv, but before comitting the move first, you lose all history)
Really never have felt a need for doing this in any repository I've worked in so I guess no, for similar reasons as my above comment
> deleting 1 file from all commits in a repo (almost impossible without using an external non-git tool)
Why would you have to do this with any kind of frequency? If I did, and it was sufficiently complicated, I would put it in automation and write it once and call it a day.
> dealing with a rebase that was pushed that shouldn't have happened
I'm seeing now from other comments what the difference here is, I don't really ever use rebase and never have felt the need to. If I ever worked for a team where I didn't get to decide that, we had tooling that managed that part or wrote it.
> I could go on and on with examples of things that should be trivial but aren't.
I am a little curious, because some of what you wrote sounds a little bizarre to me, but I guess I'm probably in a much different domain.
This is much closer a description to svn (and probably other systems), and doesn't describe git at all. Git doesn't store file renames. It reconstructs them from the history based on how similar an "add" and a "delete" were in the same commit. There's even args to change the sensitivity and look for line sources in even broader scopes* - which means git can do one thing others can't, show you where lines came from when two or more files were merged. Others can only show you one of the source files, the one that was explicitly renamed.
* See "-C" in "git help blame", the broadest scope even looks for the source in other commits.
If it did work like that, there would be no reason whatsoever for the "git mv" command to exist.
That seems like the exact opposite of the purpose of version control.
And I'd bet you have little familiarity with its documentation, which in itself is one of the worst I've ever ran into.
The fandom around it, furthermore, with most saying it's awesome to not admit that they don't know it, makes things much worse
Sarted using it at 14, still only use a handful of commands, don't think it's complex
git rebaseOften people arrive on a team that has committed a grave sin in the past and all future team members are forced to use workflows involving submodules as penance.
maybe should fork a git lite that has only the commands you listed.
Now that I have read this article half way, I decided to stop. Because I am learning a few extra things that’s kind of messing up my mental model of what that command does. I think a fair % of git users might be in my boat. Our knowledge, hence our burden is limited.
So community might not be as confused with checkout as the author or those who grok git.
# Paste this snippet into your .bashrc to help you learn `git switch`.
#
# Turn on the extdebug mode so that our `scold_git_checkout` can prevent
# us from running git-checkout by exiting 1; forcing us to re-learn our
# muscle memory.
# If we *really* need to use git checkout for something, we can do that
# by using the full path to the git executable e.g.:
# /usr/bin/git checkout # isn't caught by our scolding
shopt -s extdebug
scold_git_checkout() {
if [ "$1" != "git" ]; then
return 0
fi
if [ "$2" != "checkout" ]; then
return 0
fi
cat >&2 <<EOF
////// DON'T use git checkout! //////
The 'git checkout' command of git has been replaced with two other
commands: 'git switch' and 'git restore'. You used:
$@
EOF
if [ "$3" == "-b" ]; then
PASTESAFE="$(printf "%q " "${@:4}")"
cat >&2 <<EOF
You should use the following:
git switch -c $PASTESAFE
EOF
elif [ "$3" == "--" ]; then
PASTESAFE="$(printf "%q " "${@:4}")"
cat >&2 <<EOF
You should use the following:
git restore $PASTESAFE
EOF
else
PASTESAFE="$(printf "%q " "${@:3}")"
cat >&2 <<EOF
You can try the following:
git switch $PASTESAFE
EOF
fi
return 1
}
trap 'scold_git_checkout $BASH_COMMAND' DEBUG
Gist with this special scold commit hook, should I ever change it more: https://gist.github.com/lelandbatey/b42b527bfa41b761d0aec287...I appreciated finding out about `switch` and `restore` as alternatives to `checkout`. But they are minor, incremental improvements. Framing it like `checkout` is the devil doesn't help, because it really isn't that bad, especially to people who consider git as a means to an end.
60 seconds of time investment to be better at mentoring the next generation of technologists is worth it.
Teach RESET instead of checkout???
The command with the most likelihood to make you lose work, and leave you with permanent errors??
And with the most convoluted usage and documentation?
Simple, concise breakdown
> the only way to really understand deeply how git checkout works, you have to fundamentally understand the internal workings of git.
Thing is, there's no way around fundamentally understanding the internal workings if you're going to use git on the command line.
Checkout and reset can be tough, but for instance reflog also makes no sense if you don't understand the commit model.
Newer user and occasional git users will be better served with a nice GUI. VSCode has extensions that will help deal with the basic operations etc.
The issue to me has never been that commands are cryptic, and more that git isn't made for people enjoying simplicity. For those, an extra layer above git is the best choice IMHO.
The times I had to assist people shooting themselves with whatever favorite GUI they use... man I was happy I'm firmly in the Git CLI camp.
I'd imagine something very close to what SVN or CVS offered, and mainly targeted at users who won't be professionally working with a full team on a shared code base.
I guess that's what 15 years of (near) daily git usage does to you.
Specifically I'd suggest:
* Change "margin" on the <p> tags to "1.5rem 0"
* Change "margin" on .blog-page to "1.5rem auto" to keep the grey background centered based on width
* Add "max-width: 800px" (or whatever) to .blog-page
Edit: I had assumed the OP was one of the many people I have met who have a Tech Lead title but are not really very technical due to the grandiose request to deprecate checkout. But looking into the blog more I realize that instead the author is more new in their career and I don't mean to be as harsh to someone in that position. Instead to them I would say; when you encoutner something established you don't understand, take it as an opportunity to learn. Git isn't beyond you, it won't take as long to fully understand as you think.
(Also, tools for software engineers still benefit from clarity and focus of purpose for simple use cases.)
In the biotech startup I worked for, the biologists and the chemists absolutely had to deal with the code that we wrote, that was the only way we could get things done! I think maybe in very large organizations with people to spare, and large departments, and official policies, you can have rules like that. But at smaller places, you have to get scrappy and have everybody work with everything. That involves non-engineers using git!
I do too and I believe the problem with git is that it's somewhat usable without grasping even these basic concepts - until the day it isn't, as something goes off the beaten path and you just end up getting lost. It also doesn't really nudge you towards understanding as you keep using it; to the contrary - it happily lets you build some suboptimal mental models that are doomed to stop working at some point.
The only way to learn git well right ahead is to sit down and read. It isn't much to learn, but most people just skip this step and go straight to CLI. I can't blame them, I did the same many years ago, so it took me much longer to actually know what I'm doing than it should as well. But the thing is: I didn't know better back then, nobody told me that. Now you (the reader) know, so you have no excuse :)
If your hubris is this high, I am seriously concerned about your seniority.
It's not that the supposed concepts that are hard, it's that it has one of the messiest interfaces and set of documentation ever devised for a software.
And there are hardly any settled concepts, the terms it used came to be as it was developed, and can have different definitions in different parts of the documentation.
If you consider it simple I'm very seriously concerned about your software engineering output
A lot of the problems people have with git are actually problems with bad workflows. You can argue that git should support arbitrary workflows but that's like arguing a garbage truck should support offroading.
Software engineers benefit from good tools as well, and having to spend months to get to know a vcs well enough doesn't benefit anyone
Would would be the one good git workflow, in your opinion?