It dominated the world because GitHub was better than BitBucket and had more generous free levels.
If only that wasn’t the case we’d all be using a sensible source control system now instead of one where people repeatedly say things like “if you just learn the underlying model…”
BitBucket limited the number of repos you could have while GitHub allowed you to have as many as you wanted so long as they were public. Which had the (intended?) effect of making it more useful socially.
I for one like how git works. Some of the command names are weird, but I know more sane names are being introduced (e.g. git restore, git switch).
Yes, you need to follow a tutorial to use it effectively. Designing for beginning users is often detrementantal to advanced users, and I object to that being called "user friendly".
I'm not very vocal about git usually because I'm not an expert I have no big complaints, and I'm not that interested in arguing about it. I think there's a lot of us that are perfectly happy with git; we just make less noise.
I'd argue the opposite - that the model was so good and so much better than anything else that it was adopted despite the UI/UX flaws. Things can be good, have a flaw and still be considered good. That doesn't mean that we pretend the flaw doesn't exist.
Mercurial is built on what is almost the same model and has what I think is a better UI/UX. Some people say that git is technically superior but you really need to be an advanced user for it to matter, or work on a really large project.
At work, we switched from SVN to Mercurial, and from Mercurial to Git. Most people were happy to switch away from SVN, but few enjoyed the change from Mercurial to Git. Personally, it took me much longer to get used to Git than with Mercurial, even though Mercurial was my first DVCS. I now have a slight preference for git, but I am happy with either (just don't bring back SVN!).
Why Git came to dominate and not Mercurial? I am not sure, but I don't think technical reasons explain everything. Its association with Linux probably helped a lot.
I was also a Mercurial fan who had to reluctantly accept the rise of Git. Git was always a bit quicker, but Mercurial was much more predictable until you really understood the ins and outs of Git.
> Why Git came to dominate and not Mercurial?
The answer is actually really simple and non-technical: Github. We take these feature rich online code hosting platforms for granted now, but Github was really the leading edge of the wave. It made it easy for people to work together on writing software so it had great takeup and started a Git snowball effect.
It has been described as distributed SVN. I really hated SVN, almost to to point of wanting to go back to CVS, so I didn't want anything that took any inspiration from it. But maybe, in reality, it is not that bad.
The funny thing about the "svn vs git" debate (if one could call it that), is that people always tend to focus on the whole "svn is centralised whereas git is not" bit as the main argument.
Yet, whenever I've worked on git with others, it's been on github (i.e. a centralised model). And I've worked in a decentralised way on svn on several occasions, simply by making my own local repositories and merging changes back to the parent repository when I'm done (which is effectively what git does too when working with remote repositories).
I feel that a lot of what ends up being 'bad' about svn really boils down to the fact that you need some good conventions to be honoured across the project in order to get things done (including using it as part of a decentralised workflow), but humans being humans take shortcuts and mess things up for everyone else. Which really means at the end of the day, the problem isn't the technology per se, but human relationships, manifesting as commit behaviours. Whereas git just imposes its highly opinionated model of doing things on you in order to ward off some of the more destructive human behaviours, which in a sense is good, but at the same time, it means that git can be too rigid, and svn effectively gets a bad rep for being potentially more flexible and scriptable. I've been in many situations where I had to get into an incredibly convoluted manual process to work around git's mental model to get it to work for me, when the equivalent in svn (for better or worse) would have been fairly straightforward.
(Disclaimer: This is just my personal experience from happily using both with no specific preference for one mental model over the other. If anything, I think I may probably prefer svn a bit more now. I'm probably completely wrong about all of it.)
They are bad. Some concepts like rebase etc. I still don't understand.
In software we often go for further complexity instead of less. I think because most of us who are the lead developers are often the most intelligent. And we often enjoy these complicated abstract models and they come easy to us. However in satisfying our own intellectual vanity we often don't see how many we leave behind. Which is good for our hourly rates, but less good for creating affordable and simple software.
Anyway one of the main practical advantages git had was decentralized repos meaning you were not dependent on an external server which meant git was often much faster if working with in daily tasks compared to centralized versioning systems
I'm sorry if this sounds arrogant, but if you don't understand basic Git concepts like rebase, I don't think you're in a position to comment on the architecture of Git
Yes, but again, the number of people collaborating on Linux kernel development is truly tiny.
I think perhaps you're over-estimating the network effect for VCS's -- having multiple version control binaries on your machine is low cost, and aliases can, up to a point, give you a consistent interface to them all.
The highly sophisticated demographic of kernel developers would not have been at the pub insisting that their friends drop svn and use git (exclusively!), anymore than they would have been trying to get any other projects they worked on to adopt bitkeeper previously.
The fact that bitkeeper was required for 'collaboration in Linux development' for so long supports this position.
Git's UI/UX is one of the worst engineering sins to be committed in the last two decades, and this website shows why. Literally nothing about git is intuitive and the "underlying model" is entirely ad-hoc. Instead of celebrating how Linus built git in only a few days he should be castigated for knowingly setting up ill-conceived software to go viral.
> for knowingly setting up ill-conceived software to go viral
I doubt that was the intention. Linux just needed a versioning system tailored to its needs, and that's exactly what Git is. Can't blame its creators that other people used it for scenarios it wasn't built for.
Right, you are the cop-out I am complaining about. Linus is the premier figurehead of the premier open-source project. When a build tool he made for that project goes viral it isn't an accident. If it was anybody else's pet versioning control system would we even be talking about it?
If it went viral, yes. I agree most of the time I feel like the world is run by evil folks twiddling their fingers in secret.
This is not one of those times. I agree there's a chance there are better word choices or feedback for some commands, but overall once you 'learn the language' it really is a lean, mean, well designed piece of software.
Folks that complain about the UI/UX don't realize it wasn't designed for less technical folks. It was designed for the folks who needed it.
It's success must at least partially prove that the UI/UX is not 1/10. Any real engineer will tell you, there are times where they wish they could do something better but the requirements and constraints left them making tough decisions, and that doesn't mean they aren't proud of their work.
It's success is also partially due to the fact that it is lean and mean, which allows it to be applicable to nearly all software projects of any flavor. So I don't understand why folks argue it could have been done better. If it was 'better', in my view it wouldn't have been successful. The success was driven by it's succinct design and Linus' take it or leave it attitude.
Technically accurate studio monitors don't sound as pleasing to the ear as good well tuned speakers. But they are exceptional at the job they were designed for. This is like that.
There is no cop-out. I use git, with pleasure and gratitude, for everything, because of its excellent design and performance. It appeals to my intuition, and I like the interface. You are entitled to your opinion, but not to confuse it with objective reality.
What you're saying is that anything Linus does, for whatever purpose, should be designed to cover a wide variety of use cases and please a large number of people, even if he's just writing something for himself. I think that's an unrealistic expectation.
You shouldn’t present your own opinions as facts. I find Git intuitive, and it works as I expect it to do. I suspect a lot of people agree since Git became the champion of the DVCS crusades.
It’s the first thing in the article. Git reflog. (BTW you won’t get any disagreement from me about the lack of intuitiveness of the command name. But, in a way, git’s whole reason for existing is to undo, and almost everything you do in git can be undone by design.)
The reflog is very helpful but I don't think it counts as an "undo". Some operations (like git add or git push) won't show up in the reflog.
Even the operations which do show will often require more thought to undo than a hypothetical "git undo" would. I know how to use the reflog but I often go out of my way to avoid it because "git branch tmp HEAD; git $POSSIBLE_MISTAKE; git reset ---hard tmp; git branch -D tmp" requires less effort than deciphering the reflog's output.
Git reflog absolutely counts as the first step of undo for several workflows, but you’re right there are other commands needed for some kinds of undo.
Undoing a push does require a different set of commands, but my point, to the question @amelius asked, is that you can undo both push and add, and whatever other mistake you’re thinking of, difficult or not.
Ah yes, the classic `rm --undo` saved me so many times. No, command-line interfaces rarely offer undo. The onus is on the user to not do irreversible things when they may need to be reversed.
Git, coincidentally, does have something equivalent to undo history: the reflog.
I think the underlying model is fantastic, but the names for operations, and how they are grouped, are somewhat ad hoc.
The idea of a directed graph of file system snapshots is pretty intuitive. Add in branches as pointers to locations in that graph. This is a fantastic model for source control.
However the operations that stage a potential update to the graph of snapshots is prettt confusing. The "index" is a terrible name that is overloaded with so many other non-git meanings, non of which really map to git usage.
That, and all the rest of the names are pretty hard to understand. Particularly reset, whose documentation is inscrutable without translation from git-speak into technical language, or at least a dictionary of what all those words that are used actually mean. And since reset is such a useful tool and has about eleventy different functions, it all becomes impossible to learn from the docs on your own.
The well-documented underlying model used to confuse me a lot - especially for operations like merging, rebasing (squash, reorder, ...), cherry picking, etc. They started to make sense only when I realized that git uses diffs/patches to propagate changes between unrelated commits. While the on-disk format is purely snapshot-based, the tool itself is a hybrid. As far as I can tell, this trips up a lot of others too.
Agreed, the first step is realizing that the data structure is a graph of snap shots. And then a lot of the primary operations are about calculating deltas between arbitrary commits, and the applying those deltas elsewhere.
Mercurial is still actively developed, and isn't really "dead", although it did lose a lot of popularity and "mindshare".
Projects like Firefox and nginx use it, although others like Vim and Python switched to GitHub. I don't know if Facebook is still using Mercurial internally, but for their public stuff they use GitHub.
I think that if GitHub had supported Mercurial like BitBucket or Google Code it would still be a lot more popular, but ah well...
But some of types of things have in fact changed, sometimes for seemingly trivial reasons.
For example, the master/main branch shift. Everything broke when that change was made, but it happened and it wasn't a big deal.
I'm not seeing the difficulty here. It seems that a more reasonable interpretation is that git has the type of interface that is hard to learn, but intuitive once learned.
Please correct me if I'm wrong, but Linus probably didn't plan for git to be adopted by other people than just kernel developers. Because that was what git was created for originally.
What I don't get is that the UX problem is not hard. I still like the underlying commit tree model a lot.. it's just some commands that are not clear enough (reset has 5 meanings and can backfire quickly). Maybe it's just retrocompatibility inertia.
I recommend you create aliases. I've got a 'git-squash' 'git-back' and 'git-update' aliases created, I almost forget they are not part of core git. Combined with scm-breeze that allows files and branches to be referenced with numbers... it's really fast and powerful
Or you can spend some time once to understand how git works, and never run into a similar situation again. git is built on a beautiful and very simple model, and it's easy to unbreak the repository 99% of time if you understand it (and I'm saying this as a very mediocre programmer).
I've gotten out of sync with the remote where that would still fail and decided to just reclone. I think the reason it happened is git-history purists rewriting and pushing.
That just resets to your last local commit. To sync back up to the remote, you need at least do a git reset origin/HEAD --hard (and capitalization matters, I don't know why).
But that doesn't work if you rewrote history in some way (I'm not sure why that's even possible). In that case your local git can get pretty messed up, some gui tools (IntelliJ) start to bug out and fail to diff properly, and it's easier to just start over.
After ten years of using git, I've more confused by it than ever. People here keep talking about mental models, but I've read a shit ton of docs on it and am still totally confused. Probably some of us just aren't smart enough lol.
Not sure what origin/HEAD is supposed to point to. HEAD is a ref that points to the tip of the current branch. It's not going to be available for remotes (it just doesn't make sense).
To reset the current branch, you need to do two things:
$ git fetch
to fetch commits from the default remote and point remote branches accordingly, and
$ git reset --hard origin/master
to reset HEAD to the remote branch. Substitute master for any other remote branch if you wish.
That's it. I don't know if this is more difficult than re-cloning from scratch (especially if you're doing frontend and then have to reinstall node_modules and such…)
I don't know what the difference is, honestly :( I believe you, but I see both online and nobody seems to know the difference and it seems to work. Shrug? Most of my git life is copy pasting somebody else's commands because the model of it is really too complex for me. I've given up on caring... it's easier and faster to rewrite some code than deal with git's commands.
Same thing for node modules, lol. Delete the folder and try again. If that still doesn't work, delete the lock file and try again.
It's a terrible practice, I know, but it's the only thing that actually seems to work in my experience.
EDIT: Apparently origin/master points to a specific branch. origin/HEAD points to the top of the "default" remote branch, which is often, but not always, master. Or something like that. I don't know for sure.
Two things to make sense of how origin/HEAD is working.
First is that HEAD always points to the commit that you currently have checked out. So, for example, if you have the master branch checked out, HEAD will be whatever is the latest commit in master in your local copy of the repository.
Second is that to git there is no difference between the originating repository and your own. If you had your copy of the repo fully exposed, the remote machine that you cloned it from could push changes to your copy exactly the same way that you push changes to the remote machine.
That second part is important because it means that the remote machine you're pulling from also has its own HEAD pointer. When you reset your state to origin/HEAD, you're telling git to set your own HEAD to point to the same thing as the remote machine's HEAD. This is very likely to be the same asking it to reset to origin/master because HEAD is probably pointing to master on the remote machine.
The reason it's likely that HEAD=master is that in most circumstances the repo on the remote machine isn't manually being touched. When you first setup a repo there is only a single branch so that is what HEAD points to. If no one ever logs into the machine and executes a checkout command, that's what the server is going to continue to point to.
However since there's no guarantee that HEAD=master, you shouldn't rely on that and instead always use the actual branch name.
Thank you, that clears it up! And probably explains why I've lost work when resetting other branches; I wanted to revert to the latest remote tip of their branch, not reset that branch to master, but I screwed up because of HEAD. Thank you for clarifying!
It's not going to help when one fucks up a complicated merge/rebase and can lose a ton of work if something goes wrong. Understanding what you're doing goes a long way.
I've seen this a couple of times at $DAYJOB when someone doesn't understand how rebase works, smashes keys until it looks like they've achieved their goal, and then I have to ssh into their box and try to unravel the damage they've done, hoping that reflog has not been GC'd yet and there's something to revert to.
A 1000x this. I'm unfortunately that guy. I've learned never to use rebase and to copy and paste important code to a separate text editor (or use my IDE's separate undo cache) before trying a complex merge, and then just trying all over again if I fuck up.
That's easier than trying to get git working right, lol. It feels like every git command is a PhD rabbit hole. All I know is that if I screw something in git, trying to fix it will just screw it up worse. Reset early and often and it usually works in the end. Lol, it's terrible...
Even though I understand git really well it’s quicker to just blow it away and start again rather than accidentally make it worse realising you didn’t understand it as well as you thought you did.
I generally don't like putting linters on commit hooks. Commits are easy to change and I think people would be better off making more of them. If your lint ends up taking a long time it will discourage frequent commits.
Also, unless you jump through a lot of hoops, linters generally run against what's on-disk instead of what's actually going into the commit. So checks that run at commit time discourage partial adds.
I dislike that approach as it slows down committing. Preferably linter should be integrated in the editor and there should be one in the CI pipeline, but the steps between writing code and pushing it into CI should be as quick and smooth as possible.
I liked how the Japanese translation of the page retains the two versions, but the language difference made the variants pretty much pointless, because there are hardly any "profane" words in Japanese that are offensive enough to warrant censoring by their utterance alone. (There do exist a group of offensive-enough words that relate to class/race discrimination, but no equivalent of a censor-worthy 'shit' nor 'fuck'.) The 2 Japanese translations are merely written as one mildly colloquial version vs another very-slightly-less-colloquial version.
My mental model of a git repo is basically a bramble bush with labels on it.
The bramble stems and branches are the commit history (which may join and well as split, unlike a bramble). The labels are stuck to particular bits of the bramble. Some labels are even stuck to other labels!
You can move the labels around, and you can glue extra bits of bramble to the tips of what you have. You can even hack about with the bramble, but this is not recommended. Label moving often happens automatically, e.g. when 'growing' a bramble tip.
It gets interesting when you compare two brambles (e.g. remote and local repos). You might determine that one bramble is identical to another, just with the labels moved. Or one bramble is the same as the other but with extra stems added. Or both brambles had a common ancestor bramble, but now they both have extra stems. Or perhaps they are completely irreconcilable.
Learning the underlying model behind git is well worth the effort.
For me watching Steve Smith's talk - Knowledge is Power: Getting out of Trouble by Understanding Git - was a lightbulb moment.
I think this is what can confuse people. We have to face the facts that not everyone will need or be able to grok all of what goes on in git and what makes the car go forward. We can all drive that car still!
Take the recursive merging stuff around minute 39. Do I need to know why git's model for merging is so much better and how it works its magic? I don't think so. It's an implementation detail.
Do I need to know how a gearbox works to drive stick shift?
I have never thought of git as a bramble (tree worked fine for me :)) but the thing I always tell people about too is the labels part. What I think is enough for people to realize is that it's just like say SVN or CVS or any other source control mechanism in that there's a tree (bramble) of commits and then every commit can just be pointed to by a label. I can move those labels around any which way I want. Everything else follows from that on a surface level that is the only thing required to work with git in most situations, including some advanced ones.
You don't need to know why certain operations in git are faster, better, have less conflicts or how they work internally. You just need to know what they do and when to apply them.
I don't need to know why I can't make my car start or switch gears without pushing down the clutch. I just need to know when to push the clutch, i.e. if I want to start the car, push it. When I want to switch gears push it (well, mostly, on cars you want to last a while still lol). Of course some people won't even be able to learn how to drive stick shift and can only ever drive automatic.
To a large extent, yes. But when you find yourself stopped at a red light on a steep upgrade, and some idiot behind you decides to wait for the light three inches from your back bumper, things will go better when the light turns green if you have a decent mental model of the physical mechanism of the clutch. Sometimes you want to let those abstractions leak a bit.
Actually you don't need a mental model of the clutch at all for that. You just need to know how to handle steep inclines. There's a technique for that. All you need to know for that is the fact that on a steep incline you will go back a bit until your clutch has a chance to get you moving forward against the hill and that in this case you need to apply that technique. Like knowing when to use rebase, when to use cherry-pick etc. Now why you will roll back on the steep hill is not something a normal operator of a car needs to understand. Neither the clutch part nor the gravity one actually.
Also, if he's literally 3 inches, I would say the best approach is to slowly ease off the brakes until you actually touch the other car but do it so slowly that it's not an impact. All without even pushing the clutch. Then you don't even need to use the brake while pushing the gas pedal trick, because the guy behind you is your brake pedal ;)
For a group of people supposedly primarily in the SV area this is a scary comment thread.
I learned to drive stick in the Bay Area and the way everyone I know there drives stick on an incline is to use the parking brake with a second hand when letting off the brake and letting out the clutch.
Now, when you get good you can stop doing this for most, but for really steep streets (I now live in San Diego and we have a couple; Laurel being one) it’s still an excellent skill to have.
You should go drive a bit in (some parts of) Spain! Not sure if something like this exists where you are.
Imagine: Small towns, really narrow one way streets with foot traffic and underground parking. Getting out of some of those underground car parks is scary stuff!
You get onto a steep incline to get out of the car park but you come around a corner onto that. Cars might be coming down towards you at this point and they're sometimes hard to see. It's cramped too. So you can't just take it w/ speed to get up there. Also on the top you have pedestrians on your side, so you might need to stop on the incline, then when pedestrians have scurried away, go a little further but not directly onto the street until you can actually see if cars are coming. Half your car is still on the incline at that point.
I tested a Subaru manual at the dealer a few years back. The exit from the dealership was uphill onto a busy road. So, I stopped and reached for the handbrake.
There was no handbrake.
I managed to work my way back down to the dealership and asked, "Where's the handbrake?"
It turned out that Subaru had decided to replace it with a button and an "automatic" "hill-holder" feature.
> Do I need to know how a gearbox works to drive stick shift?
No, but it helps. If I treat my car's powertrain as a black box then it won't be intuitive that:
* I shouldn't slip the clutch to hold my car on an incline.
* Blipping the throttle gives me smoother downshifts.
* Double clutching lets me shift from 2nd to 1st while rolling.
* I can use the engine to slow my descent on longer hills and avoid brake fade
* I should park the car in gear for safety
* etc.
Whether it's better for a given person to memorise that list of facts or to understand the concepts behind them would depend on how much they drive. As a developer I've found it helpful to gain an understanding of the tools I use daily beyond "here's the commands to copy/paste when you want to do xyz".
It may not have sounded like it but I'm actually going to agree with some of this. We might be on a different part of the gradient so to speak. Maybe you can find a good analogy but at least from my point of view the gearbox analogy is falling short now and we'd need a different one or look at git commands one by one :)
Like you say, you can just remember those things. In fact 4 out of those 6 were taught in driving school and you had to remember them. One I only learned because trucks still needed that (double clutching while shifting - not just your case, just in general) but cars didn't when I learned. I personally don't like copy and pasting commands like that but I see a lot of people doing that even for stuff that should be second nature because you need it all the time. I think - to stay in the analogy - for me this is the difference between knowing that I should engine break and how to do it when I want to slow down vs. having a piece of paper in the glove compartment that I pull out and check for what to do and how every time I approach a red light ;)
I'm also someone that likes to get an understanding of the things that I use and do every day. The thing is that there are so many things we use and do all the time that I think (almost) everyone just has to keep a certain level of abstraction away from many things, because there are just so many rabbit holes out there and it's not beneficial for most people to have explored every single rabbit hole all the way to the end (the 'gradient'). Git commit graphs are a DAG and lots of cool things can be done with DAGs, most of which I totally forgot about since I learned about and enjoyed them in university and have never needed them again at that level. It's good to know they exist and be able to dig in when needed/wanted.
You're absolutely right, there are about a million and one rabbit holes you can go down if you _really_ want to understand the tools you use but in the vast majority of those cases you're better off relying on a higher level abstraction.
The interesting part to me of your analogy is that it can demonstrate how having an understanding of what's going on under your layer of abstraction allows you to generalise.
To wring the last bit of life out of the gearbox analogy: if you tell a mechanically inclined driver that blipping the throttle will make their downshifts smoother, they'd hopefully understand how rev matching can be generalised to upshifts too. If you tell a "black box" driver then they probably wouldn't be able to do the same. Of course, the analogy falls apart a bit because understanding rev-matched upshifts isn't particularly useful :)
I've forgotten most of the "advanced" git knowledge I ever learned but I get a lot of value out of the (admittedly not very advanced) understanding that, as you said, commits are a DAG and that branches are just named pointers to nodes on the DAG. That understanding lets me generalise to, for example, backing up a branch (with git branch/tag) before doing a tricky rebase so I can restore it (with git reset --hard) if I need to undo.
I agree that exploring every rabbit hole to the end isn't beneficial - understanding the DAG is typically the only "advanced" git knowledge I need and I've only very rarely had to peel back more layers. I think this:
> It's good to know they exist and be able to dig in when needed/wanted.
is a good way of putting it. My ideal low-level understanding of most tools is knowing just enough that I know what to search for if I ever need to go deeper.
> That understanding lets me generalise to, for example, backing up a branch (with git branch/tag) before doing a tricky rebase so I can restore it (with git reset --hard) if I need to undo.
You can also do `git reflog` on branches to see what they used to point to :)
thanks, I'll be watching this. seems like I get myself in trouble every couple of months and have to zip up a directory, do a fresh checkout and overlay it. Seems sometimes something gets lost and it will refuse to push to origin on a branch that I've had checked out for a while. yet if I do a fresh checkout (and losing a bit of history as I like to commit locally a lot and squash just before I'm ready to push to git repo.
Conceptualizing a commit graph with branch/tag labels is the easy part. What gets messy is operations regarding remote vs. local (as you mention) and regarding the various local state one may have (working directory, index/staging). Then there’s also the issues that arise with merging, but you have those with every VCS.
I love mercurial’s solution of having phases for commits. Anything you created locally is Draft phase and can be mutated without “force”. Anything which was pulled or pushed is automatically marked as Public and is not easily mutated. Visualizing the commit graph also colors commits based on phase making it really easy to get a grasp of what’s going on.
Is there an extension or something for Git to have similar behavior to this?
Instead of using colors, a visualization that can be combined with branch colors would be nice. For example using bold for remote (“public”) commits, or dashed lines for local (“draft”) commits.
My mental model of a git repo is basically a stack of backup tapes with sticky notes on them that say “this is tape #2983 and it's almost the same as tape #2429, except that I fixed a typo.” Things like `git log` and common workflows make you think “this is change #2983, where I fixed a typo”, and that's where the trouble starts.
It's an organizational and personal thing, but you really have to get in the habit of treating commits as single diffs to the codebase and not the equivalent of ctrl + s in your editor.
I try and make a habit of squashing my commits so that git log is a narrative of development and each commit is a page number, not a sticky note.
For someone to get out of a mess they would have to know that they caused a mess in the first place! I'm starting to think trying to get people to use rebase and squash is a losing battle when they frequently just merge without pulling.
I got my team on side to use rebase/squash instead of blind merge commits after I showed them how much easier git bisect is to use when you have a linear commit history in your main branch. Now nobody wants to be 'that person' who breaks the bisect feature in case we urgently need it.
If anyone is curious: git diff --cached is the same as git diff --staged. I don't know why they aliased them but I run git diff --cached very frequently and after seeing this post's reference to --staged I wondered what the difference was. Turns out there is no difference.
I use --cached as well but I think I will try switching to --staged since that name makes more sense to me. I similarly have gotten away from checkout in favor of switch/restore.
It always amuses me that when Linus presents git to “the Google audience” they objected to the complexity, those people are in fact elite, but measured against what? The TailScale people? The TS people are ex-Google in some places and early-Nix in others, either way no one to fuck with.
I’m frustrated if I take a day to work out some something that Jeff Dean mentioned to, uh, a friend, and it took me a day to work it out.
@bradfitz works on TailScale, there’s also a reason he’s a chapter in a book. @jwz is better, by so little you’d never notice, so his chapter is cooler.
It’s admittedly a snippy, snarky, kind of cryptic comment and I probably deserve more downvotes than I got on it.
In a more measured tone: there is, in my opinion, a kind of creeping anti-intellectualism that’s been slowly-but-surely gaining ground on HN for the last 5-ish years.
We used to just really openly admire and respect iconic pros in this business, we used to openly acknowledge that some of the software work we talk about is pretty friggin elite and few of any of us will ever even work on some of it.
Whether it’s Linus or the TailScale pros or John Blow, I’ve seen people get gang-tackled by the “no one ever uses this LeetCode stuff in a real job” crowd repeatedly in the last week.
Knowing how to use “git reflog” to do surgery on a fucked-up repo isn’t necessary every day, but when you need it, you need it bad, and there’s nothing outdated about how knowing how the damned tools work.
Came here to post this - better than the link under discussion above. The interactive steps through a problem helps a ton for diagnosing the right steps to fix something!
While I'd like to think that this is _somewhat_ useful, I am a little hesitant. The issue I see with these bite-sized recipes is that there is no context, no place for nuance, and no hint that the behavior you see might be different for a variety of reasons.
Take the following for example:
> Oh shit, I need to change the message on my last commit!
> git commit --amend
It's important to realize here that if you are simply trying to edit the last commit message, you *should not* have anything in your index (that is, staged). Otherwise those changes will be recorded in the amended commit! What Git does is essentially move all the changes recorded in the commit you are amending _into_ the index, and then run `git commit -m <amended-message>` ... so if you have files in there, those will get mixed up with the ones in the commit.
Here's another one:
> Oh shit, I accidentally committed to the wrong branch!
> A lot of people have suggested using `cherry-pick` for this situation too, so take your pick on whatever one makes the most sense to you!
Umm ... No! The solution proposed (with `git reset --soft`) and a cherry-pick are NOT the same! Not even close! You will produce two completely different histories.
This final one, given when this page was written, _may be_ understandably incorrect
My book, Head First Git, was published by O'Reilly this January. I posted a submission here on HN about it https://news.ycombinator.com/item?id=30072348 so if you want any details feel free to peruse that.
I think the steps under https://ohshitgit.com/#accidental-commit-master are wrong: it reverts the commit on the new branch -NOT on the master. This is because git branch auto checks out the new branch. You need to do
git branch NEWBRANCH
git checkout master
git revert —hard HEAD^
If you want to continue working on the new branch you do
Which is correct, assuming master is already checked out.
0. prior state is that we're on master and have committed something that should have been on a branch
1. create a new branch that is identical to master (i.e., contains the commit) -- note this does NOT checkout the new branch (`git checkout -b some-new-branch-name` would do that)
2. reset current branch (master) to point at the commit before (i.e., strips the commit from master)
3. checkout the new branch to continue work there
At the end, master doesn't contain the commit anymore, and the new branch does. It's all correct.
>The command’s second form creates a new branch head named <branchname> which points to the current HEAD, or <start-point> if given. [...] Note that this will create the new branch, but it will not switch the working tree to it; use "git switch <newbranch>" to switch to the new branch.
Another useful thing if you need to backdate commits for whatever reason are the GIT_AUTHOR_DATE and the GIT_COMMITTER_DATE environment variables, upon executing git-commit they'll override these fields in the commit to whatever date, time and timezone you specify. I use this sometimes when I'm making some previously private work public, and am redoing the commit history to make more logical sense to others who may read it.
Also useful are git-fast-export and git-fast-import, if you really need to delve into the inner details of a commit. For example, I had three separate but related git repos that I needed to merge, so I created a new repo with separate branches to hold each repo, merged everything manually, committed that to a new branch, then used export/import to edit the commit to have the tips of the three other branches as its ancestors. Maybe there's a better way to do this with other git commands but I found it easier just to delve in and edit the commit data manually.
> I had three separate but related git repos that I needed to merge, so I created a new repo with separate branches to hold each repo
Similar story here: at a previous job we had a monorepo with a Rails app and Rails engines extending its appearance and behaviour and per customer.
At some point the architecture became problematic and we moved towards a shell app, a core engine, and extension engines depending on the core.
We refactored code to that end, and "forked" the original monorepo into multiple clones, one per component, then stripping the other components in each one, ending up with 1:1 repo/gem/component. This worked for a while, easing a lot of issues we had previously, allowing for proper dependency expression, independent development and releases... Everything was great and we lived happily ever after.
Then much later we hit a snag (I can't exactly recall what that snag was, IIRC it was not technical but organisational). So we looked at options and decided to merge into one single repo again. To that end we could do a big code drop, starting afresh, but (again I can't recall why) there was a need/requirement to keep at least some git history.
But at that point, "some" ends up ~== "all". So I devised a plan.
I git init'd a blank repo, added each one of the repos as separate remotes, and fetched each of them. Thus all git objects of all these repos were present. Then I checked out each one of these remote's master as a separate branch, created a subdirectory with the component name, moved every file for that checkout into that directory, and committed that. This way a) each project could live separately in the new monorepo and b) there would be no conflict for a merge.
Then came time for the merge. Two options: a) perform N merges subsequently or b) perform an octopus merge. a) just felt wrong and ugly, so I decided to try if I could work b) out, but I ended up not being able to achieve that with porcelain commands as git was being too smart and attempted to look into the history for some reason I can't recall which produced senseless conflicts (IIRC git merge isn't entirely assymetric)
So, since merge commits are merely commits with more than one parent I figured out I may be able to do that with plumbing commands instead. So the steps were:
- for each branch, check out content (but without moving the current HEAD, so, actually, export the git tree corresponding to a specific ref/sha)
- add all that to the index
- create a merge commit object with each branch's sha as parent
And it Just Worked, with the bonus that since up til the commit where we forked, each branch had common parents that were untouched, and commit history properly zipping up, by git's design, which is really a DAG of commit objects.
I avoid using git command line for all the reasons above
Personally I use a UI for git which basically solves all of these problems. All the branches and commits are visible. If you want something somewhere, right click on it and you'll get all the available options. Nothing to memorize and everything is available!
My own favourite after testing out a few is SmartGit, but it's paid so not for everyone. There are lots of free git UIs out there as well, but what I like about SmartGit is that it's completely full featured - every obscure git command is available somewhere - so I have never ever need to use git command line when on my local machine, not even for these obscure things like resets, rebase, cherry pick, squashed commits, etc you name it.
Also SmartGit is cross platform so I can use it anywhere
236 comments
[ 5.3 ms ] story [ 280 ms ] threadEvery Git novice should check out this awesome introductory video ;)
It dominated the world despite the UX flaws, which suggests they really aren't that bad.
Especially if you'd have to lose github, thats the real force
If gh migrated to something else, then a lot of ppl would follow
If you prefer you can use mercurial as a git client in the same way.
Most of my git use stays local on my PC, uploading it to github or gitlab or whatever is a secondary concern for me.
If only that wasn’t the case we’d all be using a sensible source control system now instead of one where people repeatedly say things like “if you just learn the underlying model…”
GitHub for many years only had free public repos, not privet, BitBuckets USP was that you could have privet repositories for free.
GitHub "won" because of the social aspect around it and the tooling for open source projects - it never had more generous free levels.
(I like Python! But it's a scripting language, not a systems language. Also the "deployment story" is bananas.)
Yes, you need to follow a tutorial to use it effectively. Designing for beginning users is often detrementantal to advanced users, and I object to that being called "user friendly".
I'm not very vocal about git usually because I'm not an expert I have no big complaints, and I'm not that interested in arguing about it. I think there's a lot of us that are perfectly happy with git; we just make less noise.
At work, we switched from SVN to Mercurial, and from Mercurial to Git. Most people were happy to switch away from SVN, but few enjoyed the change from Mercurial to Git. Personally, it took me much longer to get used to Git than with Mercurial, even though Mercurial was my first DVCS. I now have a slight preference for git, but I am happy with either (just don't bring back SVN!).
Why Git came to dominate and not Mercurial? I am not sure, but I don't think technical reasons explain everything. Its association with Linux probably helped a lot.
> Why Git came to dominate and not Mercurial?
The answer is actually really simple and non-technical: Github. We take these feature rich online code hosting platforms for granted now, but Github was really the leading edge of the wave. It made it easy for people to work together on writing software so it had great takeup and started a Git snowball effect.
I was an early proponent of Mercurial over git. While they are similar, a few “minor” things made a big difference:
Mercurial distinguished between branches and heads, whereas git did not. This added extra complexity.
Git embraced checksums as identifiers while Mercurial provided local revision numbers, this obscured the mental model.
Git is faster to pronounce than either “Mercurial” (which has a tricky vowel in there) or “hg”.
don't make the mistake of comparing "git now" vs "svn then".
It has been described as distributed SVN. I really hated SVN, almost to to point of wanting to go back to CVS, so I didn't want anything that took any inspiration from it. But maybe, in reality, it is not that bad.
Yet, whenever I've worked on git with others, it's been on github (i.e. a centralised model). And I've worked in a decentralised way on svn on several occasions, simply by making my own local repositories and merging changes back to the parent repository when I'm done (which is effectively what git does too when working with remote repositories).
I feel that a lot of what ends up being 'bad' about svn really boils down to the fact that you need some good conventions to be honoured across the project in order to get things done (including using it as part of a decentralised workflow), but humans being humans take shortcuts and mess things up for everyone else. Which really means at the end of the day, the problem isn't the technology per se, but human relationships, manifesting as commit behaviours. Whereas git just imposes its highly opinionated model of doing things on you in order to ward off some of the more destructive human behaviours, which in a sense is good, but at the same time, it means that git can be too rigid, and svn effectively gets a bad rep for being potentially more flexible and scriptable. I've been in many situations where I had to get into an incredibly convoluted manual process to work around git's mental model to get it to work for me, when the equivalent in svn (for better or worse) would have been fairly straightforward.
(Disclaimer: This is just my personal experience from happily using both with no specific preference for one mental model over the other. If anything, I think I may probably prefer svn a bit more now. I'm probably completely wrong about all of it.)
In software we often go for further complexity instead of less. I think because most of us who are the lead developers are often the most intelligent. And we often enjoy these complicated abstract models and they come easy to us. However in satisfying our own intellectual vanity we often don't see how many we leave behind. Which is good for our hourly rates, but less good for creating affordable and simple software.
Anyway one of the main practical advantages git had was decentralized repos meaning you were not dependent on an external server which meant git was often much faster if working with in daily tasks compared to centralized versioning systems
https://stackoverflow.com/questions/804115/when-do-you-use-g...
No it doesn't. It suggests the other features are so damn good that it overcame the horrible UX.
I bet git produced by a no name Linus, in isolation not required by any major project, would have hardly been taken any major uptake.
I think parent's point is that that demographic is positively tiny, and there were plenty of other serviceable offerings around at the time.
The claim stands - git won despite its UX flaws, which does suggest the alternatives, while serviceable, weren't sufficiently fit for purpose.
I think perhaps you're over-estimating the network effect for VCS's -- having multiple version control binaries on your machine is low cost, and aliases can, up to a point, give you a consistent interface to them all.
The highly sophisticated demographic of kernel developers would not have been at the pub insisting that their friends drop svn and use git (exclusively!), anymore than they would have been trying to get any other projects they worked on to adopt bitkeeper previously.
The fact that bitkeeper was required for 'collaboration in Linux development' for so long supports this position.
There were perhaps 5k kernel contributors in 2005, would that be fair? There's perhaps 20k now, I guess.
github alone has 80+ million users.
It's possible, I suppose, that the former is predominantly the cause of the latter, but it seems hugely unlikely.
6 years ago Larry released BitKeeper under an Apache licence. I still don't anyone who's ever actually used that.
The worst systems are the Microsoft ones, but the one with the most complex interface is definitely Git.
I would argue it won because of github. I'm using Git because of that, but still prefer Mercurial in every way.
I doubt that was the intention. Linux just needed a versioning system tailored to its needs, and that's exactly what Git is. Can't blame its creators that other people used it for scenarios it wasn't built for.
This is not one of those times. I agree there's a chance there are better word choices or feedback for some commands, but overall once you 'learn the language' it really is a lean, mean, well designed piece of software.
Folks that complain about the UI/UX don't realize it wasn't designed for less technical folks. It was designed for the folks who needed it.
It's success must at least partially prove that the UI/UX is not 1/10. Any real engineer will tell you, there are times where they wish they could do something better but the requirements and constraints left them making tough decisions, and that doesn't mean they aren't proud of their work.
It's success is also partially due to the fact that it is lean and mean, which allows it to be applicable to nearly all software projects of any flavor. So I don't understand why folks argue it could have been done better. If it was 'better', in my view it wouldn't have been successful. The success was driven by it's succinct design and Linus' take it or leave it attitude.
Technically accurate studio monitors don't sound as pleasing to the ear as good well tuned speakers. But they are exceptional at the job they were designed for. This is like that.
Even the operations which do show will often require more thought to undo than a hypothetical "git undo" would. I know how to use the reflog but I often go out of my way to avoid it because "git branch tmp HEAD; git $POSSIBLE_MISTAKE; git reset ---hard tmp; git branch -D tmp" requires less effort than deciphering the reflog's output.
Undoing a push does require a different set of commands, but my point, to the question @amelius asked, is that you can undo both push and add, and whatever other mistake you’re thinking of, difficult or not.
Git, coincidentally, does have something equivalent to undo history: the reflog.
(FWIW, I don't consider git complicated and I'm far from a power user)
The idea of a directed graph of file system snapshots is pretty intuitive. Add in branches as pointers to locations in that graph. This is a fantastic model for source control.
However the operations that stage a potential update to the graph of snapshots is prettt confusing. The "index" is a terrible name that is overloaded with so many other non-git meanings, non of which really map to git usage.
That, and all the rest of the names are pretty hard to understand. Particularly reset, whose documentation is inscrutable without translation from git-speak into technical language, or at least a dictionary of what all those words that are used actually mean. And since reset is such a useful tool and has about eleventy different functions, it all becomes impossible to learn from the docs on your own.
The well-documented underlying model used to confuse me a lot - especially for operations like merging, rebasing (squash, reorder, ...), cherry picking, etc. They started to make sense only when I realized that git uses diffs/patches to propagate changes between unrelated commits. While the on-disk format is purely snapshot-based, the tool itself is a hybrid. As far as I can tell, this trips up a lot of others too.
Projects like Firefox and nginx use it, although others like Vim and Python switched to GitHub. I don't know if Facebook is still using Mercurial internally, but for their public stuff they use GitHub.
I think that if GitHub had supported Mercurial like BitBucket or Google Code it would still be a lot more popular, but ah well...
Git has enjoyed overwhelming success, which would seem to empirically indicate that it's done something right in terms of design
Perhaps the obviously wrong UX/UI isn't wrong? Perhaps UX/UI people aren't good at designing interfaces for experts?
It's open source, so why hasn't an alternate interface taken over?
For example, the master/main branch shift. Everything broke when that change was made, but it happened and it wasn't a big deal.
I'm not seeing the difficulty here. It seems that a more reasonable interpretation is that git has the type of interface that is hard to learn, but intuitive once learned.
For CLI, it's easy to create any number of command aliases and scriptlets to have the exact UI you want..
Thanks for posting it!
But that doesn't work if you rewrote history in some way (I'm not sure why that's even possible). In that case your local git can get pretty messed up, some gui tools (IntelliJ) start to bug out and fail to diff properly, and it's easier to just start over.
After ten years of using git, I've more confused by it than ever. People here keep talking about mental models, but I've read a shit ton of docs on it and am still totally confused. Probably some of us just aren't smart enough lol.
To reset the current branch, you need to do two things:
to fetch commits from the default remote and point remote branches accordingly, and to reset HEAD to the remote branch. Substitute master for any other remote branch if you wish.That's it. I don't know if this is more difficult than re-cloning from scratch (especially if you're doing frontend and then have to reinstall node_modules and such…)
Same thing for node modules, lol. Delete the folder and try again. If that still doesn't work, delete the lock file and try again.
It's a terrible practice, I know, but it's the only thing that actually seems to work in my experience.
EDIT: Apparently origin/master points to a specific branch. origin/HEAD points to the top of the "default" remote branch, which is often, but not always, master. Or something like that. I don't know for sure.
First is that HEAD always points to the commit that you currently have checked out. So, for example, if you have the master branch checked out, HEAD will be whatever is the latest commit in master in your local copy of the repository.
Second is that to git there is no difference between the originating repository and your own. If you had your copy of the repo fully exposed, the remote machine that you cloned it from could push changes to your copy exactly the same way that you push changes to the remote machine.
That second part is important because it means that the remote machine you're pulling from also has its own HEAD pointer. When you reset your state to origin/HEAD, you're telling git to set your own HEAD to point to the same thing as the remote machine's HEAD. This is very likely to be the same asking it to reset to origin/master because HEAD is probably pointing to master on the remote machine.
The reason it's likely that HEAD=master is that in most circumstances the repo on the remote machine isn't manually being touched. When you first setup a repo there is only a single branch so that is what HEAD points to. If no one ever logs into the machine and executes a checkout command, that's what the server is going to continue to point to.
However since there's no guarantee that HEAD=master, you shouldn't rely on that and instead always use the actual branch name.
I've seen this a couple of times at $DAYJOB when someone doesn't understand how rebase works, smashes keys until it looks like they've achieved their goal, and then I have to ssh into their box and try to unravel the damage they've done, hoping that reflog has not been GC'd yet and there's something to revert to.
That's easier than trying to get git working right, lol. It feels like every git command is a PhD rabbit hole. All I know is that if I screw something in git, trying to fix it will just screw it up worse. Reset early and often and it usually works in the end. Lol, it's terrible...
Even though I understand git really well it’s quicker to just blow it away and start again rather than accidentally make it worse realising you didn’t understand it as well as you thought you did.
Fortune favours the lazy.
[0]: https://github.com/blog/2019-how-to-undo-almost-anything-wit...
This is why it's good to put the linting in a git hook on commit or stage, so you literally can't commit without being formatted correctedly.
Also, unless you jump through a lot of hoops, linters generally run against what's on-disk instead of what's actually going into the commit. So checks that run at commit time discourage partial adds.
The bramble stems and branches are the commit history (which may join and well as split, unlike a bramble). The labels are stuck to particular bits of the bramble. Some labels are even stuck to other labels!
You can move the labels around, and you can glue extra bits of bramble to the tips of what you have. You can even hack about with the bramble, but this is not recommended. Label moving often happens automatically, e.g. when 'growing' a bramble tip.
It gets interesting when you compare two brambles (e.g. remote and local repos). You might determine that one bramble is identical to another, just with the labels moved. Or one bramble is the same as the other but with extra stems added. Or both brambles had a common ancestor bramble, but now they both have extra stems. Or perhaps they are completely irreconcilable.
Better visualisation tooling would help.
Here's a 2019 presentation of that talk.
https://www.youtube.com/watch?v=fHLcZGi3yMQ
Take the recursive merging stuff around minute 39. Do I need to know why git's model for merging is so much better and how it works its magic? I don't think so. It's an implementation detail.
Do I need to know how a gearbox works to drive stick shift?
I have never thought of git as a bramble (tree worked fine for me :)) but the thing I always tell people about too is the labels part. What I think is enough for people to realize is that it's just like say SVN or CVS or any other source control mechanism in that there's a tree (bramble) of commits and then every commit can just be pointed to by a label. I can move those labels around any which way I want. Everything else follows from that on a surface level that is the only thing required to work with git in most situations, including some advanced ones.
You don't need to know why certain operations in git are faster, better, have less conflicts or how they work internally. You just need to know what they do and when to apply them.
I don't need to know why I can't make my car start or switch gears without pushing down the clutch. I just need to know when to push the clutch, i.e. if I want to start the car, push it. When I want to switch gears push it (well, mostly, on cars you want to last a while still lol). Of course some people won't even be able to learn how to drive stick shift and can only ever drive automatic.
Even off-road, I really only need to know to not ride it all the time.
Also, if he's literally 3 inches, I would say the best approach is to slowly ease off the brakes until you actually touch the other car but do it so slowly that it's not an impact. All without even pushing the clutch. Then you don't even need to use the brake while pushing the gas pedal trick, because the guy behind you is your brake pedal ;)
I learned to drive stick in the Bay Area and the way everyone I know there drives stick on an incline is to use the parking brake with a second hand when letting off the brake and letting out the clutch.
Now, when you get good you can stop doing this for most, but for really steep streets (I now live in San Diego and we have a couple; Laurel being one) it’s still an excellent skill to have.
Imagine: Small towns, really narrow one way streets with foot traffic and underground parking. Getting out of some of those underground car parks is scary stuff!
You get onto a steep incline to get out of the car park but you come around a corner onto that. Cars might be coming down towards you at this point and they're sometimes hard to see. It's cramped too. So you can't just take it w/ speed to get up there. Also on the top you have pedestrians on your side, so you might need to stop on the incline, then when pedestrians have scurried away, go a little further but not directly onto the street until you can actually see if cars are coming. Half your car is still on the incline at that point.
There was no handbrake.
I managed to work my way back down to the dealership and asked, "Where's the handbrake?"
It turned out that Subaru had decided to replace it with a button and an "automatic" "hill-holder" feature.
No, but it helps. If I treat my car's powertrain as a black box then it won't be intuitive that:
Whether it's better for a given person to memorise that list of facts or to understand the concepts behind them would depend on how much they drive. As a developer I've found it helpful to gain an understanding of the tools I use daily beyond "here's the commands to copy/paste when you want to do xyz".Like you say, you can just remember those things. In fact 4 out of those 6 were taught in driving school and you had to remember them. One I only learned because trucks still needed that (double clutching while shifting - not just your case, just in general) but cars didn't when I learned. I personally don't like copy and pasting commands like that but I see a lot of people doing that even for stuff that should be second nature because you need it all the time. I think - to stay in the analogy - for me this is the difference between knowing that I should engine break and how to do it when I want to slow down vs. having a piece of paper in the glove compartment that I pull out and check for what to do and how every time I approach a red light ;)
I'm also someone that likes to get an understanding of the things that I use and do every day. The thing is that there are so many things we use and do all the time that I think (almost) everyone just has to keep a certain level of abstraction away from many things, because there are just so many rabbit holes out there and it's not beneficial for most people to have explored every single rabbit hole all the way to the end (the 'gradient'). Git commit graphs are a DAG and lots of cool things can be done with DAGs, most of which I totally forgot about since I learned about and enjoyed them in university and have never needed them again at that level. It's good to know they exist and be able to dig in when needed/wanted.
The interesting part to me of your analogy is that it can demonstrate how having an understanding of what's going on under your layer of abstraction allows you to generalise.
To wring the last bit of life out of the gearbox analogy: if you tell a mechanically inclined driver that blipping the throttle will make their downshifts smoother, they'd hopefully understand how rev matching can be generalised to upshifts too. If you tell a "black box" driver then they probably wouldn't be able to do the same. Of course, the analogy falls apart a bit because understanding rev-matched upshifts isn't particularly useful :)
I've forgotten most of the "advanced" git knowledge I ever learned but I get a lot of value out of the (admittedly not very advanced) understanding that, as you said, commits are a DAG and that branches are just named pointers to nodes on the DAG. That understanding lets me generalise to, for example, backing up a branch (with git branch/tag) before doing a tricky rebase so I can restore it (with git reset --hard) if I need to undo.
I agree that exploring every rabbit hole to the end isn't beneficial - understanding the DAG is typically the only "advanced" git knowledge I need and I've only very rarely had to peel back more layers. I think this:
> It's good to know they exist and be able to dig in when needed/wanted.
is a good way of putting it. My ideal low-level understanding of most tools is knowing just enough that I know what to search for if I ever need to go deeper.
You can also do `git reflog` on branches to see what they used to point to :)
Is there an extension or something for Git to have similar behavior to this?
I try and make a habit of squashing my commits so that git log is a narrative of development and each commit is a page number, not a sticky note.
At least until someone does a foxtrot merge and then it never works again
I’m frustrated if I take a day to work out some something that Jeff Dean mentioned to, uh, a friend, and it took me a day to work it out.
@bradfitz works on TailScale, there’s also a reason he’s a chapter in a book. @jwz is better, by so little you’d never notice, so his chapter is cooler.
What book are you in?
In a more measured tone: there is, in my opinion, a kind of creeping anti-intellectualism that’s been slowly-but-surely gaining ground on HN for the last 5-ish years.
We used to just really openly admire and respect iconic pros in this business, we used to openly acknowledge that some of the software work we talk about is pretty friggin elite and few of any of us will ever even work on some of it.
Whether it’s Linus or the TailScale pros or John Blow, I’ve seen people get gang-tackled by the “no one ever uses this LeetCode stuff in a real job” crowd repeatedly in the last week.
Knowing how to use “git reflog” to do surgery on a fucked-up repo isn’t necessary every day, but when you need it, you need it bad, and there’s nothing outdated about how knowing how the damned tools work.
Take the following for example:
> Oh shit, I need to change the message on my last commit!
> git commit --amend
It's important to realize here that if you are simply trying to edit the last commit message, you *should not* have anything in your index (that is, staged). Otherwise those changes will be recorded in the amended commit! What Git does is essentially move all the changes recorded in the commit you are amending _into_ the index, and then run `git commit -m <amended-message>` ... so if you have files in there, those will get mixed up with the ones in the commit.
Here's another one:
> Oh shit, I accidentally committed to the wrong branch!
> A lot of people have suggested using `cherry-pick` for this situation too, so take your pick on whatever one makes the most sense to you!
Umm ... No! The solution proposed (with `git reset --soft`) and a cherry-pick are NOT the same! Not even close! You will produce two completely different histories.
This final one, given when this page was written, _may be_ understandably incorrect
> Oh shit, I need to undo my changes to a file!
> `git checkout [saved hash] -- path/to/file`
There is the introduction of a new command called `git-restore` (https://git-scm.com/docs/git-restore) that (thankfully) is named more appropriately—it "restores" a file. I wrote a thread on it on Twitter, so if you are curious perhaps this will help: https://twitter.com/looselytyped/status/1501934009370042371
*Shameless plug for my book*
My book, Head First Git, was published by O'Reilly this January. I posted a submission here on HN about it https://news.ycombinator.com/item?id=30072348 so if you want any details feel free to peruse that.
Some links:
- Amazon: https://www.amazon.com/Head-First-Git-Learners-Understanding...
- O'Reilly's online platform (Needs subscription): https://learning.oreilly.com/library/view/head-first-git/978...
- Companion website: https://i-love-git.com/
(Edited for formatting)
My only complaint I have for git is it does not support $Id$ and other RCS variables. IIRC that was by design.
git branch NEWBRANCH
git checkout master
git revert —hard HEAD^
If you want to continue working on the new branch you do
git checkout NEWBRANCH
0. prior state is that we're on master and have committed something that should have been on a branch
1. create a new branch that is identical to master (i.e., contains the commit) -- note this does NOT checkout the new branch (`git checkout -b some-new-branch-name` would do that)
2. reset current branch (master) to point at the commit before (i.e., strips the commit from master)
3. checkout the new branch to continue work there
At the end, master doesn't contain the commit anymore, and the new branch does. It's all correct.
Source: git branch --help
Also useful are git-fast-export and git-fast-import, if you really need to delve into the inner details of a commit. For example, I had three separate but related git repos that I needed to merge, so I created a new repo with separate branches to hold each repo, merged everything manually, committed that to a new branch, then used export/import to edit the commit to have the tips of the three other branches as its ancestors. Maybe there's a better way to do this with other git commands but I found it easier just to delve in and edit the commit data manually.
Similar story here: at a previous job we had a monorepo with a Rails app and Rails engines extending its appearance and behaviour and per customer.
At some point the architecture became problematic and we moved towards a shell app, a core engine, and extension engines depending on the core.
We refactored code to that end, and "forked" the original monorepo into multiple clones, one per component, then stripping the other components in each one, ending up with 1:1 repo/gem/component. This worked for a while, easing a lot of issues we had previously, allowing for proper dependency expression, independent development and releases... Everything was great and we lived happily ever after.
Then much later we hit a snag (I can't exactly recall what that snag was, IIRC it was not technical but organisational). So we looked at options and decided to merge into one single repo again. To that end we could do a big code drop, starting afresh, but (again I can't recall why) there was a need/requirement to keep at least some git history.
But at that point, "some" ends up ~== "all". So I devised a plan.
I git init'd a blank repo, added each one of the repos as separate remotes, and fetched each of them. Thus all git objects of all these repos were present. Then I checked out each one of these remote's master as a separate branch, created a subdirectory with the component name, moved every file for that checkout into that directory, and committed that. This way a) each project could live separately in the new monorepo and b) there would be no conflict for a merge.
Then came time for the merge. Two options: a) perform N merges subsequently or b) perform an octopus merge. a) just felt wrong and ugly, so I decided to try if I could work b) out, but I ended up not being able to achieve that with porcelain commands as git was being too smart and attempted to look into the history for some reason I can't recall which produced senseless conflicts (IIRC git merge isn't entirely assymetric)
So, since merge commits are merely commits with more than one parent I figured out I may be able to do that with plumbing commands instead. So the steps were:
- for each branch, check out content (but without moving the current HEAD, so, actually, export the git tree corresponding to a specific ref/sha)
- add all that to the index
- create a merge commit object with each branch's sha as parent
And it Just Worked, with the bonus that since up til the commit where we forked, each branch had common parents that were untouched, and commit history properly zipping up, by git's design, which is really a DAG of commit objects.
It is considerably more powerful than most teams need.
Personally I use a UI for git which basically solves all of these problems. All the branches and commits are visible. If you want something somewhere, right click on it and you'll get all the available options. Nothing to memorize and everything is available!
My own favourite after testing out a few is SmartGit, but it's paid so not for everyone. There are lots of free git UIs out there as well, but what I like about SmartGit is that it's completely full featured - every obscure git command is available somewhere - so I have never ever need to use git command line when on my local machine, not even for these obscure things like resets, rebase, cherry pick, squashed commits, etc you name it. Also SmartGit is cross platform so I can use it anywhere