Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

After 15 years i still cant remember which is which. I get annoyed every time. Maybe I should invest 15 minutes finally to remember properly
 help



Seriously!

Why not show the names of the branch + short Id (and when is not direct name, at least "this is from NAME")


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

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.


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'.

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.


> 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.

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.

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.


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.)


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

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

It does

This is one of my pain points, and one time I googled and got the real answer (which is why it's such a pain point).

That answer is "It depends on the context"

> The reason the "ours" and "theirs" notions get swapped around during rebase is that rebase works by doing a series of cherry-picks, into an anonymous branch (detached HEAD mode). The target branch is the anonymous branch, and the merge-from branch is your original (pre-rebase) branch: so "--ours" means the anonymous one rebase is building while "--theirs" means "our branch being rebased".[0]

[0] https://stackoverflow.com/questions/25576415/what-is-the-pre...


I ended up creating a personal vim plugin for merges one night because of a frustrating merge experience and never being able to remember what is what. It presents just two diff panes at top to reduce the cognitive load and a navigation list in a third split below to switch between diffs or final buffer (local/remote, base/local, base/remote and final). The list has branch names next to local/remote so you always know what is what. And most of the time the local/remote diff is what I am interested in so that’s what it shows first.

Let’s see if I get this wrong after 25 years of git:

ours means what is in my local codebase.

theirs means what is being merged into my local codebase.

I find it best to avoid merge conflicts than to try to resolve them. Strategies that keep branches short lived and frequently merging main into them helps a lot.


That's kind of the simplest case, though, where "theirs" and "ours" makes obvious sense.

What if I'm rebasing a branch onto another? Is "ours" the branch being rebased, or the other one? Or if I'm applying a stash?


"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.

"Clean" is not the same as "useful". You have to be really, really disciplined to not make a superficially looking "clean" history which may appear linear but which is actually total nonsense.

For example, if one is frequently doing "fix after rebase" commits, then they are doing it wrong and are making a history which is much less useful than a seemingly more complicated merge based history. Rebased histories are only clean if they also tell a true story after the rebase, but if you push "rebase fixes" onto the end of your history, then it means that prior rebased commits no longer make any sense because they e.g. use APIs that aren't actually there. Giving up and squashing everything to one commit is almost better in this case because it at least won't throw off someone who is trying to make sense of the history in the future.

I think that rebasing has won over merges mostly because the tools for navigating git histories suck SO HARD. I have used Perforce at a previous job, and their graphical tools for navigating a merge based history are excellent and were really useful for doing code archeology.


Generally our pattern is that every PR gets rebased into sensible commits. So in a way we are doing "squash commits" but the method is an interactive rebase. This keeps our history very pretty and clean, and simultaneously easy to grok and navigate.

My favorite git GUI is Sublime Merge.


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.

> What if I'm rebasing a branch onto another?

Just checkout the branch you are merging/rebasing into before doing it.

> Or if I'm applying a stash?

The stash is in that case effectively a remote branch you are merging into your local codebase. ours is your local, theirs is the stash.


The thing is, you'll typically switch to master to merge your own branch. This makes your own branch 'theirs', which is where the confusion comes from.

Not me. I typically merge main onto a feature branch where all the conflicts are resolved in a sane way. Then I checkout main and merge the feature branch into it with no conflicts.

As a bonus I can then also merge the feature branch into main as a squash commit, ditching the history of a feature branch for one large commit that implements the feature. There is no point in having half implemented and/or buggy commits from the feature branch clogging up my main history. Nobody should ever need to revert main to that state and if I really really need to look at that particular code commit I can still find it in the feature branch history.


Yep. This is the only model that has worked well for me for more than a decade.

This is what I do, and I was taught by an experienced Git user over a decade ago. I've been doing it ever since. All my merges into main are fast forwards.

> ours means what is in my local codebase

Since it's always one person doing a merge, why isn't it "mine" instead of "ours"? There aren't five of us at my computer collaboratively merging in a PR. There is one person doing it.

"Ours" makes it sound like some branch everyone who's working on the repo already has access to, not the active branch on my machine.


That's between you and git.

a better (more confusing) example:

i have a branch and i want to merge that branch into main.

is ours the branch and main theirs? or is ours main, and the branch theirs?


I always checkout the branch I am merging something into. I was vaguely aware I could have main checked out but merge foo into bar but have never once done that.

  git checkout mybranch
  git rebase main
A conflict happens. Now "ours" is main and "theirs" is mybranch, even though from your perspective you're still on mybranch. Git isn't, however.

Ah that’s fair. This is why I would do a `git merge main` instead of a rebase here.

I have met more than one person who would doggedly tolerate rebase, not even using rerere, instead of doing a simple ‘git merge --no-ff’ to one-shot it, not understanding that rebase touches every commit in the diff between main and not simply the latest change on HEAD.

Not a problem if you are a purist on linear history.


not understanding that rebase touches every commit in the diff

it sounds like that's a problem for you. why would that be? i prefer rebase and fast forward, but i am fully aware that rebase rewrites all commits.


> Let’s see if I get this wrong after 25 years of git

You used it 5 years before Linus? Impressive!


Haha yes. You caught me :)

I was wondering when someone was going to point it out. I actually have only been using it since about 2009 after a brief flirtation with SVN and a horrible breakup with CVS.


I was thinking about creating a TUI application that points out what each part in the conflict indicator corresponds to. This idea is primarily meant for rebases where the HEAD and the ID of the updated commits change constantly. Think of it as a map view of the rebase process, that improves your situational awareness by presenting all the relevant information simultaneously. That could trivially work for merges too.

iirc ours is always the commit the merge is starting from. the issue is that with a merge your current commit is the merging commit while with a rebase it is reversed.

I suspect that this could be because the rebase command is implemented as a serie of merges/cherry-picks from the target branch.


  git checkout mybranch
  git rebase main
Now git takes main and starts cloning (cherry-picking, as you said) commits from mybranch on top of it. From git's viewpoint it's working on top of main, so if a conflict occurs, main is "ours" and mybranch is "theirs". But from your viewpoint you're still on mybranch, and indeed are left on mybranch when the rebase is complete. (It's a different mybranch, of course; once the rebase is completed, git moves mybranch to point to the new (detached) HEAD.) Which makes "ours" and "theirs" exactly the opposite of what the user expects.

I had to make an alias for rebasing, because I kept doing the opposite:

    git checkout master #check out the branch to apply commits to
    git rebase mybranch #Apply all commits from mybranch
Now I just write

    rebase-current-branch
and it does what I want: fetches origin/master and rebases my working branch on top of it.

But "ours"/"theirs" still keeps tripping me up.


You can use the --onto flag for git rebase

  git rebase --onto origin/master
It will checkout origin/master and replay the current branch on top.

P.S. I had to check the man page as I use Magit. In the latter I tap r, then u. In magit my upstream is usually the main trunk. You can also tap e instead of u to choose the base branch.


Tip, you may want to use origin/HEAD over origin/master

Is it the naming-independent identifier of the tip of the trunk?

not strictly speaking, but in practice it's the tip of the remote default branch

Man do I hate this behavior because it would be really some by just using the branch names rather then "ours" and "theirs"

Agreed. Even when the branch is the same, it would always be distinguishable by <remote-name>/<branch-name> vs. just <branch-name>.

It doesn't matter which is which. The resolution will be the same regardless.



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

Search: