Home

Learn Source Control CLI

Just about everybody who develops software professionally has come to the realization that using a source control tool is a good thing. Not using a good source control tool is like walking a tightrope without a net above a pool of hungry sharks. What seems to be lacking in a lot of cases, even in people charged with being the administrators of the source repository. is a knowledge of the command line interface of the source control tool.

My professional background involves solely windows development so most of my experience is with Visual Source Safe. Currently the job I am at utilizes Merant PVCS. Both of these tools provide a good GUI for doing most of the day to day things you would need to do with a source control management system. You can check in and check out files, compare differences by version, branch, merge, etc. all through the user interface. If the GUI allows all this easily, what value is there, really, in learning the command line interface, which isn't mentioned prominently in the documentation anyway?

Gee, I Really Wish I Could...

Becoming comfortable with the command line interface of your source control tool, like knowing a varitey of programming languages, allows you to get more things done simply because you have more tools in your toolbox. While you could, in the end, do what you need to do through the GUI, it may become a very tedious and time consuming process to do so.

The power of the command line interface of a source control tool becomes even more pronounced when combined with a scripting language. As readers of this site have probably figured out I am a big fan of python. In addition to talking about some of the things you could do with the CLI I will show some examples with python that allow for easily performing some tasks that would be very painful to do through the CLI. I imagine you could use perl or any other language that allows you to easily read stdin and stdout to do similar tasks.

The following examples will utilize python and the PVCS command line interface. Since this involves PVCS we will use their nomenclature when talking about files. A revision refers to a specific instance of a checked in file. Revisions are numbered 1.0, 1.1, etc. A version usually refers to a Version Label. These have only been tested on Windows. I can't see why they would not work on another operating system, but you have been forewarned. The python script can be found here

Labeling

Generally speaking labeling a project or group of files is a pretty simple task. You select the project or file you want and right click and assign a version label to the revision of the file you want. Or when you are checking a file in you can assign a version label during the checkin process. However, what if we need to go back and label files for a version that has already been released? We know that the release was done on November 19th 2006 and that all files before that date were in the release, but there are 17 projects to do and they are in a varity of different places in the source tree. You could go through the UI and do this, but you would need to touch each individual file and then find the proper revision and right click it and then assign the version label. To do this for hundreds of files can take a long time, is error prone and is about as much fun as watching paint dry.

To this sort of task I created a function in the script that takes a project, a label and the month, day and year you wish to be the last included in the label. Here is the script

def labelByDate(labelName, projectDir, endMonth, endDay, endYear, recursive=True):
    """
    Gets the files in the given project (projectDir) and recursively sets the label
    (labelName) to the revision that comes closes to the end date without going over.
    NOTE: recursive is currently not used
    """
    archiveLines = runPcliCmd("pcli vlog -de" + str(endMonth) + "/" + str(endDay) +"/" + str(endYear) + \
                              " -pr" + pvcsdb + " -z \"" + projectDir + "\"")
    revBreak = "-----------------------------------"
    currRev = ""
    newArc = False
    prevLine = ""
    pvcsFileName = ""
    
    for arcLine in archiveLines:
        lineStart, arcName = arcLine[:8], arcLine[18:-5]
        if lineStart == "Archive:":
            print "*" + arcName + "*"
            pvcsFileName = arcName.replace(pvcsLanDir, "").replace("\\","/")
            #print "(" + arcLine + ")", "-" + prevLine + "-"
            currRev = ""
            newArc = True

        if newArc and prevLine.strip() == revBreak:
            currRev = arcLine.split(" ")[-1].strip()
            spawnPcliCmd("Label", "-pr" + pvcsdb, "-r" + currRev, "\"-v" + labelName + "\"", "\"" + pvcsFileName + "\"")
            newArc = False

        prevLine = arcLine            

Verifying A Label

Another task that I have had to do that does not have a good interface in the source control tools I have used is to verify that the latest files checked in are properly labelled. In PVCS you are allowed to apply version labels to your projects. Let's say that you have a set of libraries that are being upgraded to use a new hashing algorithm to store user passwords. You can label any projects having modifications due to this requirement labelled "Hash". One of the functions in pyPvcs.py will let you check a project directory and its sub directories to make sure that the latest version of the file has the correct label.

