Multiple Programmer Development With Perforce

Introduction

Large projects require more than one developer and, hence, multiple programmers will be modifying the same files, adding files, and deleting files all on the same code base. Even with revision control, developers cannot be checking code in and out of the same code base because changes needed by programmer X will break programmer Y's code.

The obvious solution is to give each programmer their own complete "playground" or "sandbox" in which they can modify anything they want. The question is, how do you prevent the project from splintering into n different projects instead of a single project? The answer lies in a revision control system's ability to do branching and merging. Branching is essentially a cp -r from the main line code to a separate location, a development branch. Merging is just "pinching" the development branch back into the mainline. Perforce, the system we use, is known for its excellent branching and merging capabilites. The following image depicts a two-programming development team with the main line representing normal perforce operation (w/o branching).

Developer Y begins development by branching any existing files and then later merging the branch back in twice before discontinuing development. Developer X begins development after Y but starts with the same initial code line, ultimating merging at a release point with Y's code.

The "release 1.0" branch is a terminal line that simply represents a complete snapshot of the software for a release. Note that you might actually have multiple release candidates on this one line such as "1.0rc1", "1.0rc2", etc... Bugs fixed in the main line would be pushed to the release branch from which the release candidate would be cut.

What happens when there are collisions such as "programmers X and Y both updated Main.java". If their changes do not overlap in the file, perforce can safely resolve the conflict. You will see more about conflict resolution below as it is a bit tricky and must be done properly in order to maintain software integrity.

Setting up

Each developer will have their own branch in which to play. You start a new development branch by creating a new branch specification, creating a new client spec that maps that branch to your disk, and integrating the main line into your branch. Typically, you will see branching on the local disk as a directory structure like the following:

~/depot
    main
        src
        lib
        doc
~/depot
    dev
        X
           src
           lib
           doc
        Y
           src
           lib
           doc
~/depot
    release
        1.0
           src
           lib
           doc

Branch Specs

Branch specs are just maps from one place in the depot to another place in the depot. In our case, you will see for your dev branch userid.dev:

...
View:
	//depot/cs342/groupN/main/... //depot/cs342/groupN/dev/userid/...

Note that the branch spec name doesn't include nfs or any other machine reference as it's a depot-to-depot mapping. You just need a name that uniquely indicates what the branch is for.

Main exists already so you will not have a branch spec for main (though you will have client specs for both main and your dev branch).

Create a branch spec with

p4 branch userid.dev

which will bring up an editor. Change the View field to the above (with your userid changed appropriately).

Client specs

For each dev branch, you will have a branch spec and different client spec so that perforce can prevent you from modifying the main line when you think you are modifying your dev branch. Because your current client, userid.nfs, works with //depot/cs342/userid and not your group-shared files, you will need two completely new client specs named userid.main.nfs and userid.dev.nfs:

Client: userid.main.nfs

Root:   /home/userid/depot/main

View:
   //depot/cs342/groupN/main/... //userid.main.nfs/...

and like this for your development branch.

Client: userid.dev.nfs

Root:   /home/userid/depot/dev

View:
   //depot/cs342/groupN/dev/userid/... //userid.dev.nfs/...

Pay close attention to the file mappings.

You always make a uniquely rooted client. Never never ever have roots from more than one client map to a single local drive directory! Perforce will get very confused.

Create the client specs with

p4 client userid.main.nfs

and

p4 client userid.dev.nfs

The editor will come up and you must change the Root and View fields appropriately.

Protections

I have set up the protections as follows so that only members of a group can see underneath a group's subdirectory:

        write group groupN * //depot/cs342/groupN/...

where the groups are defined with the appropriate members such as:

$ p4 group -o groupN
...
Users:
        cfrasche
        defendio
        illim

Telling perforce which branch to use

Just as with the nonbranching use of perforce, we will use a .p4 file to inform perforce of our intended branch and user etc... You must have file /home/user/depot/.p4 with the following info:

P4USER=user
P4PASSWD=
P4PORT=dweller:1666

at least and then either

P4CLIENT=userid.main.nfs

or

P4CLIENT=userid.dev.nfs

For perforce to know to look in this "hidden" file (due to the prefix of a dot), you must have

export P4CONFIG='.p4'

in your .bash_profile or whatever.

If you have your .p4 file set to use client userid.dev.nfs, then when you try to "p4 edit File.java" in the main line, it will tell you that that file is not under your client mapping or something to that effect. To switch branches, just change your .p4 file.

Processes

Branching

Tell perforce to perform the actual branch. This is like doing a "p4 add" on all the newly branched files and copies all the stuff from main into new branch dir on your drive. You must have set your client to be your userid.dev.nfs client to be able to alter it.

p4 integrate -b parrt.dev

Now, commit the branch:

p4 submit

At this point, you can do edits and so on in this branch w/o worrying that someone is modifying files underneath you. Proceed with normal development and testing.

Getting bug fixes from the main line

