Those who cannot learn from history are doomed to repeat it.
-- George Santayana

Introduction

So I've been writing a lot of perforce related topics on my blog. Some might accuse me of loving perforce. There is some truth in that, but only a nugget. As most other software, email clients come to mind, there are only a selection of suck, death and misery. Perforce is simply the least messed up of them all, it's a compromise. For some the whole notion of git-like source control schemes are really more appealing. I digress...

Protect your history

The source control history is a precious asset. How your source code evolved over time, what and who made the changes (and why) are questions that can all be answered by the history. It's important to try to preserve the history, but it's also very easy to forget and not do it. It is very frustrating to do some detective work only to find out that the trails end cold when someone copied the file without telling perforce. Best of all, the person who did this might have quit and you have now no clue as to where that file originated.

How to move a file

Moving a file is really two operations in perforce, first a branch (copy) and then a delete of the original file. Luckily, this is automated for us through a right click on the file in the explorer view and then clicking on the "rename" option. You will have to enter the perforce path for the new file, and then you can click ok to create a changelist with the changes.

Fig. 1: The rename option is available as a right click context menu in p4win.
Fig. 2: The rename dialog prompts you to enter the new perforce pathname of the file.

I've done this with a simple text file, but most often one does it with a .cpp/.h file pair. Then you will be faced with the problem that you have to go in and edit the files to match up the header names (the new branched files are readonly). There is an option to reopen a branched file for edit. This preserves the branching history so that when you check in it will still have a link to the old file. Previous versions of perforce didn't use to do this (several years back) so make sure that your server and client is at least not ancient (upgrading is a breeze).

Fig. 3: Branched files are still readonly on the local drive, since branching is really a server side operation. But you can reopen the file for edit, thus allowing you to do edits and branch the file at the same time.

After branching the file and editing it you should wind up with something like this:

Fig. 4: Once you've branched you should wind up with a history that shows that you've branched and if you've done edits, that will show up as add in the history view. Make sure to tick the "Display branching history" option in the lower left corner in this dialog.

If you have really complicated branching, or multiple projects, there is a revision graph view that will save your bacon. Using this on large codebases can be very useful, it save me several times while doing a port of a game during development. We had two branches of the game, one for each platform, and I could follow bugfixes and changes in both of the branches with ease. Of course I kind of spent up to a day a week to merge changes from one branch to another, but it isolated my team from the others so we could have different checkin rules and had a much higher velocity in the end. The graph view and some nagging perforce policing when people got lazy and just did a local copy and an add made my day.

One thing to note is that the branching and deleting is actually operating on metadata on the perforce server. Nothing is really copied, but there will be an annotation that the contents of the file now lives in another path as well. And that the original location's head revision has been deleted. The full history is still preserved.

How to refactor a file into two

We've all been there. A simple file evolves over time into this huge massive beast that's a whole game in one single file. Different people have different thresholds, some think that a 5000 line file is just fine, while others (me included) starts to get squeamish around 1000 lines. Once you've reached your limit, the right thing to do (tm) is to break up the functionality into multiple files, right? Make sure to preserve history here, especially since this is pretty critical for finding later bugs.

First thing, the refactoring needs to be in two steps, move the text and then perhaps clean it up. Avoid the temptation to change any code functionality wise! This will bring ruin and despair upon you and the code.

As we noted from the previous section, renaming a file actually consists of branching the file to a new location and deleting the old file. There is really nothing that prevents us from branching twice. We can do that, edit the two new files individually and then check them into perforce. Since nothing in the project really should have referenced these file yet, it should be perfectly safe.

Fig. 5: Choose the integrate using filespec option in the right click context menu. This will allow you to copy the file to a new filename.
Fig. 6: Next up, choose to reopen the branched files for edit and then start to make the modifications in the files. Usually at this point you just want to make small code deletions.

Note that we've really just deleted code from the two new files, this will help the diffs to make sense. Try not to move code around internally yet, as this will confuse you reading the diffs a couple of months from now.

Once this step is done, we can really go to town on the new individual files. Now we can start doing code reformatting, moving functions around and prune out dead code. Still, no actual code changes in terms of functionality. Make sure to check in often.

After we've done, we can go through and hook the new files up to the project, and delete the old file. After doing this we can now check in and make the new files live.

Fig. 7: Once you're done, bringing up the revision graph tool should show you exactly how the files evolved over time and how they are related to all the other versions of the same file.

You should now at this point have a very clear trail on what happened if ever somebody else is trying to follow what happened to the code, or indeed confirm that the bug they found was in there since shipping your last game...

Reformatting code

Most refactors are just reformatting code. In order to do this, make sure that you're the only one currently editing the file in question first. If there is anyone else doing edits to the file at the same time, they will be royally screwed once you've done a couple of checkins with massive textual changes, but no code changes. My advice is to make sure that you're the only one that does edits, lock the file at all times and code away quickly. If it's going to take any appreciable amount of time, send an email to the most likely programmers warning them not to do any changes for a while. There is nothing more annoying than having some pretty tricky changes in a file and when you come to the merge stage you realize that the file is either gone (deleted in the head revision) or completely changed. Having to recreate the changes to the file is probably quicker than trying to figure out how to merge (if it's even possible).

In closing

For programmers, the code history is a very powerful tool. Coupled with sensible checkin comments, it can quickly become apparent why some piece of code is written the way it is, or even determine that the code is no longer needed. Moving between source control systems, we take great pains to preserve the history. We should also try to do this in our daily routines, since it's a lifesaver in the future. It is most probably yourself you are saving!

Resources

Comments