Parallel Development with Subversion Basic Branching Strategy Thomas Engelin © Itancan Consulting AB 2010 Thomas Engelin, Itancan Consulting AB 1. Abstract Code development in a large commercial environment often leads to the same source code being edited simultaneously by several developers. The standard way to deal with this is to use branches in the version control system. To support work in branches, a process is needed that sets up rules around this. This document presents a basic branching strategy when working with Subversion. The document defines a number of definitions and properties related to branches, such as: Product delivery model Branch terminology Branch lifecycle Branch types Branch consistency Roles and ownership Push- or pull deliveries between branches Using these building blocks, the document then presents a branching strategy which consists of four different levels of branch usage that can be applied depending on development needs. Finally, Subversion commands to support this branching strategy are presented. Parallel Development with Subversion - Basic Branching Strategy 1 Thomas Engelin, Itancan Consulting AB 2. The Reason for Parallel Development By parallel development I mean changes to the same source code is done in more than one track simultaneously. In practice, it means the same source code is updated by multiple developers working on different tasks. Some reasons for having parallel development might be: The product exists in multiple variants. Perhaps different customers require slightly different functionality or different release cycles. It must be possible to stabilize a release (perform bug fixing) while at the same time work on new functionality for coming releases. Prototyping work might have to be done that should not affect the normal development work. Maybe large portions of the code base must be experimented with and version controlled by a prototyping development team that works independently from the other development team. The integration strategy requires that it is possible to select the exact contents of the next release as late as possible when the gain and risk of each new feature and bug fix is known. This implies that different development teams finish different tasks in isolated areas, and that the integration activity means that selected tasks make up the next release. The project typically wants to have full control over parallel development, and a common way to deal with it is to use branching within the version control system. Parallel Development with Subversion - Basic Branching Strategy 2 Thomas Engelin, Itancan Consulting AB 3. Product Delivery Model When designing a CM- or branching strategy, the requirements come from the company’s delivery model. A delivery model answers questions like: How often will we release the product? How will the releases be named? Can more than one release be active at the same time? For how long do we have to do maintenance (bug fixes) on a release? Besides bug fixes, can there be functionality growth in maintenance work? Will we support product variants? Will there be function growth in product variants? The delivery model can be represented visually: Quality 3.2 2.2 1.2 3.1 2.1 1.1 3.0 2.0 1.0 Functionality The product starts from nothing and functionality is added. At some point release 1.0 is created. If the product delivery model states that there should be no functionality growth in maintenance work, only quality increasing work (bug fixes) are performed to reach releases 1.1 and 1.2. On the other hand, if functionality growth is allowed in maintenance work, the graph changes slightly as in releases 3.1 and 3.2. The consequences of a product delivery model can be huge. Long maintenance tails (1.1, 1.2, 1.3, 1.4 ... 1.n), different releases being active at the same time (1.0, 2.0, 3.0 ...), growth of functionality in maintenance work (difficulty to cut maintenance tails), and multiple product variants (duplicated testing etc.) all tie up valuable resources. 3.1. Integration model The product delivery model puts requirements on the integration model. Relevant questions are: How many simultaneous release branches do we need? When can a release branch be deleted (or hidden)? Do we need temporary integration branches? How many levels of branches (branching depth) do we need? 3.2. Branching strategy The product delivery model and integration model together with the development process finally dimension the branching strategy, where typical parameters are: What types of branches do we need, and how long will they live? What development roles do we have, and how does that affect branch ownership? Will we enforce consistency rules for branches, and will we use push- or pull deliveries? We will now take a closer look at these branching strategy parameters, or properties. Parallel Development with Subversion - Basic Branching Strategy 3 Thomas Engelin, Itancan Consulting AB 4. Branch Properties 4.1. Terminology and branch lifecycle This section describes the typical lifecycle of a branch, and Subversion terms used when dealing with branches. Note that this is how branches are mentioned in the Subversion manual (Ref. [1]) but when setting up a branching strategy there might be deviations. For example the number of syncs and merge backs depends on the branch type and can actually be zero in some cases. So, this is how branches should be dealt with in theory. resolve conflicts delete or lock branch child branch create branch merge back sync parent branch (might be trunk) R3 R5 R12 RN RN+1 4.1.1. Create child branch At revision R3 a child branch is created from the parent branch (which might be trunk). The child- and parent branches now have the exact same contents. 4.1.2. Sync and resolve conflicts Work is done in both parent- and child branch. Normally, the child branch is repeatedly synced with the contents of the parent branch. Here, revisions R5 and R12 are synced to the child branch. If the same code has been updated in both the parent- and child branch, there might be a conflict. Conflicts are normally resolved in the child-most branch, since we don’t want to freeze the parent branch during conflict resolution. 4.1.3. Merge back child branch When work on the child branch is finished, it is merged back to the parent branch. Before merge back, the child branch is normally synced with revision RN and possible conflicts are resolved (again in the child branch). The merge back operation results in a new revision. RN+1. 4.1.4. Delete or lock child branch When the merge back is finished, the child branch can be deleted or locked 1 for additional commits. The change set between RN and RN+1 records the changes done in the child branch. 4.1.5. Create release The tags directory in the Subversion repository collects all releases. A release is created directly from trunk or from a release branch. 1 With a pre-commit hook that triggers on a property, a branch can be set to read-only. Parallel Development with Subversion - Basic Branching Strategy 4 Thomas Engelin, Itancan Consulting AB 4.2. Branch types This section describes the purpose of different branch types. Every branch type is associated with certain type of work and certain roles, and the number of syncs and merge backs differ between the branch types. Which branch types that should be part of a branching strategy depends on the needs, such as the phase in the development cycle the project is in. 4.2.1. Trunk This is normally the main development line that plays the role of parent branch to the most other branch types. All code additions should finally be integrated to trunk, either via direct commits or via merges from other branch types. 4.2.2. Release branch A release branch provides an isolated work area where bugs can be corrected without disturbance from ongoing development work, or without disturbing ongoing development work. A release branch is created from trunk, and new releases (tags) are created from this branch. While solved bugs are merged back to trunk, the trunk content is never synced to the release branch! When no more releases or merge backs will be made from the release branch, it can be deleted or locked. 4.2.3. Feature branch A feature branch provides an isolated work area for implementing new functionality, and for sharing that functionality within a smaller team. A feature branch is created from trunk. Trunk content is repeatedly synced to the feature branch. The finished work is merged back to trunk, and the feature branch is deleted. 4.2.4. Private branch A private branch provides an isolated work area for one developer. The purpose can be to add functionality or to correct a bug. A private branch is created from trunk or from a release- or feature branch. Content from the parent branch is repeatedly synced to the private branch. The finished work is merged back to the parent branch, and the private branch is deleted. Parallel Development with Subversion - Basic Branching Strategy 5 Thomas Engelin, Itancan Consulting AB 4.3. Branch consistency This section describes the consistency state a branch can be in. When a branch is in a discrete state it contains only complete code additions such as a number of complete bug fixes and a number of complete new features. The opposite state, continuous, means that the branch contains part of a bug fix or part of a new feature. This happens when a code addition requires more than one commit before it is completely delivered to the branch. 4.3.1. Continuous Additions are made using commits from any kind of work. Any revision on the branch (which might be trunk) can contain fragments from any ongoing solution. For example; the revision at t1 contains the complete solution for bug fix BUG-95, but only parts of feature FuncX. Thus, the branch (or trunk) is in a continuous state at revision R. R bug-95 c c c trunk or branch fea-funcx c c c If a new release is created from revision R we call it a dirty release, since the customer will get a complete bug fix plus certain parts of feature FuncX 2. 4.3.2. Discrete All additions are complete features or complete bug fixes. Every revision on the branch (which might be trunk) contains a complete step of functionality. For example; the revision at t1 contains the complete solution for bug fix BUG-95, but no code from feature FuncX. Thus, the branch (or trunk) is in a discrete state at revision R. c c c R bug-95 trunk or branch fea-funcx c c c If a new release is created from revision R we call it a clean release, since the customer will get one complete bug fix and nothing else. 2 When delivering a dirty release such as this, the customer gets no value from feature FuncX - only code that adds risk! Because the feature is not completely delivered, it is probably not completely tested, so we actually send away code that adds nothing else besides possibly bugs! Parallel Development with Subversion - Basic Branching Strategy 6 Thomas Engelin, Itancan Consulting AB 4.4. Roles and ownership This section describes how branches are owned and managed. Ownership implies authority to decide about merge operations and branch creations/deletions. The owner of a branch is in charge of mainly three things: When to create the branch What to merge into the branch and when to do it When/If to delete the branch By having roles with clear responsibilities, we minimize the risk with child branches that ‘someone’ forgot the merge back to the parent branch etc. Typical roles: 4.4.1. Integration Leader The integration leader owns the trunk and all release branches. 4.4.2. Team Leader A team leader owns feature branches. 4.4.3. Developer A developer owns his or her private branches. Parallel Development with Subversion - Basic Branching Strategy 7 Thomas Engelin, Itancan Consulting AB 4.5. Delivery types This section describes how changes are delivered (merged) between different branches. 4.5.1. Pull delivery When code additions to the trunk or branch are controlled by an integrator at planned points in time, we’re talking about pull deliveries. child Pull delivery - Integration plan - Owner parent Pull deliveries require process overhead. The work is done in isolated child branches, and an integrator pulls in finished branches planned for the current release. The parent branch owner decides when to merge back a child branch, and performs the merge. What branches to merge back to the parent branch, in what order, and when to do it is decided by the parent branch owner. In the picture below the integrator is about to create a new release. With pull deliveries we gain very high flexibility. Bug-95 is integrated first, then bug-88. After that fea-funcx is integrated. The release is now complete. The new feature fea-funcy will become part of the next release – maybe it has not been tested enough or it is not requested by the customer right now. bug-88 bug-95 fea-funcy fea-funcx The integrator can apply tests between every piece that gets integrated, rollback additions, redo the integration and so on. Parallel Development with Subversion - Basic Branching Strategy 8 Thomas Engelin, Itancan Consulting AB 4.5.2. Push delivery When code additions to the trunk or branch are done directly by developers at any point in time, we’re talking about push deliveries. child Push delivery - Asynchronous - Non-owner parent Push deliveries require almost no process overhead at all. The drawback is that there is no control over what gets added to the trunk or branch. When using push deliveries, the child branch owner decides to merge back the child branch to the parent branch, and also performs the merge. Child branches are merged back to the parent branch in any order and at any point in time. Parallel Development with Subversion - Basic Branching Strategy 9 Thomas Engelin, Itancan Consulting AB 5. Branching Strategy The branching strategy presented in this section uses the branch properties described in the previous section. The strategy consists of four ‘maturity levels’ where an increase in level indicates a higher degree of branch usage. Note that maturity level N is not in any way better than maturity level N-1, both probably have their place in the development process. 5.1. Maturity level 0 Commits are made directly against trunk, no branches are used. All work is mixed on trunk; release work and new features. At certain points, feature freeze is decided and a tag is created from a specific revision. tags R R trunk c c c c c c This is probably a useful level when starting a new project and the code growth is high and only internal test releases are in sight. The need for branches is very low and would not motivate the branching overhead cost. Later in the project a shift to a higher maturity level is probably needed. The release precision is low since the branch consistency is continuous (dirty releases), more or less anything might slip into a release. It might be hard to pick the best revision for a release. Corrections for a release will enter the next release together with new features. Since we have no branches, there is no issue with push- or pull deliveries. Traceability is low; to know what went in to a release requires some detective work (reading commit logs). It is impossible to select the exact contents of a release at integration time and use planned and delayed integration3. 3 That is, to be able to pick the desired bug fixes and new features that should go into the release after they have been completed. Parallel Development with Subversion - Basic Branching Strategy 10 Thomas Engelin, Itancan Consulting AB 5.2. Maturity level 1 The trunk is used for new functionality and release branches are used for release stabilization work. Commits are made directly against the trunk and the release branches. A release branch results in one (x.0) or multiple (x.0, x.1, x.2...) tags. When a new release is to take place, a feature freeze on trunk is decided and the release branch is created where bugs can be corrected. Before every new tag made from a release branch, code freeze is decided and the tag is created. After every created tag, bug fixes are merged back to trunk. Note that there are no sync operations from trunk to the release branch since we don’t want to pollute the release branch with new functionality intended for the next release! When the release branch is no longer needed, it is deleted or locked. tags R c R c c c c release branch trunk c c c c c c This level is useful when there is a need to have control over releases but at the same time be able to develop new features without too much branch overhead. The release precision is still low since the branch consistency is continuous (dirty releases). It might still be hard to pick the best revision for a release branch; however, corrections on a release can be done separately from the development of new features! The trunk owner pulls in bug fixes from the release branch. We might even have several release branches in parallel if that is needed according to the product delivery model. Traceability is still low; to know what went in to a release branch requires reading commit logs. It is not possible to pick out the exact contents for a release and use planned and delayed integration. Parallel Development with Subversion - Basic Branching Strategy 11 Thomas Engelin, Itancan Consulting AB 5.3. Maturity level 2 Feature branches are used for new functionality, and release branches are used for release stabilization work. Commits are made directly against feature- and release branches, but never directly against trunk! When new functionality is to be implemented, a feature branch is created where a development team can work in isolation from other development teams implementing other functionality or stabilizing releases. New functionality is added to the feature branch by the development team. As soon as new code gets merged into trunk (from other feature branches or from release branches), a sync is done to keep the feature branch as synced as possible with trunk. When the feature is finished it is merged back to trunk and a complete step of functionality has been added. The feature branch is then deleted. tags R c c R c c c release branch trunk feature branch c c c At this level all additions to trunk are complete features or bug fixes. The trunk is always in a discrete state where clean releases can be made from. This level gives the project lead good control over what gets released, but more importantly it is now possible to delay the integration of finished features and bug fixes until it is time to create the next release. The trunk owner pulls in finished features and bug fixes according to an integration plan, and it is now possible to mix the contributions in different order, to apply tests at certain points in the integration work, and to rollback additions that actually don’t fit into the release. Traceability is high; we know what went in to every release branch without detective work. Parallel Development with Subversion - Basic Branching Strategy 12 Thomas Engelin, Itancan Consulting AB 5.4. Maturity level 3 Feature- and release branches are used exactly as in level 2, but now all commits are done on private branches! Every developer working on a task creates a private branch for that task. A task might be a bug to solve on a release branch, or a well-defined piece of functionality on a feature branch. In some cases a private branch could be created directly on the trunk if there is a need for a small code addition (however, to keep the branch model as homogenous as possible it is better to create a feature branch for that purpose). Event though it sometimes might be overkill to use private branches on a feature branch, it is highly recommended to solve every bug in its own private branch. If the product has variants more than one logical ‘trunk’ for the product might exist, and to solve the same bug in several product variants is a lot easier if every bug solution can be located in its own private branch. tags R c c R c c c private branch release branch trunk c c feature branch private branch c c At this level, bug fixes and pieces of new functionality are pushed from a private branch to the parent branch by the responsible developer. The trunk owner pulls in bug fixes from release branches and new functionality from feature branches. Every revision on trunk and on release- or feature branches denotes a complete step in functionality – these branches are now always in a discrete consistent state. Parallel Development with Subversion - Basic Branching Strategy 13 Thomas Engelin, Itancan Consulting AB 5.5. Mixed maturity levels In reality a development team might start out working on level 0. When the need for more controlled releases appear they might start to use release branches. To keep track of solved bugs, every bug is associated with its own private branch. For large refactoring work or prototypes the development team sometimes creates a feature branch, but most of the new functionality is committed to trunk directly. This is actually a mix of all four levels. The use of branches depends on the needs, so the project lead should beforehand define what branch types to use and when to use them. The four levels presented can be seen as templates to base your own branching strategy on. 5.6. Multiple ‘trunks’ Sometimes a branch is used to hold a product variant, that is, a configuration of the product that should live and be maintained for a long time. Such a configuration branch can be seen as a second ‘trunk’; it is never deleted, and never merged back to its parent branch. It is possible to develop and maintain a configuration branch regardless of maturity level. However, in order to have control over what gets merged between the base product and the product variant, maturity level 3 will make things a lot easier. A common case is when a bug fix has been implemented, for example in the base product, and needs to be transferred to the product variant. If the bug fix was made in a private branch, then the complete solution (with no other solution fragments) can be found between two revision numbers after it has been merged back to the parent branch on the source side (base product). In such case it is possible to transfer the bug fix to the product variant in two ways: Use cherry-picking to merge the exact revision from source to destination Use normal sync- and merge back mechanisms between parent and child to transfer the bug fix to the destination Either way, on the destination side a branch could be created where the bug fix is received. Conflict resolution could take place in this branch before it is merged into the configuration branch. 5.7. Branch naming conventions When different branch types are used it might be wise to apply a naming convention for branches such as this: Branch type <type> <identifier> Example Release branch rel Intended release ID rel-1.0 Feature branch fea Feature name fea-funcx Private bug fix branch bug Bug tag bug-95 Private feature branch prv Developer ID + sequence prv-thomas-02 Configuration branch cfg Product variant name cfg-customerx Parallel Development with Subversion - Basic Branching Strategy 14 Thomas Engelin, Itancan Consulting AB 6. Subversion Commands This section shows the Subversion commands used when dealing with the branch types described in this document. 6.1. Assumptions $REPO is the URL to a repository containing the directories branches, tags and trunk. Before any sync or merge back operation, no local changes should exist in the workspace. Subversion is configured to use a good diff/merge tool (in $HOME/.subversion/config). 6.2. Release branches Release branches are never synced, but possibly merged back several times. When the release branch is no longer needed, it can be deleted or locked. Create a new release branch from trunk, revision 200: svn cp $REPO/trunk@200 $REPO/branches/rel-1.0 -m "Created rel-1.0" Create a release from the release branch: svn cp $REPO/branches/rel-1.0 $REPO/tags/rel-1.0 -m "Release rel-1.0" Create another release from the same release branch: svn cp $REPO/branches/rel-1.0 $REPO/tags/rel-1.1 -m "Release rel-1.1" Merge back the release branch: cd <top directory in working area for trunk> svn update svn merge $REPO/branches/rel-1.0 ... resolve conflicts ... svn ci -m "Merged back rel-1.0" Lock the release branch: cd <top directory in working area for release branch> svn propset branch:locked TRUE . svn ci –m "Locked rel-1.0" Delete the release branch: svn delete $REPO/branches/rel-1.0 -m "Deleted rel-1.0" 6.3. Feature branches Feature branches are synced multiple times but only merged back only once. After the merge back, the feature branch is deleted. Create a new feature branch from trunk, revision HEAD: svn cp $REPO/trunk@HEAD $REPO/branches/fea-funcx -m "Created fea-funcx" Parallel Development with Subversion - Basic Branching Strategy 15 Thomas Engelin, Itancan Consulting AB Sync the feature branch with trunk contents: cd <top directory in working area for feature branch> svn update svn merge $REPO/trunk ... resolve conflicts ... svn ci -m "Synced" Merge back the feature branch: cd <top directory in working area for trunk> svn update svn merge --reintegrate $REPO/branches/fea-funcx ... resolve conflicts ... svn ci -m "Merged back fea-funcx" Delete the feature branch: svn delete $REPO/branches/fea-funcx -m "Deleted fea-funcx" 6.4. Private branches Private branches are synced multiple times but only merged back once. After the merge back, the private branch is deleted. Create a new private bug fix branch from a release branch, revision 243: svn cp $REPO/branches/rel-1.0@243 $REPO/branches/bug-95 -m "Created bug-95" Sync the private bug fix branch with the release branch contents, revision HEAD: cd <top directory in working area for private bug fix branch> svn update svn merge $REPO/branches/rel-1.0@HEAD ... resolve conflicts ... svn ci -m "Synced" Merge back the private bug fix branch: cd <top directory in working area for release branch> svn update svn merge --reintegrate $REPO/branches/bug-95 ... resolve conflicts ... svn ci -m "Merged back bug-95" Delete the private bug fix branch: svn delete $REPO/branches/bug-95 -m "Deleted bug -95" 6.5. Conflict resolution File conflicts are resolved using a diff/merge tool. Structural conflicts (tree conflicts) are resolved in the following manner. To see the current tree conflicts cd <top directory of working area> svn update svn status M . ! C dirA/file1 > local missing, incoming edit upon merge The first step is to investigate how the structure should look after resolved the conflicts. Also, the affected files must be investigated so their contents will be correct. When that is known, update the working area to have the desired structure and file contents. Then run svn resolve --accept=working dirA/file1 Resolved conflicted state of 'dirA/file1' When all conflicts have been resolved, the changes can be committed. Parallel Development with Subversion - Basic Branching Strategy 16 Thomas Engelin, Itancan Consulting AB 7. References [1] Subversion manual http://svnbook.red-bean.com/en/1.5/svn-book.pdf Parallel Development with Subversion - Basic Branching Strategy 17