Tome's Land of IT

IT Notes from the Powertoe – Tome Tanasovski

Why Every IT Pro Should Use Mercurial for Source Control with PowerShell

When an IT Pro hears the words “source control” there is generally an impulsive reaction that includes glossy eyes followed by sincere comprehension that the words were not directed at him/her.  The truth is that IT Pros write scripts that are quick and deadly (for the most part).  Even things that are more complex are easy to handle with constant file copies when it’s time to work on a new section of code.  Source control feels like a whole world of overkill: Checking in, checking out, merging, branching, bugging, blah blah blahing.  A whole lot of nonsense for the solo IT Pro developing scripts in his isolated bubble.

I’m here to tell you that if you are an IT Pro and you have similar sentiments to the previous paragraph then I can almost guarantee that you have never tried Mercurial.

The first thing the IT Pro will find comfort in is that Mercurial is all file based.  It consists of a directory within the folder you would like to monitor called .hg and an optional file in the root directory call .hignore.  While the true power of Mercurial comes from it’s ability to do Dev-y things like merging the work of multiple users at once, we’re only going to look at how it can help the solo IT Pro maintain versions of his code.  Put simply, when you make changes to files, Mercurial keeps track of the changes that were made.  This allows you to quickly review older versions of your code or revert any changes you have been making back to the last time the code was committed.  The beautiful thing is that it’s really easy to do, and you can automate the process of committing changes so you never have to worry about it.

Setup and Maintain the Repository

Step 1 – Install Mercurial

You have two options:

  1. Install the command line tool from the download page.  There’s a 64-bit and a 32-bit version available, and the installer comes in two flavors: for those with administrator access to their box and those who do not hav administrator access to their box.
  2. Install TortoiseHG – A cool set of GUI interfaces along with shell integration to allow you to right click on files/folders so you can do Mercurial things with them.

The rest of this article will use the command line commands that are available in both versions.

Step 2 – Configure Mercurial

Because Mercurial is designed to track changes from multiple users to the same code base you need to provide an identifier for yourself – even if you have no intention of sharing your mercurial revisions with anyone else.  To do this you must create a file named mercurial.ini in the root of your profile directory, i.e., c:\users\username for Windows 7/Vista and c:\documents and settings\username for XP.  The contents of the file should look like this:

[ui]
username = FirstName LastName <username@emaildomain.com>

Step 3 – Create a repository

Open PowerShell and CD to the root of the directory structure that contains your scripts.  Once there you can initialize the repository with hg init:

cd c:\pathtoscripts\
hg init

The above will create a folder called .hg in the directory you are running it in.  This folder contains everything that Mercurial needs to maintain the history of changes to your files.

Step 4 – Modify PowerShell ISE files (optional)

If you are using the PowerShell 2.o ISE you are probably saving your scripts as UTF-16 encoded.  Unfortunately, Mercurial handles these like binary files.  It’s really in your best interest to convert these to UTF-8 scripts so that you can easily read any changes when you look at what was changed in each revision of your files.  The following snippit will do this for you.  I’ve modified it slightly from the original version found here:

dir -recurse -include *.ps1,*.psm1,*.psd1 | %{$foo = gc $_; $foo | out-file -en utf8 $_}

Step 5 – Create an ignorelist (optional)

If you have more than just PowerShell scripts, modules, or text files you may want to ignore certain files or directories.  You generally don’t need to maintain version history of binary files like executables or zip files.  In order to ignore these files you need to create a text file in the same place where you issued the hg init command.  The file is named .hgignore and may contain two sections: syntax: glob and syntax: regex.  The glob section is an easy place to ignore extensions, e.g., *.zip or *.exe.  The regex section is a good place to ignore specific directories.  Here’s a sample of what my .hgignore looks like:

syntax: glob
*.zip
*.msi
*.csv
*.exe
*.sys
*.exe
*.cab

syntax: regexp
^VM Optimization\/VMTools Install\/
Perl-Tidy-20090616

Step 6 – Add files and commit changes

Now that we know what files to ignore and we have initialized the directory to act as a repository we can issue the final two commands:

hg add
hg commit -m Initializing Repository

The first command (hg add) will search all of the files in your directory and add them to the repository (as long as they do not match your ignore list).  The second command (hg commit) commits the changes to the repository.  The optional -m switch gives you a way to tag the change so you can quickly remember what the change was when looking at your revision history.  If you do not specify this switch notepad will open up and you will need to write your comments in the file you are presented with.  You need to save and close notepad for the commit to complete.

Step 7 – Make changes, add files, remove files, commit changes

Really this process is identical to step 4.  The only difference is the addition of the hg remove command.  You work like you normally do and make changes to your code.  If you want to see what has changed since your last commit you can run:

hg status

This will tell you if there are new files that need to be added, files that used to exist that were removed, or changes that were made to files.  If anything is added you must run hg add again in order for the file to start getting managed in the repository.  If anything is removed you must specify hg remove filename.  If anything has changed it will automatically be picked up when you finally run hg commit.

Step 7 revisited – The IT Pro way

So you create the repository, but you may not want to waste all of the time constantly commiting changes.  That’s way too much like what a developer does on a day-to-day basis: overkill.  It’s nice to have the option available, but it’s a waste of time in your opinion.  The steps are so simple, however, that it is extremely easy to automate the task nightly so that every morning you have a snapshot of all the work that was done the day prior.  Because you are only keeping track of the changes, the amount of data required to keep this version history is relatively small.  The simplest way to automate this is to create a script that runs:

