Hacker Newsnew | past | comments | ask | show | jobs | submit | fjfaase's commentslogin

Working without branches, except for releases, is the most effective way of working, using rebase instead of merge to get a single line of commits. Even release branches can be avoided with continuous deployment.

> Working without branches, except for releases, is the most effective way of working, using rebase instead of merge to get a single line of commits.

I think you're confusing workflows with commit history.

You can work with feature branches all you want, rebase them as you feel like it, and then do squash merges to main.

The likes of GitHub even have a button for this.


Why in the world would you do squash merges? ...except to clean up messy mini-branches written by total noobs. I don't do separate commits for funzies. If you want separate commits for ease of review, why not for later reading of the code.

Assumption: above mentioned total noobs don't use git rebase -i or equivalent, everyone else does


It’s pretty hard to keep the commits in a working branch in a good legible state - certainly it takes work to do it.

In 25 years of professional development I’ve never really had a situation where the commits on a branch would have helped me understand what was going on a year ago when the work was done. That includes pretty big bits of project work.

I’d much rather have a trunk with commits at the granularity of features.


I on the other hand have never come across a scenario where I run git bisect to find a commit that broke something, discover a small commit as a culprit and wish I had instead found a commit that's hundreds of lines long.

What has happened a whole lot though is the exact opposite.


It might be better to view a commit as a natural unit of working code. There are a lot of units of working code which would be tedious to be introduced as a only a few lines.

As such, a new codebase is likely to grow by large unwieldy commits and a mature one by targetted small commits.


For me, all the code in the PR at that point is in question. The code was changed as part of a bigger part of work and reverting it without understanding why might cause issues back the other way.

The other thing to say - that is missing from this conversation - is that if you’re using github, the original commits are still against the PR.


Our strategy is to squash on merge and ensure the JIRA ticket reference is in the MR title. You have the granularity of the feature which is going to help guide you on the intention. It's also much easier to enforce. People like to write and commit code in their own way.

`git log --merges --first-parent` gives you both.

I've had separate commits come in handy several times when `git blame`ing when working with people who actually described what changes were about in their commits (which, unlike comments, don't go out of date).


In 25 years of professional development I have several counter examples where some bit was either a trivial git revert of a single commit - among multiple ones in a branch - away, or an absolute pain because the squash-merge commit had flattened too many concerns together, concerns that were perfectly split in the topic branch but that branch was long gone by virtue of being auto-deleted on PR merge.

Coincidentally, every single squash-merge commit advocate I've had the unfortunate debate with was a regular practitioner of public tmp / tmp / try again / linter / tmp / fix / fix / haaaaaands commits.

Note that I'm not against squashing/history rewriting e.g rebase -i and stuff (which I'm a heavy user of so as to present sensible code aggregation reviewable per-commits), only squash-merge.


I take it you haven't had the pleasure of working with your average ("dark matter" as they're called here) developers. I wouldn't call myself an "advocate" of squashes, but it's often the only practical way of keeping git history somewhat usable when working with people who refuse to learn their VCS properly.

I chunk my changes into tiny commits ("linter"/"tmp"/"wip"), but then rebase aggressively, turning it into a set of logical changes with well-formed commit messages. git bisect/revert work great with history written in this way even years layer.

But: most of the people I've been interacting with also produce lots of "wip"/"tmp", but then skip the rebase. I can only offer my help with learning git rebase for so long before it starts taking too much time from the actual work. So squash it is: at least it produces coherent history without adding thousands of commits into `--ignore-revs-file`.


And sometimes, a patch is just that big. especially in UI works where a single change can cascade down to multiple layers.

> I chunk my changes into tiny commits ("linter"/"tmp"/"wip"), but then rebase aggressively, turning it into a set of logical changes with well-formed commit messages. git bisect/revert work great with history written in this way even years layer.

In a PR based workflow, it has become easier to have the PR be a logical unit than to `rebase -i` all the time on my end.


If you work with a ticket system, squash-merge gives you the same granularity, where a commit would refer to a single ticket.

A ticket should be atomic describing a single change request. PR in this case are the working room. It can be as messy or as clean as you want. But the goal is to produce a patch that introduces one change. Because if you would rebase -i at the end, you would have a single commit too in the PR.


No, you wouldn't. git rebase -i is to remove noise, which is about merging commits that, well, make more sense together than apart. Which is mostly about summarizing trivialities (e.g. several typo fixes) and squashing fixups into commits that introduced a problem in the same branch.

A typical bugfix branch might look like this after rebase -i:

Move property to a more appropriate place

Improve documentation of feature Foo

Fix accidental O(n^2) in feature Bar

Fix interaction of Foo with Bar


Those looks more like noise to me. A squashed merge (or a final squash before PR) would be:

  TN 43 - Fix mismatched interface between Foo and Bar

  We've moved the X property to a more appropriate place and
  improved the documentation for Feature Foo. We've also found and fix
  an O(n^2) implementation in feature Bar.
The the ticket TN-43 will have all the details that have lead to the PR being made: Bug reports, investigations, alternative solutions,...

