main
is the primary development branch in mobilecoin.git
.
Changes are typically created on topic branches, reviewed as PRs, and squash-merged into main
.
Sometimes long running feature branches are created. These are, still, eventually
squash merged into main
. (Alternatively, this could be a normal git merge
and not a squash merge, it doesn't much matter since the feature branch goes away
after it is merged.)
Major releases are started by creating a release branch e.g. release/v4.0
off
of main
.
Minor releases are started by creating a release branch e.g. release/v4.1
off of
the predecessor release/v4.0
.
Any bugs that are found are fixed in the release branch first.
Release candidates are tagged on release branches.
Changes which are made in release branches are propagated forward to main
,
by merging the release branch into main
.
- The release branch is not deleted after this merge.
- This is a normal git merge and not a squash merge or a rebase merge. The purpose of this is to avoid git conflicts. (More on this)
Merging means that git will look at the state of the release branch, check if
any commits on it have not already been merged into main
, and if not, attempt
to apply those diffs onto main
(using 3-way merge conflict resolution).
After a commit from release branch has been merged this way, it will never be considered by a future merge and can never create a conflict in a future merge. (This is a major difference with squash merging and rebase merging. This is very important when merging long-lived branches and not small PRs.)
If there are no conflicts, we can simply open a PR from e.g. release/v4.0
to
main
in github. The release branches are protected, so merging this PR will
not delete the branch.
If there are conflicts, this won't exactly work, because we will have to resolve
the conflicts, but we don't want to push more commits to release/v4.0
to accomplish
this.
The local workflow for this may look something like this:
$ git status
On branch main
$ git checkout -b merge-release-v4.0
$ git merge release/v4.0
(resolve conflicts now)
$ git push origin merge-release-v4.0
(open pull request from this branch targetting main)
Here, we create a topic branch (off of main
), merge the release branch into it,
resolve any conflicts, and then propose to merge this to main
.
It is always desirable for the release branch to merge "cleanly" to main
.
This ensures that all bug fixes (and changelog updates) in the release branch
were incorporated into main
, and there can be no confusion about what fixes
made it back.
It is acceptable to merge the release branch to main
at any of the following times:
- After making a change in the release branch
- On a periodic basis
- After finishing the release
Currently, we want this merge to happen via a PR and go through (usually perfunctory) review. Reviewers should check that this is not a squash merge or a rebase merge.
Right now we think it's desirable that whenever the release branch moves forwards,
a PR could be opened automatically that proposes to merge the release branch to main
,
exposing any git conflicts, which can then be reviewed and resolved in this PR.
Developers make changes using topic branches and PRs, and are free to squash-merge their topic branches as they historically have. (It doesn't much matter for this workflow whether or not topic branches are squash merged.)
When a developer wants to make a change, they should ask themselves, should this
go in the latest release branch, or in main
?
- Changes which go to the release branch will eventually be merged forwards to
main
. No cherry-picking will be necessary. This is usually the right thing for a bug fix. - Changes which should NOT go into the release branch should just go to
main
.
This decision tree covers the majority of changes, and does not involve any cherry-picking.
Sometimes, a developer will make a PR to main
, but then later realize it
should have gone to the release branch.
- At this point, you have to cherry-pick the change back to the release branch.
- You can cherry-pick your commit(s) from
main
. - You should expect a git conflict to occur when release is merged into
main
after this. This is because a cherry-picked commit has a different hash from the original commit, and git does not consider them "the same" when merging, they are instead very similar commits touching the same code. - You will have to resolve this conflict, which usually won't be too hard.
- To avoid this situation, make sure you consider whether a change should go
to
main
or the release branch. If in doubt, ask in the channel.
This leads to a git history like the following:
It's not very easy for mobilecoin to accommodate multiple concurrent releases. This is because there's only one alphanet, only one testnet, and only one mainnet. So the release process can't easily happen for two different releases in parallel, and historically we've never had to attempt that. (It's also very labor intensive to sign an enclave.)
Should it become necessary to support multiple concurrent releases, the workflow extends naturally:
- Changes target the earliest release branch where they are relevant
- Earlier release branches merge forward into the newer release branches
- The newest release branch merges into
main
.
Note that git merge handles well the semantics when a diamond pattern is created. Suppose the following merges occur:
graph TD;
A[release/v3.1]-->B[main];
A-->C[release/v4.0];
B-.->D[main];
C-->D;
Even though a commit in release/v3.1
may follow two paths into main
branch,
it does not conflict with itself under a git merge
. (This is another reason
why a normal git merge should always be preferred when merging two long-lived branches.)
Historically, mobilecoin.git
did not use merging at all when handling release branches.
- We branch for release e.g.
release/v2.0
of offmain
. - Developers (usually) make bug fix commits in release branch, or, cherry-pick them back to release branch.
- We tag release candidates and releases on the release branch.
- If a point release is needed off of a major release, then we branch e.g.
release/v2.1
off ofrelease/v2.0
and make more commits. - After the release is done, the release branch is abandoned and nothing more is done with it.
In this model:
- Release branches are never merged anywhere
- Any changes that need to be in
main
and release (which is most bug fixes) get cherry-picked.
This is an example of what this looks like:
The main drawbacks of this are:
- Changes are being cherry-picked in two directions, which can be confusing and can lead to additional git conflicts.
- Changes are sometimes cherry-picked from release to
main
in reverse order to how they occurred on release. This can create confusion and additional git conflicts. Sometimes, git ends up pulling "fragments" of the earlier commit that was not cherry-picked yet forwards intomain
, which is very confusing for developers and reviewers because seemingly unrelated changes appear in the diff. - Sometimes changes in release are forgotten about and never cherry-picked to
main
. This leads to regressions in the next release. Because we never merge the release branch back tomain
, and all the cherry-picks have different hashes from their originals, it's very hard to ever be sure that we got all the changes.
This merge-based policy hopes to rectify these issues.
The best argument for why topic branches should be squash merged is that,
often topic branches have a lot of small commits, and maybe only the last one
builds and passes tests. If we squash the PR into one commit, then we know that
the commit that lands on main
builds and passes tests. If we do a merge commit,
then it's possible that during git bisect
you will hit a commit that does not
build or pass tests.
In some projects, the convention is that all the commits in the PR should build
and pass tests, and you are supposed to use git rebase
to either fix all the
commits or squash the ones that don't build.
However,
- Usually you won't run CI on every single commit, because it's prohibitive.
So you will sometimes end up having commits under
main
that don't build. - Simply squash merging it all is much less effort from the developer.
- More junior developers who aren't experts with
git rebase
won't have to learn all of those techniques.
Historically mobilecoin developers have preferred to click squash merge.
Squash merging also means that if you have to revert, your only option is to revert the whole PR (and not an individual commit). However, historically that's usually what we want to do.
Historically, we have rarely used git bisect
to investigate regressions, but it
could be useful in the future.