Codelines and Branching

This chapter describes the tasks required to maintain groups of files in your depot. The following specific issues are addressed:

  • Depot directory structure and how to best organize your repository
  • Moving files and file changes among codeline and project directories
  • Identifying specific sets of files using either labels or changelists

To make codeline management easier, you should use streams, a Helix feature that encapsulates numerous best practices and automations. The “Streams” chapter explains them in detail.

This chapter focuses on maintaining a software code base, but many of the tasks are relevant to managing other groups of files, such as a web site. For advice about best practices, see the white papers on the Perforce web site.

Basic terminology

To enable you to understand the following sections, here are definitions of some relevant terms as they are used in Helix.

Term Definition

branch

(noun) A set of related files created by copying files, as opposed to adding files. A group of related files is often referred to as a codeline.

(verb) To create a branch.

integrate

To create new files from existing files, preserving their ancestry (branching), or to propagate changes from one set of files to another (merging).

merge

The process of combining the contents of two conflicting file revisions into a single file, typically using a merge tool like P4Merge.

resolve

The process you use to reconcile the differences between two revisions of a file. You can choose to resolve conflicts by selecting a file to be submitted or by merging the contents of conflicting files.

Organizing the depot

You can think of a depot as a top-level directory. Consider the following factors as you decide how to organize your depot:

  • Type of content: create depots or mainline directories according to the nature of your projects and their relationships (for example, applications with multiple components developed on separate schedules).
  • Release requirements: within a project, create branches for each release and integrate changes between branches to control the introduction of features and bug fixes.
  • Build management: use labels and changelists to control the file revisions that are built; use client specifications and views to ensure clean build areas.

A basic and logical way to organize the depot is to create one subdirectory (codeline) for each project. For example, if your company is working on Jam, you might devote one codeline to the release presently in development, another to already-released software, and perhaps one to your corporate web site. Your developers can modify their client views to map the files in their project, excluding other projects that are not of interest. For example, if Earl maintains the web site, his client view might look like this:

//depot/www/dev/...     //earl-web-catalpa/www/development/...
//depot/www/review/...  //earl-web-catalpa/www/review/...
//depot/www/live/...    //earl-web-catalpa/www/live/...

And Gale, who’s working on Jam, sets up her client view as:

//depot/dev/main/jam/... //gale-jam-oak/jam/...

You can organize according to projects or according to the purpose of a codeline. For example, to organize the depot according to projects, you can use a structure like the following:

//depot/project1/main/
//depot/project1/release 1.0/
//depot/project1/release 1.1/

Or, to organize the depot according to the purpose of each codeline, you can use a structure like the following:

//depot/main/project1/
//depot/main/project2/
//depot/release1.0/project1/
//depot/release1.0/project2/
//depot/release2.0/project1/
//depot/release2.0/project2/

Another approach is to create one depot for each project. Choose a structure that makes branching and integrating as simple as possible, so that the history of your activities makes sense to you.

Populating Codelines

If you are creating a codeline that has no history, use the p4 add command to add files to it, then use p4 copy to create branches. For example, to create the mainline structure shown in the previous section, perform the following steps:

  1. Create a local folder your workspace for the mainline files; for example:

    $ mkdir c:\p4clients\myworkspace\depot\main\
  2. Copy the files for Project1 and Project2 to the newly created folder.
  3. Add the files to the depot:

    $ p4 add //depot/main/project1/...
    $ p4 add //depot/main/project2/...
    $ p4 submit
  4. Create release branches:

    $ p4 copy //depot/main/project1/... //depot/release1.0/project1/...
    $ p4 copy //depot/main/project2/... //depot/release1.0/project2/...
    $ p4 submit

Now you can use the p4 copy, p4 merge and p4 integrate commands to propagate changes between main and release branches. (You can also seed a codeline from another codeline using the p4 integrate command, if there is a historical relationship between the source and target that you need to preserve.)

A shortcut: p4 populate

If a target codeline is completely empty (no files present, not even deleted files), Helix offers a command that automates the process of copying the files from an existing source codeline submitting the associated changelist.

For example, instead of populating a release1.0 branch with the following two commands:

$ p4 copy //depot/main/project1/... //depot/release1.0/project1/...
$ p4 submit

you can use the p4 populate command to populate the branch:

$ p4 populate //depot/main/project1/... //depot/release1.0/project1/...

Branching Codelines

Branching is a method of maintaining the relationship between sets of related files. Branches can evolve separately from their ancestors and descendants, and you can propagate (integrate) changes from one branch to another as desired. Helix’s Inter-File BranchingTM mechanism preserves the relationship between files and their ancestors while consuming minimal resources.

To create a branch, use the p4 integrate command. The p4 integrate command is also used to propagate changes between existing sets of files. For details about integrating changes, refer to Integrating changes.

When to branch

Create a branch when two sets of files have different submission policies or need to evolve separately. For example:

  • Problem : the development group wants to submit code to the depot whenever their code changes, regardless of whether it compiles, but the release engineers don’t want code to be submitted until it’s been debugged, verified, and approved.

    Solution: create a release branch by branching the development codeline. When the development codeline is ready, it is integrated into the release codeline. Patches and bug fixes are made in the release code and integrated back into the development code.

  • Problem: a company is writing a driver for a new multi-platform printer. The UNIX device driver is done and they are beginning work on an OS X driver, using the UNIX code as their starting point.

    Solution: create an OS X branch from the existing UNIX code. These two codelines can evolve separately. If bugs are found in one codeline, fixes can be integrated to the other.

One basic strategy is to develop code in //depot/main/ and create branches for releases (for example, //depot/rel1.1/). Make release-specific bug fixes in the release branches and, if required, integrate them back into the //depot/main/ codeline.

Creating branches

To create a branch, use the p4 integrate command. When you create a branch, Helix records the relationships between the branched files and their ancestors.

You can create branches using file specifications or branch specifications. For simple branches, use file specifications. For branches that are based on complex sets of files or to ensure that you have a record of the way you defined the branch, use branch specifications. Branch specifications can also be used in subsequent integrations. Branch specifications also can serve as a record of codeline policy.

Using branch specifications

To map a set of files from source to target, you can create a branch mapping and use it as an argument when you issue the p4 integrate command. To create a branch mapping, issue the p4 branch branchname command and specify the desired mapping in the View: field, with source files on the left and target files on the right. Make sure that the target files and directories are in your client view. Creating or altering a branch mapping has no effect on any files in the depot or client workspace. The branch mapping merely maps source files to target files.

To use the branch mapping to create a branch, issue the p4 integrate -b branchname command; then use p4 submit to submit the target files to the depot.

Branch specifications can contain multiple mappings and exclusionary mappings, just as client views can. For example, the following branch mapping branches the Jam 1.0 source code, excluding test scripts, from the main codeline:

Branch:   jamgraph-1.0-dev2release

View:
    //depot/dev/main/jamgraph/...       //depot/release/jamgraph/1.0/...
    -//depot/dev/main/jamgraph/test/... //depot/release/jamgraph/1.0/test/...
    //depot/dev/main/bin/glut32.dll     //depot/release/jamgraph/1.0/bin/glut32.dll

To create a branch using the preceding branch mapping, issue the following command:

$ p4 integrate -b jamgraph-1.0-dev2release

and use p4 submit to submit the changes.

To delete a branch mapping, issue the p4 branch -d branchname command. Deleting a branch mapping has no effect on existing files or branches.

As with workspace views, if a filename or path in a branch view contains spaces, make sure to quote the path:

//depot/dev/main/jamgraph/... "//depot/release/Jamgraph 1.0/..."

Using file specifications

To branch using file specifications, issue the p4 integrate command, specifying the source files and target files. The target files must be in the client view. If the source files are not in your client view, specify them using depot syntax.

To create a branch using file specifications, perform the following steps:

  1. Determine where you want the branch to reside in the depot and the client workspace. Add the corresponding mapping specification to your client view.
  2. Issue the p4 integrate source_files target_files command.
  3. Submit the changelist containing the branched files. The branch containing the target files is created in the depot.

Example 30. Creating a branch using a file specification

Version 2.2 of Jam has just been released, and work on version 3.0 is starting. Version 2.2 must be branched to //depot/release/jam/2.2/... for maintenance.

Bruno uses p4 client to add the following mapping to his client view:

//depot/release/jam/2.2/... //bruno_ws/release/jam/2.2/...

He issues the following command to create the branch:

$ p4 integrate //depot/dev/main/jam/... //bruno_ws/release/jam/2.2/...

Finally, he issues the p4 submit command, which adds the newly branched files to the depot.

Integrating changes

After you create branches, you might need to propagate changes between them. For example, if you fix a bug in a release branch, you probably want to incorporate the fix back into your main codeline. To propagate selected changes between branched files, you use the p4 integrate, p4 merge, or p4 copy commands, as follows:

  1. Issue the p4 integrate command to schedule the files for resolve. (In many cases, you can also use p4 merge or p4 copy.)
  2. Issue the p4 resolve command to propagate changes from the source files to the target files.

    To propagate individual changes, edit the merge file or use a merge program. The changes are made to the target files in the client workspace.

  3. Submit the changelist containing the resolved files.

Example 31. Propagating changes between branched files

Bruno has fixed a bug in the release 2.2 branch of the Jam project and needs to integrate it back to the main codeline. From his home directory, Bruno types:

$ p4 integrate //depot/release/jam/2.2/src/Jambase //depot/dev/main/jam/Jambase

