219 comments

[ 2.4 ms ] story [ 287 ms ] thread
conceptual designs should be created before. Not afterwards. Using it for analysis of a product will of course create a complicate view since evolution of a product is missed.
This feels more like a learning exercise than an actual critique of git that would necessitate some kind of response. I thought it was a useful way to practice thinking about software design with a tool I already know.
They should perhaps revisit their title, then.
I mean, it's absolutely a criticism of git, so in that sense the title's spot on; it's not a pull request, or a suggestion that we should all go out and stop using git.

I mostly took issue with GP's implication that there was no reason to perform a conceptual design review after the software's already created.

Huh?

This is a critique of the conceptual designs, which is valid. And an improvement upon them IMHO.

I don't understand what you're arguing.

All the examples[1] are about the index. And indeed, the index is probably a feature that should have been optional. While it's supremely useful for maintainer-side operations (splitting patches, picking out parts of them, committing only the meat of a patch and not debug code, etc...) it's really not useful for a typical new user. At all. New users would be best served by a "commit" variant that simply updated the HEAD commit in place.

[1] Actually not the last one. I couldn't figure out what they were talking about. "Only committed versions of a file can be part of a branch" -- I mean... duh? Branches are a DAG of committed trees. I don't understand what they're asking for when they complain that the working version of a file can't be part of more than one branch. It's not part of any yet!

The problem they are talking about is that you can't checkout a new branch wothout first committing, stashing or undoing your work.

In other VCS tools, including gitless apparently, your uncommitted work is also branched (usually by virtue of different branches being chekced out in different directories on disk). You can also achieve this in Git by using different working trees.

Ah, OK. And this is why I think lots of criticism like this is a little off the rails. It's too dependent on what to my eyes is just a broken mental model. I mean... branches are commits. If you want your uncommitted work to be on a branch you... commit it. There's a command for that. It's called "commit".

I mean, literally "git commit -a -m WIP; git checkout $OTHER_BRANCH" is all that's required. It's even "orthogonal" per the requirements in the article.

This is the workflow I follow, but my experience is it's pretty high-mental-overhead in practice - Git doesn't draw a distinction between commits that I created because I'm still working on something and want to switch branches, and are therefore reasonable to modify, and commits that came from some remote source and which you want to keep intact. They're both "commits." That means you, the user, have to keep track of which is which and think about which Git operations might disturb the wrong type of commits.

I'd like Git to have a mode where, say, git rebase -i won't show me published commits by default, only work-in-progress ones. (This isn't necessarily the same as "commits since the remote branch we're tracking," since it's possible to end up tracking a branch that's behind for some reason, or even in a situation like a detached HEAD where you're not tracking anything at all.)

I'd also like Git to keep track of whether I'm done with a commit and ready to publish it. Usually when I commit to switch branches, it's because I'm thinking about the branch I'm switching to, so I don't want to spend the time yet to write a three-paragraph commit message about my current work. But that makes it too easy to push an incomplete commit message.

What you describe seems to be very close to phases in Mercurial. There, the repository keeps track of whether commits were pushed to other repositories. And you can mark commits as secret, which will stop them from getting pushed out:

https://www.mercurial-scm.org/wiki/Phases

> commits that came from some remote source and which you want to keep intact

Don’t tags satisfy that use case?

Well, no, because Git doesn't prevent you from modifying commits pointed to by tags. It prevents you from modifying the tags themselves, which point to the original commits, but that's not relevant - if your workflow involves a lot of "git commit --amend" or 'git rebase -i", it isn't going to stop you from accidentally rebasing or reordering tagged commits.

Also, I don't think I've ever seen a repo where every pushed commit is tagged, and I suspect that will make lots of git operations that look at all refs scale poorly. The use case is that if I make a branch off current master, and then I run "git commit --amend" thinking I've already run "git commit," it shouldn't let me modify that commit - whatever it might be.

> modifying commits pointed to by tags

You can’t modify commits. They are immutable. Tagged commits are reachable so they won’t get gc’ed.

> Also, I don't think I've ever seen a repo where every pushed commit is tagged

Sorry, I thought we were talking about releases (aka important commits). I’ve certainly seen plenty of repos where all the releases are tagged.

I'm in the very early stages of an alternative git client. One of the ideas I've had is marking commits as "WIP". Under the hood it'd just be a prefix, but you could toggle it like a tag. By default push would ignore them.

I also think you should be able to have a bunch of WIP commits at once and stage changes to any of them equally easily (fixup/rebase but easier and by default).

When I read comments like this, I feel sad. Between the lines I always read a resignation to design choices that create unnecessary accidental complexity amd are an obvious step backwards compared to other version control systems, even ones that came before. Competing systems like Mercurial, Plastic or fossil are a simple proof by existence for this claim. Yet there is some snobbery/mysticism around git that makes any meaningful evolution in this space impossible.
Quoted without comment:

> When I read comments like this, I feel sad. Between the lines I always read a resignation to design choices that create unnecessary accidental complexity amd are an obvious step backwards compared to other version control systems, even ones that came before. Competing systems like Mercurial, Plastic or fossil are a simple proof by existence for this claim

And then IMMEDIATELY:

> Yet there is some snobbery/mysticism around git

> Competing systems like Mercurial

To clone Mozilla Firefox, Mercurial requires you have more than 2 Gigabytes of RAM. Not Firefox, Mercurial. Simply because the version history is so large, it requires over 2 Gigabytes of working memory to properly interact with enough to put a current clone of the repo on your computer.

As long as we're sharing anecdotes: I've seen Mercurial used very successfully with much, much larger repositories than Firefox and I'm fully aware that Firefox is already big. I don't know how big the full repositories were, but I am assuming at least several TB. I have never seen git getting pushed that far and I have no idea how it would behave then. My guess is that it would slow down a lot, but at least not fail entirely.
I belive the entire Windows codebase is in a git monorepo now, for some years: https://devblogs.microsoft.com/bharry/the-largest-git-repo-o...
Yes, but that's with a MS-specific extension to Git, which I don't remember if they ever upstreamed - one that allows partial clone and partial chekcout if I remeber correctly.
Partial checkout already exists:

`git clone --depth 1 <url>`

Of course you mean, only one small repo -- but the more git-like strategy is to specifically use submodules

What developers these days don't have at least 8Gb of RAM?
People who like smaller systems? Or are poor?
The idea is that that mental model isn't a very useful and error-proof one. Saying "well that's the mental model" doesn't evade criticism of the mental model.
But it's not criticism of the mental model, it's criticism that fails to recognize there are models that fit the problem better. Again, changing a branch and preserving your work is sequence of two commands; two very obvious commands applied in the obvious way, no less.

You learn what a "branch" is in git and then you don't have problem. The problem with the linked article is that it's trying to understand a "branch" to mean something like a "working area" or a "checked out directory" or something. And that's just a different mental model. And getting to the criticism in the linked article, it's a very non-orthogonal model, polluting the idea of a "working tree" (which in git just means "the filesystem contents") with weird metadata about where the files "came from".

> it's criticism that fails to recognize

I really don't think that's the right interpretation. This is a direct criticism of the correct mental model for Git. In essence is says "This model, although functional, is way more complex than it needs to be. Here is a simpler model that accomplishes the same goals with less confusion for beginners and experienced users alike."

I don't see a great argument for the concept of un-committed changes existing at all, and the tool would be a lot easier if they didn't. My implementation would be for every local change to be immediately amended onto HEAD, and then `git commit` creates a new empty commit for subsequent changes to attach to. If you want to not include a file, exclude it. Or something like that, I haven't thought about it in detail really.

> "This model, although functional, is way more complex than it needs to be.

And I don't see how that statement is consistent with:

    # First Understand "branches are commits"
    $ git commit -a -m WIP
    $ git checkout $NEW_BRANCH
I just don't, sorry. Trivial concept. Trivial composition of two commands you use all the time. Orthogonal. Clean. Straightforward. There are areas (c.f. the index, which I talked about upthread) where git does make excessive demands on the new learner. This is simply not one. Sorry.

What the linked article wants to see is objectively a more complicated UI based on an incorrect understanding[1] of what a git branch is.

[1] Which is weird, because git's idea of "working on a branch" is fundamentally unchanged from the legacy handed down from SCCS, RCS, CVS and subversion. NONE of those tools ever did this!

I dunno what to tell you. I just don't think it's a good model. I've obviously learned to deal with it, but every time I have to type two commands instead of one to do that, I think: it's a shame this was designed like this and is so hard to change now.
You're showing the stashing part, but disregarding what happens when you get back to the original work. You now have to either git reset or git ammend or git squash later and so on. Oh, and if you want to edit the commit PRIOR to the WIP one, you need to do several of these, simce only the latest commit can be eaisly edited.

And the alternative in something like P4?

  cd ../other-branch
  # do some work
  cd ../first-branch
Vs the git solution:

  git commit -a -m WIP
  git checkout other_branch
  # hopefully you didn't have any untracked files that you meant to add on both branches
  #do some work
  git checkout first_branch
  git reset -s first_branch~1
Sure, it's not THAT hard, but it's still much harder than it needs to be, it's harder to learn, easier to mess up and takes far more time (git checkout is not an instantaneous operation, unlike cd). Not to mention, there are at least 4 ways to do this, with slightly different trade-offs, further increasing the mental load.

Edit: oh, and let's not forget that sometimes you want to actually compile and run and debug two different versionsof your product, and then the git model completely breaks down.

Where I work nobody enforces which RCS to use so we have lots of SVN, lots of git and some Perforce and maybe another oddball somewhere.

Even though it's ugly I'm sold on git but every time I recommend it to someone who is not used to it I tell them: "get used to the idea that you only ever have one version of your source on your PC, no concurrent branches in different directories." I know git half-heartedly supports multiple working trees but I think you're best off doing the "commit-WIP-then-branch" approach. Even stash is pretty ugly, I only use it for things like config files someone committed that I in turn would never commit (like IDE proj files, etc.)

Which is, of course, bollocks, because the first thing anybody does when working on a hotfix is to create a new directory and create a new tree there. The only difference between git and perforce is that in perforce, the concept of directory<->branch is built into the system, but with git, git knows nothing about it and its all manual. Instead, git is one of the VCS that says "you can have only one working copy on disk" and when you switch branches we'll believe that you want to throw away what you were just working on.

I used to think the "one directory" for everything was the way to go. In a startup around 2001, I tried ten different VCS systems, including looking for ones I'd never heard of (e.g. AccuRev), and I initially vetoed perforce because it wanted to put different branches in different folders. When all was said and done, I selected perforce for precisely that reason.

But git goes further than simply not having "multiple checked out branches" as a concept. Instead, its conceptual model for why I have branches and what I want to do with them is fundamentally wrong, as this article points out.

> because the first thing anybody does when working on a hotfix is to create a new directory and create a new tree there.

This doesn't make any sense, at all, in any way or form to me. Everything from build systems to IDEs is tied to were the tree is, I most certainly _don't_ want to constantly change that.

> and when you switch branches we'll believe that you want to throw away what you were just working on.

Switching branches checks if something would be thrown away, and will not proceed if that is the case.

>Everything from build systems to IDEs is tied to were the tree is, I most certainly _don't_ want to constantly change that.

I am so sorry. That sounds awful. Life doesn't have to be that way.

Aren't the IDE and build system files IN the repo? Normally if I clone a repo from scratch, all I have to do to build is `cd new-location/ && make`, and similarly to import in the IDE (though of course that will take some extra time, because I'll be forced to do a clean build, probably also re-downloading all dependencies).
Yes, they are in the repo but we usually have one developer own those and manage the check-ins. That avoids minor differences in those files, which sometimes depend on details of the workstation setup. There's probably a better way to handle this
I mean sure but it turns this:

