Recent Posts

C# Mongo Random ObjectId Generator

August 20th, 2013

I should probably preface this by saying that if you need a true random primary key, just use a Guid (which is automatically mapped to Mongo’s GuidGenerator class). But if you happen to have an existing collection and want new ObjectId values to be less sequential, you can create a generator to do so:

/// <summary>
/// Generates a semi-random ObjectId instead of a sequential 
/// one provided by the built-in generator.
/// </summary>
public class RandomIdGenerator : IIdGenerator
{
    private static readonly Random _random = new Random();

    public object GenerateId(object container, object document)
    {
        var timestamp = DateTime.UtcNow;
        var machine = _random.Next(0, 16777215);
        var pid = (short)_random.Next(0, 32768);
        var increment = _random.Next(0, 16777215);

        return new ObjectId(timestamp, machine, pid, increment);
    }

    public bool IsEmpty(object id)
    {
        return (id == null || (ObjectId)id == ObjectId.Empty);
    }
}

Note that it still preserves the correct time stamp but is using random values for the other pieces.


Windows TimeZoneInfo to Olson Mapping

August 14th, 2013

Overview

In the .Net world, you generally use the built-in TimeZoneInfo class when you need to convert UTC dates and times to a user’s local time (and vice versa). The class uses the Windows time zones database and has unique keys such as “Pacific Standard Time”.

Unfortunately, the Linux world uses Olson time zones which generally resemble a major city such as “America/Los_Angeles”, which you might be familiar with if setting your local time zone when setting up PHP or Django.

So the problem came about - how do you map a Windows TimeZoneId value to an Olson one?

Mapping Data

Thankfully, the Unicode Consortium offers an XML mapping of the various time zones at the following URL:

With that data available, it’s pretty simple to create a dictionary for mapping one ID value to another.

Obsolete Windows Time Zones

The following Windows time zones are obsolete and you should filter them out from being used:

  • Kamchatka Standard Time
  • Mid-Atlantic Standard Time

For more information on why they are obsolete, visit the following URL:

GitHub

I created a simple class for loading the XML mapping data into a dictionary and performing the lookup.


NGit Tutorial

April 30th, 2013

I recently decided to use the NGit library to interact with a Git repository as part of a ServiceStack project that I’m working on.

Why NGit?

That’s a great question considering there’s the awesome libgit2 library available. Unfortunately, the library doesn’t support doing pull/fetch/merge according to this open issue.

NGit is a semi-automated port of the JGit library from Java over to .Net and it’s maintained by the Mono team. And although it’s kind of frustrating to use, it supports all of Git’s feature set and generally works just fine once you figure it all out.

Since I had a hard time finding examples and documentation for NGit, I’m posting some code snippets and explanations for common features I needed to use in the hopes that it helps some other developer out there in the future.

NGit Documentation Unit Tests

NGit follows the common “unit tests as documentation” pattern, so I encourage any developer to clone a copy of the NGit repository and look through their unit tests as a way to explore their API when you need to see the usage pattern of a particular command/feature.

Git Commands

NGit uses a command-based API that is built off of a Git class which is returned when you initialize, open, or clone a repository. Commands generally return themselves, so you end up chaining commands similar to working with jQuery.

Cloning a Repository

To clone a repository, you need to create a CloneCommand and set at least the local directory target for the clone on your local disk and the URI for the origin that you’re cloning from:

// Let's clone the NGit repository
var clone = Git.CloneRepository()
    .SetDirectory(@"C:\Git\NGit")
    .SetURI("https://github.com/mono/ngit.git");

// Execute and return the repository object we'll use for further commands
var repository = clone.Call();

Specifying Credentials

For simple HTTP/HTTPS credentials, you will generally create a UsernamePasswordCredentialsProvider object and either set it on the command you’re calling or set it as the default up front before executing any command:

var credentials = new UsernamePasswordCredentialsProvider("username", "password");

// On a per-command basis
var fetch = repository.Fetch()
    .SetCredentialsProvider(credentials)
    .Call();

// Or globally as the default for each new command
CredentialsProvider.SetDefault(credentials);

If you need to use SSH with private key authentication, things get a little more complicated. I will cover the solution to that at the bottom of my post.

Opening an Existing Repository

Opening an existing repository is simple:

