Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Proper way to proceed when upstream gets ahead from incomplete PR?

Tags:

git

github

INTRODUCTION

I've forked a repository that follows the so-called "git flow" and has to develop/master/factory branches. I'll reference from now on my fork as origin and the main repository as upstream.

After opening an issue and talk/agree on few things with the repo maintainer I started working on it. The first thing I did was creating a feature branch tracking (origin/develop).

After few temporary commits with temporary throw-away temporary bad names, I'd created a PR and pushed. The idea I had in mind was eventually squashing all my commits on a single one and then providing a proper commit name/description so the maintainer would be able to merge all of it to upstream/develop without any problems, my main goal was making the whole process as smooth as possible both for me and the upstream maintainer, once merged I'd happily delete my local branch, job done, easy peasy... :)

PROBLEM

I was naive thinking that would go so smoothly!

I'm not a git expert by any means and I was wrong thinking the PR would evolve so smoothly (this tend to happen when you don't know really the tools you're using). At a certain point, I'd stopped working on my PR for few days and obviously upstream/develop continued evolving and getting far away ahead of my PR, my PR wasn't passing the tests yet and the whole PR was still pending unfinished job.

A few days later I decided to come back to that PR and tried to resume my work, after fetching upstream/develop I'd seen many upstream commits were already far away ahead from my PR and I didn't really know what was the best choice on that particular situation, I didn't know and I still don't know whether merging or rebasing is the best choice...

With little knowledge about the possible implications about merging or rebasing I decided merge couldn't be that bad and everything would be possible to tidy up eventually, right? Well, as a result, after merging and pushing some more temporary commits my local history has become a little bit messy and I don't really know whether this can be cleaned somehow without messing up the upstream history.

Let's say the history looks something like PR=c1->c2->c3->upstream1->upstream2->upstream3->c4->c5. In that example c1..c6 would be my local changes and upstream1..upstream3 would be committing ahead from upstream.

QUESTIONS

  • Was my decision of merging a really bad choice when upstream got very far away ahead from my unfinished PR? What would have been the best way to proceed in that case? Consider my goal is ending up with a single squashed commit merged into upstream eventually
  • Once the harm is done and I've merged after solving the conflicts and created few more commits, would still be possible to provide a clean PR with just 1 single squashed commit without rewriting upstream history?

I guess the whole thread could be summed up by asking what's the best way to proceed when upstream gets far away from your unfinished PR that contains multiple temporary commits.

like image 234
BPL Avatar asked Sep 16 '18 22:09

BPL


2 Answers

I'll tell what I do in such a case. This approach is highly practical and probably different people have different personal choices.

I also, like you, strive to squash commits to 1 commit in PR.

So let's say, there is a dev branch.

When I start a feature branch I do:

>> git checkout dev
>> git pull
>> git checkout -b feature_branch
>> commitA
>> commitB
>> git push -u origin feature_branch // now there is origin/feature_branch available to everyone
>> and create a PR when I'm ready

Now the process goes like this:

People review my work and comment, I make my changes and commit. It's a kind of loop, it ends where all of us are satisfied with these changes and ready to merge things back

>> commitC
>> commitD

So, now I'm ready to "squash my commits into one". So I do:

>> // make sure I'm on feature_branch
>> git rebase -i HEAD~4
>> // at this point I have one 'beautiful' commit, lets_call it commit_TO_GO
>> git push -f //forcefully push my commits by overriding the current state of a remote branch in PR, this is not really important, only if I want to "preserve this state for backup or something

As long as this branch is not merged, I don't mind to do this, it doesn't matter.

Let's pretend that this process has taken some time, and meanwhile, there are some new commits in the origin/dev branch (commitX, commitY and commitZ) done by other teammates.

So, in theory, I can now do the following: Merge by applying a regular 3-way merge, if there are conflicts I'll have to resolve them first of course.

However, since I'm concerned about the history of commits (like you're), I do the following:

>> git fetch --all 
>> git rebase origin/dev
>> // at this point The my commit_TO_GO is applied on top of commitX,commitY,commitZ. 
>> // technically it has a different sha1 now, but it's not really important, its 
>> still my own branch, I do whatever I want there :)
>> git push -f // again forcefully push to origin/feature_branch

After this step, the origin/feature_branch is FF from origin/dev, which is cool, because I can apply merge and it will be in an FF mode, even merge commit won't be created.

Of course, if there are conflicts the rebase won't work, I'll have to resolve the conflicts first. I do this in my IDE and then continue the rebase (git rebase --continue), but conflict resolution is beyond the scope of this question

Now I'm ready to merge my change back to origin/dev

Usually, I do it in UI of bitbucket/github.

After the merge the history in origin/dev looks beautiful:

commitX->commitY->commitZ->commit_TO_GO

Result: No merge commits at all, my single commit is applied last

One Point to consider:

Its worths nothing to rebase from dev branch even during your development (while you're working on feature_branch), just to make sure it contains the latest changes. So you can go through the loop as many times as you want:

 >> git fetch --all 
 >> git rebase origin/dev

I understand that probably Git has more "shortcut commands" up its sleeves, and maybe this explanation was too detailed.

like image 112
Mark Bramnik Avatar answered Oct 05 '22 06:10

Mark Bramnik


In terms of end results, merging and rebasing is the same... However, in terms of the resulting history and how easy it would be to be able to move around your work 'in isolation', they are way different... If you work, then merge, then work more... Then merge... Then work some more and so on, it's very difficult to see all the revisions that make up the work of this feature alone.

All of this to say: just rebase. What should do? Go to the tip of upstream/develop, cherry-pick the real revisions that make up the feature work (no merges)... This way, you cleaned up the branch... Set the local feature pointer to this point and continue working... If more work is done on upstream/develop, then rebase on top of it.

like image 35
eftshift0 Avatar answered Oct 05 '22 05:10

eftshift0