- Checkout different branch

- Continue working

Into this:

- Checkout branch into new location

- (Close project in IDE)

- Browse to branch location, open IDE

- Move all terminal $PWDs to the new location, as needed

- Make a coffee while the IDE re-indexes everything

- (Compiled languages: Hit make, enjoy another coffee while the build runs)

- (npm and similarly structured projects: Hit install, enjoy another coffee while all dependencies are being fetched/checked/installed)

And I just don't see how that's desirable. With git I can create, merge and switch between branches in seconds, and don't really have any problems (I've almost never felt the need to have two branches checked out at the same time). But with tree-per-branch I only see problems, and all branch-related operations become slow and annoying.

One use case I could imagine where this is useful if you are simultaneously maintaining several mainline branches. But even within those "work areas" I'd rather be able to quickly switch branches.

If you're switching branches, you anyway need to reindex and download new dependencies. You may not need to for very close branches, but in the general case that is not guaranteed.
> the first thing anybody does when working on a hotfix is to create a new directory and create a new tree there.

What do you base this statement on? The only people I've ever seen do this is those who are firmly entrenched in how TFS or SVN work. That's a far cry from "anybody".

‘New users would be best served by a "commit" variant that simply updated the HEAD commit in place.’

Isn’t this what `git commit -a` does? Or do I misunderstand?

The article already contains an eloquent critique of this option: its semantics are too broad and inflexible to cover that use case.
Hmm, I wonder if GP meant `git commit --amend` which is not the same as `git commit -a` (`git commit --all`)?
An actually sane VCS would stash your changes when switching to another branch amd restore the stash when switching back, at least by default. Git instead handles this in a way that introduces a handful of potentially data shredding gotchas.
I always just commit my changes and switch branches, come back and squash.
This is my go-to as well. I use commit messages like "intermediate work" and then push to origin to make it clear things are in a temporary state from the outside (i.e. someone looking at exploding check builds).

You can usually infer exactly what I am working on just by reading the latest commit messages on all of my branches.

This actually... makes a ton of sense.

Just as one example, automatically stashing and unstashing when switching branches is so obviously convenient, in hindsight I'm puzzled as to why git doesn't just do this automatically.

So whatever happened to this project?

In the same way SourceTree adopted git-flow with a few special menu commands, I'd love to also see it be able to adopt a "gitless" mode.

Because the great thing about this model is that it can still remain the more complicated git underneath. It just makes using it more straightforward.

> Just as one example, automatically stashing and unstashing when switching branches is so obviously convenient, in hindsight I'm puzzled as to why git doesn't just do this automatically.

Yeah, I too have wished for this. Do note that that can fail, if there are conflicts, so you'd have to decide whether to fail the command (like today) or to drop the user into conflict resolution.