Sometimes bug fixes will appear in the main line that you would like in your development branch. To get them, just do another integrate, but you will need to resolve any differences. If the bug fix from main is not in any file you've modified, you will be able to just "p4 submit" immediately to get the fix locked into your branch. If your branch and the main branch have altered the same file or files but not in the same location of the file(s), a

p4 resolve -as

command will "accept safe" resolves such as this. Then do a normal "p4 submit". If you do a submit and get an error about needing to resolve, you must do a

p4 resolve

and then interactively indicate what to do with each file. More on this later.

Merging back into mainline

Nothing is ever submitted to the mainline that has not been compiled and tested with your standard unit tests otherwise all development branches will become corrupted with junk that will not compile or execute properly.

You should follow this strict process for merging changes back into the main line to cut releases and do integration testing etc...

  1. Sync your branch again in case someone has changed your branch without you knowing
    p4 sync  # make sure you client has dup of depot; VERY important step
    
  2. Pull whatever is in main into your branch
    p4 integrate -b userid.dev.nfs # grab files from main that have changed
    p4 resolve -as  # if nothing conflicts, this "safe" resolve just works
                    # You will have to do "p4 resolve" after this for conflicts
    		# that -as couldn't handle automatically.
    p4 resolve      # find out what didn't resolve
    p4 submit       # commit transaction
    
    Your dev branch is now (for a fleeting moment) the most up-to-date software your team has; you must move this back into main.
  3. Compile your dev branch, run your unit tests, fix any problems.
  4. Now to make sure you have the latest main branch on your disk. Switch to the main client in your .p4 file. Then do a p4 info to ensure you have the right client.
  5. Make sure you have an up to date version of main in your main branch by doing a p4 sync.
  6. Move your dev branch back into main. Reverse the integration to copy your dev branch into main.
    p4 integrate -r -b parrt.dev # reverse the branch direction
    p4 resolve -as		     # safe resolve
    p4 resolve		     # find out / fix what didn't resolve
    p4 submit
    
  7. Compile, run your unit tests, fix any problems.

Releases

To cut a release, just create a branch spec such as webmail-1.0 that maps

View:
  //depot/cs342/groupN/main/... //depot/cs342/groupN/release/webmail-1.0/...

Then do

$ p4 integrate -b webmail-1.0
$ resolve -as
$ p4 submit

Diff3

The diff3 process is fundamental to branching, merging as it dictates how two different versions of same file are different relative to a starting version. Imagine T.java:

public class T {
        int x;
}

and then parrt does this:

public class T {
        int x;
        public T() {
                x=3;
        }
}

and tombu does this in his branch:

public class T {
        int x = 3;
}

Now, they want to merge there results back together. diff3 figures out how they differ relative to first T.java:

$ diff3 T-parrt.java T.java T-tombu.java 
====
1:2,5c
        int x;
        public T() {
                x=3;
        }
2:2c
        int x;
3:2c
        int x = 3;

diff3 can show all sorts of things need by a branch/merge tool:

$ diff3 --overlap-only T-parrt.java T.java T-tombu.java 
2,5c
        int x = 3;
.

Demos

In this section, I demonstrate the basic processes to create branches and do some simple merges.

Create client spec for main

Assuming I am a member of the "black" team, I would create the following main client spec:

$ p4 client parrt.main.nfs
...
Root:   /home/parrt/depot/cs342/black/main

Options:        noallwrite noclobber nocompress unlocked nomodtime
normdir

LineEnd:        local

View:
        //depot/cs342/black/main/... //parrt.main.nfs/...

To work in my dev branch I create a /home/parrt/depot/cs342/black/.p4 file:

P4CLIENT=parrt.main.nfs
P4USER=parrt
P4PASSWD=...
P4PORT=dweller:1666

Note that if I try to go into a directory not under my client's root, you'll get an error:

[parrt@nexus parrt]$ pwd
/home/parrt/depot/cs342/black/dev/parrt
[parrt@nexus parrt]$ p4 add foo
Path '/home/parrt/depot/cs342/black/dev/parrt/foo' is not under
client's root '/home/parrt/depot/cs342/black/main'.

Create branch spec

Map the main line to your dev branch area:

$ p4 branch parrt.dev
...
View:
        //depot/cs342/black/main/... //depot/cs342/black/dev/parrt/...

Also I will need following client spec to handle my dev branch:

$ p4 client parrt.dev.nfs
...
Root:   /home/parrt/depot/cs342/black/dev/parrt

Options:        noallwrite noclobber nocompress unlocked nomodtime
normdir

LineEnd:        local

View:
        //depot/cs342/black/dev/parrt/... //parrt.dev.nfs/...

Note that I am making the Root as explicit as possible.

To work in my dev branch I create a /home/parrt/depot/cs342/black/.p4 file:

P4CLIENT=parrt.dev.nfs
P4USER=parrt
P4PASSWD=...
P4PORT=dweller:1666

Creation of files

I'll create an A.java file

