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

Not only that, but LLMs do a disservice to themselves by writing inconcise code, decorating lines with redundant comments, which wastes their context the next time they work with it

"Ours" and "theirs" make sense in most cases (since "ours" refers to the HEAD you're merging into).

Rebases are the sole exception (in typical use) because ours/theirs is reversed, since you're merging HEAD into the other branch. Personally, I prefer merge commits over rebases if possible; they make PRs harder for others to review by breaking the "see changes since last review" feature. Git generally works better without rebases and squash commits.


Wow, interesting to see such a diametrically opposed view. We’ve banned merge commits internally and our entire workflow is rebase driven. Generally, I find that rebases are far better at keeping Git history clean and clearly allowing you to see the diff between the base you’re merging into and the changes you’ve made.

Yes, I prefer that approach as well because it allows the person who authored the change to do all the work of deciding how to resolve conflicts up front (and allows reviewers to review that conflict resolution) instead of forcing whoever eventually does the merge to figure everything out after the fact. It also removes conflicts from the history so you never have to think about them later after the rebase/merge process is finished.

> Git generally works better without rebases and squash commits.

If squash commits make Git harder for you, that's a tell that your branches are trying to do too many things before merging back into main.


I don't know. Even when I'm working on my own private repositories across several machines, I really, really dislike regular merges. You get an ugly commit message and I can never get git log to show me the information I actually want to see.

For me, rebasing is the simplest and easiest to understand, and it allows you to squash some of your commits so that it's one commit per feature / bug-fix / logical unit of work. I'll also frequently rebase and squash commits in my work branch too, where I've temporarily committed something and then fixed a bug before it's been pushed into main, I'll just reorder and squash the relevant commits into one.


I completely agree, since doing rebase our history looks fantastic and it makes finding things, cherrypicking and generating changelogs really simple. Why not be neat, it's cost us nothing and you can make yourself a tutorial on Claude if you don't understand rebasing pretty easily.

Don't do squash commits, just rebase -i your branch before merging so you only have one commit. It's pretty trivial to do.

doesn't it? Next to the conflict markers, it'll display HEAD, the ref name, or the short commit hash.

It does now by default, since v2.12 (released 2017). Prior to that you had to set the log.decorate config. Good times.

I'll be honest, as a fairly skilled and experienced programmer who isn't a git expert, I know what HEAD means, but when I'm rebasing I really have no idea. It all seems to work out in the end because my collaborative work is simple and usually 2–3 people only, so I'm never rebasing against a ton of commits I lack context for (because 90% of them are my commits since I'm usually dealing with PRs to my open source projects rather than someone else's).

HEAD is "the thing we're editing now" but that's not terribly useful when rebasing since you're repeatedly editing a fake history.


Git leaks a lot of implementation details into its UX. Rebasing is meant to be equivalent to checking out the "base" branch and cherry picking commits onto it. Therefore "ours" during a rebase is the base branch.

The meaning of "ours" and "theirs" is always the same, but the "base" of the operation is reversed compared to what you might be used to during merge.

Rebasing can be confusing and hard and messy, but once I learned that rule and took the time to internalize it, I at least never got confused on this particular detail again.

> fake history

That's the thing, it's not actually fake history. Git really is doing the things it looks like it's doing during a rebase. That's why you can do all kinds of weird tricks like stopping in the middle to reset back a commit in order to make a new intervening commit. The reason you can abort at any time with (almost) no risk is because the old history is still hanging around in the database and won't be removed until GC runs, usually long after the rebase is settled.


Learning git properly is pretty much "read Git book at least 3 times".

All of it makes sense and is decently intuitive once you know how internals work.

People keep imagining git as a series of diffs while in reality it's series of the filesystem tree snapshots + a bunch of tools to manage that and reconcile changes in face of merge. And most of that can be replaced if the builtins are not up to task. And the experience is getting slowly better but it's balance between power users and newbies, and also trying to not break stuff when going forward.

Now of course that sucks if programming is not someone's day job but there is plenty of tools that present simpler workflows built on top of that.


Yes, Git is like a Copy-on-Write filesystem.

It's interesting that once even C programmers, like Linus, become really experienced, they embrace the wisdom that functional programmers are forced to swallow anyway.


Also git store (almost?) all its operations in the reflog. They have identifier like commits so you can reset to them and restore the original state of the working directory (mostly after an automatic rebase gone wrong).

That's the thing, they're not "like commits", they are the actual original commits. It's a history of where the HEAD ref used to be. Eventually those commits will be pruned out of the tree if/when the reflog expires because there is nothing left pointing to them. But otherwise they are normal commits.

I think what the grandfather comment meant is 'like' in the sense of 'this is an example'. Not 'like' in the sense of 'sorta / approximately'.

> HEAD is "the thing we're editing now" but that's not terribly useful when rebasing since you're repeatedly editing a fake history.

You got two things wrong here. Firstly, HEAD isn't 'the thing you're editing now'. HEAD is what you have already committed. If you want to edit the HEAD, you have to either amend the commit or reset and redo the commit. (To make the situation even more complex, the amended or overridden commit remains in the repo unchanged, but orphaned.)

The actual thing being edited is a 'patch' that will eventually be converted into a new commit (snapshot). If you're doing a rebase and want to see the next patch in the pipeline that you're editing now, try this:

  git rebase --show-current-patch
Secondly, rebase is not editing a fake history. Rebase is creating a new (and real) history by repeatedly cherry picking commits from the old history based on the rebase plan. HEAD is the tip commit of the new history under construction. On completion of the rebase, the branch ref of the old history is switched to the new history, where HEAD is now at. Meanwhile, the old history remains in the repo unchanged, but again orphaned.

All the orphaned commits are still visible in the HEAD's reflog. You can use it to undo the rebase if you wish.

I agree that the entire thing is confusing as hell. But I have a bunch of aliases and scripts that show you the process graphically in realtime. You can use that awareness to make the right call every time. I'm thinking about converting it into a TUI application and publishing it.


I avoid this problem by not rebasing.

Hey not every rebase has conflicts. I definitely rebase when there are no conflicts, then merge.

When there are conflicts I merge „theirs” into my branch to resolve those so I keep mental model for this side and don’t have to switch. Then rebase then open PR.


You could do all that. Or you could just merge every time. I know which I find easier.

I do the following to keep my sanity when doing something like rebasing a feature branch onto latest origin/master:

* First and most important: turn on rerere.

* Second: merge and resolve all conflicts, commit.

* Third: rebase.

The second step might look redundant, but thanks to rerere git remembers the merge conflict resolution. That makes step 3 have fewer conflicts; and step 2 tends to be easier on me, because we are only reconciling the final outcomes.

(Well, I'm lying: the above is what I used to do. Nowadays I let Claude handle that, and only intervene when it gets too complicated for the bot.)


Seriously! I have too many years of software development experience, but I use Visual Studio UX to handle pretty much all git operations. And always merge.

I have better things to do in my life than "internalizing" anything that doesn't matter in the grand scheme of things.


I don’t like that approach, because people who work like that commit all kind of crap to repo or cry that GIT ate their homework…

Then we have line ending conflicts, file format conflict UTF8-BOM mixes with just UTF8 it makes more work for everyone like crappy PRs. Because of people for who those are things that „don’t matter in grand scheme of things”.


I happen to know a lot about git internals, but I don't think everyone should need to.

About the line ending conflicts: set up your CI once to complain about those. And help your coworkers set up their editors right once.


If it hurts, do it more often.

It does

All software has bloat, but npm packages and web apps are notorious for it. Do you think it could be inherent to the language?

JavaScript seems to be unique in that you want your code to work in browsers of the past and future—so a lot of bloat could come from compatibility, as mentioned in the article—and it's a language for UIs, so a lot of bloat in apps and frameworks could come from support for accessibility, internationalization, mobile, etc.


The problem JS development is facing is the same most languages might go through. The "Magic" that solves all problems, frameworks and solutions that solve small issues at a great cost.

Lots of developers don't even say they are JS devs but React devs or something. This is normal given that the bandwidth and power of targets are so large nowadays. Software is like a gas, it will fill all the space you can give it since there is no reason to optimize anything if it runs ok.

I've spent countless hours optimising javascript and css to work across devices that were slow and outdated but still relevant (IE7, 8 and 9 were rough years). Cleverness breads in restrictive environments where you want to get the most out of it. Modern computers are so large that its hard for you to hit the walls when doing normal work.


Every cargo install (rust) I've down downloads 300 to 700 packages

Every C++ app I install in linux requires 250 packages

Every python app I install and then pip install requirements uses 150 packages.


A while ago I started a game project in Rust using one of the popular engines.

10GB of build artifacts for the debug target.


You should give it a try to compile other game engines, and compare them, Unreal Engine is a fun one with the source available, take a look how big their artifacts are :)

With that said, there are plenty of small game engines out there, but couple Rust's somewhat slow compile times with the ecosystems preferences for "many crates" over "one big crate", and yeah, even medium-scale game engines like Bevy take a bunch of time and space to compile. But it is a whole game engine after all, maybe not representative of general development in the community.


In Rust land, I enjoyed Macroquad, for simple 2D stuff. It's very much in the vein of XNA/MonoGame.

This is not true at all.

I wouldn't say every Rust app does, but I do think it has become more normal for Rust apps to have 200-600 dependencies. However when I look at the list, they usually all make sense, unlike with NPM. There are rarely any one-line crates. Actually I haven't seen any yet (except joke ones of course).

There's no way the average C++ app uses 250 packages though. It's usually more like 5. C++ packaging is a huge pain so people tend to use them only when absolutely necessary, and you get huge libraries like Boost primarily because of the packaging difficulty.

I would say Python varies but 150 sounds high. Something more like 50-100 is typical in my experience.


Ardour (an open-source x-platform digital audio workstation, written in C++) has on the order of 80 dependencies.

I think it has to do with temperament and incentives.

For example we often see posts on HN about, "see, it's possible to write very fast software in language foo!" And most of the time yes, especially on modern hardware, most languages do allow you to write surprisingly fast software!

It's just that the people who actually want their software to run fast -- and who want it enough to prioritize it against other, competing values -- those people will generally reach for other languages.

With JavaScript, the primary "value" is convenience. The web as a platform is chosen because it is convenient, both for the developer and the user. So it stands to reason that the developer will also make other choices in the name of convenience.

Of course, there's weirdos like me who take pride in shipping games JS that are eight kilobytes :) But there are not very many people like that.


"Do you think it could be inherent to the language?"

Not to the language but its users. Not to bash them, but most of them did not study IT on a university, did not learn about the KISS principle etc.

They just followed some tutorials to hack together stuff, now automated via LLM's.

So in a way the cause is the language as it is so easy to use. And the ecosystem grew organically from users like this - and yes, the ecosystem is full of bloat.

(I think claude nowdays is a bit smarter, but when building standalone html files without agents, I remember having to always tell chatgpt to explicitely NOT pull in yet another libary, but use plain vanilla js for a standard task, which usually works better and cleaner with the same lines of code or maybe 2 or 3 more for most cases. The standard was to use libaries for every new functionality )


> All software has bloat, but npm packages and web apps are notorious for it. Do you think it could be inherent to the language?

It sure seems like it is because JS devs, by and large, suck at programming. C has a pretty sparse standard library, but you don't see C programmers creating shared libraries to determine if a number is odd, or to add whitespace to a string.


> you don't see C programmers creating shared libraries to determine if a number is odd, or to add whitespace to a string.

Believe me, if C had a way to seamlessly share libraries across architectures, OSes, and compiler versions, something similar would have happened.

Instead you get a situation where every reasonably big modern C project starts by implementing their own version of string libraries, dynamic arrays, maps (aka dictionaries), etc. Not much different really.


You can't completely prevent the browser from sending the request—after all, it needs to figure out whether to block the website from reading the response.

However, browsers will first send a preflight request for non-simple requests before sending the actual request. If the DDOS were effective because the search operation was expensive, then the blog could put search behind a non-simple request, or require a valid CSRF token before performing the search.


I wonder this means there could be a faster npm install tool that pulls from a registry of small utility packages that can be replaced with modern JS features, to skip installing them.

Not sure about faster, but you could do something with overrides, especially pnpm overrides since they can be configured with plugins. Build a list of packages that can be replaced with modern stubs.

It couldn't inine them, but it could replace ponyfils with wrappers for native impls, and drop the fallback. It could provide simple modern implementations of is-string, and dedupe multiple major versions, tho that begs the question what breaking change lead to a new mv and why?


Because formatters are increasingly popular, I think it'd be interesting to see a language that refuses to compile if the code is improperly formatted, and ships with a more tolerant formatter whose behavior can change from version to version. This way, the language can worry less about backwards compatibility or syntax edge cases, at the cost of taking away flexibility from its users.

> I would love to see a language try to implement a rule where only an indented line is considered part of the previous expression.

Elm does this (so maybe Haskell too). For example

    x = "hello "
     ++ "world"

    y = "hello "
    ++ "world" -- problem

how to handle expressions that need more than two lines?

Not only that, but they helped push for new web APIs and language features for server runtimes, like URLPattern

I bet that for the second random number in the same session, it is significantly less likely for an LLM to repeat its first number compared to two random draws. LLMs seem to mimic the human tendency to consider 7 as the most random, and I feel like repeating a random number would be perceived as not random.

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

Search: