4.3. BranchingIn the introduction to this chapter, I defined a branch as a forked line of development in your project, with the line that has been forked off called the branch, and the main line the trunk. CVS builds the branch and the trunk from the same source data, up until the point at which they diverge, which is called the base of the branch. From that point, CVS stores the changes made to the branch separately from the changes in the trunk. Revisions on the branch are given their own revision numbers, based on the revision number of the base of the branch. You can branch a single file, any group of files, or a whole project, just as you can tag individual files or any group of them. I usually recommend branching a project. In my experience, if you branch one file, you will eventually need to branch others in the project for the same reason. It is much easier to keep track of a single branch over the whole project than a group of unrelated branches of individual files. A tag marks a specific revision of each tagged file, and a sandbox checked out with that tag cannot be changed (you can edit the files, but when you try to check them in, you'll get the error sticky tag 'tag' for file 'file' is not a branch). In contrast, a branch creates revisions that can be edited, committed, checked out, updated, and tagged independently of the revisions on the trunk. In many ways, a branch can be treated as an independent development line of a project. You can tag a file with any number of tags, as long as each tag name in that file is unique, including any tags on the branch. CVS relies on each tag and branch name to be unique within each file, so it considers the branch name to be a tag name when determining tag uniqueness. A branch is an ongoing line of development, and any tags on the branch are used to mark the specific revisions when significant events occur. Though branches are created with the cvs tag command, branches and tags are different things. A branch represents multiple revisions and a tag represents a single revision.
Branches are often used when a public release of a project is due, to allow testers to work on the release candidate while new development goes on independently of the testing. Branches can also keep major changes or experimental work, such as a complete rewrite of a code library, separate from the main line of development. Ideally, you will know you want a branch before you start modifying your sandbox copies of a project's files. You can then create a branch from the sandbox, as shown in "Making a Branch," later in this section. Sometimes you realize after making changes that your work is experimental or will require a section of the project to be redesigned. At such times, you might create a branch to allow you to keep your revision under version control without affecting the rest of the project's development. See "Retroactive Branching," later in this chapter, for instructions on how to do so. Retroactive branching can be more difficult than standard branching, so try to plan branches in advance. Figure 4-8 shows how a branch is developed from a trunk. Revision 2.6 is the base of the branch, and the branch itself is represented by the revision number 2.6.2. The branch has independent revisions from the trunk, starting with revision 2.6.2.1. The trunk continues with revision 2.7. Figure 4-8. A branchThe branch is dependent on the main trunk. However, the trunk and the branch are stored in the same file in the repository, and commands that operate on that file at the repository level can affect both trunk and branch. For example, the same tag cannot be used twice in the same file, even if one use is on the trunk and the other is on the branch. Also, the cvs status and cvs log commands show the overall state of the file, including the trunk and the branch. Changes from the trunk can be merged onto a branch, and changes from a branch can be merged back into the main trunk. The branch can either be abandoned or continued, depending on the purpose for the merge. 4.3.1. Uses for BranchesBranching has many uses. A project's trunk is usually used for the main line of development, and branches are usually used for variations on that line. In programming projects and content management, branches are often used for experimental work, candidates for product releases to the users, refactoring code or content, or bug fixes. For configuration management, the trunk can be used for the default configuration, and branches can be the standard variantsone branch for web servers, one for mail servers, and so on. The following list describes some common uses for different types of branches (branch types are explained in detail in "Branching Strategies," later in this chapter):
4.3.2. Making a BranchYou can make a branch with the -b option to the cvs tag or cvs rtag commands. This option can be combined with any of the other tag-creation options of those commands. You can use a date, existing tag, or revision number to specify the revision to be branched from. If you use cvs tag, you can also make a branch from the most recently synchronized sandbox revision. Doing so acts like tagging from the sandbox revision, as shown earlier in this chapter, under "Tagging by Sandbox." Example 4-10 demonstrates the creation of a branch from an existing tag using cvs tag. The cvs update command ensures that all files in pre_beta_0.1 are present in the sandbox. The output from the cvs update command can be used to confirm that no files have changed. Figure 4-9 shows branch creation in Cervisia. Example 4-10. Creating a branch
Figure 4-9. Creating a branch in CervisiaBranch creation occurs in the repository, not the sandbox. To edit the branch revisions of the files, check out a branch sandbox or use update to alter the current sandbox to the branch. It is good practice to tag the trunk just before splitting off a branch, because this makes it easier to merge the changes back later. To be absolutely certain that the revisions tagged with the prebranch tag (the tag you are basing the branch on) are the revisions used as the base of the branch, use cvs rtag -r prebranch-tag -b branch-tag project to create the branch. This command uses the prebranch tag to specify the revisions the branch is created from. Example 4-11 shows how to create a prebranch tag and then the branch. Then, cvs status is run to show the tag status of one of the files. Example 4-11. Tagging before branching
4.3.3. Retroactive BranchingIf you make changes and realize at the time you're ready to commit that you want to make a branch, you need to try to make a branch from the revisions before the changes. If you have not committed any of the changes, you can retroactively create a branch from the current sandbox using the following process:
This technique relies on the fact that cvs tag marks the repository at the point when the sandbox was last synchronized with the repository. The branch is created at that time, so when you update the sandbox to your branch, CVS tries to merge the base files your sandbox was created from with the files in the sandbox, leaving your sandbox unchanged. Example 4-12 shows an example of retroactive branching. Example 4-12. Retroactive branching
If you have committed changes, you can retroactively make a branch from a date with the method shown in Example 4-12, but use the -D date command option to the cvs tag command.
4.3.4. Creating a Branch SandboxTo change the files in a branch, check out a sandbox that is based on the branch you want to change. In a branch sandbox, cvs commit commits the changes to the branch in the repository and cvs update brings down changes from the repository copy of the branch to the sandbox. Create a branch sandbox with the -r branch-tag-name argument to cvs checkout or cvs update. Figure 4-10 illustrates the results of checking out a branch sandbox. Figure 4-6, in the "Tagging" section of this chapter, shows how to check out a branch sandbox in Cervisia. The only difference is that you select a branch tag rather than a static tag. Figure 4-10. Branch sandboxesCVS marks the sandbox copies of files in a branch sandbox with a sticky tag to record that those files belong to the branch. See Example 4-13 for an example of creating a branch sandbox and a status report of one of the files with a sticky branch tag. Example 4-13. Creating a branch sandbox
You can also retrieve individual branch files to a normal sandbox, but I do not recommend allowing yourself to have a sandbox of mixed branch and trunk files. Use checkout from a nonsandbox directory if you want to check out individual files that do not belong to the same branch or trunk as the current sandbox.
Use a branch sandbox like a normal sandbox. Actions based on revisions of a file affect the branch rather than the trunk. Actions based on the repository copy of the file as a whole reflect the full file. For instance, running cvs status in a branch sandbox reports the status of the local copy of the files, the trunk revision numbers for the working and repository revisions, and the current branch tag and revision as the sticky tag. 4.3.5. Adding and Removing FilesWhen cvs add or cvs remove are applied to files in a branch sandbox, the addition or removal applies only to the branch and does not affect the trunk. Example 4-14 shows the response from CVS after adding a file to a branch. Example 4-14. Adding a file to a branch
4.3.6. Merging BranchesMerging a branch to the trunk applies the differences created during the life of the branch to the most recent revisions of the trunk code. Whether this is desirable depends on the reason for the branch; you may want to apply bug fixes to the main code, or an experimental branch may have content you want to merge into the main code. It is also possible to merge changes from the trunk to the branch, or to merge the contents of two branches together. When you merge a branch, it is good practice to tag the branch at the merge point. Such tags on the branch act as markers to show you when you did each merge. Graphic tools such as TkCVS often include branch merging tools, and some automatically apply a tag at the merge point. Figure 4-11 shows the TkCVS branch merging tool. Figure 4-11. Branch merging with TkCVSOnce you have merged two branches, or a branch and a trunk, usually you are left with one unchanged and one changed. If you merge the changes from a branch to the trunk and commit the changed sandbox, the next revision of the trunk will include those changes. If you want to have a copy of the trunk to work from that doesn't have those changes, you may want to consider merging the trunk to the branch instead, or creating another branch to hold both the current trunk and branch data.
When you finish with a branch, it can seem logical to remove it or somehow close it. However, CVS does not expect you to delete branches. Instead, it keeps them as part of the record of the project. There is no command to mark a branch as no longer to be used. Use a log message to mark the end point of the branch.
4.3.6.1. Merging from branch to trunkTo merge changes from a branch to the trunk (for example, when you want to merge the development line back into the production line), check out a current sandbox of the trunk and run cvs update -j branchbasetag -j branchname, where branchbasetag is the root of the branch, and branchname is the tag of the branch you need to merge into the root. Resolve any conflicts the merge creates and commit the changes. If the changes are complex, the developers or project leads who manage the branch and the trunk should perform this conflict resolution. If the branch has previously been merged to the trunk and you tagged the branch at that point, the command cvs update -j lastmergetag -j branchname in the same sandbox merges only the changes since the last merge tag. Example 4-15 demonstrates merging a branch to a trunk. Here, CVS refuses to remove a file that was changed in the trunk but removed in the branch. The developer doing the merge will have to decide whether to keep or remove the file: in this example, he removed the file. This example also shows an update of the handheld.c file. Example 4-15. Merging a branch to the trunk
4.3.6.2. Merging from trunk to branchTo merge changes from the trunk to a branch (for example, when you want to merge the most recent bugfixes from the production line into the development line), check out a current sandbox of the branch and run cvs update -j branchbasetag -j HEAD from that sandbox. Resolve any conflicts the merge creates and commit the changes. If the changes are complex, the developers or project leads who manage the branch and the trunk should perform this conflict resolution. If the trunk has previously been merged to the branch and you tagged the trunk at that point, the command cvs update -j lastmergetag -j HEAD in the same sandbox merges only the changes since the last merge tag. Example 4-16 shows the result and error messages caused by attempting to merge a trunk to a branch. The config.h file has been removed from the branch but is still active in the trunk. This issue needs to be resolved, probably by reverting the removal. The handheld.c file did not previously exist in the trunk but was added to the trunk when the branch was merged to it; the related error message can be ignored. Example 4-16. Merging the trunk to a branch
4.3.6.3. Merging from branch to branchTo merge changes from one branch to another branch, check out a sandbox of the target branch and run cvs update -j branchbasetag -j otherbranch, merging from the other branch to the checked-out branch's sandbox. If the branches have been previously merged and you tagged the source branch at the time of the merge, the command cvs update -j lastmergetag -j branchname in the sandbox of the target branch merges the changes since the last merge. 4.3.6.4. Keyword issues when merging branchesKeyword expansion (see Chapter 3) can cause conflicts when merging two different revisions of files together. The Revision keyword is the most obvious cause of conflicts, because it expands to display the current revision of a file. Avoid these conflicts by using the -kk keyword-expansion mode, which prevents keywords from being replaced by their associated values.
4.3.6.5. Merging binary and special filesChanges to binary files and other nonmergeable files cannot be merged from the branch to the trunk automatically. If there are utilities similar to diff and patch for the file type in question, it may be possible to merge such files using those utilities. Otherwise, you'll need to merge changes manually. You should consider this issue when deciding whether to branch development of such files. 4.3.7. Branch Revision NumbersAn ordinary file's revision numbers consist of a prefix and an incrementing revision identifier; thus, revision 1.1 is succeeded by 1.2, 1.3, and 1.4 as each change is committed. A branched file's branch number is based on the revision from which it is branched. So, a branch based on revision 1.4 of a file may be branch 1.4.2. Each revision within that branch uses the branch number as its base number and then adds its own number to the end. So, revision numbers for branch 1.4.2 would be 1.4.2.x, where x is the incrementing revision identifier. Remember that a branch is not a single revision; a branch is a line of revisions. Figure 4-12 shows a branched file and its revision numbers. Figure 4-12. Branch numbersCVS never gives a user-created branch the branch number 1.1.1; this number is used for a special branch called the vendor branch. 4.3.8. Magic Branch NumbersCVS sometimes inserts a 0 in the second-rightmost position of a branch number to make internal code more efficient. This sometimes shows up in cvs log and may affect cvs admin commands on that branch, but it is otherwise hidden by CVS. If this happened to branch 1.4.2, the branch number would be displayed as 1.4.0.2. Revisions on the branch would not have the 0, so the first revision on branch 1.4.0.2 would be 1.4.2.1 and the second would be 1.4.2.2. This vagary with respect to zeros in branch numbers does not affect how you use CVS; you can refer to a branch without the 0. For example, the commands cvs update -r 1.4.2 and cvs update -r 1.4.0.2 retrieve the same revisions of the same files. 4.3.9. Deleting or Moving a BranchDeleting or moving a branch is done with the -d or -F command options to cvs tag and cvs rtag, in the same way you delete or move any other tag. CVS also requires the -B option in the command as a way of indicating that you know the tag denotes a branch and that you really mean to move or delete it. Unless the branch has just been created and no work has been done on it, I recommend against deleting or moving a branch. (Even with a newly created branch, be careful and have a backup.) Most tags can be deleted or moved without affecting a project's history, but changes on a branch become part of the change record of each file in the branch. Example 4-17 shows how to delete a just-created branch. (You may also want to delete the branch root tag.) Example 4-17. Deleting a branch
|