and sees the following message:

//depot/dev/main/jam/Jambase#134 - integrate from //depot/release/jam/2.2/src/Jambase#9

The file has been scheduled for resolve. He types p4 resolve, and the standard merge dialog appears on his screen.

//depot/dev/main/jam/Jambase - merging depot/release/jam/2.2/src/Jambase#9
Diff chunks: 0 yours + 1 theirs + 0 both + 0 conflicting
Accept(a) Edit(e) Diff(d) Merge (m) Skip(s) Help(?) [at]:

He resolves the conflict. When he’s done, the result file overwrites the file in his workspace. The changelist containing the file must be submitted to the depot.

To run the p4 integrate, p4 merge, or p4 copy commands, you must have Helix write permission on the target files, and read access on the source files. (See the Helix Versioning Engine Administrator Guide: Fundamentals for information on Helix permissions.)

By default, a file that has been newly created in a client workspace by p4 integrate cannot be edited before being submitted. To edit a newly integrated file before submission, resolve it, then issue the p4 edit command.

If the range of revisions being integrated includes deleted revisions (for example, a file was deleted from the depot, then re-added), you can specify how deleted revisions are integrated using the -Di option. For details, refer to the P4 Command Reference.

Integrating using branch specifications

To integrate changes from one set of files and directories to another, you can use a branch mapping when you issue the p4 integrate command. The basic syntax of the integrate command when using a branch mapping is:

p4 integrate -b branchname [tofiles]

Target files must be mapped in both the branch view and the client view. The source files need not be in the client view. If you omit the tofiles argument, all the files in the branch are affected.

To reverse the direction of integration using a branch mapping, specify the -r option. This option enables you to integrate in either direction between two branches without requiring you to create a branch mapping for each direction.

Example 32. Integrating changes to a single file in a branch

A feature has been added in the main Jam codeline and Bruno wants to propagate the feature to release 1.0. He types:

$ p4 integrate -b jamgraph-1.0-dev2release *.c

and sees:

//depot/release/jam/1.0/src/command.c#10 - integrate from //depot/dev/main/jam/command.c#97

The file has been scheduled for resolve. He types p4 resolve, and the standard merge dialog appears on his screen.

//depot/release/jam/1.0/src/command.c - merging //depot/dev/main/jam/command.c#97
Diff chunks: 0 yours + 1 theirs + 0 both + 0 conflicting
Accept(a) Edit(e) Diff(d) Merge (m) Skip(s) Help(?) [at]:

He resolves the conflict. When he’s done, the result file overwrites the file in his branched client workspace; the file must then be submitted to the depot.

Integrating between unrelated files

If the target file was not branched from the source, there is no base (common ancestor) revision, and Helix uses the first (most recently added) revision of the source file as its base revision. This operation is referred to as a baseless merge.

Integrating specific file revisions

By default, the integrate command integrates all the revisions following the last-integrated source revision into the target. To avoid having to manually delete unwanted revisions from the merge file while editing, you can specify a range of revisions to be integrated. If you are using p4 integrate, the base file is the closest common ancestor. If you are using p4 merge, the base file is the revision with the most edits in common.

Example 33. Integrating specific file revisions

Bruno has made two bug fixes to //depot/dev/main/jam/scan.c in the main codeline, and Earl wants to integrate the change into the release 1.0 branch. Although scan.c has gone through several revisions since the fixes were submitted, Earl knows that the bug fixes he wants were made to the 30th revision of scan.c. He types:

$ p4 integrate -b jamgraph-1.0-dev2release depot/release/jam/1.0/scan.c#30,30

The target file (//depot/release/jam/1.0/scan.c) is given as an argument, but the file revisions are applied to the source. When Earl runs p4 resolve, only the 30th revision of Bruno’s file is scheduled for resolve. That is, Earl sees only the changes that Bruno made to scan.c at revision 30.

Reintegrating and reresolving files

After a revision of a source file has been integrated into a target, that revision is skipped in subsequent integrations to the same target. To force the integration of already-integrated files, specify the -f option when you issue the p4 integrate command.

A target that has been resolved but not submitted can be resolved again by specifying the -f option to p4 resolve. When you re-resolve a file, yours is the new client file, the result of the original resolve.

Integration reporting

The reporting commands below provide useful information about the status of files being branched and integrated. Note the use of the preview option (-n) for reporting purposes.

To display this information Use this command

Preview of the results of an integration

p4 integrate -n [filepatterns]

Files that are scheduled for resolve

p4 resolve -n [filepatterns]

Files that have been resolved but not yet submitted.

p4 resolved

List of branch specifications

p4 branches

The integration history of the specified files.

p4 integrated filepatterns

The revision histories of the specified files, including the integration histories of files from which the specified files were branched.

p4 filelog -i [filepatterns]