diff --git a/.gitignore b/.gitignore
deleted file mode 100644
--- a/.gitignore
+++ /dev/null
@@ -1,4 +0,0 @@
-tmp
-*.hi
-*.o
-github-backup
diff --git a/Build/Configure.hs b/Build/Configure.hs
new file mode 100644
--- /dev/null
+++ b/Build/Configure.hs
@@ -0,0 +1,30 @@
+{- Checks system configuration and generates SysConfig.hs. -}
+
+module Build.Configure where
+
+import System.Environment
+import Control.Applicative
+import Control.Monad.IfElse
+
+import Build.TestConfig
+import Build.Version
+import Git.Version
+
+tests :: [TestCase]
+tests =
+	[ TestCase "version" getVersion
+	, TestCase "git" $ requireCmd "git" "git --version >/dev/null"
+	, TestCase "git version" getGitVersion
+	]
+
+getGitVersion :: Test
+getGitVersion = Config "gitversion" . StringConfig . show
+	<$> Git.Version.installed
+
+run :: [TestCase] -> IO ()
+run ts = do
+	args <- getArgs
+	config <- runTests ts
+	writeSysConfig config
+	whenM (isReleaseBuild) $
+		cabalSetup "github-backup.cabal"
diff --git a/Build/TestConfig.hs b/Build/TestConfig.hs
new file mode 100644
--- /dev/null
+++ b/Build/TestConfig.hs
@@ -0,0 +1,143 @@
+{- Tests the system and generates Build.SysConfig.hs. -}
+
+module Build.TestConfig where
+
+import Utility.Path
+import Utility.Monad
+import Utility.SafeCommand
+
+import System.IO
+import System.Cmd
+import System.Exit
+import System.FilePath
+import System.Directory
+
+type ConfigKey = String
+data ConfigValue =
+	BoolConfig Bool |
+	StringConfig String |
+	MaybeStringConfig (Maybe String) |
+	MaybeBoolConfig (Maybe Bool)
+data Config = Config ConfigKey ConfigValue
+
+type Test = IO Config
+type TestName = String
+data TestCase = TestCase TestName Test
+
+instance Show ConfigValue where
+	show (BoolConfig b) = show b
+	show (StringConfig s) = show s
+	show (MaybeStringConfig s) = show s
+	show (MaybeBoolConfig s) = show s
+
+instance Show Config where
+	show (Config key value) = unlines
+		[ key ++ " :: " ++ valuetype value
+		, key ++ " = " ++ show value
+		]
+	  where
+		valuetype (BoolConfig _) = "Bool"
+		valuetype (StringConfig _) = "String"
+		valuetype (MaybeStringConfig _) = "Maybe String"
+		valuetype (MaybeBoolConfig _) = "Maybe Bool"
+
+writeSysConfig :: [Config] -> IO ()
+writeSysConfig config = writeFile "Build/SysConfig.hs" body
+  where
+	body = unlines $ header ++ map show config ++ footer
+	header = [
+		  "{- Automatically generated. -}"
+		, "module Build.SysConfig where"
+		, ""
+		]
+	footer = []
+
+runTests :: [TestCase] -> IO [Config]
+runTests [] = return []
+runTests (TestCase tname t : ts) = do
+	testStart tname
+	c <- t
+	testEnd c
+	rest <- runTests ts
+	return $ c:rest
+
+{- Tests that a command is available, aborting if not. -}
+requireCmd :: ConfigKey -> String -> Test
+requireCmd k cmdline = do
+	ret <- testCmd k cmdline
+	handle ret
+  where
+	handle r@(Config _ (BoolConfig True)) = return r
+	handle r = do
+		testEnd r
+		error $ "** the " ++ c ++ " command is required"
+	c = head $ words cmdline
+
+{- Checks if a command is available by running a command line. -}
+testCmd :: ConfigKey -> String -> Test
+testCmd k cmdline = do
+	ok <- boolSystem "sh" [ Param "-c", Param $ quiet cmdline ]
+	return $ Config k (BoolConfig ok)
+
+{- Ensures that one of a set of commands is available by running each in
+ - turn. The Config is set to the first one found. -}
+selectCmd :: ConfigKey -> [(String, String)] -> Test
+selectCmd k = searchCmd
+		(return . Config k . StringConfig)
+		(\cmds -> do
+			testEnd $ Config k $ BoolConfig False
+			error $ "* need one of these commands, but none are available: " ++ show cmds
+		)
+
+maybeSelectCmd :: ConfigKey -> [(String, String)] -> Test
+maybeSelectCmd k = searchCmd
+		(return . Config k . MaybeStringConfig . Just)
+		(\_ -> return $ Config k $ MaybeStringConfig Nothing)
+
+searchCmd :: (String -> Test) -> ([String] -> Test) -> [(String, String)] -> Test
+searchCmd success failure cmdsparams = search cmdsparams
+  where
+	search [] = failure $ fst $ unzip cmdsparams
+	search ((c, params):cs) = do
+		ok <- boolSystem "sh" [ Param "-c", Param $ quiet $ c ++ " " ++ params ]
+		if ok
+			then success c
+			else search cs
+
+{- Finds a command, either in PATH or perhaps in a sbin directory not in
+ - PATH. If it's in PATH the config is set to just the command name,
+ - but if it's found outside PATH, the config is set to the full path to
+ - the command. -}
+findCmdPath :: ConfigKey -> String -> Test
+findCmdPath k command = do
+	ifM (inPath command)
+		( return $ Config k $ MaybeStringConfig $ Just command
+		, do
+			r <- getM find ["/usr/sbin", "/sbin", "/usr/local/sbin"]
+			return $ Config k $ MaybeStringConfig r
+		)
+  where
+	find d =
+		let f = d </> command
+		in ifM (doesFileExist f) ( return (Just f), return Nothing )
+
+quiet :: String -> String
+quiet s = s ++ " >/dev/null 2>&1"
+
+testStart :: TestName -> IO ()
+testStart s = do
+	putStr $ "  checking " ++ s ++ "..."
+	hFlush stdout
+
+testEnd :: Config -> IO ()
+testEnd (Config _ (BoolConfig True)) = status "yes"
+testEnd (Config _ (BoolConfig False)) = status "no"
+testEnd (Config _ (StringConfig s)) = status s
+testEnd (Config _ (MaybeStringConfig (Just s))) = status s
+testEnd (Config _ (MaybeStringConfig Nothing)) = status "not available"
+testEnd (Config _ (MaybeBoolConfig (Just True))) = status "yes"
+testEnd (Config _ (MaybeBoolConfig (Just False))) = status "no"
+testEnd (Config _ (MaybeBoolConfig Nothing)) = status "unknown"
+
+status :: String -> IO ()
+status s = putStrLn $ ' ':s
diff --git a/Build/Version.hs b/Build/Version.hs
new file mode 100644
--- /dev/null
+++ b/Build/Version.hs
@@ -0,0 +1,69 @@
+{- Package version determination, for configure script. -}
+
+module Build.Version where
+
+import Data.Maybe
+import Control.Applicative
+import Data.List
+import System.Environment
+import System.Directory
+import Data.Char
+import System.Process
+
+import Build.TestConfig
+import Utility.Monad
+import Utility.Exception
+
+{- Set when making an official release. (Distribution vendors should set
+ - this too.) -}
+isReleaseBuild :: IO Bool
+isReleaseBuild = isJust <$> catchMaybeIO (getEnv "RELEASE_BUILD")
+
+{- Version is usually based on the major version from the changelog, 
+ - plus the date of the last commit, plus the git rev of that commit.
+ - This works for autobuilds, ad-hoc builds, etc.
+ -
+ - If git or a git repo is not available, or something goes wrong,
+ - or this is a release build, just use the version from the changelog. -}
+getVersion :: Test
+getVersion = do
+	changelogversion <- getChangelogVersion
+	version <- ifM (isReleaseBuild)
+		( return changelogversion
+		, catchDefaultIO changelogversion $ do
+			let major = takeWhile (/= '.') changelogversion
+			autoversion <- readProcess "sh"
+				[ "-c"
+				, "git log -n 1 --format=format:'%ci %h'| sed -e 's/-//g' -e 's/ .* /-g/'"
+				] ""
+			if null autoversion
+				then return changelogversion
+				else return $ concat [ major, ".", autoversion ]
+		)
+	return $ Config "packageversion" (StringConfig version)
+	
+getChangelogVersion :: IO String
+getChangelogVersion = do
+	changelog <- readFile "debian/changelog"
+	let verline = takeWhile (/= '\n') changelog
+	return $ middle (words verline !! 1)
+  where
+	middle = drop 1 . init
+
+{- Set up cabal file with version. -}
+cabalSetup :: FilePath -> IO ()
+cabalSetup cabalfile = do
+	version <- takeWhile (\c -> isDigit c || c == '.')
+		<$> getChangelogVersion
+	cabal <- readFile cabalfile
+	writeFile tmpcabalfile $ unlines $ 
+		map (setfield "Version" version) $
+		lines cabal
+	renameFile tmpcabalfile cabalfile
+  where
+	tmpcabalfile = cabalfile++".tmp"
+	setfield field value s
+		| fullfield `isPrefixOf` s = fullfield ++ value
+		| otherwise = s
+	  where
+		fullfield = field ++ ": "
diff --git a/Common.hs b/Common.hs
--- a/Common.hs
+++ b/Common.hs
@@ -1,8 +1,8 @@
-{-# LANGUAGE PackageImports #-}
+{-# LANGUAGE PackageImports, CPP #-}
 
 module Common (module X) where
 
-import Control.Monad as X hiding (join)
+import Control.Monad as X
 import Control.Monad.IfElse as X
 import Control.Applicative as X
 import "mtl" Control.Monad.State.Strict as X (liftIO)
@@ -10,16 +10,15 @@
 
 import Data.Maybe as X
 import Data.List as X hiding (head, tail, init, last)
-import Data.String.Utils as X
+import Data.String.Utils as X hiding (join)
 
-import System.Path as X
 import System.FilePath as X
 import System.Directory as X
-import System.Cmd.Utils as X hiding (safeSystem)
 import System.IO as X hiding (FilePath)
-import System.Posix.Files as X
+import System.PosixCompat.Files as X
+#ifndef mingw32_HOST_OS
 import System.Posix.IO as X
-import System.Posix.Process as X hiding (executeFile)
+#endif
 import System.Exit as X
 
 import Utility.Misc as X
@@ -29,6 +28,8 @@
 import Utility.Path as X
 import Utility.Directory as X
 import Utility.Monad as X
+import Utility.Data as X
+import Utility.Applicative as X
 import Utility.FileSystemEncoding as X
 
 import Utility.PartialPrelude as X
diff --git a/Git.hs b/Git.hs
--- a/Git.hs
+++ b/Git.hs
@@ -8,6 +8,8 @@
  - Licensed under the GNU GPL version 3 or higher.
  -}
 
+{-# LANGUAGE CPP #-}
+
 module Git (
 	Repo(..),
 	Ref(..),
@@ -30,11 +32,15 @@
 ) where
 
 import Network.URI (uriPath, uriScheme, unEscapeString)
+#ifndef mingw32_HOST_OS
 import System.Posix.Files
+#endif
 
 import Common
 import Git.Types
+#ifndef mingw32_HOST_OS
 import Utility.FileMode
+#endif
 
 {- User-visible description of a git repo. -}
 repoDescribe :: Repo -> String
@@ -127,4 +133,8 @@
 	ifM (catchBoolIO $ isexecutable hook)
 		( return $ Just hook , return Nothing )
   where
+#if mingw32_HOST_OS
+	isexecutable f = doesFileExist f
+#else
 	isexecutable f = isExecutable . fileMode <$> getFileStatus f
+#endif
diff --git a/Git/Branch.hs b/Git/Branch.hs
--- a/Git/Branch.hs
+++ b/Git/Branch.hs
@@ -13,6 +13,7 @@
 import Git
 import Git.Sha
 import Git.Command
+import Git.Ref (headRef)
 
 {- The currently checked out branch.
  -
@@ -35,7 +36,7 @@
 {- The current branch, which may not really exist yet. -}
 currentUnsafe :: Repo -> IO (Maybe Git.Ref)
 currentUnsafe r = parse . firstLine
-	<$> pipeReadStrict [Param "symbolic-ref", Param "HEAD"] r
+	<$> pipeReadStrict [Param "symbolic-ref", Param $ show headRef] r
   where
 	parse l
 		| null l = Nothing
@@ -73,8 +74,7 @@
   where
 	no_ff = return False
 	do_ff to = do
-		run "update-ref"
-			[Param $ show branch, Param $ show to] repo
+		run [Param "update-ref", Param $ show branch, Param $ show to] repo
 		return True
 	findbest c [] = return $ Just c
 	findbest c (r:rs)
@@ -97,7 +97,11 @@
 	sha <- getSha "commit-tree" $ pipeWriteRead
 		(map Param $ ["commit-tree", show tree] ++ ps)
 		message repo
-	run "update-ref" [Param $ show branch, Param $ show sha] repo
+	run [Param "update-ref", Param $ show branch, Param $ show sha] repo
 	return sha
   where
 	ps = concatMap (\r -> ["-p", show r]) parentrefs
+
+{- A leading + makes git-push force pushing a branch. -}
+forcePush :: String -> String
+forcePush b = "+" ++ b
diff --git a/Git/BuildVersion.hs b/Git/BuildVersion.hs
new file mode 100644
--- /dev/null
+++ b/Git/BuildVersion.hs
@@ -0,0 +1,21 @@
+{- git build version
+ -
+ - Copyright 2011 Joey Hess <joey@kitenet.net>
+ -
+ - Licensed under the GNU GPL version 3 or higher.
+ -}
+
+module Git.BuildVersion where
+
+import Git.Version
+import qualified Build.SysConfig
+
+{- Using the version it was configured for avoids running git to check its
+ - version, at the cost that upgrading git won't be noticed.
+ - This is only acceptable because it's rare that git's version influences
+ - code's behavior. -}
+buildVersion :: GitVersion
+buildVersion = normalize Build.SysConfig.gitversion
+
+older :: String -> Bool
+older n = buildVersion < normalize n 
diff --git a/Git/Command.hs b/Git/Command.hs
--- a/Git/Command.hs
+++ b/Git/Command.hs
@@ -25,19 +25,25 @@
 gitCommandLine _ repo = assertLocal repo $ error "internal"
 
 {- Runs git in the specified repo. -}
-runBool :: String -> [CommandParam] -> Repo -> IO Bool
-runBool subcommand params repo = assertLocal repo $
+runBool :: [CommandParam] -> Repo -> IO Bool
+runBool params repo = assertLocal repo $
 	boolSystemEnv "git"
-		(gitCommandLine (Param subcommand : params) repo)
+		(gitCommandLine params repo)
 		(gitEnv repo)
 
 {- Runs git in the specified repo, throwing an error if it fails. -}
-run :: String -> [CommandParam] -> Repo -> IO ()
-run subcommand params repo = assertLocal repo $
-	unlessM (runBool subcommand params repo) $
-		error $ "git " ++ subcommand ++ " " ++ show params ++ " failed"
+run :: [CommandParam] -> Repo -> IO ()
+run params repo = assertLocal repo $
+	unlessM (runBool params repo) $
+		error $ "git " ++ show params ++ " failed"
 
-{- Runs a git subcommand and returns its output, lazily.
+{- Runs git and forces it to be quiet, throwing an error if it fails. -}
+runQuiet :: [CommandParam] -> Repo -> IO ()
+runQuiet params repo = withQuietOutput createProcessSuccess $
+	(proc "git" $ toCommand $ gitCommandLine (params) repo)
+		{ env = gitEnv repo }
+
+{- Runs a git command and returns its output, lazily.
  -
  - Also returns an action that should be used when the output is all
  - read (or no more is needed), that will wait on the command, and
@@ -52,7 +58,7 @@
   where
 	p  = gitCreateProcess params repo
 
-{- Runs a git subcommand, and returns its output, strictly.
+{- Runs a git command, and returns its output, strictly.
  -
  - Nonzero exit status is ignored.
  -}
@@ -66,15 +72,19 @@
   where
 	p  = gitCreateProcess params repo
 
-{- Runs a git subcommand, feeding it input, and returning its output,
+{- Runs a git command, feeding it input, and returning its output,
  - which is expected to be fairly small, since it's all read into memory
  - strictly. -}
 pipeWriteRead :: [CommandParam] -> String -> Repo -> IO String
 pipeWriteRead params s repo = assertLocal repo $
 	writeReadProcessEnv "git" (toCommand $ gitCommandLine params repo) 
-		(gitEnv repo) s (Just fileEncoding)
+		(gitEnv repo) s (Just adjusthandle)
+  where
+  	adjusthandle h = do
+		fileEncoding h
+		hSetNewlineMode h noNewlineTranslation
 
-{- Runs a git subcommand, feeding it input on a handle with an action. -}
+{- Runs a git command, feeding it input on a handle with an action. -}
 pipeWrite :: [CommandParam] -> Repo -> (Handle -> IO ()) -> IO ()
 pipeWrite params repo = withHandle StdinHandle createProcessSuccess $
 	gitCreateProcess params repo
@@ -88,6 +98,12 @@
   where
 	sep = "\0"
 
+pipeNullSplitStrict :: [CommandParam] -> Repo -> IO [String]
+pipeNullSplitStrict params repo = do
+	s <- pipeReadStrict params repo
+	return $ filter (not . null) $ split sep s
+  where
+	sep = "\0"
 
 pipeNullSplitZombie :: [CommandParam] -> Repo -> IO [String]
 pipeNullSplitZombie params repo = leaveZombie <$> pipeNullSplit params repo
@@ -97,8 +113,10 @@
 leaveZombie = fst
 
 {- Runs a git command as a coprocess. -}
-gitCoProcessStart :: [CommandParam] -> Repo -> IO CoProcess.CoProcessHandle
-gitCoProcessStart params repo = CoProcess.start "git" (toCommand $ gitCommandLine params repo) (gitEnv repo)
+gitCoProcessStart :: Bool -> [CommandParam] -> Repo -> IO CoProcess.CoProcessHandle
+gitCoProcessStart restartable params repo = CoProcess.start restartable "git"
+	(toCommand $ gitCommandLine params repo)
+	(gitEnv repo)
 
 gitCreateProcess :: [CommandParam] -> Repo -> CreateProcess
 gitCreateProcess params repo =
diff --git a/Git/Config.hs b/Git/Config.hs
--- a/Git/Config.hs
+++ b/Git/Config.hs
@@ -10,6 +10,7 @@
 import qualified Data.Map as M
 import Data.Char
 import System.Process (cwd, env)
+import Control.Exception.Extensible
 
 import Common
 import Git
@@ -153,3 +154,37 @@
 
 isBare :: Repo -> Bool
 isBare r = fromMaybe False $ isTrue =<< getMaybe "core.bare" r
+
+{- Runs a command to get the configuration of a repo,
+ - and returns a repo populated with the configuration, as well as the raw
+ - output of the command. -}
+fromPipe :: Repo -> String -> [CommandParam] -> IO (Either SomeException (Repo, String))
+fromPipe r cmd params = try $
+	withHandle StdoutHandle createProcessSuccess p $ \h -> do
+ 		fileEncoding h
+		val <- hGetContentsStrict h
+		r' <- store val r
+		return (r', val)
+  where
+	p = proc cmd $ toCommand params
+
+{- Reads git config from a specified file and returns the repo populated
+ - with the configuration. -}
+fromFile :: Repo -> FilePath -> IO (Either SomeException (Repo, String))
+fromFile r f = fromPipe r "git"
+	[ Param "config"
+	, Param "--file"
+	, File f
+	, Param "--list"
+	]
+
+{- Changes a git config setting in the specified config file.
+ - (Creates the file if it does not already exist.) -}
+changeFile :: FilePath -> String -> String -> IO Bool
+changeFile f k v = boolSystem "git"
+	[ Param "config"
+	, Param "--file"
+	, File f
+	, Param k
+	, Param v
+	]
diff --git a/Git/Construct.hs b/Git/Construct.hs
--- a/Git/Construct.hs
+++ b/Git/Construct.hs
@@ -5,6 +5,8 @@
  - Licensed under the GNU GPL version 3 or higher.
  -}
 
+{-# LANGUAGE CPP #-}
+
 module Git.Construct (
 	fromCwd,
 	fromAbsPath,
@@ -17,31 +19,35 @@
 	fromRemotes,
 	fromRemoteLocation,
 	repoAbsPath,
+	newFrom,
+	checkForRepo,
 ) where
 
+#ifndef mingw32_HOST_OS
 import System.Posix.User
+#endif
 import qualified Data.Map as M hiding (map, split)
 import Network.URI
 
 import Common
 import Git.Types
 import Git
+import Git.Remote
 import qualified Git.Url as Url
 import Utility.UserInfo
 
 {- Finds the git repository used for the cwd, which may be in a parent
  - directory. -}
-fromCwd :: IO Repo
-fromCwd = getCurrentDirectory >>= seekUp checkForRepo
+fromCwd :: IO (Maybe Repo)
+fromCwd = getCurrentDirectory >>= seekUp
   where
-	norepo = error "Not in a git repository."
-	seekUp check dir = do
-		r <- check dir
+	seekUp dir = do
+		r <- checkForRepo dir
 		case r of
 			Nothing -> case parentDir dir of
-				"" -> norepo
-				d -> seekUp check d
-			Just loc -> newFrom loc
+				"" -> return Nothing
+				d -> seekUp d
+			Just loc -> Just <$> newFrom loc
 
 {- Local Repo constructor, accepts a relative or absolute path. -}
 fromPath :: FilePath -> IO Repo
@@ -51,8 +57,7 @@
  - specified. -}
 fromAbsPath :: FilePath -> IO Repo
 fromAbsPath dir
-	| "/" `isPrefixOf` dir =
-		ifM (doesDirectoryExist dir') ( ret dir' , hunt )
+	| isAbsolute dir = ifM (doesDirectoryExist dir') ( ret dir' , hunt )
 	| otherwise =
 		error $ "internal error, " ++ dir ++ " is not absolute"
   where
@@ -64,7 +69,7 @@
 	{- When dir == "foo/.git", git looks for "foo/.git/.git",
 	 - and failing that, uses "foo" as the repository. -}
 	hunt
-		| "/.git" `isSuffixOf` canondir =
+		| (pathSeparator:".git") `isSuffixOf` canondir =
 			ifM (doesDirectoryExist $ dir </> ".git")
 				( ret dir
 				, ret $ takeDirectory canondir
@@ -83,7 +88,7 @@
 
 fromUrlStrict :: String -> IO Repo
 fromUrlStrict url
-	| startswith "file://" url = fromAbsPath $ uriPath u
+	| startswith "file://" url = fromAbsPath $ unEscapeString $ uriPath u
 	| otherwise = newFrom $ Url u
   where
 	u = fromMaybe bad $ parseURI url
@@ -129,47 +134,16 @@
 remoteNamedFromKey :: String -> IO Repo -> IO Repo
 remoteNamedFromKey k = remoteNamed basename
   where
-	basename = join "." $ reverse $ drop 1 $ reverse $ drop 1 $ split "." k
+	basename = intercalate "." $ 
+		reverse $ drop 1 $ reverse $ drop 1 $ split "." k
 
 {- Constructs a new Repo for one of a Repo's remotes using a given
  - location (ie, an url). -}
 fromRemoteLocation :: String -> Repo -> IO Repo
-fromRemoteLocation s repo = gen $ calcloc s
+fromRemoteLocation s repo = gen $ parseRemoteLocation s repo
   where
-	gen v	
-		| scpstyle v = fromUrl $ scptourl v
-		| urlstyle v = fromUrl v
-		| otherwise = fromRemotePath v repo
-	-- insteadof config can rewrite remote location
-	calcloc l
-		| null insteadofs = l
-		| otherwise = replacement ++ drop (length bestvalue) l
-	  where
-		replacement = drop (length prefix) $
-			take (length bestkey - length suffix) bestkey
-		(bestkey, bestvalue) = maximumBy longestvalue insteadofs
-		longestvalue (_, a) (_, b) = compare b a
-		insteadofs = filterconfig $ \(k, v) -> 
-			startswith prefix k &&
-			endswith suffix k &&
-			startswith v l
-		filterconfig f = filter f $
-			concatMap splitconfigs $ M.toList $ fullconfig repo
-		splitconfigs (k, vs) = map (\v -> (k, v)) vs
-		(prefix, suffix) = ("url." , ".insteadof")
-	urlstyle v = isURI v || ":" `isInfixOf` v && "//" `isInfixOf` v
-	-- git remotes can be written scp style -- [user@]host:dir
-	-- but foo::bar is a git-remote-helper location instead
-	scpstyle v = ":" `isInfixOf` v 
-		&& not ("//" `isInfixOf` v)
-		&& not ("::" `isInfixOf` v)
-	scptourl v = "ssh://" ++ host ++ slash dir
-	  where
-		(host, dir) = separate (== ':') v
-		slash d	| d == "" = "/~/" ++ d
-			| "/" `isPrefixOf` d = d
-			| "~" `isPrefixOf` d = '/':d
-			| otherwise = "/~/" ++ d
+	gen (RemotePath p) = fromRemotePath p repo
+	gen (RemoteUrl u) = fromUrl u
 
 {- Constructs a Repo from the path specified in the git remotes of
  - another Repo. -}
@@ -190,6 +164,9 @@
 	return $ h </> d'
 
 expandTilde :: FilePath -> IO FilePath
+#ifdef mingw32_HOST_OS
+expandTilde = return
+#else
 expandTilde = expandt True
   where
 	expandt _ [] = return ""
@@ -210,7 +187,10 @@
 	findname n (c:cs)
 		| c == '/' = (n, cs)
 		| otherwise = findname (n++[c]) cs
+#endif
 
+{- Checks if a git repository exists in a directory. Does not find
+ - git repositories in parent directories. -}
 checkForRepo :: FilePath -> IO (Maybe RepoLocation)
 checkForRepo dir = 
 	check isRepo $
diff --git a/Git/FilePath.hs b/Git/FilePath.hs
new file mode 100644
--- /dev/null
+++ b/Git/FilePath.hs
@@ -0,0 +1,58 @@
+{- git FilePath library
+ -
+ - Different git commands use different types of FilePaths to refer to
+ - files in the repository. Some commands use paths relative to the
+ - top of the repository even when run in a subdirectory. Adding some
+ - types helps keep that straight.
+ -
+ - Copyright 2012-2013 Joey Hess <joey@kitenet.net>
+ -
+ - Licensed under the GNU GPL version 3 or higher.
+ -}
+
+{-# LANGUAGE CPP #-}
+
+module Git.FilePath (
+	TopFilePath,
+	getTopFilePath,
+	toTopFilePath,
+	asTopFilePath,
+	InternalGitPath,
+	toInternalGitPath,
+	fromInternalGitPath
+) where
+
+import Common
+import Git
+
+{- A FilePath, relative to the top of the git repository. -}
+newtype TopFilePath = TopFilePath { getTopFilePath :: FilePath }
+
+{- The input FilePath can be absolute, or relative to the CWD. -}
+toTopFilePath :: FilePath -> Git.Repo -> IO TopFilePath
+toTopFilePath file repo = TopFilePath <$>
+	relPathDirToFile (repoPath repo) <$> absPath file
+
+{- The input FilePath must already be relative to the top of the git
+ - repository -}
+asTopFilePath :: FilePath -> TopFilePath
+asTopFilePath file = TopFilePath file
+
+{- Git may use a different representation of a path when storing
+ - it internally. For example, on Windows, git uses '/' to separate paths
+ - stored in the repository, despite Windows using '\' -}
+type InternalGitPath = String
+
+toInternalGitPath :: FilePath -> InternalGitPath
+#ifndef mingw32_HOST_OS
+toInternalGitPath = id
+#else
+toInternalGitPath = replace "\\" "/"
+#endif
+
+fromInternalGitPath :: InternalGitPath -> FilePath
+#ifndef mingw32_HOST_OS
+fromInternalGitPath = id
+#else
+fromInternalGitPath = replace "/" "\\"
+#endif
diff --git a/Git/HashObject.hs b/Git/HashObject.hs
new file mode 100644
--- /dev/null
+++ b/Git/HashObject.hs
@@ -0,0 +1,43 @@
+{- git hash-object interface
+ -
+ - Copyright 2011-2012 Joey Hess <joey@kitenet.net>
+ -
+ - Licensed under the GNU GPL version 3 or higher.
+ -}
+
+module Git.HashObject where
+
+import Common
+import Git
+import Git.Sha
+import Git.Command
+import Git.Types
+import qualified Utility.CoProcess as CoProcess
+
+type HashObjectHandle = CoProcess.CoProcessHandle
+
+hashObjectStart :: Repo -> IO HashObjectHandle
+hashObjectStart = CoProcess.rawMode <=< gitCoProcessStart True
+	[ Param "hash-object"
+	, Param "-w"
+	, Param "--stdin-paths"
+	, Param "--no-filters"
+	]
+
+hashObjectStop :: HashObjectHandle -> IO ()
+hashObjectStop = CoProcess.stop
+
+{- Injects a file into git, returning the Sha of the object. -}
+hashFile :: HashObjectHandle -> FilePath -> IO Sha
+hashFile h file = CoProcess.query h send receive
+  where
+	send to = hPutStrLn to file
+	receive from = getSha "hash-object" $ hGetLine from
+
+{- Injects some content into git, returning its Sha. -}
+hashObject :: ObjectType -> String -> Repo -> IO Sha
+hashObject objtype content repo = getSha subcmd $
+	pipeWriteRead (map Param params) content repo
+  where
+	subcmd = "hash-object"
+	params = [subcmd, "-t", show objtype, "-w", "--stdin", "--no-filters"]
diff --git a/Git/Queue.hs b/Git/Queue.hs
--- a/Git/Queue.hs
+++ b/Git/Queue.hs
@@ -20,7 +20,6 @@
 import qualified Data.Map as M
 import System.IO
 import System.Process
-import Data.String.Utils
 
 import Utility.SafeCommand
 import Common
@@ -41,7 +40,7 @@
 	| CommandAction 
 		{ getSubcommand :: String
 		, getParams :: [CommandParam]
-		, getFiles :: [FilePath]
+		, getFiles :: [CommandParam]
 		} 
 
 {- A key that can uniquely represent an action in a Map. -}
@@ -93,7 +92,7 @@
 		, getParams = params
 		, getFiles = newfiles
 		}
-	newfiles = files ++ maybe [] getFiles (M.lookup key $ items q)
+	newfiles = map File files ++ maybe [] getFiles (M.lookup key $ items q)
 		
 	different (CommandAction { getSubcommand = s }) = s /= subcommand
 	different _ = True
@@ -151,7 +150,7 @@
 runAction repo action@(CommandAction {}) =
 	withHandle StdinHandle createProcessSuccess p $ \h -> do
 		fileEncoding h
-		hPutStr h $ join "\0" $ getFiles action
+		hPutStr h $ intercalate "\0" $ toCommand $ getFiles action
 		hClose h
   where
 	p = (proc "xargs" params) { env = gitEnv repo }
diff --git a/Git/Ref.hs b/Git/Ref.hs
--- a/Git/Ref.hs
+++ b/Git/Ref.hs
@@ -1,6 +1,6 @@
 {- git ref stuff
  -
- - Copyright 2011 Joey Hess <joey@kitenet.net>
+ - Copyright 2011-2013 Joey Hess <joey@kitenet.net>
  -
  - Licensed under the GNU GPL version 3 or higher.
  -}
@@ -13,6 +13,9 @@
 
 import Data.Char (chr)
 
+headRef :: Ref
+headRef = Ref "HEAD"
+
 {- Converts a fully qualified git ref into a user-visible string. -}
 describe :: Ref -> String
 describe = show . base
@@ -34,8 +37,8 @@
 
 {- Checks if a ref exists. -}
 exists :: Ref -> Repo -> IO Bool
-exists ref = runBool "show-ref" 
-	[Param "--verify", Param "-q", Param $ show ref]
+exists ref = runBool
+	[Param "show-ref", Param "--verify", Param "-q", Param $ show ref]
 
 {- Checks if HEAD exists. It generally will, except for in a repository
  - that was just created. -}
@@ -54,18 +57,26 @@
 	process [] = Nothing
 	process s = Just $ Ref $ firstLine s
 
-{- List of (refs, branches) matching a given ref spec. -}
-matching :: Ref -> Repo -> IO [(Ref, Branch)]
-matching ref repo = map gen . lines <$> 
-	pipeReadStrict [Param "show-ref", Param $ show ref] repo
+{- List of (shas, branches) matching a given ref or refs. -}
+matching :: [Ref] -> Repo -> IO [(Sha, Branch)]
+matching refs repo =  matching' (map show refs) repo
+
+{- Includes HEAD in the output, if asked for it. -}
+matchingWithHEAD :: [Ref] -> Repo -> IO [(Sha, Branch)]
+matchingWithHEAD refs repo = matching' ("--head" : map show refs) repo
+
+{- List of (shas, branches) matching a given ref or refs. -}
+matching' :: [String] -> Repo -> IO [(Sha, Branch)]
+matching' ps repo = map gen . lines <$> 
+	pipeReadStrict (Param "show-ref" : map Param ps) repo
   where
 	gen l = let (r, b) = separate (== ' ') l
 		in (Ref r, Ref b)
 
-{- List of (refs, branches) matching a given ref spec.
- - Duplicate refs are filtered out. -}
-matchingUniq :: Ref -> Repo -> IO [(Ref, Branch)]
-matchingUniq ref repo = nubBy uniqref <$> matching ref repo
+{- List of (shas, branches) matching a given ref spec.
+ - Duplicate shas are filtered out. -}
+matchingUniq :: [Ref] -> Repo -> IO [(Sha, Branch)]
+matchingUniq refs repo = nubBy uniqref <$> matching refs repo
   where
 	uniqref (a, _) (b, _) = a == b
 
diff --git a/Git/Remote.hs b/Git/Remote.hs
new file mode 100644
--- /dev/null
+++ b/Git/Remote.hs
@@ -0,0 +1,112 @@
+{- git remote stuff
+ -
+ - Copyright 2012 Joey Hess <joey@kitenet.net>
+ -
+ - Licensed under the GNU GPL version 3 or higher.
+ -}
+
+{-# LANGUAGE CPP #-}
+
+module Git.Remote where
+
+import Common
+import Git
+import qualified Git.Command
+import qualified Git.BuildVersion
+
+import Data.Char
+import qualified Data.Map as M
+import Network.URI
+#ifdef mingw32_HOST_OS
+import Git.FilePath
+#endif
+
+type RemoteName = String
+
+{- Construct a legal git remote name out of an arbitrary input string.
+ -
+ - There seems to be no formal definition of this in the git source,
+ - just some ad-hoc checks, and some other things that fail with certian
+ - types of names (like ones starting with '-').
+ -}
+makeLegalName :: String -> RemoteName
+makeLegalName s = case filter legal $ replace "/" "_" s of
+	-- it can't be empty
+	[] -> "unnamed"
+	-- it can't start with / or - or .
+	'.':s' -> makeLegalName s'
+	'/':s' -> makeLegalName s'
+	'-':s' -> makeLegalName s'
+	s' -> s'
+  where
+	{- Only alphanumerics, and a few common bits of punctuation common
+	 - in hostnames. -}
+	legal '_' = True
+	legal '.' = True
+	legal c = isAlphaNum c
+	
+remove :: RemoteName -> Repo -> IO ()
+remove remotename = Git.Command.run
+	[ Param "remote"
+	-- name of this subcommand changed
+	, Param $
+		if Git.BuildVersion.older "1.8.0"
+			then "rm"
+			else "remove"
+	, Param remotename
+	]
+
+data RemoteLocation = RemoteUrl String | RemotePath FilePath
+
+remoteLocationIsUrl :: RemoteLocation -> Bool
+remoteLocationIsUrl (RemoteUrl _) = True
+remoteLocationIsUrl _ = False
+
+{- Determines if a given remote location is an url, or a local
+ - path. Takes the repository's insteadOf configuration into account. -}
+parseRemoteLocation :: String -> Repo -> RemoteLocation
+parseRemoteLocation s repo = ret $ calcloc s
+  where
+  	ret v
+#ifdef mingw32_HOST_OS
+		| dosstyle v = RemotePath (dospath v)
+#endif
+		| scpstyle v = RemoteUrl (scptourl v)
+		| urlstyle v = RemoteUrl v
+		| otherwise = RemotePath v
+	-- insteadof config can rewrite remote location
+	calcloc l
+		| null insteadofs = l
+		| otherwise = replacement ++ drop (length bestvalue) l
+	  where
+		replacement = drop (length prefix) $
+			take (length bestkey - length suffix) bestkey
+		(bestkey, bestvalue) = maximumBy longestvalue insteadofs
+		longestvalue (_, a) (_, b) = compare b a
+		insteadofs = filterconfig $ \(k, v) -> 
+			startswith prefix k &&
+			endswith suffix k &&
+			startswith v l
+		filterconfig f = filter f $
+			concatMap splitconfigs $ M.toList $ fullconfig repo
+		splitconfigs (k, vs) = map (\v -> (k, v)) vs
+		(prefix, suffix) = ("url." , ".insteadof")
+	urlstyle v = isURI v || ":" `isInfixOf` v && "//" `isInfixOf` v
+	-- git remotes can be written scp style -- [user@]host:dir
+	-- but foo::bar is a git-remote-helper location instead
+	scpstyle v = ":" `isInfixOf` v 
+		&& not ("//" `isInfixOf` v)
+		&& not ("::" `isInfixOf` v)
+	scptourl v = "ssh://" ++ host ++ slash dir
+	  where
+		(host, dir) = separate (== ':') v
+		slash d	| d == "" = "/~/" ++ d
+			| "/" `isPrefixOf` d = d
+			| "~" `isPrefixOf` d = '/':d
+			| otherwise = "/~/" ++ d
+#ifdef mingw32_HOST_OS
+	-- git on Windows will write a path to .git/config with "drive:",
+	-- which is not to be confused with a "host:"
+	dosstyle = hasDrive
+	dospath = fromInternalGitPath
+#endif
diff --git a/Git/Types.hs b/Git/Types.hs
--- a/Git/Types.hs
+++ b/Git/Types.hs
@@ -41,7 +41,7 @@
 
 {- A git ref. Can be a sha1, or a branch or tag name. -}
 newtype Ref = Ref String
-	deriving (Eq)
+	deriving (Eq, Ord)
 
 instance Show Ref where
 	show (Ref v) = v
diff --git a/Git/UpdateIndex.hs b/Git/UpdateIndex.hs
new file mode 100644
--- /dev/null
+++ b/Git/UpdateIndex.hs
@@ -0,0 +1,80 @@
+{- git-update-index library
+ -
+ - Copyright 2011-2013 Joey Hess <joey@kitenet.net>
+ -
+ - Licensed under the GNU GPL version 3 or higher.
+ -}
+
+{-# LANGUAGE BangPatterns, CPP #-}
+
+module Git.UpdateIndex (
+	Streamer,
+	pureStreamer,
+	streamUpdateIndex,
+	lsTree,
+	updateIndexLine,
+	unstageFile,
+	stageSymlink
+) where
+
+import Common
+import Git
+import Git.Types
+import Git.Command
+import Git.FilePath
+import Git.Sha
+
+{- Streamers are passed a callback and should feed it lines in the form
+ - read by update-index, and generated by ls-tree. -}
+type Streamer = (String -> IO ()) -> IO ()
+
+{- A streamer with a precalculated value. -}
+pureStreamer :: String -> Streamer
+pureStreamer !s = \streamer -> streamer s
+
+{- Streams content into update-index from a list of Streamers. -}
+streamUpdateIndex :: Repo -> [Streamer] -> IO ()
+streamUpdateIndex repo as = pipeWrite params repo $ \h -> do
+	fileEncoding h
+	forM_ as (stream h)
+	hClose h
+  where
+	params = map Param ["update-index", "-z", "--index-info"]
+	stream h a = a (streamer h)
+	streamer h s = do
+		hPutStr h s
+		hPutStr h "\0"
+
+{- A streamer that adds the current tree for a ref. Useful for eg, copying
+ - and modifying branches. -}
+lsTree :: Ref -> Repo -> Streamer
+lsTree (Ref x) repo streamer = do
+	(s, cleanup) <- pipeNullSplit params repo
+	mapM_ streamer s
+	void $ cleanup
+  where
+	params = map Param ["ls-tree", "-z", "-r", "--full-tree", x]
+
+{- Generates a line suitable to be fed into update-index, to add
+ - a given file with a given sha. -}
+updateIndexLine :: Sha -> BlobType -> TopFilePath -> String
+updateIndexLine sha filetype file =
+	show filetype ++ " blob " ++ show sha ++ "\t" ++ indexPath file
+
+{- A streamer that removes a file from the index. -}
+unstageFile :: FilePath -> Repo -> IO Streamer
+unstageFile file repo = do
+	p <- toTopFilePath file repo
+	return $ pureStreamer $ "0 " ++ show nullSha ++ "\t" ++ indexPath p
+
+{- A streamer that adds a symlink to the index. -}
+stageSymlink :: FilePath -> Sha -> Repo -> IO Streamer
+stageSymlink file sha repo = do
+	!line <- updateIndexLine
+		<$> pure sha
+		<*> pure SymlinkBlob
+		<*> toTopFilePath file repo
+	return $ pureStreamer line
+
+indexPath :: TopFilePath -> InternalGitPath
+indexPath = toInternalGitPath . getTopFilePath
diff --git a/Git/Version.hs b/Git/Version.hs
new file mode 100644
--- /dev/null
+++ b/Git/Version.hs
@@ -0,0 +1,43 @@
+{- git versions
+ -
+ - Copyright 2011, 2013 Joey Hess <joey@kitenet.net>
+ -
+ - Licensed under the GNU GPL version 3 or higher.
+ -}
+
+module Git.Version where
+
+import Common
+
+data GitVersion = GitVersion String Integer
+	deriving (Eq)
+
+instance Ord GitVersion where
+	compare (GitVersion _ x) (GitVersion _ y) = compare x y
+
+instance Show GitVersion where
+	show (GitVersion s _) = s
+
+installed :: IO GitVersion
+installed = normalize . extract <$> readProcess "git" ["--version"]
+  where
+  	extract s = case lines s of
+		[] -> ""
+		(l:_) -> unwords $ drop 2 $ words l
+
+{- To compare dotted versions like 1.7.7 and 1.8, they are normalized to
+ - a somewhat arbitrary integer representation. -}
+normalize :: String -> GitVersion
+normalize v = GitVersion v $ 
+	sum $ mult 1 $ reverse $ extend precision $ take precision $
+		map readi $ split "." v
+  where
+	extend n l = l ++ replicate (n - length l) 0
+	mult _ [] = []
+	mult n (x:xs) = (n*x) : mult (n*10^width) xs
+	readi :: String -> Integer
+	readi s = case reads s of
+		((x,_):_) -> x
+		_ -> 0
+	precision = 10 -- number of segments of the version to compare
+	width = length "yyyymmddhhmmss" -- maximum width of a segment
diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -1,42 +1,26 @@
 PREFIX=/usr
-BASEFLAGS=-Wall -outputdir tmp
-GHCFLAGS=-O2 $(BASEFLAGS)
-bins=github-backup
-mans=github-backup.1
-all=$(bins)
-
-ifdef PROFILE
-GHCFLAGS=-prof -auto-all -rtsopts -caf-all -fforce-recomp $(BASEFLAGS)
-endif
-
-GHCMAKE=ghc $(GHCFLAGS) --make
-
-# Am I typing :make in vim? Do a fast build.
-ifdef VIM
-all=fast
-endif
-
-all: $(all)
+CABAL?=cabal # set to "./Setup" if you lack a cabal program
 
-# Disables optimisation. Not for production use.
-fast: GHCFLAGS=$(BASEFLAGS)
-fast: $(bins)
+build: Build/SysConfig.hs
+	$(CABAL) build
+	ln -sf dist/build/github-backup/github-backup github-backup
 
-$(bins):
-	$(GHCMAKE) $@
+Build/SysConfig.hs: configure.hs Build/TestConfig.hs Build/Configure.hs
+	if [ "$(CABAL)" = ./Setup ]; then ghc --make Setup; fi
+	$(CABAL) configure
 
-install: all
+install: build
 	install -d $(DESTDIR)$(PREFIX)/bin
-	install $(bins) $(DESTDIR)$(PREFIX)/bin
+	install github-backup $(DESTDIR)$(PREFIX)/bin
 	install -d $(DESTDIR)$(PREFIX)/share/man/man1
-	install -m 0644 $(mans) $(DESTDIR)$(PREFIX)/share/man/man1
+	install -m 0644 github-backup.1 $(DESTDIR)$(PREFIX)/share/man/man1
 
 clean:
-	rm -rf $(bins) tmp dist
+	rm -rf github-backup dist configure Build/SysConfig.hs Setup
+	find -name \*.o -exec rm {} \;
+	find -name \*.hi -exec rm {} \;
 
 # Upload to hackage.
 hackage: clean
 	./make-sdist.sh
 	@cabal upload dist/*.tar.gz
-
-.PHONY: $(bins)
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -15,6 +15,8 @@
 
     cabal install github-backup --bindir=$HOME/bin
 
+(Cabal is bundled with the [Haskell Platform](http://www.haskell.org/platform/).)
+
 ## Use
 
   Run `github-backup` with no parameters, inside a git repository cloned
diff --git a/Setup.hs b/Setup.hs
--- a/Setup.hs
+++ b/Setup.hs
@@ -1,2 +1,16 @@
+{-# LANGUAGE NamedFieldPuns #-}
+
+{- cabal setup file -}
+
 import Distribution.Simple
-main = defaultMain
+import Distribution.Simple.Setup
+
+import qualified Build.Configure as Configure
+
+main = defaultMainWithHooks simpleUserHooks
+	{ preConf = configure
+	}
+
+configure _ _ = do
+	Configure.run Configure.tests
+	return (Nothing, [])
diff --git a/Utility/Applicative.hs b/Utility/Applicative.hs
new file mode 100644
--- /dev/null
+++ b/Utility/Applicative.hs
@@ -0,0 +1,16 @@
+{- applicative stuff
+ -
+ - Copyright 2012 Joey Hess <joey@kitenet.net>
+ -
+ - Licensed under the GNU GPL version 3 or higher.
+ -}
+
+module Utility.Applicative where
+
+{- Like <$> , but supports one level of currying.
+ - 
+ - foo v = bar <$> action v  ==  foo = bar <$$> action
+ -}
+(<$$>) :: Functor f => (a -> b) -> (c -> f a) -> c -> f b
+f <$$> v = fmap f . v
+infixr 4 <$$>
diff --git a/Utility/CoProcess.hs b/Utility/CoProcess.hs
--- a/Utility/CoProcess.hs
+++ b/Utility/CoProcess.hs
@@ -1,35 +1,93 @@
 {- Interface for running a shell command as a coprocess,
  - sending it queries and getting back results.
  -
- - Copyright 2012 Joey Hess <joey@kitenet.net>
+ - Copyright 2012-2013 Joey Hess <joey@kitenet.net>
  -
  - Licensed under the GNU GPL version 3 or higher.
  -}
 
+{-# LANGUAGE CPP #-}
+
 module Utility.CoProcess (
 	CoProcessHandle,
 	start,
 	stop,
-	query
+	query,
+	rawMode
 ) where
 
 import Common
 
-type CoProcessHandle = (ProcessHandle, Handle, Handle, CreateProcess)
+import Control.Concurrent.MVar
 
-start :: FilePath -> [String] -> Maybe [(String, String)] -> IO CoProcessHandle
-start command params env = do
-	(from, to, _err, pid) <- runInteractiveProcess command params Nothing env
-	return (pid, to, from, proc command params)
+type CoProcessHandle = MVar CoProcessState
 
+data CoProcessState = CoProcessState
+	{ coProcessPid :: ProcessHandle
+	, coProcessTo :: Handle
+	, coProcessFrom :: Handle
+	, coProcessSpec :: CoProcessSpec
+	}
+
+data CoProcessSpec = CoProcessSpec
+	{ coProcessRestartable :: Bool
+	, coProcessCmd :: FilePath
+	, coProcessParams :: [String]
+	, coProcessEnv :: Maybe [(String, String)]
+	}
+
+start :: Bool -> FilePath -> [String] -> Maybe [(String, String)] -> IO CoProcessHandle
+start restartable cmd params env = do
+	s <- start' $ CoProcessSpec restartable cmd params env
+	newMVar s
+
+start' :: CoProcessSpec -> IO CoProcessState
+start' s = do
+	(pid, from, to) <- startInteractiveProcess (coProcessCmd s) (coProcessParams s) (coProcessEnv s)
+	return $ CoProcessState pid to from s
+
 stop :: CoProcessHandle -> IO ()
-stop (pid, from, to, p) = do
-	hClose to
-	hClose from
-	forceSuccessProcess p pid
+stop ch = do
+	s <- readMVar ch
+	hClose $ coProcessTo s
+	hClose $ coProcessFrom s
+	let p = proc (coProcessCmd $ coProcessSpec s) (coProcessParams $ coProcessSpec s)
+	forceSuccessProcess p (coProcessPid s)
 
+{- To handle a restartable process, any IO exception thrown by the send and
+ - receive actions are assumed to mean communication with the process
+ - failed, and the failed action is re-run with a new process. -}
 query :: CoProcessHandle -> (Handle -> IO a) -> (Handle -> IO b) -> IO b
-query (_, from, to, _) send receive = do
-	_ <- send to
-	hFlush to
-	receive from
+query ch send receive = do
+	s <- readMVar ch
+	restartable s (send $ coProcessTo s) $ const $
+		restartable s (hFlush $ coProcessTo s) $ const $
+			restartable s (receive $ coProcessFrom s) $
+				return
+  where
+  	restartable s a cont
+		| coProcessRestartable (coProcessSpec s) =
+			maybe restart cont =<< catchMaybeIO a
+		| otherwise = cont =<< a
+	restart = do
+		s <- takeMVar ch
+		void $ catchMaybeIO $ do
+			hClose $ coProcessTo s
+			hClose $ coProcessFrom s
+		void $ waitForProcess $ coProcessPid s
+		s' <- start' (coProcessSpec s)
+		putMVar ch s'
+		query ch send receive
+
+rawMode :: CoProcessHandle -> IO CoProcessHandle
+rawMode ch = do
+	s <- readMVar ch
+	raw $ coProcessFrom s
+	raw $ coProcessTo s
+	return ch
+  where
+  	raw h = do
+		fileEncoding h
+#ifdef mingw32_HOST_OS
+		hSetNewlineMode h noNewlineTranslation
+#endif
diff --git a/Utility/Data.hs b/Utility/Data.hs
new file mode 100644
--- /dev/null
+++ b/Utility/Data.hs
@@ -0,0 +1,17 @@
+{- utilities for simple data types
+ -
+ - Copyright 2013 Joey Hess <joey@kitenet.net>
+ -
+ - Licensed under the GNU GPL version 3 or higher.
+ -}
+
+module Utility.Data where
+
+{- First item in the list that is not Nothing. -}
+firstJust :: Eq a => [Maybe a] -> Maybe a
+firstJust ms = case dropWhile (== Nothing) ms of
+	[] -> Nothing
+	(md:_) -> md
+
+eitherToMaybe :: Either a b -> Maybe b
+eitherToMaybe = either (const Nothing) Just
diff --git a/Utility/Directory.hs b/Utility/Directory.hs
--- a/Utility/Directory.hs
+++ b/Utility/Directory.hs
@@ -5,10 +5,12 @@
  - Licensed under the GNU GPL version 3 or higher.
  -}
 
+{-# LANGUAGE CPP #-}
+
 module Utility.Directory where
 
 import System.IO.Error
-import System.Posix.Files
+import System.PosixCompat.Files
 import System.Directory
 import Control.Exception (throw)
 import Control.Monad
@@ -18,7 +20,7 @@
 import System.IO.Unsafe (unsafeInterleaveIO)
 
 import Utility.SafeCommand
-import Utility.TempFile
+import Utility.Tmp
 import Utility.Exception
 import Utility.Monad
 
@@ -36,15 +38,19 @@
  - and lazily. If the directory does not exist, no exception is thrown,
  - instead, [] is returned. -}
 dirContentsRecursive :: FilePath -> IO [FilePath]
-dirContentsRecursive topdir = dirContentsRecursive' [topdir]
+dirContentsRecursive topdir = dirContentsRecursiveSkipping (const False) topdir
 
-dirContentsRecursive' :: [FilePath] -> IO [FilePath]
-dirContentsRecursive' [] = return []
-dirContentsRecursive' (dir:dirs) = unsafeInterleaveIO $ do
-	(files, dirs') <- collect [] [] =<< catchDefaultIO [] (dirContents dir)
-	files' <- dirContentsRecursive' (dirs' ++ dirs)
-	return (files ++ files')
+dirContentsRecursiveSkipping :: (FilePath -> Bool) -> FilePath -> IO [FilePath]
+dirContentsRecursiveSkipping skipdir topdir = go [topdir]
   where
+  	go [] = return []
+	go (dir:dirs)
+		| skipdir dir = go dirs
+		| otherwise = unsafeInterleaveIO $ do
+			(files, dirs') <- collect [] []
+				=<< catchDefaultIO [] (dirContents dir)
+			files' <- go (dirs' ++ dirs)
+			return (files ++ files')
 	collect files dirs' [] = return (reverse files, reverse dirs')
 	collect files dirs' (entry:entries)
 		| dirCruft entry = collect files dirs' entries
@@ -85,9 +91,16 @@
 			(Left _) -> return False
 			(Right s) -> return $ isDirectory s
 
-{- Removes a file, which may or may not exist.
+{- Removes a file, which may or may not exist, and does not have to
+ - be a regular file.
  -
  - Note that an exception is thrown if the file exists but
  - cannot be removed. -}
 nukeFile :: FilePath -> IO ()
-nukeFile file = whenM (doesFileExist file) $ removeFile file
+nukeFile file = void $ tryWhenExists go
+  where
+#ifndef mingw32_HOST_OS
+	go = removeLink file
+#else
+	go = removeFile file
+#endif
diff --git a/Utility/Env.hs b/Utility/Env.hs
new file mode 100644
--- /dev/null
+++ b/Utility/Env.hs
@@ -0,0 +1,63 @@
+{- portable environment variables
+ -
+ - Copyright 2013 Joey Hess <joey@kitenet.net>
+ -
+ - Licensed under the GNU GPL version 3 or higher.
+ -}
+
+{-# LANGUAGE CPP #-}
+
+module Utility.Env where
+
+#ifdef mingw32_HOST_OS
+import Utility.Exception
+import Control.Applicative
+import Data.Maybe
+import qualified System.Environment as E
+#else
+import qualified System.Posix.Env as PE
+#endif
+
+getEnv :: String -> IO (Maybe String)
+#ifndef mingw32_HOST_OS
+getEnv = PE.getEnv
+#else
+getEnv = catchMaybeIO . E.getEnv
+#endif
+
+getEnvDefault :: String -> String -> IO String
+#ifndef mingw32_HOST_OS
+getEnvDefault = PE.getEnvDefault
+#else
+getEnvDefault var fallback = fromMaybe fallback <$> getEnv var
+#endif
+
+getEnvironment :: IO [(String, String)]
+#ifndef mingw32_HOST_OS
+getEnvironment = PE.getEnvironment
+#else
+getEnvironment = E.getEnvironment
+#endif
+
+{- Returns True if it could successfully set the environment variable.
+ -
+ - There is, apparently, no way to do this in Windows. Instead,
+ - environment varuables must be provided when running a new process. -}
+setEnv :: String -> String -> Bool -> IO Bool
+#ifndef mingw32_HOST_OS
+setEnv var val overwrite = do
+	PE.setEnv var val overwrite
+	return True
+#else
+setEnv _ _ _ = return False
+#endif
+
+{- Returns True if it could successfully unset the environment variable. -}
+unsetEnv :: String -> IO Bool
+#ifndef mingw32_HOST_OS
+unsetEnv var = do
+	PE.unsetEnv var
+	return True
+#else
+unsetEnv _ = return False
+#endif
diff --git a/Utility/Exception.hs b/Utility/Exception.hs
--- a/Utility/Exception.hs
+++ b/Utility/Exception.hs
@@ -9,9 +9,12 @@
 
 module Utility.Exception where
 
-import Prelude hiding (catch)
 import Control.Exception
+import qualified Control.Exception as E
 import Control.Applicative
+import Control.Monad
+import System.IO.Error (isDoesNotExistError)
+import Utility.Data
 
 {- Catches IO errors and returns a Bool -}
 catchBoolIO :: IO Bool -> IO Bool
@@ -31,7 +34,7 @@
 
 {- catch specialized for IO errors only -}
 catchIO :: IO a -> (IOException -> IO a) -> IO a
-catchIO = catch
+catchIO = E.catch
 
 {- try specialized for IO errors only -}
 tryIO :: IO a -> IO (Either IOException a)
@@ -49,3 +52,8 @@
 
 tryNonAsync :: IO a -> IO (Either SomeException a)
 tryNonAsync a = (Right <$> a) `catchNonAsync` (return . Left)
+
+{- Catches only DoesNotExist exceptions, and lets all others through. -}
+tryWhenExists :: IO a -> IO (Maybe a)
+tryWhenExists a = eitherToMaybe <$>
+	tryJust (guard . isDoesNotExistError) a
diff --git a/Utility/FileMode.hs b/Utility/FileMode.hs
--- a/Utility/FileMode.hs
+++ b/Utility/FileMode.hs
@@ -5,12 +5,17 @@
  - Licensed under the GNU GPL version 3 or higher.
  -}
 
+{-# LANGUAGE CPP #-}
+
 module Utility.FileMode where
 
 import Common
 
 import Control.Exception (bracket)
-import System.Posix.Types
+import System.PosixCompat.Types
+#ifndef mingw32_HOST_OS
+import System.Posix.Files
+#endif
 import Foreign (complement)
 
 {- Applies a conversion function to a file's mode. -}
@@ -71,7 +76,11 @@
 
 {- Checks if a file mode indicates it's a symlink. -}
 isSymLink :: FileMode -> Bool
+#ifdef mingw32_HOST_OS
+isSymLink _ = False
+#else
 isSymLink = checkMode symbolicLinkMode
+#endif
 
 {- Checks if a file has any executable bits set. -}
 isExecutable :: FileMode -> Bool
@@ -80,6 +89,7 @@
 {- Runs an action without that pesky umask influencing it, unless the
  - passed FileMode is the standard one. -}
 noUmask :: FileMode -> IO a -> IO a
+#ifndef mingw32_HOST_OS
 noUmask mode a
 	| mode == stdFileMode = a
 	| otherwise = bracket setup cleanup go
@@ -87,26 +97,39 @@
 	setup = setFileCreationMask nullFileMode
 	cleanup = setFileCreationMask
 	go _ = a
+#else
+noUmask _ a = a
+#endif
 
 combineModes :: [FileMode] -> FileMode
 combineModes [] = undefined
 combineModes [m] = m
 combineModes (m:ms) = foldl unionFileModes m ms
 
-stickyMode :: FileMode
-stickyMode = 512
-
 isSticky :: FileMode -> Bool
+#ifdef mingw32_HOST_OS
+isSticky _ = False
+#else
 isSticky = checkMode stickyMode
 
+stickyMode :: FileMode
+stickyMode = 512
+
 setSticky :: FilePath -> IO ()
 setSticky f = modifyFileMode f $ addModes [stickyMode]
+#endif
 
 {- Writes a file, ensuring that its modes do not allow it to be read
- - by anyone other than the current user, before any content is written. -}
+ - by anyone other than the current user, before any content is written.
+ -
+ - On a filesystem that does not support file permissions, this is the same
+ - as writeFile.
+ -}
 writeFileProtected :: FilePath -> String -> IO ()
 writeFileProtected file content = do
 	h <- openFile file WriteMode
-	modifyFileMode file $ removeModes [groupReadMode, otherReadMode]
+	void $ tryIO $
+		modifyFileMode file $
+			removeModes [groupReadMode, otherReadMode]
 	hPutStr h content
 	hClose h
diff --git a/Utility/FileSystemEncoding.hs b/Utility/FileSystemEncoding.hs
--- a/Utility/FileSystemEncoding.hs
+++ b/Utility/FileSystemEncoding.hs
@@ -1,6 +1,6 @@
 {- GHC File system encoding handling.
  -
- - Copyright 2012 Joey Hess <joey@kitenet.net>
+ - Copyright 2012-2013 Joey Hess <joey@kitenet.net>
  -
  - Licensed under the GNU GPL version 3 or higher.
  -}
@@ -10,7 +10,8 @@
 	withFilePath,
 	md5FilePath,
 	decodeW8,
-	encodeW8	
+	encodeW8,
+	truncateFilePath,
 ) where
 
 import qualified GHC.Foreign as GHC
@@ -75,3 +76,18 @@
  - represent the FilePath on disk. -}
 decodeW8 :: FilePath -> [Word8]
 decodeW8 = s2w8 . _encodeFilePath
+
+{- Truncates a FilePath to the given number of bytes (or less),
+ - as represented on disk.
+ -
+ - Avoids returning an invalid part of a unicode byte sequence, at the
+ - cost of efficiency when running on a large FilePath.
+ -}
+truncateFilePath :: Int -> FilePath -> FilePath
+truncateFilePath n = go . reverse
+  where
+  	go f =
+		let bytes = decodeW8 f
+		in if length bytes <= n
+			then reverse f
+			else go (drop 1 f)
diff --git a/Utility/Misc.hs b/Utility/Misc.hs
--- a/Utility/Misc.hs
+++ b/Utility/Misc.hs
@@ -5,16 +5,20 @@
  - Licensed under the GNU GPL version 3 or higher.
  -}
 
+{-# LANGUAGE CPP #-}
+
 module Utility.Misc where
 
 import System.IO
 import Control.Monad
 import Foreign
 import Data.Char
+import Data.List
 import Control.Applicative
+#ifndef mingw32_HOST_OS
 import System.Posix.Process (getAnyProcessStatus)
-
 import Utility.Exception
+#endif
 
 {- A version of hgetContents that is not lazy. Ensures file is 
  - all read before it gets closed. -}
@@ -70,6 +74,23 @@
 		| p i = go [] ([i]:c:r) is
 		| otherwise = go (i:c) r is
 
+{- Replaces multiple values in a string.
+ -
+ - Takes care to skip over just-replaced values, so that they are not
+ - mangled. For example, massReplace [("foo", "new foo")] does not
+ - replace the "new foo" with "new new foo".
+ -}
+massReplace :: [(String, String)] -> String -> String
+massReplace vs = go [] vs
+  where
+
+	go acc _ [] = concat $ reverse acc
+	go acc [] (c:cs) = go ([c]:acc) vs cs
+	go acc ((val, replacement):rest) s
+		| val `isPrefixOf` s =
+			go (replacement:acc) vs (drop (length val) s)
+		| otherwise = go acc rest s
+
 {- Given two orderings, returns the second if the first is EQ and returns
  - the first otherwise.
  -
@@ -106,7 +127,12 @@
  - on a process and get back an exit status is going to be confused
  - if this reap gets there first. -}
 reapZombies :: IO ()
+#ifndef mingw32_HOST_OS
 reapZombies = do
 	-- throws an exception when there are no child processes
 	catchDefaultIO Nothing (getAnyProcessStatus False True)
 		>>= maybe (return ()) (const reapZombies)
+
+#else
+reapZombies = return ()
+#endif
diff --git a/Utility/Monad.hs b/Utility/Monad.hs
--- a/Utility/Monad.hs
+++ b/Utility/Monad.hs
@@ -8,7 +8,7 @@
 module Utility.Monad where
 
 import Data.Maybe
-import Control.Monad (liftM)
+import Control.Monad
 
 {- Return the first value from a list, if any, satisfying the given
  - predicate -}
@@ -26,6 +26,10 @@
  - stopping once one is found. -}
 anyM :: Monad m => (a -> m Bool) -> [a] -> m Bool
 anyM p = liftM isJust . firstM p
+
+allM :: Monad m => (a -> m Bool) -> [a] -> m Bool
+allM _ [] = return True
+allM p (x:xs) = p x <&&> allM p xs
 
 {- Runs an action on values from a list until it succeeds. -}
 untilTrue :: Monad m => [a] -> (a -> m Bool) -> m Bool
diff --git a/Utility/Path.hs b/Utility/Path.hs
--- a/Utility/Path.hs
+++ b/Utility/Path.hs
@@ -1,31 +1,62 @@
 {- path manipulation
  -
- - Copyright 2010-2011 Joey Hess <joey@kitenet.net>
+ - Copyright 2010-2013 Joey Hess <joey@kitenet.net>
  -
  - Licensed under the GNU GPL version 3 or higher.
  -}
 
+{-# LANGUAGE PackageImports, CPP #-}
+
 module Utility.Path where
 
 import Data.String.Utils
-import System.Path
 import System.FilePath
 import System.Directory
 import Data.List
 import Data.Maybe
+import Data.Char
 import Control.Applicative
 
+#ifdef mingw32_HOST_OS
+import Data.Char
+import qualified System.FilePath.Posix as Posix
+#else
+import qualified "MissingH" System.Path as MissingH
+import System.Posix.Files
+#endif
+
 import Utility.Monad
 import Utility.UserInfo
 
-{- Returns the parent directory of a path. Parent of / is "" -}
+{- Makes a path absolute if it's not already.
+ - The first parameter is a base directory (ie, the cwd) to use if the path
+ - is not already absolute.
+ -
+ - On Unix, collapses and normalizes ".." etc in the path. May return Nothing
+ - if the path cannot be normalized.
+ -
+ - MissingH's absNormPath does not work on Windows, so on Windows
+ - no normalization is done.
+ -}
+absNormPath :: FilePath -> FilePath -> Maybe FilePath
+#ifndef mingw32_HOST_OS
+absNormPath dir path = MissingH.absNormPath dir path
+#else
+absNormPath dir path = Just $ combine dir path
+#endif
+
+{- Returns the parent directory of a path.
+ -
+ - To allow this to be easily used in loops, which terminate upon reaching the
+ - top, the parent of / is "" -}
 parentDir :: FilePath -> FilePath
 parentDir dir
-	| not $ null dirs = slash ++ join s (init dirs)
-	| otherwise = ""
+	| null dirs = ""
+	| otherwise = joinDrive drive (join s $ init dirs)
   where
-	dirs = filter (not . null) $ split s dir
-	slash = if isAbsolute dir then s else ""
+	-- on Unix, the drive will be "/" when the dir is absolute, otherwise ""
+	(drive, path) = splitDrive dir
+	dirs = filter (not . null) $ split s path
 	s = [pathSeparator]
 
 prop_parentDir_basics :: FilePath -> Bool
@@ -41,7 +72,7 @@
  - are all equivilant.
  -}
 dirContains :: FilePath -> FilePath -> Bool
-dirContains a b = a == b || a' == b' || (a'++"/") `isPrefixOf` b'
+dirContains a b = a == b || a' == b' || (a'++[pathSeparator]) `isPrefixOf` b'
   where
 	norm p = fromMaybe "" $ absNormPath p "."
 	a' = norm a
@@ -102,11 +133,13 @@
 	 - location, but it's not really the same directory.
 	 - Code used to get this wrong. -}
 	same_dir_shortcurcuits_at_difference =
-		relPathDirToFile "/tmp/r/lll/xxx/yyy/18" "/tmp/r/.git/annex/objects/18/gk/SHA256-foo/SHA256-foo" == "../../../../.git/annex/objects/18/gk/SHA256-foo/SHA256-foo"
+		relPathDirToFile (joinPath [pathSeparator : "tmp", "r", "lll", "xxx", "yyy", "18"])
+			(joinPath [pathSeparator : "tmp", "r", ".git", "annex", "objects", "18", "gk", "SHA256-foo", "SHA256-foo"])
+				== joinPath ["..", "..", "..", "..", ".git", "annex", "objects", "18", "gk", "SHA256-foo", "SHA256-foo"]
 
 {- Given an original list of paths, and an expanded list derived from it,
  - generates a list of lists, where each sublist corresponds to one of the
- - original paths. When the original path is a direcotry, any items
+ - original paths. When the original path is a directory, any items
  - in the expanded list that are contained in that directory will appear in
  - its segment.
  -}
@@ -150,7 +183,12 @@
 	| otherwise = getSearchPath >>= getM indir
   where
 	indir d = check $ d </> command
-	check f = ifM (doesFileExist f) ( return (Just f), return Nothing )
+	check f = firstM doesFileExist
+#ifdef mingw32_HOST_OS
+		[f, f ++ ".exe"]
+#else
+		[f]
+#endif
 
 {- Checks if a filename is a unix dotfile. All files inside dotdirs
  - count as dotfiles. -}
@@ -162,3 +200,55 @@
 	| otherwise = "." `isPrefixOf` f || dotfile (takeDirectory file)
   where
 	f = takeFileName file
+
+{- Converts a DOS style path to a Cygwin style path. Only on Windows.
+ - Any trailing '\' is preserved as a trailing '/' -}
+toCygPath :: FilePath -> FilePath
+#ifndef mingw32_HOST_OS
+toCygPath = id
+#else
+toCygPath p
+	| null drive = recombine parts
+	| otherwise = recombine $ "/cygdrive" : driveletter drive : parts
+  where
+  	(drive, p') = splitDrive p
+	parts = splitDirectories p'
+  	driveletter = map toLower . takeWhile (/= ':')
+	recombine = fixtrailing . Posix.joinPath
+  	fixtrailing s
+		| hasTrailingPathSeparator p = Posix.addTrailingPathSeparator s
+		| otherwise = s
+#endif
+
+{- Maximum size to use for a file in a specified directory.
+ -
+ - Many systems have a 255 byte limit to the name of a file, 
+ - so that's taken as the max if the system has a larger limit, or has no
+ - limit.
+ -}
+fileNameLengthLimit :: FilePath -> IO Int
+#ifdef mingw32_HOST_OS
+fileNameLengthLimit _ = return 255
+#else
+fileNameLengthLimit dir = do
+	l <- fromIntegral <$> getPathVar dir FileNameLimit
+	if l <= 0
+		then return 255
+		else return $ minimum [l, 255]
+  where
+#endif
+
+{- Given a string that we'd like to use as the basis for FilePath, but that
+ - was provided by a third party and is not to be trusted, returns the closest
+ - sane FilePath.
+ -
+ - All spaces and punctuation are replaced with '_', except for '.'
+ - "../" will thus turn into ".._", which is safe.
+ -}
+sanitizeFilePath :: String -> FilePath
+sanitizeFilePath = map sanitize
+  where
+  	sanitize c
+		| c == '.' = c
+		| isSpace c || isPunctuation c || c == '/' = '_'
+		| otherwise = c
diff --git a/Utility/Process.hs b/Utility/Process.hs
--- a/Utility/Process.hs
+++ b/Utility/Process.hs
@@ -6,7 +6,7 @@
  - Licensed under the GNU GPL version 3 or higher.
  -}
 
-{-# LANGUAGE Rank2Types #-}
+{-# LANGUAGE CPP, Rank2Types #-}
 
 module Utility.Process (
 	module X,
@@ -21,11 +21,13 @@
 	createProcessSuccess,
 	createProcessChecked,
 	createBackgroundProcess,
+	processTranscript,
 	withHandle,
 	withBothHandles,
 	withQuietOutput,
+	withNullHandle,
 	createProcess,
-	runInteractiveProcess,
+	startInteractiveProcess,
 	stdinHandle,
 	stdoutHandle,
 	stderrHandle,
@@ -33,15 +35,20 @@
 
 import qualified System.Process
 import System.Process as X hiding (CreateProcess(..), createProcess, runInteractiveProcess, readProcess, readProcessWithExitCode, system, rawSystem, runInteractiveCommand, runProcess)
-import System.Process hiding (createProcess, runInteractiveProcess, readProcess)
+import System.Process hiding (createProcess, readProcess)
 import System.Exit
 import System.IO
 import System.Log.Logger
 import Control.Concurrent
 import qualified Control.Exception as E
 import Control.Monad
+#ifndef mingw32_HOST_OS
+import System.Posix.IO
+import Data.Maybe
+#endif
 
 import Utility.Misc
+import Utility.Exception
 
 type CreateProcessRunner = forall a. CreateProcess -> ((Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle) -> IO a) -> IO a
 
@@ -116,7 +123,10 @@
 		ExitSuccess -> return ()
 		ExitFailure n -> fail $ showCmd p ++ " exited " ++ show n
 
-{- Waits for a ProcessHandle and returns True if it exited successfully. -}
+{- Waits for a ProcessHandle and returns True if it exited successfully.
+ - Note that using this with createProcessChecked will throw away
+ - the Bool, and is only useful to ignore the exit code of a process,
+ - while still waiting for it. -}
 checkSuccessProcess :: ProcessHandle -> IO Bool
 checkSuccessProcess pid = do
 	code <- waitForProcess pid
@@ -133,19 +143,61 @@
 createProcessSuccess p a = createProcessChecked (forceSuccessProcess p) p a
 
 {- Runs createProcess, then an action on its handles, and then
- - an action on its exit code. -}
+ - a checker action on its exit code, which must wait for the process. -}
 createProcessChecked :: (ProcessHandle -> IO b) -> CreateProcessRunner
 createProcessChecked checker p a = do
 	t@(_, _, _, pid) <- createProcess p
-	r <- a t
+	r <- tryNonAsync $ a t
 	_ <- checker pid
-	return r
+	either E.throw return r
 
 {- Leaves the process running, suitable for lazy streaming.
  - Note: Zombies will result, and must be waited on. -}
 createBackgroundProcess :: CreateProcessRunner
 createBackgroundProcess p a = a =<< createProcess p
 
+{- Runs a process, optionally feeding it some input, and
+ - returns a transcript combining its stdout and stderr, and
+ - whether it succeeded or failed. -}
+processTranscript :: String -> [String] -> (Maybe String) -> IO (String, Bool)
+#ifndef mingw32_HOST_OS
+processTranscript cmd opts input = do
+	(readf, writef) <- createPipe
+	readh <- fdToHandle readf
+	writeh <- fdToHandle writef
+	p@(_, _, _, pid) <- createProcess $
+		(proc cmd opts)
+			{ std_in = if isJust input then CreatePipe else Inherit
+			, std_out = UseHandle writeh
+			, std_err = UseHandle writeh
+			}
+	hClose writeh
+
+	-- fork off a thread to start consuming the output
+	transcript <- hGetContents readh
+	outMVar <- newEmptyMVar
+	_ <- forkIO $ E.evaluate (length transcript) >> putMVar outMVar ()
+
+	-- now write and flush any input
+	case input of
+		Just s -> do
+			let inh = stdinHandle p
+			unless (null s) $ do
+				hPutStr inh s
+				hFlush inh
+			hClose inh
+		Nothing -> return ()
+
+	-- wait on the output
+	takeMVar outMVar
+	hClose readh
+
+	ok <- checkSuccessProcess pid
+	return (transcript, ok)
+#else
+processTranscript = error "processTranscript TODO"
+#endif
+
 {- Runs a CreateProcessRunner, on a CreateProcess structure, that
  - is adjusted to pipe only from/to a single StdHandle, and passes
  - the resulting Handle to an action. -}
@@ -190,13 +242,22 @@
 	:: CreateProcessRunner
 	-> CreateProcess
 	-> IO ()
-withQuietOutput creator p = withFile "/dev/null" WriteMode $ \devnull -> do
+withQuietOutput creator p = withNullHandle $ \nullh -> do
 	let p' = p
-		{ std_out = UseHandle devnull
-		, std_err = UseHandle devnull
+		{ std_out = UseHandle nullh
+		, std_err = UseHandle nullh
 		}
 	creator p' $ const $ return ()
 
+withNullHandle :: (Handle -> IO a) -> IO a
+withNullHandle = withFile devnull WriteMode
+  where
+#ifndef mingw32_HOST_OS
+	devnull = "/dev/null"
+#else
+	devnull = "NUL"
+#endif
+
 {- Extract a desired handle from createProcess's tuple.
  - These partial functions are safe as long as createProcess is run
  - with appropriate parameters to set up the desired handle.
@@ -239,27 +300,25 @@
 	go (ShellCommand s) = s
 	go (RawCommand c ps) = c ++ " " ++ show ps
 
-{- Wrappers for System.Process functions that do debug logging.
- - 
- - More could be added, but these are the only ones I usually need.
- -}
+{- Starts an interactive process. Unlike runInteractiveProcess in
+ - System.Process, stderr is inherited. -}
+startInteractiveProcess
+	:: FilePath
+	-> [String]
+	-> Maybe [(String, String)]
+	-> IO (ProcessHandle, Handle, Handle)
+startInteractiveProcess cmd args environ = do
+	let p = (proc cmd args)
+		{ std_in = CreatePipe
+		, std_out = CreatePipe
+		, std_err = Inherit
+		, env = environ
+		}
+	(Just from, Just to, _, pid) <- createProcess p
+	return (pid, to, from)
 
+{- Wrapper around System.Process function that does debug logging. -}
 createProcess :: CreateProcess -> IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
 createProcess p = do
 	debugProcess p
 	System.Process.createProcess p
-
-runInteractiveProcess
-	:: FilePath	
-	-> [String]	
-	-> Maybe FilePath	
-	-> Maybe [(String, String)]	
-	-> IO (Handle, Handle, Handle, ProcessHandle)
-runInteractiveProcess f args c e = do
-	debugProcess $ (proc f args)
-			{ std_in = CreatePipe
-			, std_out = CreatePipe
-			, std_err = CreatePipe
-			, env = e
-			}
-	System.Process.runInteractiveProcess f args c e
diff --git a/Utility/SafeCommand.hs b/Utility/SafeCommand.hs
--- a/Utility/SafeCommand.hs
+++ b/Utility/SafeCommand.hs
@@ -1,6 +1,6 @@
 {- safely running shell commands
  -
- - Copyright 2010-2012 Joey Hess <joey@kitenet.net>
+ - Copyright 2010-2013 Joey Hess <joey@kitenet.net>
  -
  - Licensed under the GNU GPL version 3 or higher.
  -}
@@ -12,6 +12,8 @@
 import System.Process (env)
 import Data.String.Utils
 import Control.Applicative
+import System.FilePath
+import Data.Char
 
 {- A type for parameters passed to a shell command. A command can
  - be passed either some Params (multiple parameters can be included,
@@ -24,14 +26,20 @@
 {- Used to pass a list of CommandParams to a function that runs
  - a command and expects Strings. -}
 toCommand :: [CommandParam] -> [String]
-toCommand = (>>= unwrap)
+toCommand = concatMap unwrap
   where
 	unwrap (Param s) = [s]
 	unwrap (Params s) = filter (not . null) (split " " s)
-	-- Files that start with a dash are modified to avoid
-	-- the command interpreting them as options.
-	unwrap (File s@('-':_)) = ["./" ++ s]
+	-- Files that start with a non-alphanumeric that is not a path
+	-- separator are modified to avoid the command interpreting them as
+	-- options or other special constructs.
+	unwrap (File s@(h:_))
+		| isAlphaNum h || h `elem` pathseps = [s]
+		| otherwise = ["./" ++ s]
 	unwrap (File s) = [s]
+	-- '/' is explicitly included because it's an alternative
+	-- path separator on Windows.
+	pathseps = pathSeparator:"./"
 
 {- Run a system command, and returns True or False
  - if it succeeded or failed.
@@ -55,8 +63,16 @@
 		{ env = environ }
 	waitForProcess pid
 
+{- Wraps a shell command line inside sh -c, allowing it to be run in a
+ - login shell that may not support POSIX shell, eg csh. -}
+shellWrap :: String -> String
+shellWrap cmdline = "sh -c " ++ shellEscape cmdline
+
 {- Escapes a filename or other parameter to be safely able to be exposed to
- - the shell. -}
+ - the shell.
+ -
+ - This method works for POSIX shells, as well as other shells like csh.
+ -}
 shellEscape :: String -> String
 shellEscape f = "'" ++ escaped ++ "'"
   where
@@ -85,3 +101,20 @@
 prop_idempotent_shellEscape s = [s] == (shellUnEscape . shellEscape) s
 prop_idempotent_shellEscape_multiword :: [String] -> Bool
 prop_idempotent_shellEscape_multiword s = s == (shellUnEscape . unwords . map shellEscape) s
+
+{- Segements a list of filenames into groups that are all below the manximum
+ - command-line length limit. Does not preserve order. -}
+segmentXargs :: [FilePath] -> [[FilePath]]
+segmentXargs l = go l [] 0 []
+  where
+	go [] c _ r = c:r
+	go (f:fs) c accumlen r
+		| len < maxlen && newlen > maxlen = go (f:fs) [] 0 (c:r)
+		| otherwise = go fs (f:c) newlen r
+	  where
+		len = length f
+		newlen = accumlen + len
+
+	{- 10k of filenames per command, well under Linux's 20k limit;
+	 - allows room for other parameters etc. -}
+	maxlen = 10240
diff --git a/Utility/TempFile.hs b/Utility/TempFile.hs
deleted file mode 100644
--- a/Utility/TempFile.hs
+++ /dev/null
@@ -1,58 +0,0 @@
-{- temp file functions
- -
- - Copyright 2010-2011 Joey Hess <joey@kitenet.net>
- -
- - Licensed under the GNU GPL version 3 or higher.
- -}
-
-module Utility.TempFile where
-
-import Control.Exception (bracket)
-import System.IO
-import System.Posix.Process
-import System.Directory
-
-import Utility.Exception
-import Utility.Path
-import System.FilePath
-
-{- Runs an action like writeFile, writing to a temp file first and
- - then moving it into place. The temp file is stored in the same
- - directory as the final file to avoid cross-device renames. -}
-viaTmp :: (FilePath -> String -> IO ()) -> FilePath -> String -> IO ()
-viaTmp a file content = do
-	pid <- getProcessID
-	let tmpfile = file ++ ".tmp" ++ show pid
-	createDirectoryIfMissing True (parentDir file)
-	a tmpfile content
-	renameFile tmpfile file
-
-type Template = String
-
-{- Runs an action with a temp file, then removes the file. -}
-withTempFile :: Template -> (FilePath -> Handle -> IO a) -> IO a
-withTempFile template a = bracket create remove use
-  where
-	create = do
-		tmpdir <- catchDefaultIO "." getTemporaryDirectory
-		openTempFile tmpdir template
-	remove (name, handle) = do
-		hClose handle
-		catchBoolIO (removeFile name >> return True)
-	use (name, handle) = a name handle
-
-{- Runs an action with a temp directory, then removes the directory and
- - all its contents. -}
-withTempDir :: Template -> (FilePath -> IO a) -> IO a
-withTempDir template = bracket create remove
-  where
-	remove = removeDirectoryRecursive
-	create = do
-		tmpdir <- catchDefaultIO "." getTemporaryDirectory
-		createDirectoryIfMissing True tmpdir
-		pid <- getProcessID
-		makedir tmpdir (template ++ show pid) (0 :: Int)
-	makedir tmpdir t n = do
-		let dir = tmpdir </> t ++ "." ++ show n
-		r <- tryIO $ createDirectory dir
-		either (const $ makedir tmpdir t $ n + 1) (const $ return dir) r
diff --git a/Utility/Tmp.hs b/Utility/Tmp.hs
new file mode 100644
--- /dev/null
+++ b/Utility/Tmp.hs
@@ -0,0 +1,88 @@
+{- Temporary files and directories.
+ -
+ - Copyright 2010-2013 Joey Hess <joey@kitenet.net>
+ -
+ - Licensed under the GNU GPL version 3 or higher.
+ -}
+
+module Utility.Tmp where
+
+import Control.Exception (bracket)
+import System.IO
+import System.Directory
+import Control.Monad.IfElse
+
+import Utility.Exception
+import System.FilePath
+import Utility.FileSystemEncoding
+
+type Template = String
+
+{- Runs an action like writeFile, writing to a temp file first and
+ - then moving it into place. The temp file is stored in the same
+ - directory as the final file to avoid cross-device renames. -}
+viaTmp :: (FilePath -> String -> IO ()) -> FilePath -> String -> IO ()
+viaTmp a file content = do
+	let (dir, base) = splitFileName file
+	createDirectoryIfMissing True dir
+	(tmpfile, handle) <- openTempFile dir (base ++ ".tmp")
+	hClose handle
+	a tmpfile content
+	renameFile tmpfile file
+
+{- Runs an action with a tmp file located in the system's tmp directory
+ - (or in "." if there is none) then removes the file. -}
+withTmpFile :: Template -> (FilePath -> Handle -> IO a) -> IO a
+withTmpFile template a = do
+	tmpdir <- catchDefaultIO "." getTemporaryDirectory
+	withTmpFileIn tmpdir template a
+
+{- Runs an action with a tmp file located in the specified directory,
+ - then removes the file. -}
+withTmpFileIn :: FilePath -> Template -> (FilePath -> Handle -> IO a) -> IO a
+withTmpFileIn tmpdir template a = bracket create remove use
+  where
+	create = openTempFile tmpdir template
+	remove (name, handle) = do
+		hClose handle
+		catchBoolIO (removeFile name >> return True)
+	use (name, handle) = a name handle
+
+{- Runs an action with a tmp directory located within the system's tmp
+ - directory (or within "." if there is none), then removes the tmp
+ - directory and all its contents. -}
+withTmpDir :: Template -> (FilePath -> IO a) -> IO a
+withTmpDir template a = do
+	tmpdir <- catchDefaultIO "." getTemporaryDirectory
+	withTmpDirIn tmpdir template a
+
+{- Runs an action with a tmp directory located within a specified directory,
+ - then removes the tmp directory and all its contents. -}
+withTmpDirIn :: FilePath -> Template -> (FilePath -> IO a) -> IO a
+withTmpDirIn tmpdir template = bracket create remove
+  where
+	remove d = whenM (doesDirectoryExist d) $
+		removeDirectoryRecursive d
+	create = do
+		createDirectoryIfMissing True tmpdir
+		makenewdir (tmpdir </> template) (0 :: Int)
+	makenewdir t n = do
+		let dir = t ++ "." ++ show n
+		either (const $ makenewdir t $ n + 1) (const $ return dir)
+			=<< tryIO (createDirectory dir)
+
+{- It's not safe to use a FilePath of an existing file as the template
+ - for openTempFile, because if the FilePath is really long, the tmpfile
+ - will be longer, and may exceed the maximum filename length.
+ -
+ - This generates a template that is never too long.
+ - (Well, it allocates 20 characters for use in making a unique temp file,
+ - anyway, which is enough for the current implementation and any
+ - likely implementation.)
+ -}
+relatedTemplate :: FilePath -> FilePath
+relatedTemplate f
+	| len > 20 = truncateFilePath (len - 20) f
+	| otherwise = f
+  where
+	len = length f
diff --git a/Utility/UserInfo.hs b/Utility/UserInfo.hs
--- a/Utility/UserInfo.hs
+++ b/Utility/UserInfo.hs
@@ -5,6 +5,8 @@
  - Licensed under the GNU GPL version 3 or higher.
  -}
 
+{-# LANGUAGE CPP #-}
+
 module Utility.UserInfo (
 	myHomeDir,
 	myUserName,
@@ -12,21 +14,38 @@
 ) where
 
 import Control.Applicative
-import System.Posix.User
-import System.Posix.Env
+import System.PosixCompat
 
+import Utility.Env
+
 {- Current user's home directory.
  -
  - getpwent will fail on LDAP or NIS, so use HOME if set. -}
 myHomeDir :: IO FilePath
-myHomeDir = myVal ["HOME"] homeDirectory
+myHomeDir = myVal env homeDirectory
+  where
+#ifndef mingw32_HOST_OS
+	env = ["HOME"]
+#else
+	env = ["USERPROFILE", "HOME"] -- HOME is used in Cygwin
+#endif
 
 {- Current user's user name. -}
 myUserName :: IO String
-myUserName = myVal ["USER", "LOGNAME"] userName
+myUserName = myVal env userName
+  where
+#ifndef mingw32_HOST_OS
+	env = ["USER", "LOGNAME"]
+#else
+	env = ["USERNAME", "USER", "LOGNAME"]
+#endif
 
 myUserGecos :: IO String
+#ifdef __ANDROID__
+myUserGecos = return "" -- userGecos crashes on Android
+#else
 myUserGecos = myVal [] userGecos
+#endif
 
 myVal :: [String] -> (UserEntry -> String) -> IO String
 myVal envvars extract = maybe (extract <$> getpwent) return =<< check envvars
diff --git a/configure.hs b/configure.hs
new file mode 100644
--- /dev/null
+++ b/configure.hs
@@ -0,0 +1,6 @@
+{- configure program -}
+
+import Build.Configure
+
+main :: IO ()
+main = run tests
diff --git a/debian/changelog b/debian/changelog
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,42 @@
+github-backup (1.20131006) unstable; urgency=low
+
+  * Ported to Windows.
+  * Improve error message when it fails to query github for repositories
+    belonging to a user. Closes: #705084
+  * Various updates to internal git and utility libraries shared with git-annex.
+  * Makefile now uses cabal to build.
+
+ -- Joey Hess <joeyh@debian.org>  Sun, 06 Oct 2013 18:04:56 -0400
+
+github-backup (1.20130622) unstable; urgency=low
+
+  * Add missing unix-compat build dependency. Closes: #713279
+
+ -- Joey Hess <joeyh@debian.org>  Sat, 22 Jun 2013 13:08:57 -0400
+
+github-backup (1.20130618) unstable; urgency=low
+
+  * Much better creation and committing to the github branch. 
+
+ -- Joey Hess <joeyh@debian.org>  Mon, 17 Jun 2013 17:40:02 -0400
+
+github-backup (1.20130617) unstable; urgency=low
+
+  * Build-Depend on libghc-extensible-exceptions-dev. Closes: #712549
+  * Various updates to internal git and utility libraries shared with
+    git-annex, including some Windows portability.
+  * Fixed to never touch the git work tree or index file, instead using
+    its own to commit to the github branch.
+
+ -- Joey Hess <joeyh@debian.org>  Mon, 17 Jun 2013 12:28:30 -0400
+
+github-backup (1.20130614) unstable; urgency=low
+
+  * Pass --ignore-removal to git-add, in preparation for a future change
+    to its default behavior. Requires git 1.8.3. Closes: #711287
+
+ -- Joey Hess <joeyh@debian.org>  Fri, 14 Jun 2013 15:50:49 -0400
+
 github-backup (1.20130414) experimental; urgency=low
 
   * Updated to use haskell-github 0.6.0, which supports pagination of queries
diff --git a/debian/control b/debian/control
--- a/debian/control
+++ b/debian/control
@@ -9,15 +9,17 @@
 	libghc-hslogger-dev,
 	libghc-pretty-show-dev,
 	libghc-ifelse-dev,
+	libghc-extensible-exceptions-dev,
+	libghc-unix-compat-dev
 Maintainer: Joey Hess <joeyh@debian.org>
-Standards-Version: 3.9.3
+Standards-Version: 3.9.4
 Vcs-Git: git://github.com/joeyh/github-backup.git
 Homepage: http://github.com/joeyh/github-backup
 
 Package: github-backup
 Architecture: any
 Section: utils
-Depends: ${misc:Depends}, ${shlibs:Depends}
+Depends: ${misc:Depends}, ${shlibs:Depends}, git
 Description: backs up data from GitHub
  github-backup is a simple tool you run in a git repository you cloned from
  GitHub. It backs up everything GitHub publishes about the repository,
diff --git a/debian/copyright b/debian/copyright
--- a/debian/copyright
+++ b/debian/copyright
@@ -2,7 +2,7 @@
 Source: native package
 
 Files: *
-Copyright: © 2010-2012 Joey Hess <joey@kitenet.net>
+Copyright: © 2010-2013 Joey Hess <joey@kitenet.net>
 License: GPL-3+
  The full text of version 3 of the GPL is distributed as doc/GPL in
  this package's source, or in /usr/share/common-licenses/GPL-3 on
diff --git a/debian/rules b/debian/rules
--- a/debian/rules
+++ b/debian/rules
@@ -1,4 +1,11 @@
 #!/usr/bin/make -f
+
+# Avoid using cabal, as it writes to $HOME
+export CABAL=./Setup
+
+# Do use the changelog's version number, rather than making one up.
+export RELEASE_BUILD=1
+
 %:
 	dh $@
 
diff --git a/github-backup.cabal b/github-backup.cabal
--- a/github-backup.cabal
+++ b/github-backup.cabal
@@ -1,5 +1,5 @@
 Name: github-backup
-Version: 1.20130414
+Version: 1.20131006
 Cabal-Version: >= 1.6
 License: GPL
 Maintainer: Joey Hess <joey@kitenet.net>
@@ -7,7 +7,7 @@
 Stability: Stable
 Copyright: 2012 Joey Hess
 License-File: GPL
-Build-Type: Simple
+Build-Type: Custom
 Homepage: https://github.com/joeyh/github-backup
 Category: Utility
 Synopsis: backs up everything github knows about a repository, to the repository
@@ -19,8 +19,11 @@
 Executable github-backup
   Main-Is: github-backup.hs
   Build-Depends: MissingH, hslogger, directory, filepath, containers, mtl,
-   network, extensible-exceptions, unix, bytestring, base >= 4.5, base < 5,
-   IfElse, pretty-show, text, process, github >= 0.6.0
+   network, extensible-exceptions, unix-compat, bytestring,
+   base >= 4.5, base < 5, IfElse, pretty-show, text, process, github >= 0.6.0
+
+  if (! os(windows))
+    Build-Depends: unix
 
 source-repository head
   type: git
diff --git a/github-backup.hs b/github-backup.hs
--- a/github-backup.hs
+++ b/github-backup.hs
@@ -15,7 +15,7 @@
 import Data.Either
 import Data.Monoid
 import System.Environment
-import Control.Exception (bracket, try, SomeException)
+import Control.Exception (try, SomeException)
 import Text.Show.Pretty
 import "mtl" Control.Monad.State.Strict
 import qualified Github.Data.Readable as Github
@@ -37,6 +37,9 @@
 import qualified Git.Command
 import qualified Git.Ref
 import qualified Git.Branch
+import qualified Git.UpdateIndex
+import Git.HashObject
+import Git.FilePath
 
 -- A github user and repo.
 data GithubUserRepo = GithubUserRepo String String
@@ -272,44 +275,78 @@
 	, "ssh://git@github.com/~/"
 	]
 
-onGithubBranch :: Git.Repo -> IO () -> IO ()
-onGithubBranch r a = bracket prep cleanup (const a)
-  where
-	prep = do
-		oldbranch <- Git.Branch.current r
-		when (oldbranch == Just branchref) $
-			error $ "it's not currently safe to run github-backup while the " ++
-				branchname ++ " branch is checked out!"
-		ifM (null <$> Git.Ref.matching branchref r)
-			( checkout [Param "--orphan", Param branchname]
-			, checkout [Param branchname]
-			)
-		return oldbranch
-	cleanup Nothing = noop
-	cleanup (Just oldbranch)
-		| name == branchname = noop
-		| otherwise = checkout [Param "--force", Param name]
-	  where
-		name = show $ Git.Ref.base oldbranch
-	checkout params = Git.Command.run "checkout" (Param "-q" : params) r
-	branchname = "github"
-	branchref = Git.Ref $ "refs/heads/" ++ branchname
-
-{- Commits all files in the workDir into git, and deletes it. -}
+{- Commits all files in the workDir into the github branch, and deletes the
+ - workDir.
+ -
+ - The commit is made to the github branch without ever checking it out,
+ - or otherwise disturbing the work tree.
+ -}
 commitWorkDir :: Backup ()
 commitWorkDir = do
 	dir <- workDir
+	whenM (liftIO $ doesDirectoryExist dir) $ do
+		branchref <- getBranch
+		withIndex $ do
+			r <- getState gitRepo
+			liftIO $ do
+				-- Reset index to current content of github
+				-- branch. Does not touch work tree.
+				Git.Command.run
+					[Param "reset", Param "-q", Param $ show branchref, File "." ] r
+				-- Stage workDir files into the index.
+				h <- hashObjectStart r
+				Git.UpdateIndex.streamUpdateIndex r
+					[genstream r dir h]
+				hashObjectStop h
+				-- Commit
+				void $ Git.Branch.commit "github-backup" fullname [branchref] r
+				removeDirectoryRecursive dir
+  where
+  	genstream r dir h streamer = do
+		fs <- filter (not . dirCruft) <$> dirContentsRecursive dir
+		forM_ fs $ \f -> do
+			sha <- hashFile h f
+			path <- toTopFilePath f r
+			streamer $ Git.UpdateIndex.updateIndexLine
+				sha Git.Types.FileBlob path
+
+{- Returns the ref of the github branch, creating it first if necessary. -}
+getBranch :: Backup Git.Ref
+getBranch = maybe (hasOrigin >>= create) return =<< branchsha
+  where
+  	create True = do
+		inRepo $ Git.Command.run
+			[Param "branch", Param $ show branchname, Param $ show originname]
+		fromMaybe (error $ "failed to create " ++ show branchname)
+			<$> branchsha
+	create False = withIndex $
+		inRepo $ Git.Branch.commit "branch created" fullname []
+	branchsha = inRepo $ Git.Ref.sha fullname
+
+{- Runs an action with a different index file, used for the github branch. -}
+withIndex :: Backup a -> Backup a
+withIndex a = do
 	r <- getState gitRepo
-	let git_false_worktree ps = boolSystem "git" $
-		[ Param ("--work-tree=" ++ dir)
-		, Param ("--git-dir=" ++ Git.localGitDir r)
-		] ++ ps
-	liftIO $ whenM (doesDirectoryExist dir) $ onGithubBranch r $ do
-		_ <- git_false_worktree [ Param "add", Param "." ]
-		_ <- git_false_worktree [ Param "commit",
-			 Param "-a", Param "-m", Param "github-backup"]
-		removeDirectoryRecursive dir
+	let f = Git.localGitDir r </> "github-backup.index"
+	e <- liftIO getEnvironment
+	let r' = r { Git.Types.gitEnv = Just $ ("GIT_INDEX_FILE", f):e }
+	changeState $ \s -> s { gitRepo = r' }
+	v <- a
+	changeState $ \s -> s { gitRepo = (gitRepo s) { Git.Types.gitEnv = Git.Types.gitEnv r } }
+	return v
 
+branchname :: Git.Ref
+branchname = Git.Ref "github"
+
+fullname :: Git.Ref
+fullname = Git.Ref $ "refs/heads/" ++ show branchname
+
+originname :: Git.Ref
+originname = Git.Ref $ "refs/remotes/origin/" ++ show branchname
+
+hasOrigin :: Backup Bool
+hasOrigin = inRepo $ Git.Ref.exists originname
+
 updateWiki :: GithubUserRepo -> Backup ()
 updateWiki fork =
 	ifM (any (\r -> Git.remoteName r == Just remote) <$> remotes)
@@ -321,7 +358,7 @@
 				removeRemote remote
 		)
   where
-	fetchwiki = inRepo $ Git.Command.runBool "fetch" [Param remote]
+	fetchwiki = inRepo $ Git.Command.runBool [Param "fetch", Param remote]
 	remotes = Git.remotes <$> getState gitRepo
 	remote = remoteFor fork
 	remoteFor (GithubUserRepo user repo) =
@@ -342,16 +379,18 @@
 {- Adds a remote, also fetching from it. -}
 addRemote :: String -> String -> Backup Bool
 addRemote remotename remoteurl =
-	inRepo $ Git.Command.runBool "remote"
-		[ Param "add"
+	inRepo $ Git.Command.runBool
+		[ Param "remote"
+		, Param "add"
 		, Param "-f"
 		, Param remotename
 		, Param remoteurl
 		]
 
 removeRemote :: String -> Backup ()
-removeRemote remotename = void $ inRepo $ Git.Command.runBool "remote"
-	[ Param "rm"
+removeRemote remotename = void $ inRepo $ Git.Command.runBool
+	[ Param "remote"
+	, Param "rm"
 	, Param remotename
 	]
 
@@ -359,9 +398,8 @@
  - it would be weird for a backup to not fetch all available data.
  - Even though its real focus is on metadata not stored in git. -}
 fetchRepo :: Git.Repo -> Backup Bool
-fetchRepo repo = inRepo $
-	Git.Command.runBool "fetch"
-		[Param $ fromJust $ Git.Types.remoteName repo]
+fetchRepo repo = inRepo $ Git.Command.runBool
+	[Param "fetch", Param $ fromJust $ Git.Types.remoteName repo]
 
 {- Gathers metadata for the repo. Retuns a list of files written
  - and a list that may contain requests that need to be retried later. -}
@@ -429,8 +467,9 @@
 newState :: Git.Repo -> BackupState
 newState = BackupState S.empty S.empty
 
-backupRepo :: Git.Repo -> IO ()
-backupRepo repo = evalStateT (runBackup go) . newState =<< Git.Config.read repo
+backupRepo :: (Maybe Git.Repo) -> IO ()
+backupRepo Nothing = error "not in a git repository, and nothing specified to back up"
+backupRepo (Just repo) = evalStateT (runBackup go) . newState =<< Git.Config.read repo
   where
 	go = do
 		retriedfailed <- retry
@@ -444,13 +483,15 @@
 
 backupName :: String -> IO ()
 backupName name = do
-	userrepos <- either (const []) id <$>
-		Github.userRepos name Github.All
-	orgrepos <- either (const []) id <$>
-		Github.organizationRepos name
-	let repos = userrepos ++ orgrepos
+	l <- sequence
+	 	[ Github.userRepos name Github.All
+		, Github.organizationRepos name
+		]
+	let repos = concat $ rights l
 	when (null repos) $
-		error $ "No GitHub repositories found for " ++ name
+		if (null $ rights l)
+			then error $ unlines $ "Failed to query github for repos:" : map show (lefts l)
+			else error $ "No GitHub repositories found for " ++ name
 	status <- forM repos $ \repo -> do
 		let dir = Github.repoName repo
 		unlessM (doesDirectoryExist dir) $ do
@@ -461,7 +502,7 @@
 				, Param dir
 				]
 			unless ok $ error "clone failed"
-		try (backupRepo =<< Git.Construct.fromPath dir)
+		try (backupRepo . Just =<< Git.Construct.fromPath dir)
 			:: IO (Either SomeException ())
 	unless (null $ lefts status) $
 		error "Failed to successfully back everything up. Run again later."