var repository = Git.Open(@"C:\Git\NGit");

Fetch, Pull, Status, Clean, Add, Remove

Most commands are fairly simple:

// Fetch changes without merging them
var fetch = repository.Fetch().Call();

// Pull changes (will automatically merge/commit them)
var pull = repository.Pull().Call();

// Get the current branch status
var status = repository.Status().Call();

// The IsClean() method is helpful to check if any changes
// have been detected in the working copy. I recommend using it,
// as NGit will happily make a commit with no actual file changes.
bool isClean = status.IsClean();

// You can also access other collections related to the status
var added = status.GetAdded();
var changed = status.GetChanged();
var removed = status.GetRemoved();

// Clean our working copy
var clean = repository.Clean().Call();

// Add all files to the stage (you could also be more specific)
var add = repository.Add()
    .AddFilePattern(".")
    .Call();

// Remove files from the stage
var remove = repository.Rm()
    .AddFilePattern(".gitignore")
    .Call();

Reset

If we fetched changes from origin/master and want to reset our current branch to match:

var reset = repository.Reset()
    .SetMode(ResetCommand.ResetType.HARD)
    .SetRef("origin/master")
    .Call();

Commit, Push

To commit and push a change, you would do the following:

var author = new PersonIdent("Lance Mcnearney", "lance@mcnearney.net");
var message = "My commit message";

// Commit our changes after adding files to the stage
var commit = repository.Commit()
    .SetMessage(message)
    .SetAuthor(author)
    .SetAll(true) // This automatically stages modified and deleted files
    .Call();

// Our new commit's hash
var hash = commit.Id;

// Push our changes back to the origin
var push = repository.Push().Call();

Private Key Authentication Using SSH

Credit for figuring out how to wire up SSH authentication using a private key goes to Doug and his Stack Overflow question:

My implementation of it does not require the public key but as a trade-off you must make sure to specify the username in the ssh:// URI. I also wipe out the GIT_SSH environment variable as Jsch will use that instead of using the configured JschConfigSessionFactory when initializing its SSH connection. In my case, it will calling TortoisePlink.exe.

You must create a custom JschConfigSessionFactory class:

/// <summary>
/// Handles setting up the public key authentication when using a remote SSH repository
/// </summary>
public class PrivateKeyConfigSessionFactory : JschConfigSessionFactory
{
    private string PrivateKeyPath { get; set; }

    public PrivateKeyConfigSessionFactory(string privateKeyPath)
    {
        PrivateKeyPath = privateKeyPath;

        // Clear the GIT_SSH environment variable as NGit will use it 
        // for SSH transport instead of the session factory
        Environment.SetEnvironmentVariable("GIT_SSH", string.Empty, EnvironmentVariableTarget.Process);
    }

    protected override void Configure(OpenSshConfig.Host hc, Session session)
    {
        var config = new Properties();
        config["StrictHostKeyChecking"] = "no";
        config["PreferredAuthentications"] = "publickey";
        session.SetConfig(config);

        var jsch = GetJSch(hc, FS.DETECTED);
        jsch.AddIdentity("KeyPair", File.ReadAllBytes(PrivateKeyPath), null, null);
    }
}

Once you have your custom class, you can then configure NGit/Jsch to use it:

// Use our custom SSH session when accessing remote SSH:// repositories
// The username must be in the repository Uri: ssh://git@host/var/git/repo.git
var privateKeyPath = @"C:\Git\private.key";
var factory = new PrivateKeyConfigSessionFactory(privateKeyPath);
SshSessionFactory.SetInstance(factory);

Cleaning up after NGit

Since NGit is a port from Java, it doesn’t implement IDisposable when accessing files. To remove its lock on files, you can dispose of the Git object by doing the following:

// Handle disposing of NGit's locks
repository.GetRepository().Close();
repository.GetRepository().ObjectDatabase.Close();
repository = null;

You may also want to recursively remove any read-only file attributes set by NGit in the repository’s path if you need to remove the repository later or you will receive permission exceptions when attempting to do so.

var files = Directory.GetFiles(@"C:\Git\NGit", "*", SearchOption.AllDirectories);

// Remove the read-only attribute applied by NGit to some of its files
foreach (var file in files)
{
    file.Attributes = FileAttributes.Normal;
}