Chapter 16

Developing with Feature Branches

If everyone in the team is developing on the same branch, you will get a very confusing first-parent history with many merge commits. This will make it difficult to track changes for a specific feature or a bug fix. Especially for code review and troubleshooting, it is useful to know exactly which lines of code have been changed for a feature. Through the use of feature branches, Git can develop this information.

During feature development incremental commits are helpful at the time you need to fall back on an old functioning version. However, if you want to get an overview of the new features included in a release, coarse-grained commits make more sense. In this workflow, incremental commits on the feature branch and the release commits will be created on the master branch. The coarse-grained history of the master branch may well serve as the basis for the release documentation. The tester will welcome the coarse-grained commits with clear reference feature. This workflow shows how feature branches are used, so that

  • commits that implement a feature are easy to locate,
  • the first-parent history of the master branch includes only coarse-grained feature commits that can serve as release documentation,
  • partial feature deliveries are possible, and
  • important changes to the master branch during feature development can be used.

Overview

Figure 16.1 shows the basic structure that gets created when a team is working with feature branches. Starting from the master branch, a new branch is created for each feature or bug fix (bugs are not explicitly listed below). This branch is used to make all the changes and enhancements. Once the feature is ready to be integrated into the master branch, a merge is performed. Care must be taken to ensure that the merge is always initiated by the master branch and that fast-forward merges are not allowed. This produces a clear first-parent history on the master branch, which only includes merge commits of features.

If there are dependencies between features or if a feature is developed incrementally, then partial deliveries will be integrated into the master branch and then further developed on the feature branch.

Requirements

Feature-based approach: The planning of the project or product must be based on features, i.e. functional requirements are converted into feature work packages. There is a very small overlap between features.

Small features: The development of a feature must be completed in hours or days. The longer the development of the feature runs parallel to the rest of the development, the greater the risk that the integration of features incurs large expenses.

Local regression testing: Before the new feature is merged into the master branch , local regression tests need to be executed on the developer machine. It checks if the changes from the feature are compatible with the changes from other features and if there are unwanted side effects. If no such local regression tests are conducted, errors are often discovered only in the merged master branch. The correction of these errors would lead to a non-feature-based branch of history, and thus defeat the main advantage of feature branches.

Workflow “Developing with Feature Branches”

Each feature or bug fix is developed on a separate branch. After a feature or bug fix is completed, it is merged into the master branch.

Figure 16.1: Workflow overview

Process and Implementation

The following operations assume the existence of a central repository. As usual, development takes place in a local clone. The central repository is accessed from the clone via the remote origin.

In the following flow, the push command is used to transmit local changes to the central repository.

When working with feature branches, you often have several branches in the local repository. Without a parameter, the push command only sends the currently active branch to the remote repository. You can change this behavior by setting the push.default option.

> git config push.default upstream

The default value matching would transfer all local branches for which there is the same remote branch. You would have to explicitly specify the branch at every push command to transfer only these.

Creating A Feature Branch

Once a new feature is ready to be processed, a new branch is created. It is important to ensure that the branch is always created starting from the master branch.

Step 1: Update the master branch

If there is access to the central repository, it makes sense to first bring the local master branch up to date. So that there may not be merge conflicts, do not work on feature-based tasks in the local repository on the master branch before you do this.

> git checkout master

> git pull --ff-only 

The --ff-only parameter indicates that only fast-forward merges are allowed. In other words, if there are local changes, the merge will be canceled.

If the merge aborts with an error message, then you must have worked directly on the master branch by mistake. The changes need to first be moved to a feature branch.

Step 2: Creating a feature branch

The new branch can be created and work can begin.

> git checkout -b feature-a

It helps if the whole team agrees on consistent naming for feature and bug fix branches. Git also supports hierarchical names for branches, for example feature/a.

Often features and bug fixes are managed using a tracking tool (e.g. Bugzilla or Mantis). These tools assign unique numbers or tokens to features and bugs. These numbers can be used as branch names.

Step 3: Optional: Secure a central feature branch

Often, feature branches are created only locally, especially if they only have a short lifetime.

However, if a feature implementation takes longer or if more developers are working on the same feature, the intermediate results are particularly important. Then the branch can be secured in the central repository.

For this, you can create a branch in the central repository with the push command.

> git push --set-upstream origin feature-a 