public class A {
        int i;
}

and then add to the main line in the depot (since my client is set to parrt.main.nfs):

[parrt@nexus main]$ vi A.java
[parrt@nexus main]$ p4 add A.java
//depot/cs342/black/main/A.java#1 - opened for add
[parrt@nexus main]$ p4 submit
Change 780 created with 1 open file(s).
Submitting change 780.
Locking 1 files ...
add //depot/cs342/black/main/A.java#1
Change 780 submitted.

Remember that only changes that have been submitted will be available to push to dev branches.

Create the dev branch

Change your .p4 file so it points to the dev client.

[parrt@nexus main]$ grep CLIENT ~/depot/cs342/black/.p4
P4CLIENT=parrt.dev.nfs

Then integrate the branch:

[parrt@nexus main]$ p4 integrate -b parrt.dev
//depot/cs342/black/dev/parrt/A.java#1 - branch/sync from
//depot/cs342/black/main/A.java#1
[parrt@nexus main]$ p4 resolve -as
No file(s) to resolve.
[parrt@nexus main]$ p4 submit
Change 781 created with 1 open file(s).
Submitting change 781.
Locking 1 files ...
branch //depot/cs342/black/dev/parrt/A.java#1
Change 781 submitted.

Afterwards you will see A.java in the dev branch:

[parrt@nexus main]$ pwd
/home/parrt/depot/cs342/black/main
[parrt@nexus main]$ cd ../dev/parrt
[parrt@nexus parrt]$ ls 
A.java

If you try to create the dev branch when still set to the main client spec, you will see this error:

[parrt@nexus main]$ p4 integrate -b parrt.dev
No target file(s) in both client and branch view.

Merge back into main

First, let's add a B.java file to the dev branch:

[parrt@nexus parrt]$ vi B.java
[parrt@nexus parrt]$ p4 add B.java
//depot/cs342/black/dev/parrt/B.java#1 - opened for add
[parrt@nexus parrt]$ p4 submit
Change 782 created with 1 open file(s).
Submitting change 782.
Locking 1 files ...
add //depot/cs342/black/dev/parrt/B.java#1
Change 782 submitted.

and then make a change to the A.java we got from main line:

[parrt@nexus parrt]$ p4 edit A.java
//depot/cs342/black/dev/parrt/A.java#1 - opened for edit
[parrt@nexus parrt]$ vi A.java
[parrt@nexus parrt]$ cat A.java
/** A comment */
public class A {
        int i;
}
[parrt@nexus parrt]$ p4 submit
Change 783 created with 1 open file(s).
Submitting change 783.
Locking 1 files ...
edit //depot/cs342/black/dev/parrt/A.java#2
Change 783 submitted.

Notice that my .class files are not kept in the depot.

Following the steps above for merging:

[parrt@nexus parrt]$ p4 sync
File(s) up-to-date.
[parrt@nexus parrt]$ p4 integrate -b parrt.dev
All revision(s) already integrated.
[parrt@nexus parrt]$ p4 resolve -as
No file(s) to resolve.
[parrt@nexus parrt]$ p4 submit
No files to submit from the default changelist.

Since nothing changed in the mainline, this operation did nothing.

I switch my .p4 to client spec parrt.main.nfs and then check with p4 info:

[parrt@nexus parrt]$ p4 info
User name: parrt
Client name: parrt.main.nfs
...

Now reverse the branch

[parrt@nexus parrt]$ cd ~/depot/cs342/black/main
[parrt@nexus main]$ p4 sync
File(s) up-to-date.
[parrt@nexus main]$ p4 integrate -r -b parrt.dev
//depot/cs342/black/main/A.java#1 - integrate from
//depot/cs342/black/dev/parrt/A.java#2
//depot/cs342/black/main/B.java#1 - branch/sync from
//depot/cs342/black/dev/parrt/B.java#1
[parrt@nexus main]$ p4 resolve -as
/home/parrt/depot/cs342/black/main/A.java - merging
//depot/cs342/black/dev/parrt/A.java#2
Diff chunks: 0 yours + 1 theirs + 0 both + 0 conflicting
//parrt.main.nfs/A.java - copy from
//depot/cs342/black/dev/parrt/A.java
[parrt@nexus main]$ p4 resolve
No file(s) to resolve.
[parrt@nexus main]$ p4 submit
Change 784 created with 2 open file(s).
Submitting change 784.
Locking 2 files ...
integrate //depot/cs342/black/main/A.java#2
branch //depot/cs342/black/main/B.java#1
Change 784 submitted.

Yours vs Theirs

One point of confusion with perforce concerning branching: their use of relative pronouns yours and theirs such as in:

Diff chunks: 0 yours + 1 theirs + 0 both + 0 conflicting

Just think of yours as the current branch (or main) and theirs as the branch you are pulling from. So when we merged my dev branch back into main, yours implies "main line" and theirs implies "my dev branch" because I am using my main client spec not my dev branch client spec.