def compareDefaultAndLabelVersions(labelName, projectdir):
    """
    recursively checks the given project (projectdir) to make sure that
    the label of the latest revision matches labelName. Each project
    gets an HTML file generated that has all the files in it and a
    status of either 'good' (the label is applied to the latest revision)
    or 'BAD' (label is NOT applied to the latest revision). HTML files
    that contain one or more BAD files will automatically be shown to the user
    in their default browser.
    """
    dbgprint( "comparing version and default labels for project")

    projectlist = runPcliCmd("pcli List -pr" + pvcsdb + " -l -z -tProject \"" + projectdir + "\"")

    dbgprint( "projects found")
    projectsWritten = open("c:\\pvcsLabelCheckProjects" + re.sub("\W","_",projectdir) + ".txt","w")
    projectsWritten.writelines(projectlist)
    projectsWritten.close()

    DefaultVersionFiles = dict()
    NamedVersionFiles = dict()
    filesToShow = dict()
    for currProject in projectlist:
        if currProject[0] == "/":

            DefaultVersionlist = runPcliCmd("pcli ListRevision -pr" + pvcsdb + " \"" + currProject + "\"")

            for versionFile in DefaultVersionlist:
                #print "vf",versionFile.strip()
                if versionFile[0] == "/":
                    currMatch = re.match("(.*?)\s+DefaultVersion", versionFile)
                    if currMatch != None:
                        DefaultVersionFiles[currMatch.group(1)] = versionFile.strip()[versionFile.find(":=") + 2:]
                        dbgprint (versionFile.strip() + " is *" + DefaultVersionFiles[currMatch.group(1)] + "*")

            print "\n\n****************************\n\n"
            
            NamedVersionlist = runPcliCmd("pcli ListRevision \"-v" + labelName + "\" -pr" + pvcsdb + " \"" + currProject + "\"")

            for versionFile in NamedVersionlist:
                #print "vf",versionFile.strip()
                if versionFile[0] == "/":
                    currMatch = re.match("(.*?)\s+" + labelName, versionFile)
                    if currMatch != None:
                        NamedVersionFiles[currMatch.group(1)] = versionFile.strip()[versionFile.find(":=") + 2:]
                        dbgprint( versionFile.strip() + " is *" + NamedVersionFiles[currMatch.group(1)] + "*")

            newFileName = "c:\\" + re.sub("\W", "_", projectdir) + " Label " + \
                              re.sub("\W", "_", labelName) + ".html"
            outputFile = open(newFileName,"w")
            outputFile.write("<html><body><table>\r\n")
            outputFile.write("<tr><td>File</td><td>Default Version</td><td>" + \
                             labelName + "</td><td>Status</td></tr>")
            
            for vFile in NamedVersionFiles.keys():
                if vFile in DefaultVersionFiles:
                    if DefaultVersionFiles[vFile] == NamedVersionFiles[vFile]:
                        dbgprint( vFile + " is good")
                        outputFile.write("<tr><td>" + vFile + "</td><td>" + \
                                         DefaultVersionFiles[vFile] + "</td><td>" + \
                                         NamedVersionFiles[vFile] + "</td><td style=\"color:#22cc22\">good</td></tr>\n")
                    else:
                        dbgprint( vFile + " is BAD")
                        outputFile.write("<tr><td>" + vFile + "</td><td>" + \
                                         DefaultVersionFiles[vFile] + "</td><td>" + \
                                         NamedVersionFiles[vFile] + "</td><td style=\"text-style:bold;color:#dd2200\">BAD</td></tr>\n")
                        filesToShow[newFileName] = newFileName

            outputFile.write("</table></body></html>")
            outputFile.close()

    # for files with bad output, show them now
    for showFile in filesToShow:
        os.startfile(showFile)

What this function will do is to go through a PVCS project and if the recursively check to see if the most recent checked in file has the specified label. Any file that is not correctly labelled is marked as "BAD" and then a list of these files is kept. Every file that has at least one bad entry is spawned in a browser instance so the user can easily see projects with files that need to be properly labelled. In this way you can make sure that any automated build tool is bringing down the correct versions of files when it builds. If a file comes up as bad you can apply the proper label yourself if necessary.

Conclusion

Hopefully the above two examples give you an idea of what can be done when you have a good grasp of the command line interface of your source control tool. The enclosed scripts have evolved since work was begun on this article and they perform quite a few more tasks. As these are specific to our build environment and applications the generic functions included here will not be updated. I encourage you to become familiar with all the interfaces of the tools you use. While a good GUI can be very powerful you are definitely missing out by not having an understanding of how to use the command line interface of your source control tool.