The --set-upstream parameter links the local feature branch with the new remote branch. That is, you can be spared from specifying a remote explicitly in all future push and pull commands.

The origin parameter specifies the name of the remote (the alias for the central repository) on which the feature branch is to be secured.

Future changes to the local feature branch can be guaranteed centrally with a simple push command call.

> git push 

Integrating A Feature in the master Branch

As we have already defined in the requirements, it is important that features not exist long in parallel. Otherwise, the risk of merge conflicts and content incompatibilities increases greatly. Even if the feature will not be included in the next release, it is recommended that the integration be carried out promptly and the functionality be disabled with a feature switch.

This section describes how a feature is integrated with the master branch. It is important that the necessary merge be always executed in the master branch. Otherwise, no meaningful first-parent history can be obtained in the master branch.

Step 1: Update the master branch

Before the actual merge command, the local master branch has to be brought up to date. There will be no conflict if you do not work on the local master branch.

> git checkout master 

> git pull --ff-only  

Step 2: Merge the feature branch

The changes in the feature branch are transferred to the master branch with the merge command. So that the first-parent history of the master branch can serve as a feature history, fast-forward merges must not be allowed.

Figure 16.2: Problems with fast-forward and first-parent history

Figure 16.2 illustrates the problems that may occur with a fast-forward merge. Before the merge, the master branch points to commit B and the feature branch to commit E. After a fast-forward merge, the master branch is now also available on commit E. The first-parent history of the master branch would now include intermediate commits D and C.

The following command performs a merge and prevents a fast-forward merge:

> git merge feature-a --no-ff --no-commit 

The --no-ff parameter prevents a fast-forward merge. The --no-commit parameter indicates that no commit should be carried out yet as the following tests might fail.

Figure 16.3: Merge without fast-forward

Figure 16.3 shows the commit history of the example if fast forwards are suppressed. The new merge commit F can be seen. The first-parent history of the master branch now does not include commits C to E.

This merge may cause conflicts if other features have changed the same files as those changed by the local feature. These conflicts must be resolved by the normal way.

Step 3: Do regression testing and create a commit

After the merge was performed, the regression tests need to be run. In this case, the tests check if the new feature caused errors in the other features.

If the tests result in errors, they must be analyzed. For troubleshooting, the currently performed merge must be discarded using the reset command.

> git reset --hard HEAD

The --hard parameter makes sure all changes in the staging area and workspace are discarded. HEAD indicates that the current branch must be the last closed commit.

Next, the feature branch is activated again. The errors will be rectified there and then this flow is repeated again starting from Step 1.

If the regression tests found no error, the commit can be completed.

> git commit -m "Delivering feature-a"

To use the master branch history as documentation, the comments in the merge commits should be set uniformly. In particular, it makes sense to accommodate the unique identification of the feature, such as the number. This makes it possible to search the features on the master branch later, using the log command and the --grep parameter.

Step 4: Transfer the master branch in the central repository

After the final step, the local repository will contain the reunification of the features with the master branch. In the next step the master branch must be transmitted to the central repository using the push command.

> git push

If errors occur for this command, another feature on the master branch must have already been integrated and a fast-forward merge is no longer possible. Normally you would now issue a pull command and merge the changes locally. In this case, however, the first-parent history would no longer be usable.

Figure 16.4 : No usable first-parent history after the pull command

The top and middle sections of Figure 16.4 outlines the situation described: Commit C would be the remote commit and D the locally applied commit. If you now issue a pull command, you would create a new merge commit E (bottom section of Figure 16.4). Thus, commit C would no longer be included in the first-parent history of the master branch.

In the first-parent history all the features should be included, so the local feature merge commit must be removed with the reset command in a failed push command.

> git reset --hard ORIG_HEAD 

Here, ORIG_HEAD references the commit that was active before the merge in the current branch.

Subsequently, this sequence has to be started again from Step 1, i.e. the new commit must be retrieved with the pull command from the master branch.

If the push command was successful, the new feature is now included in the central repository.

Step 5: Delete or continue to use the feature branch Option

Variant 1: Delete the feature branch

If after merging with the master branch the feature development is complete, the feature branch can be deleted.

> git branch -d feature-a

The -d option deletes the given branch.

If deletion results in an error message, then chances are you forgot to merge the feature branch with the master branch. The -d option will only delete a branch if all commits in the branch are referenced by another branch. If you want to delete a feature branch without all its commits being taken over by the master branch, you can use the -D option. If the feature branch was secured in the central repository, the branch has to be deleted there as well.

> git push origin :feature-a

The colon before the branch name is important. The command means: Do not paste anything in the feature branch.

Variant 2: Resume feature development

If the feature development is not yet complete (i.e. the first integration with the master branch was only a partial delivery), the feature branch can be used further.

Figure 16.5: Resume work with a feature branch

In Figure 16.5, further work is outlined after a partial delivery. The branch continues to be used as the feature branch.

> git checkout feature-a 

As soon as the next shipment is ready, normal integration with the master branch is performed again. In this case, Git is smart enough to take only the changes in the new commits in the master branch.

Transferring Changes on the Master Branch to the Feature Branch

In the best-case scenario, the development of a feature takes place independently of other features. However, sometimes there are important changes on the master branch that are necessary for the feature development, such as major refactoring or changes to basic services. In this case, the changes on the master branch need to be transferred to the feature branch.

Figure 16.6: Changes on the master branch transferred to the feature branch

Figure 16.6 illustrates the situation. It is a merge from the master branch that needs to be performed on the feature branch.

Step 1: Update the master branch

First, the changes on the master branch must be imported to the local repository.

> git checkout master 

> git pull --ff-only

The --ff-only option indicates that only fast-forward merges are permitted. This prevents you from accidentally performing a merge on the master branch.

Step 2: Apply changes in the feature branch

In the second step, the changes must be applied in the feature branch with a merge.

> git checkout feature-a 

> git merge --no-ff master 

Here, --no-ff prohibits fast-forward merges. A fast-forward merge can only happen at this point if the master branch and the feature branch have been merged right before this merge. A fast-forward merge would then destroy the first-parent history of the feature branch.

Any conflicts that arise must be resolved normally.

The feature branch can take any intermediate version from the master branch. Git can deal with multiple merges very well. However, the commit history would then be complex and difficult to read.

Why not the Alternatives?

Why not Continue to Work on the Merged Version after a Partial Delivery?

If a partial delivery of a feature occurs on the master branch, the master branch will contain a merge commit with changes of its own features and updates from other features. On the other hand, the updates from the other features are not yet available on the feature branch (See Figure 16.7).

Figure 16.7: Another merge after a partial delivery

Wouldn’t it make sense to perform a merge from the master branch into the feature branch and thus work on the merged branch?

The short answer is: The history would be unnecessarily complex, and in most cases, this procedure would bring no benefit to the feature development.

A merge of the master branch on the feature branch right after a partial delivery usually always leads to a fast-forward merge, i.e. both branches would point to the same commit. However, this would ruin the first-parent history of the feature branch—which changes were made in the course of the feature development would no longer be traceable. As such, you have to suppress the fast-forward merge, which in turn will lead to a new “empty” merge commit. That is, there would always be two merge commits for each partial delivery of a feature.

In most cases, it is not important for a feature development to be proceed on the most up-to-date version of the master branch.

If it is necessary to apply changes, then you can do that. However, it should not always be right after each partial delivery.

Why Feature Branches Are Not Integrated until Shortly before the Release?

When working with feature branches, release management often comes up with the idea, the decision, shortly before the delivery date, as to which features should be shipped with the new release.

Conceptually, it seems it is very easy to go with the feature branch approach too. Each feature is fully developed on a branch, but not yet integrated into the master branch. Which features will be integrated into the master branch are decided just before the D-day.

In an ideal world—where features are fully independent and no programming errors exist—this approach would work. In reality, however, this approach usually leads to major merge conflicts in integration and a long stabilization phase.

Furthermore, it is more complicated to develop features that have dependencies. Normally you would make a partial delivery of a feature on the master branch. In a solution with late integration of a feature, branches must exchange changes (see the following section). This would make independent integration of these features impossible to be carried out just before the release.

Also, proven processes for quality software, such as continuous integration with a build server, integration and refactoring are difficult to implement in late integration.

Why Not Exchange Commits between Feature Branches?

The workflow described provides no direct exchange of commits between feature branches. The integration always takes place on partial deliveries on the master branch.

Wouldn’t it be easier to perform a merge directly between feature branches?

The decisive advantage of feature branches is the simple and comprehensible history. Add merges directly between feature branches, and this advantage goes away.

Figure 16.8: An example of a feature branch

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.93.64