One example is how git will deceive you and tell you you’re up to date with your remote (e.g. origin/main). What it means is that your local branch is up to date with its local concept of remote, and makes no statement guarantees about the actual state of the remote. Which is really a nonsense concept that does not need to exist.
Similarly the whole concept of needing to specify “origin” at all is a bit bonkers and does no favors. Why is it that I can pull from a remote branch, commit some changes, run ‘git push’ and git has no idea what branch I want to push to. Another example: if main is a protected branch, don’t let me accidentally commit to it locally. I could keep going with the examples but I won’t.
And yeah you can forgive all this by quibbling that git was written in a time when internet access was not ubiquitous, and of course all these decisions make sense because x, y, or z advanced edge case for advanced users only, and I’m a shitty engineer because all of this complexity secretly makes my life better and I’m just too simpleminded to appreciate it.
Really though, if you rewrote git from a principles first approach (with developer experience being one of those principles), it certainly would not look like how it looks today. There is too much complexity, too many ways to do things, and too many bad decisions around defaults. Treat it like a proper distributed system, perhaps even backed by a real database. It’s not special because the data is code. The fact that it’s treated as such is the reason it feels so weird.
Git doesn't do special. All branches, on all machines, are just branches. "main" on your device and "origin/main" (main on the machine you called "origin"), are two different branches. They don't need to share a name, you can just as easily set your local "main" to have "origin/Release" as its upstream. The name "main" isn't special. You can name your branches anything you desire. If you want to lock yourself out from controlling your own code, that's a customization for you to make. To git, there's no difference between committing to "main","feature111", or anything else. "origin" isn't special, either: you can have many different repo servers hosting different branches. Or maybe you don't have a repo server at all, and you just have all your fellow devs machines and you coordinate via email.
While being perhaps a bit un-opinionated, it also makes Git conceptually extremely consistent, and thus simple.
Is it elegant that the same version control system can be used whether you have a remote server or you just coordinate over email? It certainly is! Is it necessary to have this level of generality and lack of opinions when 95% of users just want to do the same basic flow (and it does involve a server of some kind)? I don’t really think so, which is why I think there is so much confusion around really basic git functionality.
We live in a world where git is philosophically more like Perl than python, and I think that it’s not unreasonable to think that it’s possible that if we flipped that, then that might actually be a good thing.
Why it "won" over alternatives with cleaner UX (eg Hg) is a different question, and I think has a lot to do with github.
It gets in the way of the workflow IMO.
Named branches being baked in turned out to actually be a real pain in mercurial. Sure, you can use bookmarks (and we did) but starts to quickly feel like you're swimming against the current.
People use git because everybody else uses git and it started because GitHub provides easy code sharing and a better UI than the alternative — SourceForge and mailing lists.
At the time it was the only distributed and open source VCS. Fossil may have come quickly on its heels, but it was already too late.
> The first open-source DVCS systems included Arch, Monotone, and Darcs. However, open source DVCSs were never very popular until the release of Git and Mercurial.
https://en.wikipedia.org/wiki/Distributed_version_control#Hi...
I differ from your opinion that the user experience is poor. I think it's great, and I prefer using the command line interface.
Jujutsu is a Git-compatible DVCS. It combines features from Git (data model, speed), Mercurial (anonymous branching, simple CLI free from "the index", revsets, powerful history-rewriting), and Pijul/Darcs (first-class conflicts), with features not found in most of them (working-copy-as-a-commit, undo functionality, automatic rebase, safe replication via rsync, Dropbox, or distributed file system).
[1] https://github.com/martinvonz/jjSometimes you want to keep 2 different copies of Main with different names and state
Wouldn't that just be two different branches? What does it have to do with 'main'?
Maybe i misunderstood what they mean, I thought they wanted local branches to be mirrors of origin branches.
https://git-scm.com/docs/git-worktree
Can't recall the source I learned from anymore, but this looks pretty good: https://www.gitkraken.com/learn/git/git-worktree
I have two projects where I've got a directory that I do most of my work out of and another directory (worktree) focused on syncing from remote/assisting others on the team.
If you don't need the extras worktree gives you, clone another copy?
Git has automatically tracked remote branches for years now. "git switch foo" will do exactly what you expect if "origin/foo" exists.
> if main is a protected branch, don’t let me accidentally commit to it locally
"Protected branches" don't exist in Git. They're a GitHub concept. How would you be able to fork a repository if the permissions on a remote copy of the repository prevent you from making changes to your own copy?
Of course, you could always opt out of that setting.
Git doesn't know what 'protected' means, but you could teach it with a relatively small change to its code. Similarly, it might be useful to teach git about 'volatile' branches (in the same sense as C's volatile variables); a volatile branch is one where git would always checks for upstream changes first before any operation. Eg origin/main would typically be marked as both protected and volatile.
Again, volatile would be something you can override locally, but it might be useful as a default.
https://stackoverflow.com/questions/40462111/prevent-commits...
Alternately, you can simply not check out that branch locally at all, and then you'll never have to worry about accidentally committing to it.
Git actually works like that. (Though you might have to set push.autoSetupRemote to true in the config?)
However, when you have multiple remotes, it's only natural that you will sometimes need to specify which one you want.
> And yeah you can forgive all this by quibbling that git was written in a time when internet access was not ubiquitous, and of course all these decisions make sense because x, y, or z advanced edge case for advanced users only, and I’m a shitty engineer because all of this complexity secretly makes my life better and I’m just too simpleminded to appreciate it.
Git was explicitly written for Linux kernel development.
You are right that almost all other projects are simpler.
> Really though, if you rewrote git from a principles first approach (with developer experience being one of those principles), it certainly would not look like how it looks today.
I agree that git's UI is lacking. You don't need a from-scratch rewrite of whole system. You can just rewrite the UI only and think carefully about the defaults in the config.
Eg 'git checkout' is an incoherent mess of barely related features. 'git switch' is a later addition, and goes in the right direction.
> Treat it like a proper distributed system, perhaps even backed by a real database.
Git is very much backed by a real database. That's actually one of the stronger points of its design. You can see that the guy who started git, Linus Torvalds, has a lot of experience writing filesystems (which are also a kind of database).
Git is also very much a distributed system with no trust between the nodes necessary, including no central trusted authority. How would you 'treat' it even more 'like a proper distributed system'?
> It’s not special because the data is code. The fact that it’s treated as such is the reason it feels so weird.
Where do you get that impression from?
The main influence of being designed for code first comes in the form of the default merge driver being line oriented. But you can plug in your own merge drivers for your own data formats.
But most everything else works the same, whether you stick C source code or funny cat pictures into your repository.
I don't think that's right. Git's remote concepts were built heavily on even more ubiquitous internet access than you are assuming, to some extent. Git was built where some upstreams were patches on email mailing lists. Git was built in environments where every contributor could relatively easily stand up a small server of their own (even as just a temporary server on a personal device with specified time windows) and you might have branch remotes tied to different colleagues' servers in a distributed fashion, the D in DVCS.
At the time those weren't advanced features for advanced users, those were simple features for flexible source control. There's a sort of simplicity to pull requests in email flows. There's a sort of simplicity in "hey, can you check out my branch and make notes on it, I'll serve it on my lab machine for a couple of hours so you can grab it, here's the URL." In some of those cases you don't even care to remember that remote URL after you've grabbed the branch because it will be a different IP address and port the next time they bring up that lab machine. (Git was a built in a world where there was no "origin" and multiple repos were valid representations of progress, some of them transient and as-needed, and "origin" was a name and concept that came later.)
Some of that only exists in a world that assumes internet connectivity is ubiquitous, not just access, but service hosting and upload capabilities. The internet has some strange centralizing forces making access easier but anything other than raw consumption harder.
There are certainly a lot of good reasons for some of that centralization. Whether or not is "simpler", there's a convenience on everyone sharing big centralized hosts. There's a lot of convenience of "there is mostly only one remote that everyone shares and it has a high uptime SLA and a ton of extra collaboration features in one place". There were certainly a lot of centralized version control systems before the DVCS was invented, and beyond convenience also a lot of familiarity that such centralizing operations benefited from.
It's interesting to me that in your last paragraph you think the solution is to make git a more "proper" distributed system, but one of the features you find too complex and don't like exists so much because it was defined and built as a distributed system and just isn't as convenient when working with centralized providers. git repos support multiple remotes because it was built to be distributed, git repos require to fetch remotes explicitly because it was built to be fault-tolerant in a distributed system and remotes may have very different SLAs from each other; losing access to one remote host shouldn't stop you from fetching updates from a different one. The DX there was built for a distributed system. It is mostly where we see everything revolving around some super special "origin" remote that the DX feels overly-complicated and maybe missing better "defaults". It is mostly on the internet where running a simple CLI command to spin up a quick code server on a random port on a random machine with an accessible IP address is increasingly hard that it also becomes harder to imagine why people ever needed remotes beyond that special sauce "origin" idea.
Git just happened to be flexible enough to be a reasonably good fit for other workflows, including GitHub-centered workflow which is relatively common. Mostly because of easy branching/merging and great support for working off-line, I guess.
However, it does not have built-in support for such workflows, their special requirements need to be be satisfied by tools and/or practices external to Git.
What you are saying is that Git is not a perfect fit for your workflow, and that this workflow is fairly common, so why there is not something better for that need? If you stopped there, your post would be perfectly reasonable from my point of view. But you called your workflow "developer experience", as if that's the only one that exists. It's kind of offensive for the people with different workflows, and so with different experience.
Anyway, I don't know the answer to your question, but my guess is that Git + external tools/practices is "good enough" for your workflow, so nobody created a completely separate system.
The worse thing I've had happen is that someone was lazy when merging and just did a "take mine" on 100s of files, thinking that they'd do a "proper" merge later. Of course git doesn't work like that and their lazy merge had to unwound very carefully.
What do you mean? Like you resolve the conflict correctly and git stops working? Stops working how? That sounds very strange, and anything else just seems like PEBKAC.
It's true that git does not 'break' from a merge; but merge conflicts (and rebase conflicts) can still be frustrating to resolve for ordinary users. And after things get too frustrating, they often do really random stuff that then might accidentally break the repo for real, or at least get it into a state where they don't have enough knowledge to recover.
Git's underlying data model is fine, but the user interface can be quite lacking. For example, 'git checkout' is a mess of almost unrelated functionality thrown together under one command.
I think the idea behind 'git switch' is a good one, and git could benefit from a complete overhaul of its user interface. Well, at least if you ignore the switching costs. Old farts like you and me have gotten used to the quirks of the bad old interface, and learned all the barely coherent options to 'git reset'.
As I see it, git is a distributed database of snapshots of a directory of files. Every commit is a new snapshot. It's designed the way it is to achieve that goal while minimizing the space used, yet keeping an entire history of the project. It also has tools like git-diff to better compare those snapshots.
But distributed matters most because it was literally designed for managing the Linux kernel development- a large open source project with thousands of contributors working concurrently.
Merge conflicts are hard in this model because you're trying to put two snapshots together, not just two diffs.
Most of git's design was done before they thought of 'minimizing the space used'. Originally, they didn't even do deltas and just stored complete snapshots for every object.
> Merge conflicts are hard in this model because you're trying to put two snapshots together, not just two diffs.
I don't think that's the case at all. Thanks to three-way-merges git has just as much access to the diffs as any other version control system when merging. It's just that in git diffs are a derived data structure, not a source of truth, but that doesn't make a difference.
That's the key reason Git can't work the same way, and what makes it so powerful, and sometimes hard to grok.
Git is based on a Directed Acyclic Graph (DAG) of committed changes. The DAG is shared by everyone. Each commit is immutable, and only extends the DAG with a new node. You do not alter any of the DAG that other people already possess, you are creating a NEW chain of nodes connected to the DAG.
And that's it, that's how it allows anyone to make changes, at any time, on any number of systems. Because all operations are strictly additive to the DAG. Every time you commit changes, you add another immutable node onto the DAG. And at the very same moment, on another unrelated server, someone else is adding unrelated immutable nodes as well.
Later, you may obtain some of those remote changes, they can be fetched into your local repository. You can fetch them without merging them. You will just have a copy of how someone else extended the DAG. But there will be no connection between the changes you made locally, and the changes the other person made in their repository.
When you merge, you're simply updating the DAG with a new node, it will point at two, previously disconnected chains of commits. Both sides of the merge represent immutable changes that extended the DAG starting from a single commit, the branching point.
And that's what you see in merge conflicts. The first block shows what a section of code looks like in the local branch. And the second block (after the equal signs) shows what that same section of code looks like in the remote branch you're merging.
<<<<<<< local
a
X
c
========
a
Z
c
>>>>>>>> remote
That's it.I would choose GIT every single time over ClearCase.
- Everything is a blob, a text file named after the SHA1 of its content.
- Files are just themselves.
- Directories list <entry sha1> <name> for their entries (file or subdirectory).
- Commits list a Directory (the project root), and some metadata about the commit like the author, commit message, and the parent(s) of the commit.
- A branch then is just an end-user-named reference to a commit's hash.
Everything flows from that - SHAs are reused, if you're doing a diff and two directory entries have the same sha referencing a file, there's no change. Switching a branch is modifying the special HEAD branch content, and recursively walking it to rehydrate the filesystem (and comparing to the previous checkout to optimize, skipping whole directories that don't change).
A merge is when you join two commits and thus may result in one merge conflict, while a rebase is resetting your branch to some other target commit and then /re-applying/ _each_ of your commits on top, which may generate more than 1 merge conflict (due to each _one_ of your commits effectively being re-committed all over again) but hides the fact that you applying old changes to a newer base.
But the tooling around Git is not great. I think showing merge conflicts properly so you don't mess them up is also tooling issue more than anything.
I'm going to plug this app called SmartGit which I have no relationship with that I don't think a lot of people have tried but it's awesome. Doesn't obscure anything about Git and shows merge conflicts well IMO. Costs $ though.
That's backwards. Git commits are complete snapshots of the state of your repository at the time. You can compute diffs between arbitrary commits, be that between child and parent commits, or completely unrelated commits.
Have a look at https://stackoverflow.com/questions/4129049/why-is-a-3-way-m...
But I said conceptually and that’s how I see them and it works for me.
When you do a checkout or reset etc. you are indeed operating on snapshots. You're just saying "get me this version right here".
But when you do cherry-pick or rebase etc, git is operating on diffs. A cherry-pick doesn't set your head or workdir to that version[0], it applies the changes to a new commit that sits on top of your current head. Similarly, a rebase walks through the changes and applies each one to the target.
Commits and diffs are a duality and it matters not one bit how git stores them underneath. You can always calculate one from the other.
[0] If you did want to do that for some reason then git reset will do it for you. That sets your current branch to that commit and with --hard it makes your workdir match. `git checkout <sha1> .` makes your workdir match that older commit, but it doesn't do deletes. If you want to make just your workdir match an older commit the only way I know is something like: `git reset --hard <sha1>; git reset --soft HEAD@{1}` which can be read as "set my current branch to be at sha1 and make working directory match that snapshot exactly, then set my branch to be at where it was before that operation, leaving my workdir alone". It's not a very common thing to want to do.
Technically that's correct, but it is not how branches are thought of and used. Conceptually a branch is, well, a line of development, a sequence of commits with a name. (Actually it is not necessary just a sequence, but this is not important here)
I think the implementation of branches in Git as just a kind of fancy tags is one of the sources of confusion about/around Git.
And I love Git, don't get me wrong! But some things in it make things harder than necessary.
I am old enough to have used SCCS, RCS and CVS extensively. Each had their faults, but Git is the only VCS I have used where dealing with merge conflicts is unintuitive enough that I sometimes end up with the repository in an unusable state. I am sure I am doing something wrong, but I would like to understand why.
The VCS that maps closes to the way my brain works is ClearCase. You essentially have a versioned file system, and you can set up a view to present any previous state of that file system. Of course, administration is a nightmare, it is not distributed, it is expensive, yada, yada. But when using it I always felt I knew exactly what was going on under the covers, which is not the case with Git at all.