That's the exception, though. If the changes are automatically stashed when you switch away from a branch, restored when you switch back, and operations that act on branches require you to first switch to that branch, the stash and branch won't get out of sync, the stash will be right on top of the branch. Conflicts shouldn't happen unless you do weird things like reset a branch (git checkout -B) or update it directly without checking it out (git update-ref), in which case you might not even need conflict resolution, such operations might just be specified to immediately drop the stash.
I've used Tower for a couple of years, and that's exactly what it does. Auto-stash, re-apply stash when switching branches, and then you have to resolve the merge conflict in the UI if it doesn't re-apply cleanly. Or you just stash if you didn't actually want to re-apply those changes after all, but I've found the default to re-apply to be what I wanted the majority of the time.
Git could not do this (automatically stashing and unstashing) in a backwards-compatible fashion, but it could be opt-in with a new config key and does seem like a very useful feature. It also seems like one that should be relatively easy to implement as all the main logic to actually stash and unstash is already part of Git, it would merely need to be saved to a different ref than refs/stash, so all it needs is someone with enough interest to actually implement it.
My biggest complaints with git is more around how it is difficult to throw away changes and how ignoring files is such a stateful process. I almost never need something nearly as specific as "git checkout" or "git reset". I generally either want to throw everything away up to the last local commit or I want to throw away everything and get my version to be the same as the branch on the server/another local branch. If I need something more fine grained, I will still do this, just on a per file basis. At no point have I ever wanted to just get rid of untracked files.

That leads to my next point about ignores. I hate that I have to manually untrack a file even if it would be ignored according to a new ignore file. The best practice there in my opinion would be to explicitly exclude said file IN THE IGNORE FILE ITSELF from being ignored. The other problem with ignore rules in Git is that for some reason Git tries to hide the fact that it has a per repo ignore file in the .git folder which doesn't get synced at all.

Instead of having untracked/tracked files, I would really rather just use ignore files, both local and remote, so that excluding something from being committed is an explicit action.

It is also really absurd that you can reset everything with `git reset --hard` but you have to use `git checkout -- ` to reset individual files. It's not even clear why `checkout` is involved in resetting files at all.
`git reset --hard` resets your worktree to the HEAD state.

`git checkout -- <file>` checks out the HEAD version of a given file.

Yes, but you can also word it in two other ways that make the redundancy more apparent at the cost of a tiny bit of inaccuracy:

A:

`git reset --hard` checks out the HEAD version of your worktree.

`git checkout -- <file>` checks out the HEAD version of a given file.

B:

`git reset --hard` resets your worktree to the HEAD state.

`git checkout -- <file>` resets a file to the HEAD state.

I don't think a newbie could even grok the difference between these three descriptions.

git reset --hard does not only modify the work tree though, it also updates where your current branch points to. In fact, that is the main purpose of ‘git reset’, the fact that it modified your work tree at all is only because of the ‘--hard’ flag. So it’s not correct to say that “reset --hard and checkout do basically the same thing”.

  git reset --hard HEAD
Does in fact only modify the work tree, since updating the current branch to HEAD is a nop.
You could word it that way, but it's not actually correct. Check the man pages to see why. (Or read the other commenter's note which gives a brief explanation.)

> I don't think a newbie could even grok the difference between these three descriptions.

This part is definitely true - Git offers only a very light abstraction on top of its data structures.

  git-reset - Reset current HEAD to the specified state
  git-checkout - Switch branches or restore working tree files
reset works primarily to modify HEAD, and when you include `--hard` it's like saying: oh and btw also update the working tree.

checkout works primarily (in the case of checkout <pathspec>) to copy file from somewhere (commit, index) into the working tree. You can use checkout to mimic a hard reset: `git reset --hard` is pretty much equivalent to `git checkout HEAD .`

I understand how it works, but I still think it's dumb.
Is it perhaps because most people are taught about `reset --hard` early on? If all they knew was `checkout` there wouldn't be any confusion.

Nobody really needs to know about `reset --hard`, the same effect can be achieved with a normal reset and a checkout, it's a two step process anyway, regardless of how you do it.

Using reset+checkout is not the quickest but gets the job done. Power users can create themselves a shell alias, or write themselves a small shell script, or learn about one of the thousands commandline switches that does just what they need.

For me, checkout is easy to remember because I'm literally checking out an older version of the file.
The future is `git restore` and `git switch`, which clear up the ambiguity between different modes of reset/checkout.
The logic is that you checkout the old file from the repository into your worktree, overwriting it.

If there's something inconsistent about this, it's that it works for individual files, but (fortunately) not for clobbering your entire worktree by checking out a branch.

I find that I often want to throw everything away except 2 files. Occasionally I want to stash everything except 2 files. These are both especially hard to do, especially if you renamed or copied a file, which is now a staged change.
> I want to throw away everything and get my version to be the same as the branch on the server/another local branch.

Isn't that `git reset --hard remote/branch` ?

That doesn't remove untracked files. You need to do the memorable "git clean -fd" to get rid of those. This is my point: two commands for what should be doable in one simple command.
I have a dedicated shell script for each thing I do in git, because it is impossible for me to memorize the original syntax and I'm terrified I'll gut the repo by getting a flag wrong. I have an "undo" script, that just gets rid of unstaged changes, a branch deletion script for removing local & remote branches, and so on. I just take my scripts with me everywhere I go. They're not ingenious, just a necessity. I advise doing this.

People like to tell me, "Oh see you just need to understand that git has something called a 'directed acyclic graph' blahblahblahblah" and no, that has nothing to do with it. It's just incredibly arcane and bizarre syntax for the most obvious everyday operations. But since my scripts make life easy, I sort of don't care at this point.

Also I don't rebase and thus I don't force push and thus I don't reduce my repo to a pile of burning rubble and work til 3AM repairing it and if people ask me to rebase enough times I'll threaten to literally take a dump on their desk/car/front porch which usually makes them stop. I advise doing this as well.

I have never understood the benefit of hiding away the actual history. Many people i know find rebasing to be superior because you have this pretty history, but i actually love having the ability to understand what happended when exactly as it happened in reality.
"No broken commits" is one, "nobody cares how often you merged master into your feature branch" is another. Most of the real reasons revolve around making a bisect quicker and easier.
If it helps any, I usually squash-merge my final output into sparkling, concise commits with nice messages (my original branch usually has "x" for every commit message). And yes I have a dedicated script for squash-merging too...
My preference is to disallow/disable fast-forward (and squash) merges to master, and then use --first-parent when you want the clean non-broken easily bisectable history, which is most of the time, but the full messy history is still there.

I actually don't care so much about seeing the messy history, but its annoying when everyone on a team is always rebasing there branches and force pushing them. I've definitely been at places where one team member is waiting on another to merge their code to master so they can start working.

Of course, rebase all you want locally before you push.

Try not pulling other team members branches. What I mean by that is instead of having your own branch HEAD/tag (whatever the term?) for their branch, do `git checkout origin/their-branch` when checking out their branch. Git will complain about a detached HEAD, that's OK. Then later when they rebase and force push, just do the same thing again, and you never have to worry about making sure your own local branch for their branch is in sync.
I agree here. It feels like the popularity of git has lead us to believe that you either have to choose a clean history or the real history.

But I imagine there exists a design for a source control system where you can preserve history and also provide a clean interpretation of that history.

For example, why does rebase have to be a destructive operation?

This depends on mindset: what do you consider the primary building block for a changeset - the changes, as made by the developer in chronological order or changes that are related by logic, despite the time (and order) in which they were made. I favor the latter approach - developers in the future will have no use in knowing the different approaches I attempted before coming to the actual solution - that is noise. What they should care about (IMO) are logical commits with related changes.

Additionally, "preserving detailed history" makes cherry-picking fixes across multiple release branches a royal pain: not everyone has the luxury of having on single, ever-green release branch. So having a feature/fix in a single, self-contained commit that hides away the actual history is a good thing in this scenario (with the added bonus of not running into fluff/half-cooked commits when running git bisect)

I usually squash my commits before merging, i think that makes things easier while still keeping the chronological view intact
It's not about history. It's about correctness.

Let's say there's a master branch at commit M1. You branch off of it. You develop your feature branch. In the meantime, master is at commit M26. Now on your feature branch all your tests pass, maybe even there's no merge conflict with the current master. But! Master's behaviour changed and you have no idea about it, and no idea whether it affects your branch. You merge feature to branch and kaboom!

Frequent rebasing protects you from this.

You could also frequently merge master into your branch and squash at the end.
But squashing changes chronological view, right? Either you want "real" history or not.

If you're already willing to do that then with the same amount of work you get cleaner history with rebasing, i.e. no intertwined branches.

> I'm terrified I'll gut the repo by getting a flag wrong.

There are pretty few operations in git which can't be undone by a quick glance at git-reflog.

Or even just frequent backups. A coworker of mine used to copy the entire on-disk repository including the .git directory before doing anything slightly unusual in git. Very paranoid I thought, but then he could recover from a fat-fingered wrong flag very quickly.
"Or even just frequent backups."

? Well, git is supposed to be that 'history' for us. I understand it's not exactly a backup, but it is pragmatically that. I don't want to have a 'backup of backup' I want to feel confident with what's there.

Which is ironic since source control is supposed to prevent the need for manually making backup copies.
> Or even just frequent backups. A coworker of mine used to copy the entire on-disk repository including the .git directory before doing anything slightly unusual in git.

To recover from commit screwups, it should be enough to just export the git repository somewhere else like a remote repo, or even just apply changes in a separate branch.

It just seems that a developer is doing something extremely wrong with their repository if they can't recover from their experiments by checking out a branch or commit.

Or slightly more comfortable —- create a temp branch before doing something. Of course that’s basically the same thing under the hood.
> I have a dedicated shell script for each thing I do in git, because it is impossible for me to memorize the original syntax and I'm terrified I'll gut the repo by getting a flag wrong. I have an "undo" script, that just gets rid of unstaged changes, a branch deletion script for removing local & remote branches, and so on. I just take my scripts with me everywhere I go. They're not ingenious, just a necessity. I advise doing this.

I don't blame you, and I'm actually an enthusiastic proponent of this approach. It's just more ergonomic. I personally have an extensive list of idiosyncratic aliases that perform (conceptually) high-level operations on top of my git repo to let me work faster and use up less mental power to reason about what I'm doing. I use them at home and have carried them from one job to another (and am planning to do so indefinitely), taking care to keep things bash-based and portable between OSes.

I barely ever drop down to using built-in Git operations directly, besides `git push`. I don't need to, and I don't want to, even though I know enough about git to untangle complicated rebase clusterf*cks by reasoning about and operating directly on the DAG if I have to.

As a tech lead on game projects that feature less than the most technical developers (artists, game designers) the biggest "problem" with git happens to be its strength.

The inconsistent cli and naming can be pasted over with cleaner GUIs. No, the real issue is gits configurability. Not just the configurability but the enforced decentralization of that configurability.

For better or worse, it seems like git is actively hostile to any kind of concept of a central authority. Can I configure hooks for my team? No. Can I configure diff tools for my team? No. Can set recursive pulls to be the default? No. Can I enforce line endings? No. Can I even enforce LFS is setup right? No.

I certainly can't even enforce things in receive hooks and expect team members to know how to change their histories to adhere to our policies.

I love git but its such a pain.

I really wish someone would come up with some kind of "accept remote config" policy or some such thing.

Someone can correct me if I’m wrong but I think mercurial would solve the issues you describe.
Git isn’t chosen on its own merits - it’s what the other tools support. If CI servers, issue managers, container tools etc all had a nice VCS-agnostic interface that would be great. But they don’t. We’re stuck with git or at least something that speaks git. Choosing an objectively “better” VCS is often directly coupled to the loss of some other tool.
(comment deleted)
What you’re looking for is called Perforce.
Perforce for assets? Sure.

Perforce for code? It tries.. but, no thank you. Even if the client was good (it's really not, and will spin a CPU core when it detects a network hiccup) -- it still has a frustrating workflow paradigm, p4ignore is configured per-client, everything is read-only (leading to ugly context switches unless your editor directly integrates with p4) and the command line is so terrible that I think it was actually a joke that someone ran with.

No, perforce is not what you want, without even going into the "decentralised vs centralised" aspect of it.

It's not p4 but Google3 + Hg CLI was way more fluent than I expected. I wish the third party portion were available as a service outside.
Well, piper is "perforce that doesn't suck (and costs a heck of a lot less)"

I wish it were public.

Last time I used Perforce was around ~2006 in a mobile OS company of approx 3000 devs. Boy was that thing slow.

Still though, P4Merge is my go-to diffing tool.

I worked at a company that used Perforce. It took 2 weeks to get a branch created. No thank you.
Really not defending perforce, but "branches" are not how you usually work, a branch is basically a fork of the repo, it's almost impossible to reconcile.

"Branches", the way we use them in git are more like perforce Shelved CLs.

Yes, I remember using "shelves" a lot. The branch was created for a major refactor of the code base, and the team I was on moved to that branch. Not sure what happened to it. I hated that company and left after a few months.
I wonder if it’s a workplace I worked at. We ended up having a bunch of generic branch names (one org had colors, another had star-related things), and when a project was done, they just used that branch for something else.
It's worth pointing out that shelved changes don't have any history though, so cross your fingers to make sure you don't accidentally delete/overwrite something.

Fwiw the only major issue I've had with branching is the fact its a full fat copy of your source which can end up taking up a load of space depending on how large your application is post build and how many binaries you dump into perforce.

They seem to be pushing towards "streams" now, but I've heard they make it easier to create development branches but haven't worked with them to find out.

Nah, shelves have no history. They're more like a patch. P4 streams are the closest to git branches.

  p4 branch <name>
  (fill out the mapping)
  p4 integrate -b branch //...
I wonder what took the "IT department" so long then...
I'm sure that using commercial source control software doesn't HAVE to suck, per se, but the kind of companies that can afford Perforce are usually so bloated and political that you can't avoid that kind of bureaucracy.
Yep. There are reasons Perforce is so prevalent in game development, even if we gripe about it:

- Large binary asset support.

- Exclusive locks for binary assets.

- Speed. When configured properly, it's very fast, particularly for large repositories.

- Tooling support. It's nearly ubiquitous in gaming.

I'm working on repositories that are 100+ GB and need to support both programmers and artists. Git just isn't the right tool.

We do use git for backend server-side projects. Having to support two different version control systems is a pain, but, again, it's useful to have the right tool for the job.

Perforce has a product called Helix4Git, which apparently allows you to use Git with Perforce. Is it something you tried?
Its still pretty fragile but it does work. I wouldn't recommend it for daily usage though. Too many things in p4 show up as impossible breaking changes to the git repo.
I really love the pragmatism behind saying: yes, X sucks, but it’s still useful.
Git LFS is really robust these days. Personally I think its faster then Perforce now. Reconciles can hang and there's just so much more that can be done in git without hitting the network.

GitLFS supports locking too.

I like that a changelist is one atomic unit in time across everything. Don't underestimate the number of footshoots this avoids. and you can build your tooling on top of it.
(comment deleted)
P4 is ok even if I find it buggy and slow.

Ironically, what files count for p4ignore isn't even centrally configurable. You can't configure diff tools or merge strategies centrally either.

Because every commit has to hit central, receive hooks are actually useful and you can do big branch remappings in a more seamless way than adding a git submodule.

I forget where line ending configs live.

P4 solves some of these things but not everything and adds its own headaches.

There are two ways to solve this:

1. Write and distribute a wrapper script that sets up the clone in the way you want, with hooks, diff tools, LFS, etc.

If you're talking about diff tools and LFS, you need some way to actually install those components, so presumably it shouldn't be hard to get this wrapper script onto end user machines.

My employer uses this and it works well - we have a special command that creates a new clone and configures it appropriately. Once you have a clone, you can use the git commands as usual. People seem fine with this workflow. (Previously the special command was named something like "git-xyzclone" so you could just run "git xyzclone" instead of "git clone". But as we added a few more build/review/release features, we made a general "xyzdev" command with a few subcommands, of which "clone" is one.)

2. Set up /etc/gitconfig (or ~/.gitconfig) and in particular the init.templateDir setting to customize the settings you need.

Again, presumably you have some way to install things - but if you don't, tell employees to run "curl -O https://corp.example.com/.gitconfig" as part of setup, and then you can bootstrap yourself from there.

Git doesn't and cannot accept executable hooks/LFS helpers/etc. from an arbitrary remote server because then as soon as one of your employees clones something from GitHub they'll get event-stream'd. Your authority to enforce configuration comes from your authority (either technical or social) to get employees to install things on their machines - if you have that authority, then there are multiple ways to accomplish this.

(I suppose Git could have a "core.executeArbitraryCodeFrom=https://git.example.com" setting or something, but if you have the ability to push out that setting to all your users, you have the ability to push the actual settings you want.)

>Git doesn't and cannot accept executable hooks/LFS helpers/etc.

I understand the sentiment but I feel like this is just not true. Its designed to share code you plan to build and run, after all. As you say, it seems like we could solve it with signatures and trust but I doubt we'll ever get there, sadly.

Sure, then you have option 3: Put a "setup-git.sh" script in your repo, and have the README tell users to run that to set things up. If your users are willing to run code from your repo, you can just have them run code from your repo.

In fact you can set the default branch to contain nothing but those two files, and have the setup script switch to the real branch once it's done with its work.

(And if your users have some other way they expect to run code - make, setup.py, yarn run, whatever - you can use that instead as the entry point.)

> Sure, then you have option 3: Put a "setup-git.sh" script in your repo, and have the README tell users to run that to set things up.

This is pretty good advice. If projects expect onboarders to run one-time scripts to install third-party dependencies, the least they can do is to also provide scripts to setup the development environment.

(comment deleted)
It seems like what you're asking for is a way to configure your coworkers remote computers. Shouldn't you do that with a more general configuration management tool, in the same way you configure any other application for your team? For example, on Windows you could use a custom Default User Profile with .git/config already set up the way you like it.
Sure there are other solutions. You can kick the can to something else.

I still think git should have a solution for a consistent default configuration if features are going to be supported as opt in configurations.

No, I don't care what computer they are on. I want anyone that pulls my repo to have my hooks etc by default. Even me! Listen, I know there are workarounds, I know there are ways to solve this. My point is they suck.
> I want anyone that pulls my repo to have my hooks etc by default.

That would be a security vulnerability. Hooks in git are not sandboxed, so you'd have arbitrary code execution in the machine of anyone that pulls your repository. In fact, some of the worst security issues the git project has had were ways of tricking it (usually through case-insensitive or normalizing filesystems) into setting arbitrary hooks.

It doesn't need to be without user approval. I would be more than happy to email out a trustable commit hash or what have you.
Having an option to opt-out of some enforcement is somehow not much better than no enforcement at all.
Which may be an issue in open-source, but not so much in a captive environment. When I clone an internal repo and run it on local, I myself don't do a security review of it. I trust rest of the people in company to have reviewed the code before committing to main branch. Company has the remote root access on the device it provided and we have internal binaries/scripts regularly used on laptops.
How is this risk any different from that of a Makefile?
When one executes code from a Makefile, one expects arbitrary code execution. When cloning a repository, or updating a clone of a repository, one does not expect arbitrary code execution. Moreover, one can inspect a Makefile before running code from it, by cloning the repository and looking at the Makefile with a text editor; but one cannot inspect what will be cloned before cloning it (and even if you could, for instance by looking at it through a web interface, there's an obvious TOCTOU risk).
I see the problem with hooks that run when a repository is cloned. I don't see it with hooks that run when code is committed.
I might consider fixing a readme typo in somebody's project while taking less precautions than I would running make.
(comment deleted)
Regarding hooks, you can put hooks into the repo

https://www.viget.com/articles/two-ways-to-share-git-hooks-w...

Granted, each team member must set core.hooksPath in their config.

Hope this helps.

If you provide a script for setting up their user environment then that can easily be added. I've done so with Chocolatey and PowerShell (because it's available on all OSes).
If you want central authority, isn't it the job of CI?

Use CI to verify the commits fits the styles and review the commits before merging.

CI can punish but it can't solve the problem.
What works really well for me is using the pre-commit framework (https://pre-commit.com/). This offers an easy way for everybody to run defined hooks locally before committing or pushing changes. That allows people to solve problems before pushing code, but if they don't, they'll get punished by CI, as the same hooks run there as well.
> If you want central authority, isn't it the job of CI?

You can't fault a tool for not providing a centralized authority when it's very central design principle is being decentralized.

Why is that git's responsibility? I've always solved that by making an install script for my team. It installs the our "version control system" (which might be git). It also installs it in such a way that it gets auto-updated. Can push a new config and everyone gets it.
> Can I enforce line endings?

.gitattributes text setting

Aha cool. I thought this was a slightly different setting than whats in the config but it looks like the naming is just inconsistent.

That's one down. Thank you very much.

This is a lot to address but please consider all of the facts.

There are hooks for pulling and whatnot. This is a vector for malicious code execution.

Not everyone has a GUI system (window system).

Git _has_ to be able to expose low level commands while still being usable.

Decentralization is a _good thing_ when it comes to git. It is directly one of the goals of open source software, for which Git was built.

You can configure all of these things for your team provided they run an init command at least once. Then set up your system however you like via hooks that automatically run to update their configs as necessary.

Remember, your development process is not my development process. Also, not every system that clones a repository wants to run hooks. Git shouldn't force them to.

You can 100% enforce line endings, please research. You just have to configure it in the unit command as mentioned above.

If your team members are not adhering to your policies, then you're not managing them well. It's not git's problem to do your job for you.

For those of us who use git on a daily basis across a wide range of environments, repositories, usecases and websites, git is a dream to use.

Really tired of the "git hate".

> If your team members are not adhering to your policies, then you're not managing them well. It's not git's problem to do your job for you.

Well it could help you in that, and we know that because other VCSes do. Have you managed a large inexperienced team with an offshore component? You are herding cats and there is never enough time to look at everything.

> Have you managed a large inexperienced team with an offshore component?

Yep! The engineers we had worked fine like this with just Git, github issues and slack at the time. I've left the company but I know that slack didn't suit them well so they switched to something else.

They swear by git still though. It works fine.

> Not everyone has a GUI system (window system).

This is true. But the number of devs who work entirely in shell is dwindling as basically everything transitions to IDEs.

> If your team members are not adhering to your policies, then you're not managing them well. It's not git's problem to do your job for you.

I totally disagree. There is a reason why we use linters to enforce style guides. Tooling and automation always helps, even if you have strong engineers who are trying to do the right thing.

> everything transitions to IDEs.

You're in a bubble then.

This line of thinking is low effort and pointless. There's no hate here (in fact, I said love). Tools are meant to be helpful and talking about how to improve them is always good.

Git has lots of configs that are automatic and shared and that you can locally override yourself such as the ignore and attribute files. Why not more? I have no issue (and in fact prefer) if clients can override the projects configuration but git should support a turnkey workflow, at least for its own features. As it stands, you can't add submodules seamlessly, for example.

Its not about adherence. Workflow as Simon-Says is just a bad workflow. This stuff should take zero cognitive load.

(comment deleted)
> I really wish someone would come up with some kind of "accept remote config" policy or some such thing.

But there is a workaround: centralising the .gitconfig in a repo, then symbolically linking it to your home directory, so it gets updated on every pull. I haven't done the experiment, but you might be able to do the same with your .git/hooks.

If need be, you could even cron a git pull in that repo, or add it to your .bashrc, $profile for PowerShell or schedule a task.

I agree, it is a bit of a pain.

I think what's often overlooked is that git wasn't created as a general solution to version control, but for the specific needs of Linux kernel development.

Game development is pretty much on the opposite end of the spectrum, 99.9% of game project data isn't text (maybe not by number of files, but definitely by file size), and game development also traditionally isn't as extremely decentralized as Linux kernel development.

The problem isn't git, but that it has become so popular that it's now the defacto standard version control system, and people expect it to work for scenarios it wasn't created for (at least people who don't know much about the specific workflow requirements of large-scale game development).

Specifically the thing that Git was meant for was there being multiple authoritative roots. In the sense that RedHat can keep their own kernel line and just bring in patches from Linus and other maintainers as well. Everyone can be authoritative! This is great for example if you need to fork a library to make some changes (eg replace a library it's using in it's implementation) but want to keep current with it's improvements.

...this is not the case for most projects. They want to have a clear source of truth for the code.

I'm not sure that is overlooked. It is just not especially relevant for a modern world where the huge majority of versioned development at centralized organizations is done with git.

Almost none of these thinkpieces about git are saying that Linus was foolish in his approach or that he should have anticipated git becoming the lingua franca for junior engineers at megacorps. Instead almost all of them are talking about git's failing in modern development and bemoaning the lack of a better option, followed by a bunch of people insisting that git is actually awesome as long as you can reason about graphs.

I think your criticism here is actually implicitly already part of almost every discussion about git in modern times.

It's not a criticism really, just an observation ;)

It just happens so that most software development is more like the Linux kernel and less like game development, so for most software projects git is a good fit indeed, just not for gamedev. Maybe it's worth looking into how CGI studios are managing their data, I guess they have the same problems to solve as gamedev studios.

There are specialized solutions though, like https://www.plasticscm.com/ (quite recently acquired by Unity).

> It just happens so that most software development is more like the Linux kernel and less like game development

I don't think that is true. Linux kernel development is truly distributed. It is expected that there are multiple completely deployed branches. Most software development using git does have a single point of ownership and a single release branch. It is done on company machines with managed environments.

> git wasn't created as a general solution to version control, but for the specific needs of Linux kernel development.

This is an instance of a more general problem in all software development: tools are created usually to serve a purpose, but when they become popular they end up being used, not because they're a good fit for the problem, but because they're popular -- everybody knows how to use them. The tradeoff (which may be worth making) is the time to learn a new tool vs. the continual cost of using a tool that's not quite right for what you're doing.

I use magit and everything seems a lot less wrong :).

I personally found Mercurials a lot more palatable but at this stage git is pretty much ubiquitous.

I was trying to explain Mercurial to other folks at a Subversion shop to make the case for DVCS to them, seemed like it'd be easier to describe the shift from SVN to Hg rather than SVN to Git. As part of this I drew a diagram on a whiteboard I had put up in my cubicle, which I then forgot to erase.

A week later I was in the middle of coding when one of the nontechnical people from the company came by my cubicle, I motioned for him to wait a minute for me to finish what I was writing before talking to him, which he did. When I finally looked up, "okay, what's up," he said "first, let me see if I got the gist of this diagram. Inside of a folder you have this repository file named .hg..." and proceeded to give me a half-good explanation of how Mercurial works. And I was just floored and I said "yep, yep, there you go," and at the end he was like "so why do we save all of these things in Fileserv with a timestamp at the end?" and I was like "well I'll send you a link to TortoiseHg and you can just start using it, but yeah most people just want to do the thing that's in front of them and don't want to learn a whole tool that does things differently to get started." (And sadly that was true and as far as I know he also never invested the time to learn TortoiseHg and get started with that, haha.)

Left an impression on me, for sure.

Magit is by far the best way to use git. I've been the git expert at every job I've been in but I owe it all to magit. It really can't be overstated how big of a game changer it is.

I've thought about writing a stand-alone TUI version for people who are scared of emacs. But then I realise it's impossible. Magit is inseparable from emacs. Emacs is what makes it so powerful. Emacs is not a text editor, it's an environment for interacting with text-based tools. Having your text editor and git, as well as every other text tool, in the same environment with the same interface is unbeatable.

I'm also a big fan of magit. I used to use emacs for everything, but I found myself taking longer to do tasks on average and having an unacceptably high worst case of a day spent debugging why it's borked. Now I only use emacs for magit. I'm currently working on a magit TUI in Rust. It's very early stages. I'm hoping even if the project fails at least I'll understand git a lot better.
> I personally found Mercurials a lot more palatable but at this stage git is pretty much ubiquitous.

What I really want at this point is somebody to build a mercurial frontend for the git backend, so that I get to use the clean frontend of mercurial to work with git repos. No, hg-git doesn't really work for this purpose, and I have tried it for my work git repos (which are in the 100k of commit range).

Apparently, there's a new git extension for mercurial that is closer to the model I want, but I haven't had a chance to try it yet.

I've had hg said to be the James Bond to git's MacGyver.
It's interesting. I had to use mercurial for work at some point and I just could not get comfortable with it. I know several people that feel like me, and several that feel the same about git. I wonder what the difference leading to that are.
In my case, I got accustomed to very rebase heavy workflows in git, and history rewriting in mercurial feels feels tacked on.

With git, the repo is basically just a DAG plus refs, and once you understand that model, it mostly makes sense.

hg has... three? four? different ways to rewrite history, that all work in different ways under the hood. It's not that bad once you realize you should just use the evolve extension and ignore everything else, but as a novice it's hard to know which method to use, and it's not hard to stumble upon people recommending awful footguns like `rollback`.

> the elimination of the staging area was enthusiastically received as a major reduction in complexity

Well, that'd be a huge negative to me… it is much easier to build up a commit piece by piece, verify that it's correct, and then commit it. The only two alternatives I can imagine are worse: disallowing partial commits (I use this feature all the time; I guess I could replace it by a temp commit on a branch and then so long as cherry-pick -p still exists, use that) or specifying it to git-commit (that would be one gnarly set of flags…)

Sure, a git without a staging area is less complex. But it removes essential complexity, and would no longer solve my problems.

(And in systems that lack it, like Perforce, I've sorely wanted it, and there was just not a good workaround. But yeah, that aspect of the VCS was simpler, I suppose…)

I do agree teaching the staging area seems problematic. Newcomers struggle with it. I also agree with how the article mentions other commands sometimes effecting one or more of the staging area & working dir, and it being unclear when are where they effect what. `reset` is perhaps the most confusing command in that regard. Outside of "git reset", "git reset -p" and "git reset --hard", I'm looking at the manual while I run it. (Granted, those first 3 cover probably 99% of the use cases…)

> The only two alternatives I can imagine are worse: disallowing partial commits (I use this feature all the time; I guess I could replace it by a temp commit on a branch and then so long as cherry-pick -p still exists, use that) or specifying it to git-commit (that would be one gnarly set of flags…)

Are you aware that git commit --amend exists? That replaces anything I might ever be tempted to use the staging area for.

I'm not sure how you use --amend to do partial commits of a file.
Well, that would be solved with --interactive, would it not?
"git commit" takes the -p (or --patch) flag for partial commits of a file, just like "git add". (Some people prefer --interactive, but I think the patch mode is significantly superior.)
(comment deleted)
There's a good chance the person you are replying uses git commit --amend all the time to modify commits in conjunction with git add -p.

No, hg amend -i is much worse than git add -p. I do that when I have to use mercurial and there's significantly more mental overhead to make sure I'm not accidentally modifying a commit I did not intend to modify. Creating a "temporary" commit before I have a complete change is also annoying.

And once you need to add a file, you're back to having a staging-like concept.

IMO the staging area is fundamental complexity.

Perhaps it's a difference in workflow, but I really don't understand why managing a "temporary" commit is so mentally taxing. Especially because in my workflows, I'm often not managing a single commit but an entire stack of commits, for which the staging area adds no value.

As I see it, the staging area is useless because it's basically a weird not-quite-commit-thing that you are more or less required to deal with (no way to opt out of it--several commands end up mucking about with the staging area even if you're not trying to use it). It feels like the better way to model it is to make it an actual commit, let it be manipulated as a regular commit, and then potentially improve the tooling around manipulating unpublished head commits as well.

Agreed. If you want to commit something that's not "reviewable" yet, just put it on another branch. Get used to committing things that aren't done yet! Hg does it right.
OK, then rebase and squash after making a bunch of commits. I can see why someone might like staging, but clearly it can be built on top of other git functions.

So, sorry, staging being "fundamental complexity" sounds petty BS to me. You like it, and that's fine, but it's in no way fundamental. I've used git for a long, long time, tried staging a few times and have never reached for it again.

What about hg add (or git add)? Does that not "stage" an addition for the next commit?
So I'm working and committing as I go along instead of staging, I need to be super sure I only rebase and never pull or else I've made a huge mess?

I think I'll stick with staging.

I can’t build the staging area. I would much rather git stash -p the changes I don’t want to commit yet, and commit the workspace because that’s what I tested.
Yeah I want my actual files on disk to reflect what I'm about to commit.

So ideally, IMHO, the staging area should work exactly in reverse of what it's currently doing.

I regularly hack on a slightly larger feature, fix, whatever. Once it's working, I incrementally stage lines for the separate commits that'll make up something reviewable. Doing a line-by-line review of the change at the same time. Often I'll omit some too-verbose or too hacky debugging that I don't want to include.

Once that's done I'll start a script that verifies that all the incremental commits individually pass tests (and maybe grab a coffee). While the tests run I'll do a separate pass over the commits.

For me the staging is absolutely crucial, and I can't imagine wanting to go back.

I'm not saying staging should go away. I'm saying it should work in reverse.

Instead of having all the changes in your working directory and commit some temporary state not reflected in the working directory, I'd like to move my changes into a temporary area and then pick the lines into my working directory and commit that.

That way I can compile and run tools etc on the code that will be committed before committing.

If I'm not ready to commit everything, I could then stop the "commit mode" and my uncommitted changes would be brought back into the working directory.

At least that's what I dream of.

Huh this is actually a kind of interesting idea. So basically if `git stash apply` had a --patch or --interactive mode?

So the flow would be:

    <working on big change churn churn churn>
    > git stash
    > git stash apply -p
    <pick some things to have in working tree>
    > make test
    > git commit -a -m "First change!"
    > git stash apply -p
    <pick some more things>
    ...repeat...

?

I could get behind that, and I personally like the index.

I haven't thought about the specifics, but yeah that looks like a very nice variant.

Since Git has stash, it feels like my idea is mostly a frontend thing that shouldn't require much backend changes, if at all.

Seems equivalent to a

  git add -p # Review and stage
  git commit # Create commit
  git stash # Save uncommitted changes
  run_tests.sh # double-check
  git stash pop # or drop
I'll do something like occasionally, but mostly I just rely on the CI to double-check me.
Yes, that is equivalent, except that you had to be aware of the staging area and its implications.

CIs on large teams can't run fast enough to check every individual commit. They have to run them in bulk. So the CI will often not catch that of your 2 consecutive commits the first one breaks if the 2nd one fixes that break.

I've also worked on large teams, and for us every published version of every change within our project is automatically tested. If your CI is too expensive to run on every commit it probably needs optimization -- something I've done myself a few times.

Not investing time or resources into making your CI fast and reliable is a false economy IMO.

> ...I'd like to move my changes into a temporary area and then pick the lines into my working directory and commit that.

One way to achieve this is by putting the changes into stash 'git stash push', then interactively grab the desired hunks from the stash into the work dir with 'git checkout --patch stash@{id}'. This will also add the accepted changes into the index, staging them for the commit.

Kinda granular, but allows me to keep those nice verbose debugging parts yet out of commit.

The TortoiseGit client basically has this feature[1]. In the commit GUI, you can right click a file and select "Restore after commit", then you can edit the file to remove all the changes you don't want to commit (which means you can also test it, etc, and that your edits can be as granular as you want). Then, after you commit, it resets it to have all the changes you left out.

I like it a lot.

[1] https://stackoverflow.com/a/48788906

I feel like a lot of the issues surrounding Git spring from its original insistence that the git repo exist in a subdirectory of your source directory, meaning that you can't have multiple checkouts in different directories. (I'm aware that you can do this now, with worktrees, but those are yet another distinct concept!) If the repo existed somewhere outside of your source directory, then a) the stash just becomes another directory (or directories), and b) so does the staging area. You could do anything with them (e.g., build, test) that you could do with any other directory of files.
For me the staging area is almost the most important feature of git. I often use it to stage new code that already and then step by step add more code that works.

This only works if you use a GUI like Sourcetree . With the command like staging isn’t much fun.

I use the git command line almost exclusively and find it preferable to other tools for manipulating the staging area.
I have repeatedly tried to use it with the command line but I just can't get the hang of it.
Which part gives you issues, adding things or removing things?
Working at file granularity is fine, but I've never gotten a good workflow for using the CLI to stage individual hunks. Nothing beats selecting the lines I care about in magit (or any other GUI) and clicking to stage.
git add --interactive <file>, then mark the file as staged and select the patch option if memory serves. It's not complex once you get the hang of it. The only problem I run into is not being able to split chunks further.

I do agree a GUI lowers the barrier to entry.

Use git add -p <file>

And you'll go straight to the patch option. Then you can split the chunks further, and for hunks that can't be split further, you can edit them and add/remove lines from the hunk as needed.

Thanks, didn't know about that shorthand!
The way it’s displayed and then also the commands. With Sourcetree you have a really nice display and it’s very efficient to stage/upstage/discard lines and hunks. I am sure I could get used to the command line with enough practice but source control tools are pretty low on my list of priorities of things I want to get better with. So I stick to what works for me.
If anything, I kind of wish git had as many named stages as you wanted.

Its one of the nice features of Perforce but they screw it up by only allowing separation at the whole file level.

If you think the staging area is unintuitive you should see what emacs magit does( and I love emacs magit). You stage A, start typing a commit message, stage B, finish the commit message, commit, and lo and behold B isn’t in the commit. So in magit it feels like there’s a secret second staging area.
Magit brings an immense clarity to git workflows; I suspect I would not rely on staging as heavily as I do, if it weren't for Magit.
I recently switched from gitk to qgit as my always-open repo status tool, and qgit doesn't show staged/unstaged changed separately like gitk. As a result, using the staging area suddenly has higher friction.

What I found myself doing is using a plain old commit instead of a staging area. Where I would previously build a commit in the staging area before I finally create it, I now simply keep amending it until it suits my need. It oftens starts out with a message like "WIP". This also scales to building multiple commits (use --fixup instead of --amend, and checkout instead of stash).

In practice the workflow is very similar and I was surprised by how little the removal of a core feature that I used all the time actually affects me.

I set PyCharm's Git client to auto-stage everything, and build up my commits using PyCharm's tools. You can go through your visual diff and hit checkboxes to say "put this chunk in the commit, exclude this chunk". Or you can get one piece of your commit ready, then create a new Changeset and have any new changes go into that changeset, able to be reviewed, committed, shelved, or rolled back separately.

The problem with staging is I want every file I changed in one place so I can notice what changed and look through the diff together. Not to just show up at the bottom of the list .

For me it's the enormous number of (mostly hidden) states that a local repository can be in. That's what gives me anxiety as soon as I go off the golden path: you have to keep a running mental model of where exactly you are (what state the repo is in) in any given ongoing process, and if you don't do the right thing as the next step, you can get into an even worse and more obscure state that you then have to chart your way out of (mostly blindly).

It's like that old saying about democracy: "Git is the worst version-control system, except for all the others."

Have you looked at using a good gui for the repository? I find that helps a lot with the mess.

I used to use Git Extensions and lately I've been using the one built into IntelliJ's products. It's not as good, but it's available on mac, so my options are limited.

For git on mac, i've found https://www.git-tower.com/ to be ridiculously good. Now that i primarily work on linux, i've settled for Sublime Merge, which has customizable actions, but isn't as intuitive a match for how i want scm to work, like Tower was.
I pay for this out of my own pocket without hesitation. They allow you to use your license on 2 different machines. I do most of my work on my personal Mac, but also keep Tower on my work laptop, so I can do the things I need to do there. I briefly tried Fork, but went back to Tower (and Kaleidosope.) You can pry Sublime Text out of my cold, dead hands, but I find Merge to be confusing.
Yeah, I usually use a GUI. I used SourceTree for a long time but it's gone downhill in recent years. VSCode's integration is great for simple stuff, but it's fairly limited. I haven't found one that I'm happy with across the board.
I like lazygit so far -- it's a TUI however. It makes working with git more tolerable to me.
Try the gitlens extension for VSCode
Git would unbearable if I hadn’t started using it as a cli+gui combo.
Personally I love using `git gui` for picking and choosing what goes into my commits line by line. Git command line is plenty powerful, but a dash of GUI here and there does sometimes do wonders for precision and visualisation.
>anxiety as soon as I go off the golden path

I used to have such anxiety, but not any more. The final piece of desired state was revealed when I found the reflog -- curiously not mentioned in the article nor by any other comment here.

Try

  git log --stat --walk-reflogs
in some repo where you've previously renamed some branches, or done 'git commit --amend'. All the transitional state changes are exposed, and can be undone individually.

Fill out the toolkit with 'git branch -avv', and 'git status' with its various options, and you'll be ready to recover from almost any local mishap.

What about a git merge with conflicts?

What about a git rebase with conflicts that you were doing interactively and is going to squash some of the commits?

In the first scenario I've learned to very carefully tread the golden path: make changes to the conflicting files to bring them to the state you want (I do this manually because it's less mysterious), and then "continue" the operation (usually I do this through the GUI; when I have to use the CLI I have to look it up). What would happen if I tried to commit something in here? Or unstaged and restaged something? Or stashed? Or pulled? Would it stop me from doing those things, or would I end up in some new weird state? I have no idea; I just avoid all anomalies and hope it goes smoothly and I end up back in a good place.

A DAG of features rather than a DAG of changes would make bug patching a nicer experience.
I had a hard time learning git but once I did it helped my productivity tremendously. Quite often I meet devs who use wrappers around git and they are always getting stuck on something. git in the terminal is your friend, it's powerful even with just a subset of its capabilities
My biggest problem with git is the bad, buggy code I manage with it. Git blame tells everyone who commited the bug... and sometimes that's me.

There is a lot of complexity around managing code that gets wrapped into the discussion about git. Everything from CI to code formatters to linters to audit tools. Most of these tools aren't VCS's. They are often language and toolchain specific or are some other devops lifecycle stuff that really isn't what git is doing. What git does do, it does wonderfully well - and makes it as easy to work with an entire codebase as it used to be to work on a single file. If there is a complaint about git, it's the thing many other comments hit - switching branches with staged to uncommitted changes requires some care... and removing history (e.g. someone commits a private key) is a little tricky.

It’s about its design but rather the adoption. I find it kinda concerning that git pretty much has a monopoly.
Its too complicated. Hardly anyone actually needs a distributed version control system. It's a tool and should stay out of my road, not require hours of Stack Overflow to roll back a couple of mistakes.
As far as I can see, SVN and Hg are distributed. What Git brings to the table is the ability to work offline, but in a distributed multi-developer environment.

I spent two years full-time working with a team that used Git. The work wasn't distributed; we all worked in the same room, doing the same hours. I never really got Git; too much unwanted complication, and I had quite enough to learn without having to learn Git at the same time.

I did like the ability to edit commits at commit-time, line-by-line. That was nice, but it's the kind of feature that could be back-patched onto something like SVN. It doesn't depend on any intrinsic design-element of Git.

I never got rebasing. It scared the shit out of me.