April 1, 2015

Neatness Counts: Cleaning your History Before Sharing Your Work

Media & Entertainment

To err is human, to forgive… well, that simply isn’t going to happen when you break the build. Not if you work with the kind of code monkeys I’ve known, who will gleefully adorn your head with a dunce cap and possibly even fling their poo in your general direction.

Jocularity aside, we all screw up. There’s no getting around that. And it’s even less fun than usual when you just submitted your mistakes to a shared server for all to see, including the continuous integration systems that will mock you via email to the entire team.

Which brings us to another great reason to love the Distributed Version Control System (DVCS) way of working: the ability to clean up your history before sharing your work. How many times have you committed only to discover you forgot to add an important new file? Or forgot to delete a bunch of output statements useful only for debugging an odd problem?

DVCS features make it possible to treat your history as a first-order work product in its own right and rearrange it for sake of clarity and meaning. When you’re working with a team, the intent and progression of your commits can sometimes be as useful as the final code. In short, neatness counts.

The DVCS features of Perforce Helix have you covered with the new unsubmit and resubmit commands. The former lets you undo work you’ve submitted very flexibly, removing that history and leaving it as a series of shelved files. This gives you a chance to correct your mistake(s) before using the latter to submit the work as a single, new changelist. It’s the developer equivalent of a time-travelling DeLorean minus the ugly possibility of undoing your own existence.

The total scope of what you can do with unsubmit/resubmit is well beyond a single blog post, so for our purposes today I’ll focus on two very common cases. For sake of discussion I’ve started a new repo, added some files, and then in my last submit I added a new C++ implementation file (delorean.cpp) without its header (delorean.h). If I had a dime for every time I’ve seen this, I would have retired already. Let’s take a look at the last five changes to establish a baseline of history:

perforce@p4demo:~/newProject$ p4 changes -m 5
Change 7 on 2015/03/18 by sysop@sysop-dvcs-1426714639 'Added the DeLorean.'
Change 6 on 2015/03/18 by sysop@sysop-dvcs-1426714639 'Made almanac scores public.'
Change 5 on 2015/03/18 by sysop@sysop-dvcs-1426714639 'Added scores to almanac.'
Change 4 on 2015/03/18 by sysop@sysop-dvcs-1426714639 'Added Sports Almanac.'
Change 3 on 2015/03/18 by sysop@sysop-dvcs-1426714639 'Added Doc Brown.'

In this case, I can simply unsubmit the file I added, even if I’ve done work on it later. Let’s first take a look at the commands:

perforce@p4demo:~/newProject$ p4 unsubmit delorean.cpp
Change 7 unsubmitted and shelved.
perforce@p4demo:~/newProject$ p4 resubmit –e
perforce@p4demo:~/newProject$ p4 add -c 7 delorean.h
//stream/main/delorean.h#1 - opened for add
perforce@p4demo:~/newProject$ p4 status
delorean.cpp - submit change 7 to add //stream/main/delorean.cpp#1
delorean.h - submit change 7 to add //stream/main/delorean.h#1
No file(s) to reconcile.
perforce@p4demo:~/newProject$ p4 resubmit –Re
Submitting change 7.
Locking 1 files ...
add //stream/main/delorean.cpp#1
add //stream/main/delorean.h#1
Change 7 submitted.

The unsubmit command was pretty straightforward: I just told it to get rid of the implementation file. It helpfully told me which changelist it was in, so I could then use the resubmit command with the edit argument to begin the process, add the forgotten header file to that changelist number, and then resubmit as it should have been in the first place. Note also the status command I used to confirm that, in fact, both of the files I wanted were going to be added.

But what if I wanted to undo a whole range of changes? Reexamining the original baseline, I can’t help but notice I made the sports almanac scores public in changelist six. And as anyone knows, that will inevitably result in Biff turning Hill Valley into a degenerate, dystopian nightmare; i.e., Las Vegas minus family friendly marketing. I’ve just fixed changelist seven, so now I’m in a bind, right? As before, resubmit is your friend. We just have to unsubmit a range of changelists instead:

perforce@p4demo:~/newProject$ p4 unsubmit "//...@>=6"
Change 7 unsubmitted and shelved.
Change 6 unsubmitted and shelved.
perforce@p4demo:~/newProject$ p4 resubmit –e
perforce@p4demo:~/newProject$ vim sports_almanac.h
perforce@p4demo:~/newProject$ p4 resubmit –Re
Submitting change 6.
edit //stream/main/sports_almanac.h#3
Change 6 renamed change 8 and submitted.

The unsubmit command accepts a very flexible set of arguments, and here I told it to unsubmit everything from changelist six and after. I was then able to begin the resubmit process and correct my horrible error with the sports almanac. And notice what it did: it created a new change and submitted it for me. So now let me check my history again:

perforce@p4demo:~/newProject$ p4 changes -m 5
Change 8 on 2015/03/18 by sysop@sysop-dvcs-1426714639 'Made almanac scores public.'
Change 7 on 2015/03/18 by sysop@sysop-dvcs-1426714639 *pending* 'Added the DeLorean.'
Change 5 on 2015/03/18 by sysop@sysop-dvcs-1426714639 'Added scores to almanac.'
Change 4 on 2015/03/18 by sysop@sysop-dvcs-1426714639 'Added Sports Almanac.'
Change 3 on 2015/03/18 by sysop@sysop-dvcs-1426714639 'Added Doc Brown.'

Sure enough, change six is gone, replaced by change eight. But what’s that whole “pending” business with change seven? Well, when you unsubmit multiple changelists, beginning the resubmit process with the edit argument lets you subsequently walk through each changelist you unsubmitted, fixing anything wrong with them as needed. Let’s check and see what files we now have opened:

perforce@p4demo:~/newProject$ p4 opened
//stream/main/delorean.cpp#1 - add change 7 (text) *locked*
//stream/main/delorean.h#1 - add change 7 (text) *locked*
perforce@p4demo:~/newProject$ p4 resubmit –Re
Submitting change 7.
add //stream/main/delorean.cpp#1
add //stream/main/delorean.h#1
Change 7 renamed change 9 and submitted.

When our resubmit command fixed changelist six for us, it then essentially queued up changelist seven for us to fix next. The header and implementation file I wanted to add together were already good to go, so I simply ran the resubmit command again and it added them for me. My final history looks like it should with changes six and seven replaced by eight and nine respectively:

perforce@p4demo:~/newProject$ p4 changes -m 5
Change 9 on 2015/03/18 by sysop@sysop-dvcs-1426714639 'Added the DeLorean.'
Change 8 on 2015/03/18 by sysop@sysop-dvcs-1426714639 'Made almanac scores public.'
Change 5 on 2015/03/18 by sysop@sysop-dvcs-1426714639 'Added scores to almanac.'
Change 4 on 2015/03/18 by sysop@sysop-dvcs-1426714639 'Added Sports Almanac.'
Change 3 on 2015/03/18 by sysop@sysop-dvcs-1426714639 'Added Doc Brown.'

I have literally just scratched the surface of what you can do with unsubmit/resubmit. They also allow you to remove files you shouldn’t have submitted at all, strip extra stuff you should have removed before submitting, combine changes from multiple revisions, alter the order of changes, and more. I’d love to tell you more, but I need to find some garbage for my Mr. Fusion.

The latest version of the Perforce Helix Versioning Engine includes DVCS capabilities. Download now and give it a try.

All Perforce Version Management & Collaboration Products Are Free for 20 Users.