The commit message is what's more important. I don't think I've ever needed what is in a merged branch. But I've always wanted the commit at one point to have tests passing and a good description of the patch. And all the talk in the engineering team are always about ticket. It does makes sense to align those.


They aren't noise at all and have found them useful a bunch in the past when I worked at a place that didn't squash. Commits at this level act as immutable comments that don't get out of date. Provided you do --no-fast-forward merges, the merge commit is the feature commit and you can get the "clean" feature history with `git log --merges --first-parent`. Best of both worlds! Being able to `git blame` and get a granular message about why something was done can be really handy, especially when looking unfamiliar code.

I get where you came from, but I prefer having a more holistic view of a change, especially from a product perspective. So even when git-blaming, either I’m reading the current file or I go straight to the log of the commit (with message and diff).

I prefer granularity at a product or team level decision. Not workflow details.


I'm not trying to convince you to adopt or anything, but I'm saying you can have all of that without squashing with the caveat that you would need an alias to jump to the merge commit. Otherwise, you just treat merge commits as you would a squash one. Merge commits are just like regular commits that can have a custom message and show a diff.

> If you work with a ticket system, squash-merge gives you the same granularity, where a commit would refer to a single ticket.

With GitHub you can squash any PR merge. The link to the PR will include the complete history of the feature branch prior to the merge. Even the commit history prior to force pushes is tracked.


100%. I don't want to know how the sausage was made. It's similar to research papers, or history books, where the way we arrive at results or outcomes in the real world is often quite different from the way it's presented in the final form.

A good commit history is more like a well-written sausage recipe than like a TV documentary about scandalous sanitary conditions at Foo sausage factory ;)

> In 25 years of professional development I’ve never really had a situation where the commits on a branch would have helped me understand what was going on a year ago when the work was done.

My professional experience contrasts with yours. I've even worked at a company where commit history and PRs were so central to understand and explain changes that PRs were even used as the authoritative sources on how to implement features and use frameworks.


Maybe a slight misinterpretation of what I meant. The commit that goes with a PR is definitely useful context, but I’ve found more granular than that is seldom useful. Even big ones like “move from angular to react” - the details of someone getting something wrong in there don’t matter, it’s the scale of it that just makes me go “oh yeah, this is bound to be a mistake”.

Maybe different in other places, but after 15 years in my codebase, I’m still happy with a simple linear history.


I'd much rather reduce the risk of mutation to the trunk, by having small easily reviewable commits direct to trunk.

It's less about reviewing commits from a year ago, than making change low-risk today. And small commits can easily be rolled back. The bigger the commit, the more likely rollback will be entangled.

It better to have partial features committed and in production and gated behind a feature flag, than risk living in some long-lived branch.


> I'd much rather reduce the risk of mutation to the trunk, by having small easily reviewable commits direct to trunk.

You're not addressing the problem. You're just wishing that the problem wouldn't happen as frequently as it does.

But that's like wishing that race conditions don't happen by making your allocations at a higher frequency.


I'm describing how Google works with teams with high release cadence, fwiw.

Also, your comment reads a little bit as a non sequitur.


Each commit should be small, have a descriptive commit message and be stand alone. I consider the Linux kernel a good example of how to do commits and messages right. Often the commit message is longer than the code change.

I strive to do that when making commits for work too, and that helps when going back in history and looking at history to motivate why a change was made.

While working I rebase all the time to move changes into the relevant commit, I don't find that particularly hard or time consuming. Doing this upfront is easy, splitting commits after the fact is not.

I consider this standard practice, at least in the sector I work in (industrial equipment control software, some of which is considered human safety critical).


I want every commit to represent a buildable state in which I have confidence automated tests pass. Bisecting is made unnecessarily difficult otherwise, and it's nice to be able checkout any commit and just build something for testing.

This constraint is usually enforced by code review.

On Github, the unit of code review is the PR.

Therefore, I prefer squashing.


>On Github, the unit of code review is the PR.

It is, and that is some bullshit. The only sensible way to work with that is to break up larger features into several PRs - which is often positive anyway, but sometimes it doesn't fit the nature of the change.


I am not a fan of Github's interface.

But my point is, is that I believe the important thing to preserve in history is whatever your unit of review is. If you could stack PRs and each were subject to the individual review, I would not combine and squash those (just the individual commits within each PR).


>I believe the important thing to preserve in history is whatever your unit of review is

I do not share that belief, but this thread (your posts and others) gave me an idea about code reviews. They became commonplace when I'd been working in software for a while already and, while I see the benefits, I never really felt like I handled them well. I guess one of the ingredients for handling them better is to center the workflow around them - an area in which I have never noticed major changes in an existing project, although they are visible when looking from the right angle: Long-lived, large feature branches have become noticeably less common.


> Why in the world would you do squash merges?

Why would you not want to squash merges? It's one of three options offered by GitHub in their PR merge buttons.

They create a linear commit history, which is what you want when you have to audit changes.

> except to clean up messy mini-branches written by total noobs.

Nonsense. You get a messed up commit history as easily as when you create a PR in GitHub, and after team members merge their commits, you click on GitHub's "update branch" button.

