You should want those merges so that you can later bisect where a problem was introduced. If you start/end each day with a merge and leave the merge commits in, and you notice something goes wrong later, it's much easier to trace where the problem is.
Rebase definitely satisfies my OCD and makes everything look pretty, but it's actually worse.
What, specifically, do you mean by "something goes wrong later"? Like a full outage of some kind? A production bug? A mistake according to the design spec? A failed test in CI? A failed human QA validation? What are we talking about here?
>it's much easier to trace where the problem is.
Furthermore, why would the git commit structure have anything to do with debugging any type of issue, regardless? Isn't debugging mostly comprised of combing through logs, adding random print statements to console output, and using an IDE debugger? Or some kind of more advanced tracing/profiling tools? Are you debugging by hunting in commit histories and Jira tickets or something?
Or are you talking about blaming, not "tracing where the problem is", but "tracing who the problem is"?
That article is some FUD about a real bug in a feature branch being masked due to other bugs that were introduced by the rebase itself.
That is not realistic because, firstly, a very specific test can (and should) be used for git bisect. You're not just testing for "software fails", but "very specific case fails in a specific way".
If some intermediate issues prevent you from testing the specific test case (you're not able to call "good" or "bad"), you can tell git bisect to skip that commit.
Shit happens! Bug was introduced a month ago, but two weeks ago, a range of commits happened that don't even build in your environment. git bisect will hit that.
The article also glosses over the possibility that git bisect goes back farther than the bad commit. Commits that are not testable can occur earlier or later than the one we are looking for.
Basically the article is just a new version of the old meme "merging is dangerous". except that it doesn't make any sense, since both "git merge" and "git rebase" textually merge code.
I think the best point of this article is accidentally introducing breakages into history that later defeat the purpose of git-bisect. However, in practice I haven't run into this problem, at least not in an insurmountable way.
One solution is, if you can run your tests with e.g. `make test`, you can run `git rebase --exec "make test"` which will run your test suite against every commit being rebased. I've only done it a handful of times, and because of how long it takes I'd only run it after finishing a rebase that wound up needing resolutions, instead of doing it up front and having to wait for the tests to finish at each commit before potentially resolving another upcoming conflict.
If you are very paranoid about breaking commits being pushed, disallowing rebase isn't enough: any dev could simply write a commit with a breakage. You'd have to set up pre-commit hooks to run your tests, or a pre-push hook to run `git rebase --exec "make test"`. TIL there is also a post-rewrite hook, which sounds like it solves exactly your worry with rebase, but as I mentioned, introduces long delays between rebase commits/resolutions–I'd probably forget what I was doing during each test run, haha.
It can definitely be made workable, but why do so much in pursuit of a slightly neater git log? I place a much higher value on the hashes of my commits not changing than having extra merge commits.
FWIW I prefer rebasing locally (to squash/split/cleanup commits, or catch up to upstream) and merging remotely (create a merge commit at the completion of each work branch). I just wanted to get the info out there for those interested!
Good point. I think people don't realize just how much git is capable of doing. Pretty much any strategy can be made to work with git, and it's super extensible if you need it to be.
I generally follow your strategy myself as long as it's something small, but I think there's this meme out there that having merge commits in your branch is somehow shameful, when in reality it should celebrated as an accurate history.
>Why do we use Git at all? Because it is our most important tool for tracking down the source of bugs in our code. Git is our safety net.
I disagree with git as a bug tracking tool, it's a distributed code repository. It's closer to google docs than it is to an IDE debugger or a system log.
I also disagree with the idea that "tracking down the source of bugs" has anything to do with quality or a "safety net."
Can someone please explain to me where this kind of "commit blame" culture comes from? And what the point is? What is the next step that happens after you identify the "offending commit"? I don't understand why you wouldn't just open a new PR to fix the bug and move the fuck on with your life? What business value is there in this type of exercise to specifically identify which git commit "caused a bug"?
We have been struggling between the merge/rebase approaches with no clear winner in our minds so far. In principle the rebase seems to be superior, but I can certainly see arguments for using merge if it satisfies downstream concerns like traceability (I am still not clear on this stated benefit).
Perhaps someone has a list of criteria that can help you pick between the 2 approaches, or perhaps both merge/rebase are valid at all times, but there is a certain set of rules to evaluate when choosing which to use?
rebase has good traceability, if you retain the original artifacts.
When we rebase a public feature branch and merge it into a mainline, we nicely have two versions of that.
Basically, rebase and merge could be reconciled like this: we could merge a feature branch by iterating over it, and invidiually doing a git merge for every commit. Then that would be a rebase. Except that every commit would have an additional parent pointer which cross-references it to the original branch.
I.e. good rebase:
B0---B1---B2
/
--- A --- B ---- C --- D --- B0' --- B1' --- B2'
good merge (exact same thing, just with parent pointers):
---------------B0-----B1-----B2
/ \ \ \
--- A --- B ---- C --- D --- B0' --- B1' --- B2' ---
typical silly git merge:
--B0-----B1-----B2
/ \
--- A --- B ---- C --- D --- B{0+1+2}
still, that cross-referencing parent info in the second example above is not that useful. It's still making a salad out of the git graph. And is not two-way navigable, either. (Say I'm looking at B1 and want to locate B1'.)
It's better to have something in the commit messages to track changes that get cherry picked in multiple places. The Gerrit review system has a Change-ID: line with a 160 bit random ID, for instance. All rebases/picks of the same change carry the same ID, which correlates them to the same review item.
The concept of "git bisect" is inherently predicated on a simple linear history consisting of commits that were applied on top of each other. We are searching for the bad one which flips the test case from "good" to "bad".
If you throw an ugly graph into it and merges, it's not even comprehensible. Say that instead of git bisect we just go backwards, commit by commit, starting at HEAD. Oops, we encounter a merge commit: which way do we go now to find the breakage? Parent[0], or parent[1]? Or [2] ...? What is the correct traversal order? Do we go down [0] first? At which commit do we backtrack and try going into [1]?
There is absolutely no issue with git bisect over a repository where you have the unmodified upstream commits, and your own local ones rebased on top of them. It nicely finds the bad commit in one or the other.
Now of course the rebase rewrites your code by merging it (in the real sense of the word). If you discovered the bug yesterday (and it's in your own commits), then you rebased this morning, you're now chasing that bug over different commits that have been subject to auto-merge and maybe even manual conflict resolution. Oh well; yesterday was yesterday! Repro the bug with the newly rebased repo and bisect away.
Or, here is an idea: if you're chasing a bug, don't fetch anything and don't rebase! Right? If you've repro-d a bug, you don't want to start changing code, so the last thing you want is to bring in reams of changes from other developers. I've never rebased while chasing a bug with git bisect; I stopped what I was doing until I found it, in the nice linear history as it existed at that point.
If external forces cause you to drop what you're doing and rebase while you have some bug, what you can always do is "git tag bug-hunt" to set a bookmark where you were. Do what you have to do, and then when you pop your stack, return to that tag and debug from there. You can find the original bad commit. If it's in your uncommitted work, you can cross-ref it to the latest rebase. (There is a chance it might not exist any more; e.g. upstream changes made some of your local code obsolete, so you threw them away in the rebase, and the problem was in those discarded changes.)
If you have a long running branch, you're going to have conflicts. When you resolve those conflicts during a rebase, you lose your history of the conflict resolution.
What you're thinking of as a "simple linear history" is actually not. Notice that when you rebase there are new hashes for each of your commits.
If you're rebasing your branch with master and resolving merge conflicts along the way, you're going to get bit eventually. Even if you're somehow not dealing with merge conflicts, you're losing your ability to look at the git log and figure out when the last time you synced up with master was.
You can have conflicts even if you have a single-commit local change, that's less than an hour old.
Long running branches are a bad idea. Try not to do that.
Not everything in a long running branch can be merged; selective things will end up cherry-picked (which is a generalization of rebase). Suppose the trunk maintainers like 75% of what was done in the branch. They might go through the commits one by one and take what is relevant.
When you merge a branch, and want to follow a good SCM process, you must treat it as a closed topic; do not commit anything more to that branch. Start a new branch, even for a closely-related continuation of the same work.
The correct way to merge a branch is to rework every single commit onto the new baseline. That is true whether that rework is represented as a merge, or a cherry-pick. Therefore, we can't avoid the issue that every change turns into a new object with a different hash.
What we can do is put tracking information in the commit message to correlate these together. The Gerrit review system uses a Change-Id: line for this. Each commit has random ID which is not a content digest; it stays the same across cherry picks.
If I'm debugging a problem on trunk T, across some commits that came from a branch R, I do not care what those commits looked like in R. Only in a very rare case will I be interested how a change looked like there. I want every commit that came from R to be reworked on T, as if it was originally developed on T. If I find the problem and have a hunch that it was introduced during the merge, then I might out of curiosity find the commit's cousin in the original R branch just to see how it looked like there. Other than that, I'm shipping T, not R, so that's what I'm debugging.
It's actually not! It will work fine. Linear history is one strategy for using git (and it's a reasonable one), but try introducing (manual, planned, not done with git pull) merges sometime, at points where the actual intent of the maintainer is to perform a merge - you may be pleasantly surprised. (If you use the program naively you will eventually confuse the algorithm, which may have bitten you in the past - forcing linearity is way better than naive use.)
I did that until about 9 years ago. It was too annoying and error-prone (due to forgetting to set that up in new environments, or in some cases not being able to, like in some build machine account or whatever).
Instead I simply learned not to use git pull. I always pick up new changes with git fetch, followed by git rebase.
Just purely out of computer science principle, I want to avoid commands that bundle multiple unrelated operations and behave differently based on magic globals.
If I notice upstream has been forced (shouldn't happen in any well-run public repos), I tread carefully. Instead of just a git rebase, I will do something like git rebase HEAD~3 --onto origin/master. (If 3 of the top commits are my local ones.) That tries to transplant the 3 commits onto the new material coming from the remote. Basically, reset to the remote, then cherry-pick my commits.
Not in remote if you disallow force-push. For local I agree. For hairy rebases I normally set a marker tag (easier than to comb through the reflog). Then it is easy to reset the branch to its initial irrespective of what you've done.
Quite the opposite, sometimes rebasing means you need to force-push, and not doing so wreaks havoc.
Imagine I'm working on my own feature branch, pushing regularly to remote. I make my pull request to develop, but develop is ahead of me and my pull request can't be merged. So I pull develop, rebase my own local commits on top of that, and push the result in my feature branch to remote, which contains some of my old, un-rebased commits. The push is refused, and instead either git or intelliJ automatically pulls from the remote feature branch into my local branch, merging with no problem, so it can be pushed again.
It works, but not all my commits occur two times in the history, and that's bad.
I suspect it's not git doing this but IntelliJ, but it's a popular IDE, so this can easily happen to anyone if you're accidentally rebasing commits you already pushed.
Force pushing prevents the problem, but you're probably better off not rebasing in the first place. Don't change history that also exists in another location.
Any good git "best practices" doc will inform the user to never use rebase on a shared remote repo. The details are given, but sadly I can never really follow the logic. Rebase always sounds like a good idea (imo). And being the only one to use rebase on my team... I've had several instances where I go to rebase a remote branch onto my local work, and I'm met with tons of conflicts.. except the conflicts are all my own commits from a later time. I've never really figured out why, but I've since just stopped using rebase unless I really know there is nothing funny on the remote branch.
The conflicts from a later time happens because rebase applies one commit at a time.
If you solve a conflict from your first commit by applying changes that were made only in the later ones, you'll need to solve this same conflict again and again.
If resolving the same conflicts over and over again in many commits gets onerous enough to make it worthwhile investing a little time learning another part of git, you can check out git-rerere (Reuse recorded resolution): https://git-scm.com/docs/git-rerere
Rebase should not be done on a large history. Merge long histories, only rebase short ones. Long histories are probably useful to see separately in the git history anyway, and it's only for a history of one or two commits that the extra merge commits begin to dominate the real commits in your history.
Personally, I prefer to never use git pull, and instead use git fetch then manually merge or rebase as appropriate. It's one more command, but I find it helps me visualize exactly what I want to happen.
> are you saying that git pull can produce some unwanted artifacts? (e.g. not do exactly what you want it to do?)
Not in the sense that git pull has unpredictable behavior, but I like being able to view the remote history and manually diff against my local branch if there are changes that have been made.
Though it's still better to do fetch when you want to compare first, pull outputs the ranges of commits that it's pulling for each branch, so you can just copy and paste that into a diff or log command, too.
I say this because I used to just ignore the output, but then I found that it's useful, too.
So, if git-pull outputs
b3b2007..9bad624 master -> origin/master
I can do `git diff b3b2007..9bad624` or `git log b3b2007..9bad624` to see what I just pulled.
I prefer to always use fast-forwarding when pulling, either with `git pull --ff-only` or setting pull.ff to "only" in configuration. This way, if a merge conflict is encountered, it will refuse the pull and you can then do your preferred manual resolution flow of either merging or rebasing. This way I can get the majority of pulls done quickly, because more often than not, I don't have to resolve conflicts (of course YMMV). I also prefer the slightly more verbose output from `git pull --ff-only` vs plain `git pull`.
There's also `git pull --rebase` (mentioned in TFA but not really explained), which sort of combines `git pull --ff-only` followed by a `git rebase` if it failed.
I do this as an advanced user with a strong mental model of git, but I find the easier recommendation to others is to just not use the command - '--ff-only' just becomes another incomprehensible incantation without understanding.
You're correct to do so, since git pull is designed for Linus Torvalds or one of his vice-Linuses to merge a large remote patchset they've already reviewed
I like how straightforward this blog post is. The graphics are very helpful in getting the point across.
Unfortunately, this is dangerous advice in a reasonably sized engineering team.
The assumption of pushing to master is kind of a dangerous one. Don't do that unless it's a project that is yours and yours alone.
If you have a change you want to get in, consider a pull request. Even if you don't need it reviewed, it generally encourages you to use a separate branch.
If you find that you have a conflict with origin/master, you can then easily rebase your changes. Because of the nature of refs in git, you can also create a separate branch before rebasing so that you don't have to worry about about losing anything, or just to diff what you ended up changing after a particularly hairy rebase.
So yes in short, you can use rebases to avoid merge commits. But the use case here and the workflow described is not one I would recommend for teams using a centralized origin or a stable head of master.
I didn't get the impression the article assumed pushing to master. It was more a case of you're working on a feature branch and another dev pushes to that same feature branch before you push your local commits.
I agree that even in larger teams you shouldn't face this problem in the first place if devs work just on their own branches instead of sharing. Rebasing is also a great way way to avoid merge commits.
I started using git two years ago, and when I first asked all our senior engineers about it (after reading an article or two on HN about best practices) - the advice I was given was, "Never rebase." Among the more Jr. Engineers there was this feeling that it was a dangerous command that should be steered clear of, and the typical approach, of branching off of remote master, making changes, pushing to your branch, testing your code in the CI/CD environment (with appropriate unit/integration tests), and then issuing a PR to be reviewed and merged into master was the appropriate use of git.
I don't think I've ever seen anybody (except very advanced git users) ever use "rebase" - so, no, I don't think I would consider it a basic command. Despite using git dozens of times every day, I don't believe I've ever rebased outside of a test-environment to see how it worked.
It's a bit frustrating that the whole "never rebase" thing has become a cargo cult -- because it's only correct in specific circumstances (thus it's completely wrong to say "never").
The reason why the Linux kernel has a no-rebase policy for maintainer trees is that thousands of developers base their work on the maintainer trees. Developers are constantly rebasing their work (either to update to the latest development branch by a maintainer, or to restructure and modify their commits with "git rebase -i"). Thus, rebasing maintainer trees would result in many developers having to deal with frustrating issues when they rebase (they have to curate which commits the maintainer has removed to keep with "git rebase -i"). It addition, maintainer trees get tested automatically and rebasing a tree invalidates all of the previous testing (so if you ask Linus to pull from you and the tree was just rebased, you're sending untested code and will get shouted at).
Yes, you should never rebase master -- because there is guaranteed to be more than one user of master. But rebasing (for developers working on a feature) should be a very familiar thing to do -- unless you're working on a feature branch with someone else.
I'm far from an advanced user, but I use rebase fairly regularly for long running feature branches. As the parent branch continues along, I periodically rebase complicated feature branches so that I'm doing a number of small easy to handle conflict resolutions, rather than one almighty one at the end. I've never had any trouble, and I'd never considered it to be anything out of the ordinary or anything other than basic!
Huh. Personally, I merge master into my feature branches to keep up to date instead of rebasing - I find it helps the tree match the actual actions that were taken.
`git rebase` is fairly useful when differences are small. Rebase changes the history of your own commits. When that history is long (you've got a lot of commits that are not in the other branch), you'll end up resolving the same conflicts over and over and over again, and in that case, a simple merge is much less painful.
The only real advantage of rebasing is that it keeps your history linear. A history with merges is harder to navigate. But at some point, merges become unavoidable, and the larger the differences are, the more important it becomes to use merge instead of rebase.
And as any time traveler knows, changing history comes with risks.
49 comments
[ 2.1 ms ] story [ 167 ms ] threadRebase definitely satisfies my OCD and makes everything look pretty, but it's actually worse.
What, specifically, do you mean by "something goes wrong later"? Like a full outage of some kind? A production bug? A mistake according to the design spec? A failed test in CI? A failed human QA validation? What are we talking about here?
>it's much easier to trace where the problem is.
Furthermore, why would the git commit structure have anything to do with debugging any type of issue, regardless? Isn't debugging mostly comprised of combing through logs, adding random print statements to console output, and using an IDE debugger? Or some kind of more advanced tracing/profiling tools? Are you debugging by hunting in commit histories and Jira tickets or something?
Or are you talking about blaming, not "tracing where the problem is", but "tracing who the problem is"?
That is not realistic because, firstly, a very specific test can (and should) be used for git bisect. You're not just testing for "software fails", but "very specific case fails in a specific way".
If some intermediate issues prevent you from testing the specific test case (you're not able to call "good" or "bad"), you can tell git bisect to skip that commit.
Shit happens! Bug was introduced a month ago, but two weeks ago, a range of commits happened that don't even build in your environment. git bisect will hit that.
The article also glosses over the possibility that git bisect goes back farther than the bad commit. Commits that are not testable can occur earlier or later than the one we are looking for.
Basically the article is just a new version of the old meme "merging is dangerous". except that it doesn't make any sense, since both "git merge" and "git rebase" textually merge code.
One solution is, if you can run your tests with e.g. `make test`, you can run `git rebase --exec "make test"` which will run your test suite against every commit being rebased. I've only done it a handful of times, and because of how long it takes I'd only run it after finishing a rebase that wound up needing resolutions, instead of doing it up front and having to wait for the tests to finish at each commit before potentially resolving another upcoming conflict.
If you are very paranoid about breaking commits being pushed, disallowing rebase isn't enough: any dev could simply write a commit with a breakage. You'd have to set up pre-commit hooks to run your tests, or a pre-push hook to run `git rebase --exec "make test"`. TIL there is also a post-rewrite hook, which sounds like it solves exactly your worry with rebase, but as I mentioned, introduces long delays between rebase commits/resolutions–I'd probably forget what I was doing during each test run, haha.
I generally follow your strategy myself as long as it's something small, but I think there's this meme out there that having merge commits in your branch is somehow shameful, when in reality it should celebrated as an accurate history.
I disagree with git as a bug tracking tool, it's a distributed code repository. It's closer to google docs than it is to an IDE debugger or a system log.
I also disagree with the idea that "tracking down the source of bugs" has anything to do with quality or a "safety net."
Can someone please explain to me where this kind of "commit blame" culture comes from? And what the point is? What is the next step that happens after you identify the "offending commit"? I don't understand why you wouldn't just open a new PR to fix the bug and move the fuck on with your life? What business value is there in this type of exercise to specifically identify which git commit "caused a bug"?
Perhaps someone has a list of criteria that can help you pick between the 2 approaches, or perhaps both merge/rebase are valid at all times, but there is a certain set of rules to evaluate when choosing which to use?
When we rebase a public feature branch and merge it into a mainline, we nicely have two versions of that.
Basically, rebase and merge could be reconciled like this: we could merge a feature branch by iterating over it, and invidiually doing a git merge for every commit. Then that would be a rebase. Except that every commit would have an additional parent pointer which cross-references it to the original branch.
I.e. good rebase:
good merge (exact same thing, just with parent pointers): typical silly git merge: still, that cross-referencing parent info in the second example above is not that useful. It's still making a salad out of the git graph. And is not two-way navigable, either. (Say I'm looking at B1 and want to locate B1'.)It's better to have something in the commit messages to track changes that get cherry picked in multiple places. The Gerrit review system has a Change-ID: line with a 160 bit random ID, for instance. All rebases/picks of the same change carry the same ID, which correlates them to the same review item.
The concept of "git bisect" is inherently predicated on a simple linear history consisting of commits that were applied on top of each other. We are searching for the bad one which flips the test case from "good" to "bad".
If you throw an ugly graph into it and merges, it's not even comprehensible. Say that instead of git bisect we just go backwards, commit by commit, starting at HEAD. Oops, we encounter a merge commit: which way do we go now to find the breakage? Parent[0], or parent[1]? Or [2] ...? What is the correct traversal order? Do we go down [0] first? At which commit do we backtrack and try going into [1]?
There is absolutely no issue with git bisect over a repository where you have the unmodified upstream commits, and your own local ones rebased on top of them. It nicely finds the bad commit in one or the other.
Now of course the rebase rewrites your code by merging it (in the real sense of the word). If you discovered the bug yesterday (and it's in your own commits), then you rebased this morning, you're now chasing that bug over different commits that have been subject to auto-merge and maybe even manual conflict resolution. Oh well; yesterday was yesterday! Repro the bug with the newly rebased repo and bisect away.
Or, here is an idea: if you're chasing a bug, don't fetch anything and don't rebase! Right? If you've repro-d a bug, you don't want to start changing code, so the last thing you want is to bring in reams of changes from other developers. I've never rebased while chasing a bug with git bisect; I stopped what I was doing until I found it, in the nice linear history as it existed at that point.
If external forces cause you to drop what you're doing and rebase while you have some bug, what you can always do is "git tag bug-hunt" to set a bookmark where you were. Do what you have to do, and then when you pop your stack, return to that tag and debug from there. You can find the original bad commit. If it's in your uncommitted work, you can cross-ref it to the latest rebase. (There is a chance it might not exist any more; e.g. upstream changes made some of your local code obsolete, so you threw them away in the rebase, and the problem was in those discarded changes.)
What you're thinking of as a "simple linear history" is actually not. Notice that when you rebase there are new hashes for each of your commits.
If you're rebasing your branch with master and resolving merge conflicts along the way, you're going to get bit eventually. Even if you're somehow not dealing with merge conflicts, you're losing your ability to look at the git log and figure out when the last time you synced up with master was.
Long running branches are a bad idea. Try not to do that.
Not everything in a long running branch can be merged; selective things will end up cherry-picked (which is a generalization of rebase). Suppose the trunk maintainers like 75% of what was done in the branch. They might go through the commits one by one and take what is relevant.
When you merge a branch, and want to follow a good SCM process, you must treat it as a closed topic; do not commit anything more to that branch. Start a new branch, even for a closely-related continuation of the same work.
The correct way to merge a branch is to rework every single commit onto the new baseline. That is true whether that rework is represented as a merge, or a cherry-pick. Therefore, we can't avoid the issue that every change turns into a new object with a different hash.
What we can do is put tracking information in the commit message to correlate these together. The Gerrit review system uses a Change-Id: line for this. Each commit has random ID which is not a content digest; it stays the same across cherry picks.
If I'm debugging a problem on trunk T, across some commits that came from a branch R, I do not care what those commits looked like in R. Only in a very rare case will I be interested how a change looked like there. I want every commit that came from R to be reworked on T, as if it was originally developed on T. If I find the problem and have a hunch that it was introduced during the merge, then I might out of curiosity find the commit's cousin in the original R branch just to see how it looked like there. Other than that, I'm shipping T, not R, so that's what I'm debugging.
See https://mirrors.edge.kernel.org/pub/software/scm/git/docs/gi... for more info
Instead I simply learned not to use git pull. I always pick up new changes with git fetch, followed by git rebase.
Just purely out of computer science principle, I want to avoid commands that bundle multiple unrelated operations and behave differently based on magic globals.
If I notice upstream has been forced (shouldn't happen in any well-run public repos), I tread carefully. Instead of just a git rebase, I will do something like git rebase HEAD~3 --onto origin/master. (If 3 of the top commits are my local ones.) That tries to transplant the 3 commits onto the new material coming from the remote. Basically, reset to the remote, then cherry-pick my commits.
It just a normal workflow and, sometimes, housekeeping in your small "feature" branch.
Imagine I'm working on my own feature branch, pushing regularly to remote. I make my pull request to develop, but develop is ahead of me and my pull request can't be merged. So I pull develop, rebase my own local commits on top of that, and push the result in my feature branch to remote, which contains some of my old, un-rebased commits. The push is refused, and instead either git or intelliJ automatically pulls from the remote feature branch into my local branch, merging with no problem, so it can be pushed again.
It works, but not all my commits occur two times in the history, and that's bad.
I suspect it's not git doing this but IntelliJ, but it's a popular IDE, so this can easily happen to anyone if you're accidentally rebasing commits you already pushed.
Force pushing prevents the problem, but you're probably better off not rebasing in the first place. Don't change history that also exists in another location.
Written by Drew Devault, author/maintainer of swaywm, sourcehut and others.
If you solve a conflict from your first commit by applying changes that were made only in the later ones, you'll need to solve this same conflict again and again.
When in doubt, just merge. It's always safer.
are you saying that git pull can produce some unwanted artifacts? (e.g. not do exactly what you want it to do?)
Not in the sense that git pull has unpredictable behavior, but I like being able to view the remote history and manually diff against my local branch if there are changes that have been made.
I say this because I used to just ignore the output, but then I found that it's useful, too.
So, if git-pull outputs
I can do `git diff b3b2007..9bad624` or `git log b3b2007..9bad624` to see what I just pulled.There's also `git pull --rebase` (mentioned in TFA but not really explained), which sort of combines `git pull --ff-only` followed by a `git rebase` if it failed.
Unfortunately, this is dangerous advice in a reasonably sized engineering team.
The assumption of pushing to master is kind of a dangerous one. Don't do that unless it's a project that is yours and yours alone.
If you have a change you want to get in, consider a pull request. Even if you don't need it reviewed, it generally encourages you to use a separate branch.
If you find that you have a conflict with origin/master, you can then easily rebase your changes. Because of the nature of refs in git, you can also create a separate branch before rebasing so that you don't have to worry about about losing anything, or just to diff what you ended up changing after a particularly hairy rebase.
So yes in short, you can use rebases to avoid merge commits. But the use case here and the workflow described is not one I would recommend for teams using a centralized origin or a stable head of master.
I agree that even in larger teams you shouldn't face this problem in the first place if devs work just on their own branches instead of sharing. Rebasing is also a great way way to avoid merge commits.
Also, isn't this very basic? I thought pull --rebase was one of the most basic commands everyone learned while getting acquainted with git.
I don't think I've ever seen anybody (except very advanced git users) ever use "rebase" - so, no, I don't think I would consider it a basic command. Despite using git dozens of times every day, I don't believe I've ever rebased outside of a test-environment to see how it worked.
The reason why the Linux kernel has a no-rebase policy for maintainer trees is that thousands of developers base their work on the maintainer trees. Developers are constantly rebasing their work (either to update to the latest development branch by a maintainer, or to restructure and modify their commits with "git rebase -i"). Thus, rebasing maintainer trees would result in many developers having to deal with frustrating issues when they rebase (they have to curate which commits the maintainer has removed to keep with "git rebase -i"). It addition, maintainer trees get tested automatically and rebasing a tree invalidates all of the previous testing (so if you ask Linus to pull from you and the tree was just rebased, you're sending untested code and will get shouted at).
Yes, you should never rebase master -- because there is guaranteed to be more than one user of master. But rebasing (for developers working on a feature) should be a very familiar thing to do -- unless you're working on a feature branch with someone else.
If you consider it's a trick, please don't use it, it means you don't understand it yet.
If you consider sticking so much to literality, please don't use languages, it means you don't understand them yet.
The only real advantage of rebasing is that it keeps your history linear. A history with merges is harder to navigate. But at some point, merges become unavoidable, and the larger the differences are, the more important it becomes to use merge instead of rebase.
And as any time traveler knows, changing history comes with risks.