hg add
hg commit -m "Nightly Changes - $(Get-Date)"

This may be all you need.  Personally, I found great benefit in making sure that deleted files are removed permanently from the repository.  When you get into looking at old revisions you’ll see that if you didn’t remove files from Mercurial that you deleted from disk the files will come back.  This is directly tied to how Mercurial reverts to old versions, and how it brings you back to your current state; It replays changes.  If the removal of a file is not listed as a change it will not properly remove the file when you try to go back to your current state after looking at an older version of your code that had the file in it.  I handled this by scripting the logic around dealing with what is returned from hg status, i.e., if it shows a file is missing I issue the remove command on that file.

Below is the complete script that I run nightly.  Plop it in the directory you are using and run it or schedule it to run.  It will add/remove/commit, and it will identify any utf-16 encoded PowerShell files (I’ve modified it heavily from the original version to check that it’s UTF-16 prior to converting it):

# Get the directory the script is in
if ($MyInvocation.MyCommand.path) {
    cd (Split-Path $MyInvocation.MyCommand.Path)
}
# Find any UTF-16 encoded PowerShell files and re-encode them as UTF8
foreach ($file in (dir -Recurse -Include *.ps1, *.psd1, *.psm1)) {
    $stream = New-Object System.IO.Filestream -ArgumentList @($file.fullname, [System.IO.FileMode]::Open)
    # Look for UTF-16 ISE encoded files - with first two bytes = 254, 255
    if ($stream.ReadByte() -eq 254) {
        if ($stream.ReadByte() -eq 255) {
            $file.fullname#
            $stream.Position = 0
            $datareader = New-Object System.IO.Streamreader -ArgumentList $stream
            $buf = $datareader.ReadToEnd()
            $datareader.Close()
            $stream.Close()
            "Re-Encoding $($file.Fullname)"
            $buf |Out-File $file.FullName -Encoding UTF8
        }
    }
    $stream.close()
}

$note = ""
$modified = $true
# The next section handles the return from hg status
# It will add or remove files according to changes made to the file system
# It will also create an easy to read note for the hg commit that will mention what was added, removed, or changed
while ($modified) {
    $note = "Automated Commit:"
    $modified = $false
    foreach ($line in hg status) {
        switch -regex ($line) {
            '^\? ([\s\S]+)$' {
                hg add $matches[1]
                $modified=$true
            }
            '^\! ([\s\S]+)$' {
                hg remove $matches[1]
                $modified=$true
            }
            '^A ([\s\S]+)$' {
                $note += "Added $($matches[1])|"
            }
            '^M ([\s\S]+)$' {
                $note += "Modified $($matches[1])|"
            }
            '^R ([\s\S]+)$' {
                $note += "Removed $($matches[1])|"
            }
        }
    }
}
# Final step - commit the change
hg commit -m $note

Using the Repository to Revert and Review Changes

Reverting to your last commit

This is really very simple.  If you are working on a file and you decide you want to throw away what you were doing and go back to the last version of the file commited to the repository you issue hg revert file.  If you want to revert all of the files you were working on you would use: hg revert -a.

Reviewing revisions

The first thing you’ll want to do is review the log of changes to determine a revision number you want to take a look at.  To do this you run hg log.  The results will look something like this:

changeset:   1:25a8bbbfa59b
tag:         tip
user:        Tome Tanasovski
date:        Sun Dec 12 02:19:38 2010 -0500
summary:     Automated Commit:Removed blah\blah.txt|Modified test.ps1

changeset:   0:fdf8f0ac590a
user:        Tome Tanasovski
date:        Sat Dec 11 16:19:52 2010 -0500
summary:     Initial Save

The first number to the left of the colon in the changeset field signifies the revision number.  If you wanted to see the exact changes made by a specific revision you can use the -r parameter to specify a revision number, and the -p parameter to show the patch (changes made):

hg log -r 1 -p

If you want to switch your code to a revision you use the update command.  To move your code back to revision 1 you would do this:

hg update 1

You now have your code as revision 1.  You can copy out files and look at older versions of your code.  When you want to put everything back just use the update command and specify the tip (this is a shortcut to the latest revision):

hg update tip

Summary

I hope you at least get inspired to give Mercurial a quick try.  Having a version history of your code can be priceless while scripting.  It’s an easy way to save yourself from that stomach-drop feeling when you realize you didn’t save a copy of your code when you needed to – causing you to realize that you now how another 2 hours of work ahead of you to get you back to the state you were in when the code was working (prior to your enhancements).  Taking the 30 minutes now to set this up will be payed back when you need it most.

If you want to learn more about the Dev side of Mercurial you should visit the official website and check out the definitive guide.

2 responses to “Why Every IT Pro Should Use Mercurial for Source Control with PowerShell

  1. Thomas Biebl December 17, 2010 at 7:21 am

    Thank you very very much for this hint. I tried it and it is really a big benefit for me.

    Thanks

    Thomas

  2. GeekJimmy November 1, 2012 at 6:25 pm

    not sure if you care to update, but:

    When I installed Mercurial 2.3.2, it didn’t put the install folder in the env:path variable, so the ‘hg init’ command didn’t work.

    In Step 6, the syntax should be: hg commit -m “message with spaces must go inside quotes”
    without the quotation marks, hg throws a “The system cannot find the file specified”. Also, leaving the message off when using -m throws “option -m requires argument” error.

Leave a reply to Thomas Biebl Cancel reply