You also get a messed up commit history if you merge a PR after someone else merged theirs.

You will always mess up your pristine commit history if you have more than one person posting and merging branches. With one notable exception: squashed commits.


Rebasing fixes all of the problems for which you present squash merges as the only solution.

It needs bun to install. Could not find how to install bun. I wonder if bun is free, because its website looks to fancy for a free tool.

I remember seeing a sketch on a (Chinese) new years eve TV show in the mid nineteens, showing regional differences in language use between two partners during the night where one had to make use of the toilet. It started with one that used long sentences and ended with regional one where the whole coversation consisted of four sentences of one word: Who? Me. What? Pee.

The code of TCC (0.9.26) is kind hard to compile, I have discovered in the past year, while developing a minimal C compiler to compile the TCC sources [1]. For that reason, I have concluded that TCC is its own test set. It uses the constant 0x80000000, which is an edge case for if you want to print it as a signed integer only using 32-bit operators. There is a switch statement with an post-increment operator in the switch expression. There are also switch statements with fall throughs and with goto statements in the cases. It uses the ## operator where the result is the name of a macro. Just to name a few.

[1] https://github.com/FransFaase/MES-replacement


You have simply made one tiny step, that the guys who used AI and $25,000 to write a C compiler in Rust, could not make:

You are using the compiler to compile itself.

"TCC is its own test set." Absolutely brilliant.


Back in the 90s gcc did a three-stage build to isolate the result from weakness in the vendor native compiler (so, vendor builds gcc0, gcc0 builds gcc1, gcc1 builds gcc2 - and you compare gcc2 to gcc1 to look for problems.) It was popularly considered a "self test suite" until someone did some actual profiling and concluded that gcc only needed about 20% of gcc to compile itself :-)


To be honest, these all seem like pretty basic features.

Goto is easier to implement than an if statement. Postincrement behaves no differently in a switch statement than elsewhere.


Yes, you are right that a post-increment in a switch statement is no differently than elsewhere. The goal I had set was to implement a small easy to read C compiler. For that reason I tried to implement it as a single pass compiler that would generate code on the fly. The target was a small stack based language, which did support variable scoping and gotos, but not a switch. My first attempt was to implement the switch statement with chained if-statements where the switch expression would be evaluate over and over again. This only works if the switch expression did not have side effects and that the 'default' case would always come at the end. But that did not work, so I had to come up with another solution, a solution that would only evaluate the switch expression once. I decided to store the value on the stack and duplicate the value whenever needed for comparison. But that would require the value to be popped once a case was selected. A goto jumping from one case to another should land after the location where the value is popped, otherwise it would corrupt the stack. I fear that this solution does not work correctly when a case occurs within a for, while, do-loop, or if-statement. cases may occur everywhere in the enclosed code. This is sometimes used to emulate co-routines or generator functions.

Did you know that C has a keyword that is only a keyword in some places and that there is a function that can have two or three parameters?


Maybe the strategy from TCC itself is useful here: emit case bodies as a giant block first then jump back with cascaded if's at end. https://godbolt.org/z/TdE11jjxb

> Did you know that C has a keyword that is only a keyword in some places and that there is a function that can have two or three parameters?

What are those? Please tell!


A good idea that I had not thought about.

defined is a pre-processor 'keyword' that only is used in conditions after #if and #elif. Elsewhere it is ignored by the pre-processor. You could argue that it is not a real keyword. But lets see what happens when you define it as a define with: '#define defined 1'.

The function main can be defined with two or three parameters. The third contains the environment. You can also get the environment with 'argv + (argc + 1)'.


If you have reliable file locking you can implement a journal-only with multiple users without needing a server. You have to take care of write errors and deal with partial writes, which can be tricky with a binary format. A long time ago, I implemented one based on XML. Some non-Windows file-severs (citrix?) did not have reliable file locking, causing corrupted files.


Maybe not as big before the processor dies. The numbers that are talked about are unimaginably large, far larger than the number of atoms in the visible universe.


I understand that he is working on C++ according to blog post.


More like a hybrid to get to a safe C, with constructors, destrcutors, member functions and such.

Fil-C is similar, but less promising than this. But Fil-C is far more advanced than TrapC. There several more such attempts also, not just those two


I have implemented similar behavior in some of my projects. For one, I also have also implemented 'cursors' that point to some part of a value bound to a variable and allow you to change that part of the value of the variable. I have used this to implement program transformations on abstract parse (syntax) trees [1]. I also have implemented a dictionary based on a tree where only part of the tree is modified that needs to be modified [2]. I have also started working on a language that is based on this, but also attempts to add references with defined behavior [3].

[1] https://github.com/FransFaase/IParse/?tab=readme-ov-file#mar...

[2] https://www.iwriteiam.nl/D1801.html#7

[3] https://github.com/FransFaase/DataLang


Enginering?


Did not find a single atheist or agnostic on it. Maybe there are some among those who refused to tell it. Sometimes I get the idea that it more acceptable to call yourself a satanist than an atheist in the USA, because if you associate as a satanist you at least acknowledge there is a god.


Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: