0

In a game project I am working on (small team of about 10), we have the common Git workflow of having a development branch (the main branch) and then several feature branches, which once completed and pass code review, get merged back to the development branch.

One issue we are facing though, is how to proceed with the next feature (that depends on the last feature) when the last feature hasn't yet passed code review and so is not merged back into development.

For example.

Lets say I have 3 tasks to do. Where each task builds on the code of the previous task (they can't be worked on independently)

To keep it simple, the tasks might be something like :

[Task1] - Add player code [Task2] - Add player code extensions (needs code from Task1) [Task3] - Add more player code bits (needs code from Task2)

So I :

Create a feature branch from development for [Task1] Work on [Task1] until completion Submit a pull request for [Task1] (lets say it takes a few days for code review)

Now I need to work on [Task2], but [Task2] builds on what I did in [Task1]. I can't create my feature branch from development as the [Task1] code has not been merged yet (as it's still in code review)

Should I :

a) Create the [Task2] feature branch from development and merge the [Task1] feature branch into it

or

b) Create the [Task2] feature branch from the [Task1] feature branch

Whilst the above might be manageable, it all gets really messy when you now have to work on [Task3], yet [Task1] is still being code reviewed.

Not to mention that if [Task1] has code review changes, they now have to be merged to [Task2] and then to [Task3] etc

What are my options in this scenario?

TTT
  • 22,611
  • 8
  • 63
  • 69
Bert Terb
  • 7
  • 1

1 Answers1

1

Usually you simply try to avoid this, but of course sometimes you can't. A simple alternative is if the later tasks might alter the architecture of the first task, then you could work on them together as one larger feature and merge them together in a single Code Review. Or even break it up into 3 smaller merges after you're done with all 3 tasks. But assuming neither of those are good options for you, let's proceed with your scenario as asked.

The general algorithm goes something like this:

  1. Base task2 off of task1, and base task3 off of task2.
  2. Monitor task2, task1, and main and rewrite higher numbered branches when anything changes.

#1 is simple, and #2 is usually what people stress out about, particularly when the branches are worked on by different people, or when any branch other than main is shared by multiple people. The reality though, is that "rewriting" a branch is relatively simple to do; it's communicating the change properly and instructing others what to do about it that's more difficult. In your case that may not be a problem at all if you're the one working on all 3 tasks, because you can rebase (rewrite) all 3 branches whenever you feel like it without messing up anyone else. As an added bonus, Git version 2.38 added a new option called rebase --update-refs which enables you to quickly rewrite all of the branches in one command.

Here's an example of how it could work with the new --update-refs option.

Sneak Preview: You're going to make all of your changes on the task3 branch!

Let's assume each task branch is 2 commits ahead of the previous task branch:

A(main)-B-C(task1)-D-E(task2)-F-G(task3)

During the code review of task1, someone recommends a change. Since you are the owner of all three task branches, you can do all of your changes to any of them while having task3 checked out. So make your fix on task3 in commit X:

A(main)-B-C(task1)-D-E(task2)-F-G-X(task3)

Now you are going to do an interactive rebase (-i) in conjunction with the new option (--update-refs):

git rebase -i --update-refs main task3 # You can remove "task3" if it's already checked out

You'll be presented with a file in an editor showing the commits in reverse order, so X will be at the bottom. You'll also see the commands that will update the other branches for you, for example something like:

pick 655be8a Commit B
pick 5a07e2f Commit C
update-ref refs/heads/task1

pick 40564e0 Commit D
pick 2706e8c Commit E
update-ref refs/heads/task2

pick 43b4c59 Commit F
pick 4945a1b Commit G
pick c09f14a Fix Commit B # Note this is Commit X

Now move commit X up to somewhere between commit B and the update-ref command for task1. Perhaps you want to add it as an additional commit, or squash it into another commit if it's just a minor tweak:

pick 655be8a Commit B
fixup c09f14a Fix Commit B # Note this is Commit X
pick 5a07e2f Commit C
update-ref refs/heads/task1

pick 40564e0 Commit D
pick 2706e8c Commit E
update-ref refs/heads/task2

pick 43b4c59 Commit F
pick 4945a1b Commit G

Now save the file and close it, and the rebase will begin. When it's finished it will have re-written all 3 branches, and then you can force push out all of the branches that are being reviewed. Here's a related question that talks about pushing multiple branches after using rebase --update-refs.

Note: This answer assumed you were the only one working on all 3 branches. If that's not the case, you'll need to let others know that you force-pushed these branches so they can either delete their local copy and re-check it out, or do a hard reset to the latest version, or if they have unshared commits they can rebase their changes onto the latest version.

TTT
  • 22,611
  • 8
  • 63
  • 69
  • Yes, I will be the only one working on all 3 tasks. Just to clarify, with #1, did you mean rebase task2 off of task1, and rebase task3 off of task2? I have to say, I have not used rebase before. Would it be possible to extend you answer to include how you would do it without the new refs feature? (just standard rebasing) – Bert Terb Dec 23 '22 at 08:30
  • @BertTerb in #1, by "base" I mean "branch off of" which is the same as the way you wrote it, "Create the [Task2] feature branch from the [Task1] feature branch". In #2, when something changes in `task1`, that's when you use `rebase` to "rewrite" the `task2` and `task3` branches. When I get back to my desk I can update the answer to provide the command for rebasing `task2` and `task3` independently. Note that the new update-refs feature is just a faster way of rebasing all the branches at the same time without rebasing them individually. – TTT Dec 23 '22 at 14:56
  • @BertTerb I just realized I never updated with the individual rebase commands. The basic premise is to use the `rebase --onto` such that you can rebase just the range of commits. [See here for more details](https://stackoverflow.com/q/29914052/184546). Note, after using `rebase --onto` for a while I now strongly prefer putting the `--onto` as the last argument instead of at the beginning, for example to rewrite `task2` after `task1` changed, you could use: `git rebase C task2 --onto task1`, where `C` is the old `task1` and `task1` is the newly rewritten `task1`. – TTT Dec 27 '22 at 16:50