Oops I did it again: git disaster recovery

Houston, we have a problem

So, you’ve accidentally pushed your commit before realising it breaks everything? Or maybe the merge you’ve been working on for the last 2 hours has turned out to have gone south in the most inexplicable and unimaginable way? Okay, no worries, we’re all use git nowadays, right?
When I end up in situation like this (and I find myself there quite more often than I’d like to admit), this is the decision tree I follow to get back up and running:

I am assuming you’ve got your working copy checked out and you have backed it up prior to running any commands pasted from the internet.

To rewrite or not to rewrite

Generally speaking you should avoid rewriting history if you could. Changing commit chain would affect other developers that might have been relying on some of the commits in their feature branches. This would mean more clean up for everyone in the team and more potential stuff-ups. Sometimes, however, there’s just no choice but to rewrite. For example when someone checks in their private keys or large binary files that just do not belong in the repo.

Revert last commit

This does not roll back. It in fact creates a new commit, that ends up negating the previous one.

git revert HEAD
git push

Reset master to previous commit

This is by far the most common scenario. Note the ^ symbol at the end. It tells git to get parent of that commit instead. Very handy.

git reset HEAD^ --hard
git push origin -f

Rebase interactively

It is likely that vi is the default text editor the shipped with your git installation. So before you go down this rabbit hole – make sure you understand the basics of text editing with vi.

git log --pretty=oneline
git rebase -i ^

you will be presented with a file with commit chain one per line along with action codes git should run over each commit. Don’t worry, quick command reference is just down the bottom. Once you have saved and exited – git will run commands sequentially producing the desired state in the end.

Go cherry picking

Enjoy your cherry picking responsibly. Cherry picking is generally frowned upon in the community as it essentially creates a duplicate commit with another SHA. But our justification here would be we’re ultimately going to drop the old branch so there’d be no double-ups in the end. My strategy here is to branch off a last known good state, cherry pick all good commits over to new branch. Finally swap branches around and kill the old one. This is not the end of story however as all other developers would need to check out the new branch and potentially rebase their unmerged changes. Once again – use with care.

git log --pretty=oneline
git branch fixing_master ^
git push

Restore deleted file

This is pretty straightforward. First we find the last commit to have touched the file in question (deleting it). And then we check out a copy from parent commit (which would be the last version available at the time):

$file='/path/to/file'
git checkout $(git rev-list -n 1 HEAD -- "$file")^ -- "$file"

Another take on Mocking HttpContext.Current away

Static classes and methods are a pain to unit test

Back in days of WebForms, HttpContext was the one object to rule them all. It’s got session state, request and response, cache, errors, server variables and so much more for developers to inspect and play with. HttpContext.Current was by far the easiest way to tap into all of this. But guess what? Static member invocation does not make mocking it out easy.

MVC controllers are much more unit-test friendly

Although technically HttpContext hasn’t gone anywhere with the coming of MVC, it’s been neatly wrapped into a HttpContextWrapper and exposed as controller instance .Context property. Just mock it out and everything will be fine.

End of story? Well may be

If you wanted to completely abstract from all HTTP specifics and happen to not need and utility methods that come with it – you’re sweet.
If, however, for some reason you feel like relying on some utility methods to reduce amount of non-productive mocking, try this trick:

public class MockHttpContext : IDisposable
{
public HttpContext Current { get; set; }
private AppDomain CurrentDomain { get; }

public MockHttpContext(string url, string query = null, IPrincipal principal = null)
{
CurrentDomain = Thread.GetDomain();
var path = CurrentDomain.BaseDirectory;
var virtualDir = "/";

CurrentDomain.SetData(".appDomain", "*");
CurrentDomain.SetData(".appPath", path);
CurrentDomain.SetData(".appId", "testId");
CurrentDomain.SetData(".appVPath", virtualDir);
CurrentDomain.SetData(".hostingVirtualPath", virtualDir);
CurrentDomain.SetData(".hostingInstallDir", HttpRuntime.AspInstallDirectory);
CurrentDomain.SetData(".domainId", CurrentDomain.Id);

// User is logged out by default
principal = principal ?? new GenericPrincipal(
new GenericIdentity(string.Empty),
new string[0]
);

Current = new HttpContext(
new HttpRequest("", url, query),
new HttpResponse(new StringWriter())
)
{
User = principal
};
HttpContext.Current = Current;
}

public void Dispose()
{
//clean up
HttpContext.Current = null;
}
}

First it looks very similar to this implementation taken from SO (well, this is where it’s been taken off to begin with). But then what’s up with all these CurrentDomain.SetData calls? This allows us to mock paths and transition between relative and absolute urls as if we were hosted somewhere.
Consider the code:

public static string ToAbsoluteUrl(this string relativeUrl)
{
if (string.IsNullOrEmpty(relativeUrl)) return relativeUrl;
if (HttpContext.Current == null) return relativeUrl;

if (relativeUrl.StartsWith("/")) relativeUrl = relativeUrl.Insert(0, "~");
if (!relativeUrl.StartsWith("~/")) relativeUrl = relativeUrl.Insert(0, "~/");

var url = HttpContext.Current.Request.Url;
var port = !url.IsDefaultPort ? ":" + url.Port : string.Empty;

return $"{url.Scheme}://{url.Host}{port}{VirtualPathUtility.ToAbsolute(relativeUrl)}";// and this is where the magic happens. Now static invocation of VirtualPathUtility does not fail with NullReferenceException anymore!
}

The code outside makes afew assumptions regarding the environment being mocked, but it should be a trivial task to introduce more parameters/settings and mock everything away.