https://stackoverflow.com/a/28510260
That completely fixed the problem. Now I only ever get conflicts on my feature branches. The rule is always: rebase away from main, and merge towards main. All conflicts are then on your branches, never on main, and always from rebase, never from merge. Then I set the pull behaviour to rebase, too.
I've never had a silent regression since, and never had a problematic conflict scenario.
I did recently learn about ORIG_HEAD though which was very cool, because I had accidentally rebased to main instead of to a dev branch from which I had created a bunch of worktrees, then when I merged back to the dev branch all hell broke loose, and I learned that I could revert a merge by checking out ORIG_HEAD:
#~/.config/git/config
[rerere]
enabled = true
autoUpdate = true
while you're editing git config, consider these: [pull]
rebase = true
[rebase]
autoSquash = true
autoStash = true
[merge]
# zdiff3 adds original text markers and removes matching lines from conflict regions
# https://git-scm.com/docs/git-config#Documentation/git-config.txt-mergeconflictStyle
conflictStyle = zdiff3
autoStash = true
[push]
autoSetupRemote = true
default = simple
[init]
defaultBranch = mainYou got 4 devs. Each branched off from master. And we never merge from each other. Suppose 3 other people merged to master I pull it from master and fix only those conflicts. I’m not bringing your code into my branch unless it’s already finished and on master. If I need something you’re working on and it’s not on master when I need it, it’s a much larger planning issue.
If you have multiple environments before a stable master, do it only in one direction: feature > dev > staging > master. Don’t merge branches straight into staging or master.
I thought this was how everyone worked.
THEN, I move to jujutsu. Only has a few problems at start trying to use it as git, but after get the idea, all fine.
BTW, this was with the same team and they never know, so JJ is in fact better: It survive OTHERS workflows.
Let's say in my feature branch in the first commit I change a line in a file which also gets changed in the main branch. Then I have done 3-4 change commits doing changes in the same file. Now while doing rebase, I will have to resolve this conflict 3–4 times again and again while git re-applies commit one by one, during rebase.
I think I get this sometimes even if rerere is enabled, I am doing rebase using Intellij though, so maybe rerere doesn't get used here somehow or maybe diff context changes, so rerere is not applicable.
I do that a lot to avoid commits mutating mid-review, so you avoid having to force push over reviewed commits (which is a sin)
these are changes tacked to my git config over time. they're the result of working in a constant crunch state, where smooth forward move was more import than the state of the git history you left behind. these options remove small annoying road bumps. you should avoid some of them if you're working under different constraints, or the commit process differs such that these options don't apply.
it all starts with `pull.rebase=true` it makes `git pull --rebase` the default behavior. then comes `rebase.autoStash`, which just wraps the rebase with a stash push/pop envelop. if rebase is not your thing, and you prefer merge, `merge.autoStash=true` works the same. finally `push.autoSetupRemote=true` will skip asking you to set a remote tracking branch, it makes `git push` default to `git push --set-upstream`.
Instead of
`git push --force`
always use
`git push --force-with-lease`
https://git-scm.com/docs/git-push
This probably should be the default in git (as in there should be a `git push --force-without-lease` instead) and asks git to make sure the commits locally on your branch are up-to-date with those on remote/origin. It then fails if you try to overwrite commits that you haven't seen, and has saved me a few times when working between computers on the same project when i could have lost history on the remote that i failed to fetch.
If you are sure that the repo you are pushing to is a stable target (nobody else is accessing it), you just use --force.
If the repo you are pushing to is a moving target, you ... don't force push to it. Or else you warn all the repo users that you are about to rewrite history. Which means they not only should refrain from pushing, but have to be prepared for a second announcement which informs them that the rewrite is done; they must then fetch the rewritten head and fix up their unpublished work against the non-fast-forward change.
[pull]
rebase = true
Don't use this one unless you really know what you're doing. Multiple co-workers have gotten into really bizarre rebases because of it (like rebasing 70+ commits from master on top of their branch instead of the other way around), it seems to cause more problems than it solves.The man page for "git pull" even has a warning about using this flag.
Sometimes we will have a huge stack of changes one of us is "finished but not clear to merge".
either:
- We just swap ownership of the branches and eng 2 now commits directly in branch 1. We review the final content together and typically pull in a 3rd person to review our combined work. Eng 1 either pairs with eng 2 until its finished or starts on a task that is decoupled from those changes.
- We use an integration branch that gets threated like the temporary master branch until the feature is ready to merge.
You might get conflicts regularly, if people are touching the same area of the code that you are touching, but not the same ones.
rerere mainly comes into play when people have to backport commits into multiple similar branches, due to having a heavily forked git landscape.
git checkout feature
git rebase main
git checkout main
git merge feature
that way you get all your conflicts on the feature branch during rebase and your merge is always clean.
Pretty sure it's the other way around. You're on the branch and rebase it atop current master. If you merge after that, you won't have merge conflicts.
How? Usually I rebase the same branch multiple times onto different, but successive commits of the master branch. But after I solved a bunch of conflicts of the first rebase, I shouldn't have the same conflicts again in a second one, since the rebased branch contains the merged conflict. Rebasing again could only turn up new conflicts (with newer, other commits on the master branch).
How can I have the same conflict again for repeated rebases?
I wish it were :)
This is the right way to do it. Whether using trunk-based development, git-flow, etc -- you're controlling the flow of merges in a particular direction.
However, I think the "larger planning issue" is harder to easily avoid when you have more devs, more changes, or the AI-boosted output we have today. If feature B requires feature A, and feature A isn't up-to-date with main, I could rebase feature A to main, then feature B to feature A. When feature A is merged and we're ready to PR feature B, I can rebase to main again then make my PR.
this kind of interaction shows up more in a 'release' based model than CI environment, and one where the cost of testing is non-zero (because we're actually doing testing). I know that's not a very popular model, but not all software development is CI websites.
What then happens is that when a bug is found that affects all branches, it must be cherry picked into all of them. If that cherry pick runs into conflicts, it is often the same conflicts, over and over again on each branch.
Of course, the fix is not to do that, but it's easier to say that than to get away from that kind of workflow once you are steeped in it up to the chin.
the docs checkout, ty
the problem comes up the other way too. the person making the refactor can get stuck in a perpetual hell of constantly reapplying the changes to smaller deltas that are being committed daily to master.
It’s not easy though and you need to have a very good understanding of the codebase. But the nice thing is that you can do it without having to maintain a long lived branch (which is an antipattern).
Have you ever tried to merge two branches only to end up in conflict hell? You fix a bunch of conflicts only to run git merge --continue and be presented with the same conflicts. Repeat this process and after a few iterations you give up because it just isn't worth the pain and effort.
Would you be surprised to know that there is a git feature specifically for this problem? It's called rerere and I'm going to enrich your life with it now. (I'm going to talk specifically about merging but I think it also helps rebasing)
rerere stands for Reuse Recorded Resolution. The TL;DR version is you ask git to remember how you've resolved hunks in the past, and if the same one comes up for a file in future just redo what you did last time.
To enable this feature just run this lovely command git config --global rerere.enabled true. You can also turn it on by creating this directory in your projects .git/rr-cache, although the global setting is much clearer.
I'll try to take you through an example of how this works, bear with me it might get long.
We have our (tiny) project with only one file in it, which looks like this
.
└── user.rb
0 directories, 1 file
I branch off master to create a branch called dev and I add a line to user.rb. Now I would like to stage this change so I pull down staging and try to merge my dev branch but uh oh, someone has merged a change to staging affecting the same line in user.rb that I am editing.
/tmp/example [staging] » git merge dev
Auto-merging user.rb
CONFLICT (content): Merge conflict in user.rb
Automatic merge failed; fix conflicts and then commit the result.
We've all seen this before, a run of the mill conflict message. However if you were to have rerere enabled you would get this output
/tmp/example [staging] » git merge dev
Auto-merging user.rb
CONFLICT (content): Merge conflict in user.rb
Recorded preimage for 'user.rb'
Automatic merge failed; fix conflicts and then commit the result.
You can now see a new line saying Recorded preimage for 'user.rb'. Running git rerere diff right now will give you the current state of the resolution file:
/tmp/example [c013552] » git rerere diff
--- a/user.rb
+++ b/user.rb
@@ -1,5 +1,5 @@
-<<<<<<<
-hello
-=======
+<<<<<<< HEAD
hi
->>>>>>>
+=======
+hello
+>>>>>>> commit from dev
You go about the usual conflict workflow, choose which changes to keep, and commit the result. If you run git rerere diff again, you see the recorded resolution:
/tmp/example [c013552] » git rerere diff
--- a/user.rb
+++ b/user.rb
@@ -1,5 +1 @@
-<<<<<<<
hello
-=======
-hi
->>>>>>>
Running git merge --continue will apply your commit and tell you about the new resolution for our file:
/tmp/example [c013552] » git merge --continue
Recorded resolution for 'user.rb'.```
Let's undo that merge with `git reset --hard HEAD^` and merge again:
```/tmp/example [staging] » git merge dev
Auto-merging user.rb
CONFLICT (content): Merge conflict in user.rb
Resolved 'user.rb' using previous resolution.
Automatic merge failed; fix conflicts and then commit the result.
/tmp/example [staging] » git add .
/tmp/example [staging] » git merge --continue
[staging f4a7d36] Merge branch 'dev' into staging
The important line in this output is Resolved 'user.rb' using previous resolution.. I didn't need to even look at the file, just commit the result. This worked because git saw the conflict, looked in the rr-cache folder and recognised this hunk from this file from a previous merge and applied your decision from last time!
As always, I hope you found that useful.