Hacker News new | ask | show | jobs
by hackrmn 15 days ago
My observation has been that those who have never learned Git properly -- disregarding for a moment the issue with what "properly" means here -- just don't think they need to, sticking to very simple workflows that produce linear graphs featuring squash-rebased work throughout. Because they don't know Git's fundamental model (including what you'd think was the obligatory piece of information that commits are essentially immutable), they don't venture farther than their confidence suggests, keeping it within the circle so to speak.

The tragedy, if you ask me at least, is rather that they then start imposing the "keep it simple" culture onto everyone else, which frankly turns Git into what SVN and CVS were accomplishing before it, with all the drawbacks of those. This keeps 90% of the team satisfied 90% of the time because there's nothing wrong with strictly linear graphs and the pro's that are asked to produce these, can always squash-merge their intricate local development history before they push to Github (which some Git users also think is part of Git proper).

Something about using Git in the aforementioned way just bugs me strongly. I admit it's a me problem, very likely, but lately I've also had to admit that it's not just that either.

For instance, if I need to fix a bug I use `git blame` to find the commit where I have learned the bug originated. I do a `git checkout -b ... <commit>` to start working on a fix, creating a branch starting at the problematic commit. Already that is head and shoulders above what many people I work with do or know _what_ does (or why would one want to do that).

I do it because it creates a _trail_, certainly more so than just committing on top of `master`, even with a good commit message.

But my practice doesn't produce linear graphs, even as pushed to Github (which we have to use, for better and for worse) and shared with everyone. I then get people coming to me and complaining they can't merge and it becomes obvious they don't understand Git sufficiently to do anything else but `git reset --hard` or worse, `rm -rf * && git pull` and hope for the best.

Not sure where I was going with this, something about why use Git when it's the new SVN with everything SVN had and none of what SVN didn't have...

4 comments

Ok but the final outcome, most of the time, is a linear graph unless you're maintaining extremely long-lived branches for some reason.

Setting practices so that linear graph will be easy to understand in history is still a good idea, no? I'm not religious about squash-merge on master but when I look at master's history, I'd expect each individual commit to have a good message and be releasable.

A linear graph doesn't do any developer in the team except Github (which isn't a developer in this context) any good because they have to pull down the graph at some point and when they have to fix something, although they will be able to track the blame to some commit in the middle of the graph, when they fix it and merge it it has to be squashed again (by hard or soft enforcement), which like I said negates much of the point with Git -- there's no usable history left.

Point being that branches aren't something to avoid, implying that complicated graphs are not to be avoided either, but embraced and knowing how Git works it's not a problem at all (unless your "bush" is accidentally complex in a bad way) -- except that if noone bothers to learn Git, it _becomes_ a problem, solved at the cost of the value that Git provides -- branching (not just ephemeral branching for your local convenience). Exacerbated by Github since everyone pulls from there -- meaning that production-grade code is not a true graph but a linked list, on average.

> the value that Git provides -- branching (not just ephemeral branching for your local convenience).

I think this is the disconnect. I could see big libraries maintaining branches for each major release and I understand why linux does it. But if I'm maintaining FooService at work then I actually do just want a linear graph of all the commits that have been in production, in commit order.

If you want that, mandate developers use prefixes like `!fixup` and do `git rebase` _on your end_ -- why force upon everyone your ideas especially if it also squashes all development history? This also removes development friction because people can work with Git on their end the way they like without regard to some convention that can be avoided -- save for the "!fixup" thing which can be considered useful metadata.
I want development history to be squashed. I want to have a nice clean `git log` that I can look back thru and not see a mess of 'WIP' and 'fix' and other noise.

Unless you're saving every keystroke from your editor you're already squashing history, we're just arguing about degrees.

I do enforce rebased, releasable merges to master on my end, I'm not campaigning to remove branching from git for everyone.

Just saying, in the common case, the simple approach actually is pretty good.

My philosophy is that if you could've written your patch on current master, you should rebase it, but if there's any divergence that requires actual merging, then you cannot discard the divergence and merge from history because that will mess up history. You can only discard that if you do an actual rebase onto master, fixing up bugs and ensuring each commit that you are pushing compiles and runs by itself - a "fix conflicts with master" commit at the very end isn't allowable.

This is because of bisect. Bisecting broken commits doesn't work. Every commit should build.

In either case you should still rebase -i your branch as necessary to make it look clean when standing on its own.

Ok, I admit arguing about Git's merits, best practices, workflows and so on, can be hard online like this, not the least because I have limited amount of time I can invest in this admittedly very interesting to me discussion, so it requires upfront reasoning (in addition to the typing, which is relatively cheap) but I feel obliged to respond.

First, I agree commits shouldn't store broken snapshots -- if that's what you mean -- code that doesn't compile or outright is _known_ (as opposed to omissions that weren't caught otherwise and made it into the commit) to be broken, not run, produce runtime/type-checking errors, miss dependencies/assets etc. Not everyone would agree -- in our shop people routinely commit "stuff" for reasons they don't disclose that also remain unexplained to me (not having any explanations my way), and then more stuff on top of that. At some point they tag one of these commits as "v2.0.0" triggering automation that releases a package, often broken. Then they scramble for "v2.0.1" etc. I find all of it tedious and unnecessary -- my best practices would rather be to run automation that does checking and sufficient filtering for "bullshit code" as early as possible -- at the "pre-commit" stage, so only whatever passes through that ends up being committed in the first place. If it yet runs into regressions or other problems in production, I fix (re-triggering all the checking again) and release a strictly patch version -- unless fixing was impossible without breaking compatibility in which case it may be a minor or even a major version bump. I do tag commits, too, obviously. But in principle every commit is "production", the difference is not every commit makes up a _release_ (implying to the general public / intended audience).

Regarding rebasing -- that depends on what you mean exactly -- there's different ways to rebase -- you can squash, auto-squash and/or apply fix-ups, or you can just transplant stuff -- when you say "you should rebase" and "patch on current master", what do you mean? Github-driven projects often don't permit pushing `master` without a PR, so a rebase upfront would have been necessary if the author for some reason is not on a branch [other than `master`].

"Actual merging" -- do you mean merge commits as opposed to fast-forward merging which doesn't produce an additional commit? If the latter, what does "merge from history" mean, again? You just need to produce a working snapshot, expressing it with a commit with N parents, there's no more to it. It's fixing of the merge conflict that requires you to understand the graph, the resulting commit is just the period at the end of a long sentence, so to speak.

Anyway, I really missed the point you were trying to express with your first paragraph, I am sorry. Do elaborate, please.

Bisecting also _benefits_ from non-linear "actual development" graphs -- apart from commits that don't feature broken code committed haphazardly for "reasons" (some people use Git as backup, that I know for a fact) -- since they don't hide the work (one of the reasons I prefer raw graphs over squash-merged "polished" stuff).

One thing I should mention in any case is that I do find slicing work into atomic commits often very tedious and counter to what Git is supposed to sell (distributed friction-free concurrent versioning). Because my brain doesn't always work in terms of concepts aligned with Git's -- I routinely may start with one feature fix, on some branch, then discover N related issues, fix those because they're in front of my face then and there, end up with an unreadable diff I can't cleanly add in chunks, etc. None of that is facilitated by Git in any more capacity than just being a scalpel. I don't always want to cut down a tree with a scalpel, obviously.

Pijul, an alternative VCS, uses this nice patch theory guaranteeing you can "apply" commits in any order, compositing your changes essentially -- a feature is just some baseline that is added N commits (in any order, like the mathematical `+` operator). That way you don't have to rebase anything because parenthood doesn't break merging -- if you fix a bug with a patch in Pijul, you can store it once and count on it being applicable to any snapshot made later, even if the result isn't visible -- unlike with Git where rebasing some old commit will abort and ask you to fix a conflict because Git cannot continue.

> creating a branch starting at the problematic commit

Code always lives in a context, and when you're fixing a problem that exists in top-of-main the context of the fix needs to be top-of-main, not some commit way back in history. Also if you do it that way, you'd better hope that no later commit also touches that same line.

> I then get people coming to me and complaining they can't merge

A team needs to agree on git practice in advance, and not have one wildcard go off and do their own thing.

It depends on how much the code has changed, how easy it is to test old releases, how far back you need to merge the fix... "Cherry picking from the top of tree" is the most common workflow but git itself uses "merge the same topic branch in all trees".
SVN was damn slow and working with branches was a hell.

Git sucks on so many points that there ain't be enough walls in the world to write our lamentations about it. But it's damn fast. If I have a doubt, I just create a backup branch before messing with the work at stake, and the bottleneck will be the time I take to type the command.

And I don't know if SVN had some advanced mode to do any branch in local, but in my frightening memories full of scarces, one had to synchronize everything to the central repo to do anything.

can you elaborate what concrete value you've managed to extract from doing things like this? my gut reaction is that any benefit would be overshadowed by multiplying effort for other team members to catch up on their work, and if it makes it harder to work when even one person does it I can't imagine it scaling on a full team (or worse, something like a monorepo)