diff --git a/Github/Data/Readable.hs b/Github/Data/Readable.hs
new file mode 100644
--- /dev/null
+++ b/Github/Data/Readable.hs
@@ -0,0 +1,12 @@
+{-# LANGUAGE StandaloneDeriving #-}
+
+-- | This module re-exports the @Github.Data.Definitions@ module, adding
+-- instances of @Read@ to it.
+
+module Github.Data.Readable (module Github.Data.Definitions) where
+
+import Github.Data.Definitions
+
+deriving instance Read GithubDate
+deriving instance Read GithubUser
+deriving instance Read Repo
diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,7 @@
-GHCFLAGS=-O2 -Wall
+BASEFLAGS=-Wall -fno-warn-orphans
+GHCFLAGS=-O2 $(BASEFLAGS)
 bins=github-backup
+mans=github-backup.1
 all=$(bins)
 
 ifdef PROFILE
@@ -16,11 +18,17 @@
 all: $(all)
 
 # Disables optimisation. Not for production use.
-fast: GHCFLAGS=-Wall
+fast: GHCFLAGS=$(BASEFLAGS)
 fast: $(bins)
 
 $(bins):
 	$(GHCMAKE) $@
+
+install: all
+	install -d $(DESTDIR)$(PREFIX)/bin
+	install $(bins) $(DESTDIR)$(PREFIX)/bin
+	install -d $(DESTDIR)$(PREFIX)/share/man/man1
+	install -m 0644 $(mans) $(DESTDIR)$(PREFIX)/share/man/man1
 
 clean:
 	rm -f $(bins)
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,43 +1,51 @@
 github-backup is a simple tool you run in a git repository you cloned from
-Github. It backs up everything Github knows about the repository, including
-other forks, issues, comments, milestones, pull requests, and watchers.
+GitHub. It backs up everything GitHub publishes about the repository,
+including other forks, issues, comments, wikis, milestones, pull requests,
+and watchers.
 
 ## Installation
 
-  git clone git://github.com/joeyh/github-backup
-  cd github-backup
-  make
-  ./github-backup
+    git clone git://github.com/joeyh/github-backup
+    cd github-backup
+    make
 
 Or use cabal:
 
-  cabal install github-backup --bindir=$HOME/bin
+    cabal install github-backup --bindir=$HOME/bin
 
-## Why backup Github
+## Use
 
+  Run `github-backup` with no parameters, inside a git repository cloned
+  from GitHub to back up that repository.
+
+  Or, run `github-backup username` to clone and back up all of a GitHub
+  user's repositories.
+
+## Why backup GitHub repositories
+
 There are a couple of reasons to want to back this stuff up:
 
-* In case something happens to Github. More generally because
+* In case something happens to GitHub. More generally because
   keeping your data in the cloud *and* relying on the cloud to
   back it up is foolish.
 
 * So you can keep working on your repository while on a plane, or
-  on a remote beach or mountiaintop. Just like Linus intended.
+  on a remote beach or mountaintop. Just like Linus intended.
 
 ## What to expect
 
-Each time you run github-backup, it will find any new forks of your project
-on github. It will add remotes to your repository for the forks, using names
-like `github_linus_divemonitor`. It will fetch from every fork.
+Each time you run github-backup, it will find any new forks on GitHub. It
+will add remotes to your repository for the forks, using names like
+`github_linus_divemonitor`. It will fetch from every fork.
 
-Then the next pass will download metadata from each fork. This is stored
+It downloads metadata from each fork. This is stored
 into a branch named "github". Each fork gets a directory in there,
 like `linus_divemonitor`. Inside the directory there will be some
-files, like `linus_divemonitor/watchers`. There maybe be further
+files, like `linus_divemonitor/watchers`. There may be further
 directories, like for comments: `linus_divemonitor/comments/1`.
 
 You can follow the commits to the github branch to see what information
-changed on github over time.
+changed on GitHub over time.
 
 The format of the files in the github branch is currently Haskell
 serialized data types. This is plain text, and readable, if you squint.
@@ -45,18 +53,36 @@
 ## Limitations
 
 github-backup is repository-focused. It does not try to back up other
-information from Github. In particular, social network stuff, like
+information from GitHub. In particular, social network stuff, like
 users who are following you, is not backed up.
 
-github-backup will find and backup all forks of a repository, and all forks
+github-backup will find and backup forks of a repository, and all forks
 of those forks, etc. However, it cannot go *up* the fork tree. So if
-your Github repositoriy is a fork of something else, the something else
+your GitHub repositoriy is a fork of something else, the something else
 won't be backed up. There is an easy solution though. Just add the
 parent as a git remote. Then github-backup will find it, and back it up.
 
-Currently, github-backup re-downloads all issues, comments, and so on
-each time it's run. This may be slow if your repo has a lot of them.
+Currently, only 30 of each thing will be returned. This is a bug in 
+the haskell github library I'm using. Hope to get it fixed soon.
 
+Currently, the GitHub API does not seem to provide a way to access notes
+added to commits and notes added to lines of code. So those notes won't get
+backed up. The GitHub folks have been told about this limitation of their API.
+
+The labels that can be added to issues and milestones are not backed up.
+Neither are the hooks. They could be, but don't seem important
+enough for the extra work involved. Yell if you need them.
+
+github-backup re-downloads all issues, comments, and so on
+each time it's run. This may be slow if your repo has a lot of them,
+or even if it just has a lot of forks.
+
+Bear in mind that this uses the GitHub API; don't run it every 5 minutes.
+GitHub [rate limits](http://developer.github.com/v3/#rate-limiting) the
+API to 5000 requests per hour. However, github-backup *does* do an
+incremental backup, picking up where it left off, so will complete the
+backup eventually even if it's rate limited.
+
 ## Author
 
 github-backup was written by Joey Hess <joey@kitenet.net>
@@ -64,4 +90,4 @@
 It is made possible thanks to:
 
 * Mike Burns's [haskell github library](http://hackage.haskell.org/package/github)
-* Github, for providing an API exposing this data. 
+* GitHub, for providing an API exposing this data. 
diff --git a/Utility/State.hs b/Utility/State.hs
new file mode 100644
--- /dev/null
+++ b/Utility/State.hs
@@ -0,0 +1,26 @@
+{- state monad support
+ -
+ - Copyright 2012 Joey Hess <joey@kitenet.net>
+ -
+ - Licensed under the GNU GPL version 3 or higher.
+ -}
+
+module Utility.State where
+
+import Control.Monad.State.Strict
+
+{- Modifies Control.Monad.State's state, forcing a strict update.
+ - This avoids building thunks in the state and leaking.
+ - Why it's not the default, I don't know.
+ -
+ - Example: changeState $ \s -> s { foo = bar }
+ -}
+changeState :: MonadState s m => (s -> s) -> m ()
+changeState f = do
+	x <- get
+	put $! f x
+
+{- Gets a value from the internal state, selected by the passed value
+ - constructor. -}
+getState :: MonadState s m => (s -> a) -> m a
+getState = gets
diff --git a/github-backup.1 b/github-backup.1
new file mode 100644
--- /dev/null
+++ b/github-backup.1
@@ -0,0 +1,17 @@
+.\" -*- nroff -*-
+.TH github-backup 1 "Commands"
+.SH NAME
+github-backup \- backs up data from GitHub
+.SH SYNOPSIS
+.B github-backup [\fIusername\fP]
+.SH DESCRIPTION
+.I 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,
+including other forks, issues, comments, wikis, milestones, pull requests,
+and watchers.
+.PP
+Alternately, if you pass it the username of a GitHub user, it will check
+out, and back up, all that user's repositories.
+.SH AUTHOR 
+Joey Hess <joey@kitenet.net>
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.20120126
+Version: 1.20120131
 Cabal-Version: >= 1.6
 License: GPL
 Maintainer: Joey Hess <joey@kitenet.net>
@@ -8,11 +8,12 @@
 Copyright: 2012 Joey Hess
 License-File: GPL
 Build-Type: Simple
-Extra-Source-Files: README.md Makefile Git/Construct.hs Git/Command.hs
- Git/Branch.hs Git/Url.hs Git/Ref.hs Git/Sha.hs Git/Types.hs
- Git/Config.hs Git/Queue.hs Utility/Misc.hs Common.hs
- Utility/TempFile.hs Utility/Monad.hs Utility/PartialPrelude.hs
- Utility/Path.hs Utility/Directory.hs Utility/SafeCommand.hs Git.hs
+Extra-Source-Files: README.md Makefile github-backup.1
+ Common.hs github-backup.hs Git/Url.hs Git/Types.hs Git/Queue.hs Git/Ref.hs
+ Git/Branch.hs Git/Sha.hs Git/Config.hs Git/Command.hs Git/Construct.hs
+ Setup.hs Github/Data/Readable.hs Utility/Path.hs Utility/TempFile.hs
+ Utility/State.hs Utility/Monad.hs Utility/Misc.hs Utility/SafeCommand.hs
+ Utility/Directory.hs Utility/PartialPrelude.hs Git.hs
 Homepage: https://github.com/joeyh/github-backup
 Category: Utility
 Synopsis: backs up everything github knows about a repository, to the repository
diff --git a/github-backup.hs b/github-backup.hs
--- a/github-backup.hs
+++ b/github-backup.hs
@@ -5,13 +5,18 @@
  - Licensed under the GNU GPL version 3 or higher.
  -}
 
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+
 module Main where
 
+import qualified Data.Map as M
+import qualified Data.Set as S
+import Data.Either
 import System.Environment
-import Control.Exception (bracket)
-import System.Posix.Directory (changeWorkingDirectory)
+import Control.Exception (bracket, try, SomeException)
 import Text.Show.Pretty
-import qualified Github.Data.Definitions as Github
+import Control.Monad.State.Strict
+import qualified Github.Data.Readable as Github
 import qualified Github.Repos as Github
 import qualified Github.Repos.Forks as Github
 import qualified Github.PullRequests as Github
@@ -21,6 +26,7 @@
 import qualified Github.Issues.Milestones
 
 import Common
+import Utility.State
 import qualified Git
 import qualified Git.Construct
 import qualified Git.Config
@@ -28,69 +34,212 @@
 import qualified Git.Command
 import qualified Git.Ref
 import qualified Git.Branch
-import qualified Git.Queue
 
-usage :: String
-usage = "usage: github-backup [directory]"
-
-getLocalRepo :: [String] -> IO Git.Repo
-getLocalRepo [] = Git.Construct.fromCwd
-getLocalRepo (d:[]) = Git.Construct.fromPath d
-getLocalRepo _ = error usage
-
 -- A github user and repo.
 data GithubUserRepo = GithubUserRepo String String
-
-instance Show (GithubUserRepo) where
-	show (GithubUserRepo user remote) = "git://github.com/" ++ user ++ "/" ++ remote ++ ".git"
+	deriving (Eq, Show, Read, Ord)
 
 toGithubUserRepo :: Github.Repo -> GithubUserRepo
 toGithubUserRepo r = GithubUserRepo 
 	(Github.githubUserLogin $ Github.repoOwner r)
 	(Github.repoName r)
 
-remoteFor :: Github.Repo -> String
-remoteFor r = "github_" ++
-	(Github.githubUserLogin $ Github.repoOwner r) ++ "_" ++
-	(Github.repoName r)
+repoUrl :: GithubUserRepo -> String
+repoUrl (GithubUserRepo user remote) =
+	"git://github.com/" ++ user ++ "/" ++ remote ++ ".git"
 
-showRepo :: Github.Repo -> String
-showRepo = show . toGithubUserRepo
+repoWikiUrl :: GithubUserRepo -> String
+repoWikiUrl (GithubUserRepo user remote) =
+	"git://github.com/" ++ user ++ "/" ++ remote ++ ".wiki.git"
 
-runRepoWith :: (String -> String -> b -> IO (Either Github.Error v)) -> b -> Github.Repo -> IO (Maybe v)
-runRepoWith a b = run (\user repo -> a user repo b) . toGithubUserRepo
+-- A name for a github api call.
+type ApiName = String
 
-runRepo :: (String -> String -> IO (Either Github.Error v)) -> Github.Repo -> IO (Maybe v)
-runRepo a = run a . toGithubUserRepo
+-- A request to make of github. It may have an extra parameter.
+data Request = RequestSimple ApiName GithubUserRepo
+	| RequestNum ApiName GithubUserRepo Int
+	deriving (Eq, Show, Read, Ord)
 
-run :: (String -> String -> IO (Either Github.Error v)) -> GithubUserRepo -> IO (Maybe v)
-run a r@(GithubUserRepo user repo) = handle =<< a user repo
+requestRepo :: Request -> GithubUserRepo
+requestRepo (RequestSimple _ repo) = repo
+requestRepo (RequestNum _ repo _) = repo
+
+requestName :: Request -> String
+requestName (RequestSimple name _) = name
+requestName (RequestNum name _ _) = name
+
+data BackupState = BackupState
+	{ failedRequests :: S.Set Request
+	, retriedRequests :: S.Set Request
+	, backupRepo :: Git.Repo
+	}
+
+{- Our monad. -}
+newtype Backup a = Backup { runBackup :: StateT BackupState IO a }
+	deriving (
+		Monad,
+		MonadState BackupState,
+		MonadIO,
+		Functor,
+		Applicative
+	)
+
+inRepo :: (Git.Repo -> IO a) -> Backup a
+inRepo a = liftIO . a =<< getState backupRepo
+
+failedRequest :: Request -> Github.Error-> Backup ()
+failedRequest req e = unless (ignorable e) $ do
+	set <- getState failedRequests
+	changeState $ \s -> s { failedRequests = S.insert req set }
 	where
-		handle (Right v) = return $ Just v
-		handle (Left e) = do
-			hPutStrLn stderr $ "problem accessing API for " ++ show r ++ ": " ++ show e
-			return Nothing
+		ignorable (Github.JsonError m) =
+			"disabled for this repo" `isInfixOf` m
+		ignorable _ = False
 
-{- Finds already configured remotes that use github.
- - Fetches from them because it would be surprising if a github backup
- - didn't do so, and this is the only place the remote names are available. -}
-gitHubRemotes :: Git.Repo -> IO [Github.Repo]
-gitHubRemotes r = do
-	let (repos, remotes) = unzip  $ gitHubUserRepos r
-	when (null remotes) $ error "no github remotes found"
-	forM_ repos $ \repo ->
-		Git.Command.runBool "fetch" [Param $ fromJust $ Git.Types.remoteName repo] r
-	catMaybes <$> mapM (run Github.userRepo) remotes
+runRequest :: Request -> Backup ()
+runRequest req = do
+	-- avoid re-running requests that were already retried
+	retried <- getState retriedRequests
+	unless (S.member req retried) $
+		(lookupApi req) req
 
-gitHubUserRepos :: Git.Repo -> [(Git.Repo, GithubUserRepo)]
-gitHubUserRepos = mapMaybe check . Git.Types.remotes
+type Storer = Request -> Backup ()
+data ApiListItem = ApiListItem ApiName Storer Bool
+apiList :: [ApiListItem]
+apiList = 
+	[ ApiListItem "userrepo" userrepoStore True
+	, ApiListItem "watchers" watchersStore True
+	, ApiListItem "pullrequests" pullrequestsStore True
+	, ApiListItem "pullrequest" pullrequestStore False
+	, ApiListItem "milestones" milestonesStore True
+	, ApiListItem "issues" issuesStore True
+	, ApiListItem "issuecomments" issuecommentsStore False
+	-- comes last because it recurses on to the forks
+	, ApiListItem "forks" forksStore True
+	]
+
+{- Map of Github api calls we can make to store their data. -}
+api :: M.Map ApiName Storer
+api = M.fromList $ map (\(ApiListItem n s _) -> (n, s)) apiList
+
+{- List of toplevel api calls that are followed to get all data. -}
+toplevelApi :: [ApiName]
+toplevelApi = map (\(ApiListItem n _ _) -> n) $
+	filter (\(ApiListItem _ _ toplevel) -> toplevel) apiList
+
+lookupApi :: Request -> Storer
+lookupApi req = fromMaybe bad $ M.lookup name api
 	where
+		name = requestName req
+		bad = error $ "internal error: bad api call: " ++ name
+
+userrepoStore :: Storer
+userrepoStore = simpleHelper Github.userRepo $ \req r -> do
+	when (Github.repoHasWiki r == Just True) $
+		updateWiki $ toGithubUserRepo r
+	store "repo" req r
+
+watchersStore :: Storer
+watchersStore = simpleHelper Github.watchersFor $ storeSorted "watchers"
+
+pullrequestsStore :: Storer
+pullrequestsStore = simpleHelper Github.pullRequestsFor $
+	forValues $ \req r -> do
+		let repo = requestRepo req
+		let n = Github.pullRequestNumber r
+		runRequest $ RequestNum "pullrequest" repo n
+
+pullrequestStore :: Storer
+pullrequestStore = numHelper Github.pullRequest $ \n ->
+	store ("pullrequest" </> show n)
+
+milestonesStore :: Storer
+milestonesStore = simpleHelper Github.Issues.Milestones.milestones $
+	forValues $ \req m -> do
+		let n = Github.milestoneNumber m
+		store ("milestone" </> show n) req m
+
+issuesStore :: Storer
+issuesStore = withHelper Github.issuesForRepo [] $ forValues $ \req i -> do
+	let repo = requestRepo req
+	let n = Github.issueNumber i
+	store ("issue" </> show n) req i
+	runRequest (RequestNum "issuecomments" repo n)
+
+issuecommentsStore :: Storer
+issuecommentsStore = numHelper Github.Issues.Comments.comments $ \n ->
+	forValues $ \req c -> do
+		let i = Github.issueCommentId c
+		store ("issue" </> show n ++ "_comment" </> show i) req c
+
+forksStore :: Storer
+forksStore = simpleHelper Github.forksFor $ \req fs -> do
+	storeSorted "forks" req fs
+	mapM_ (traverse . toGithubUserRepo) fs
+	where
+		traverse fork = whenM (addFork fork) $
+			gatherMetaData fork
+
+forValues :: (Request -> v -> Backup ()) -> Request -> [v] -> Backup ()
+forValues handle req vs = forM_ vs (handle req)
+
+type ApiCall v = String -> String -> IO (Either Github.Error v)
+type ApiWith v b = String -> String -> b -> IO (Either Github.Error v)
+type ApiNum v = ApiWith v Int
+type Handler v = Request -> v -> Backup ()
+type Helper = Request -> Backup ()
+
+simpleHelper :: ApiCall v -> Handler v -> Helper
+simpleHelper call handle req@(RequestSimple _ (GithubUserRepo user repo)) =
+	either (failedRequest req) (handle req) =<< liftIO (call user repo)
+simpleHelper _ _ r = badRequest r
+
+withHelper :: ApiWith v b -> b -> Handler v -> Helper
+withHelper call b handle req@(RequestSimple _ (GithubUserRepo user repo)) =
+	either (failedRequest req) (handle req) =<< liftIO (call user repo b)
+withHelper _ _ _ r = badRequest r
+
+numHelper :: ApiNum v -> (Int -> Handler v) -> Helper
+numHelper call handle req@(RequestNum _ (GithubUserRepo user repo) num) =
+	either (failedRequest req) (handle num req) =<< liftIO (call user repo num)
+numHelper _ _ r = badRequest r
+
+badRequest :: Request -> a
+badRequest r = error $ "internal error: bad request type " ++ show r
+
+store :: Show a => FilePath -> Request -> a -> Backup ()
+store filebase req val = do
+	file <- location (requestRepo req) <$> workDir
+	liftIO $ do
+		createDirectoryIfMissing True (parentDir file)
+		writeFile file (ppShow val)
+	where
+		location (GithubUserRepo user repo) workdir =
+			workdir </> user ++ "_" ++ repo </> filebase
+
+workDir :: Backup FilePath
+workDir = (</>)
+		<$> (Git.gitDir <$> getState backupRepo)
+		<*> pure "github-backup.tmp"
+
+storeSorted :: Ord a => Show a => FilePath -> Request -> [a] -> Backup ()
+storeSorted file req val = store file req (sort val)
+
+gitHubRepos :: Backup [Git.Repo]
+gitHubRepos = fst . unzip . gitHubPairs <$> getState backupRepo
+
+gitHubRemotes :: Backup [GithubUserRepo]
+gitHubRemotes = snd . unzip . gitHubPairs <$> getState backupRepo
+
+gitHubPairs :: Git.Repo -> [(Git.Repo, GithubUserRepo)]
+gitHubPairs = filter (not . wiki ) . mapMaybe check . Git.Types.remotes
+	where
 		check r@Git.Repo { Git.Types.location = Git.Types.Url u } =
 			headMaybe $ mapMaybe (checkurl r $ show u) gitHubUrlPrefixes
 		check _ = Nothing
 		checkurl r u prefix
 			| prefix `isPrefixOf` u && length bits == 2 =
-				Just $ (r,
+				Just (r,
 					GithubUserRepo (bits !! 0)
 						(dropdotgit $ bits !! 1))
 			| otherwise = Nothing
@@ -100,6 +249,7 @@
 		dropdotgit s
 			| ".git" `isSuffixOf` s = take (length s - length ".git") s
 			| otherwise = s
+		wiki (_, GithubUserRepo _ u) = ".wiki" `isSuffixOf` u
 
 {- All known prefixes for urls to github repos. -}
 gitHubUrlPrefixes :: [String]
@@ -111,133 +261,209 @@
 	, "ssh://git@github.com/~/"
 	]
 
-{- Recursively look for forks, and forks of forks, of the repos.
- - Note that this guards against cycles in the fork tree, although
- - presumably that should never happen. -}
-findForks :: [Github.Repo] -> IO [Github.Repo]
-findForks = findForks' []
-findForks' :: [Github.Repo] -> [Github.Repo] -> IO [Github.Repo]
-findForks' _ [] = return []
-findForks' done rs = do
-	forks <- filter new . concat . catMaybes <$>
-		mapM (runRepo Github.forksFor) rs
-	forks' <- findForks' (done++rs) forks
-	return $ nub $ forks ++ forks'
-	where
-		new r = not $ r `elem` rs || r `elem` done
-
-{- Adds new forks, fetching from them. -}
-addForks :: Git.Repo -> [Github.Repo] -> IO ()
-addForks _ [] = putStrLn "no new forks"
-addForks r forks = do 
-	putStrLn $ "new forks: " ++ unwords (map showRepo forks)
-	forM_ forks $ \fork -> do
-		Git.Command.runBool "remote"
-			[ Param "add"
-			, Param "-f"
-			, Param $ remoteFor fork
-			, Param $ Github.repoGitUrl fork
-			] r
-
 onGithubBranch :: Git.Repo -> IO () -> IO ()
 onGithubBranch r a = bracket prep cleanup (const a)
 	where
 		prep = do
 			oldbranch <- Git.Branch.current r
-			exists <- Git.Ref.matching (Git.Ref $ "refs/heads/" ++ branchname) r
+			when (oldbranch == Just branchref) $
+				error $ "it's not currently safe to run github-backup while the " ++
+					branchname ++ " branch is checked out!"
+			exists <- Git.Ref.matching branchref r
 			if null exists
-				then Git.Command.run "checkout"
-					[Param "--orphan", Param branchname] r
-				else Git.Command.run "checkout"
-					[Param branchname] r
+				then checkout [Param "--orphan", Param branchname]
+				else checkout [Param branchname]
 			return oldbranch
 		cleanup Nothing = return ()
 		cleanup (Just oldbranch)
 			| name == branchname = return ()
-			| otherwise = Git.Command.run "checkout" [Param name] r
+			| 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
 
-commitFiles :: Git.Repo -> [FilePath] -> IO ()
-commitFiles _ [] = return ()
-commitFiles r files = do
-	mass "add" [Param "-f"]
-	mass "commit" [Param "-m", Param "github-backup"]
-	where
-		mass subcommand params = do
-			let q = Git.Queue.add Git.Queue.new subcommand params files
-			_ <- Git.Queue.flush q r
+{- Commits all files in the workDir into git, and deletes it. -}
+commitWorkDir :: Backup ()
+commitWorkDir = do
+	dir <- workDir
+	r <- getState backupRepo
+	let git_false_worktree ps = boolSystem "git" $
+		[ Param ("--work-tree=" ++ dir)
+		, Param ("--git-dir=" ++ Git.gitDir 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
+
+updateWiki :: GithubUserRepo -> Backup ()
+updateWiki fork = do
+	remotes <- Git.remotes <$> getState backupRepo
+	if any (\r -> Git.remoteName r == Just remote) remotes
+		then do
+			_ <- fetchwiki
 			return ()
+		else do
+			-- github often does not really have a wiki,
+			-- don't bloat config if there is none
+			unlessM (addRemote remote $ repoWikiUrl fork) $
+				removeRemote remote
+			return ()
+	where
+		fetchwiki = inRepo $ Git.Command.runBool "fetch" [Param remote]
+		remote = remoteFor fork
+		remoteFor (GithubUserRepo user repo) =
+			"github_" ++ user ++ "_" ++ repo ++ ".wiki"
 
-saveMetaData :: Show a => Github.Repo -> String -> a -> IO FilePath
-saveMetaData repo file val = do
-	let file' = remoteFor repo </> file
-	createDirectoryIfMissing True (parentDir file')
-	writeFile file' (ppShow val)
-	return file'
+addFork :: GithubUserRepo -> Backup Bool
+addFork fork = do
+	remotes <- gitHubRemotes
+	if fork `elem` remotes
+		then return False
+		else do
+			liftIO $ putStrLn $ "New fork: " ++ repoUrl fork
+			_ <- addRemote (remoteFor fork) (repoUrl fork)
+			return True
+	where
+		remoteFor (GithubUserRepo user repo) =
+			"github_" ++ user ++ "_" ++ repo
 
-gatherMetaData :: Github.Repo -> IO [FilePath]
-gatherMetaData repo = do
-	putStrLn $ "gathering metadata for " ++ showRepo repo
-	concat <$> mapM (\a -> a repo)
-		[ pullRequests
-		, watchers
-		, issues
-		, milestones
+{- Adds a remote, also fetching from it. -}
+addRemote :: String -> String -> Backup Bool
+addRemote remotename remoteurl =
+	inRepo $ Git.Command.runBool "remote"
+		[ Param "add"
+		, Param "-f"
+		, Param remotename
+		, Param remoteurl
 		]
 
-pullRequests :: Github.Repo -> IO [FilePath]
-pullRequests repo = runRepo Github.pullRequestsFor repo >>= collect
-	where
-		collect Nothing = return []
-		collect (Just rs) = forM rs $ \r -> do
-			let n = Github.pullRequestNumber r
-			details <- runRepoWith Github.pullRequest n repo
-			saveMetaData repo ("pullrequest" </> show n) details
+removeRemote :: String -> Backup ()
+removeRemote remotename = do
+	_ <- inRepo $ Git.Command.runBool "remote"
+		[ Param "rm"
+		, Param remotename
+		]
+	return ()
 
-watchers :: Github.Repo -> IO [FilePath]
-watchers repo = runRepo Github.watchersFor repo >>= collect
-	where
-		collect Nothing = return []
-		collect (Just ws) = do
-			f <- saveMetaData repo "watchers" $ sort ws
-			return [f]
+{- Fetches from the github remote. Done by github-backup, just because
+ - 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]
 
-issues :: Github.Repo -> IO [FilePath]
-issues repo = runRepoWith Github.issuesForRepo [] repo >>= collect
+{- Gathers metadata for the repo. Retuns a list of files written
+ - and a list that may contain requests that need to be retried later. -}
+gatherMetaData :: GithubUserRepo -> Backup ()
+gatherMetaData repo = do
+	liftIO $ putStrLn $ "Gathering metadata for " ++ repoUrl repo ++ " ..."
+	mapM_ call toplevelApi
 	where
-		collect Nothing = return []
-		collect (Just is) = concat <$> forM is get
-		get i = do
-			let n = Github.issueNumber i
-			f <- saveMetaData repo ("issue" </> show n) i
-			fs <- issueComments n repo
-			return $ f:fs
+		call name = runRequest $ RequestSimple name repo
 
-issueComments :: Int -> Github.Repo -> IO [FilePath]
-issueComments n repo = runRepoWith Github.Issues.Comments.comments n repo >>= collect
+storeRetry :: [Request] -> Git.Repo -> IO ()
+storeRetry [] r = do
+	_ <- try $ removeFile (retryFile r) :: IO (Either SomeException ()) 
+	return ()
+storeRetry retryrequests r = writeFile (retryFile r) (show retryrequests)
+
+loadRetry :: Git.Repo -> IO [Request]
+loadRetry r = maybe [] (fromMaybe [] . readish)
+	<$> catchMaybeIO (readFileStrict (retryFile r))
+
+retryFile :: Git.Repo -> FilePath
+retryFile r = Git.gitDir r </> "github-backup.todo"
+
+retry :: Backup (S.Set Request)
+retry = do
+	todo <- inRepo loadRetry
+	unless (null todo) $ do
+		liftIO $ putStrLn $
+			"Retrying " ++ show (length todo) ++
+			" requests that failed last time..."
+		mapM_ runRequest todo
+	retriedfailed <- getState failedRequests
+	changeState $ \s -> s
+		{ failedRequests = S.empty
+		, retriedRequests = S.fromList todo
+		}
+	return retriedfailed
+
+summarizeRequests :: [Request] -> [String]
+summarizeRequests = go M.empty
 	where
-		collect Nothing = return []
-		collect (Just cs) = forM cs $ \c -> do
-			let i = Github.issueCommentId c
-			saveMetaData repo ("issue" </> show n ++ "_comment" </> show i) c
+		go m [] = map format $ sort $ map swap $ M.toList m
+		go m (r:rs) = go (M.insertWith (+) (requestName r) (1 :: Integer) m) rs
+		format (num, name) = show num ++ "\t" ++ name
+		swap (a, b) = (b, a)
 
-milestones :: Github.Repo -> IO [FilePath]
-milestones repo = runRepo Github.Issues.Milestones.milestones repo >>= collect
+{- Save all backup data. Files that were written to the workDir are committed.
+ - Requests that failed are saved for next time. Requests that were retried
+ - this time and failed are ordered last, to ensure that we don't get stuck
+ - retrying the same requests and not making progress when run again.
+ -}
+save :: S.Set Request -> Backup ()
+save retriedfailed = do
+	commitWorkDir
+	failed <- getState failedRequests
+	let toretry = S.toList failed ++ S.toList retriedfailed
+	inRepo $ storeRetry toretry
+	unless (null toretry) $
+		error $ unlines $
+			["Backup may be incomplete; " ++ 
+				show (length toretry) ++ " requests failed:"
+			] ++ map ("  " ++) (summarizeRequests toretry) ++ 
+			[ "Run again later."
+			]
+
+newState :: Git.Repo -> BackupState
+newState = BackupState S.empty S.empty
+
+backup :: Git.Repo -> IO ()
+backup repo = evalStateT (runBackup go) . newState =<< Git.Config.read repo
 	where
-		collect Nothing = return []
-		collect (Just ms) = forM ms $ \m -> do
-			let n = Github.milestoneNumber m
-			saveMetaData repo ("milestone" </> show n) m
+		go = do
+			retriedfailed <- retry
+			remotes <- gitHubPairs <$> getState backupRepo
+			when (null remotes) $
+				error "no github remotes found"
+			forM_ remotes $ \(r, remote) -> do
+				_ <- fetchRepo r
+				gatherMetaData remote
+			save retriedfailed
 
+backupUser :: String -> IO ()
+backupUser username = do
+	repos <- either (error . show) id <$>
+		Github.userRepos username Github.All
+	when (null repos) $
+		error $ "No GitHub repositories found for user " ++ username
+	status <- forM repos $ \repo -> do
+		let dir = Github.repoName repo
+		unlessM (doesDirectoryExist dir) $ do
+			putStrLn $ "New repository: " ++ dir
+			ok <- boolSystem "git"
+				[ Param "clone"
+				, Param (Github.repoGitUrl repo)
+				, Param dir
+				]
+			unless ok $ error "clone failed"
+		try (backup =<< Git.Construct.fromPath dir)
+			:: IO (Either SomeException ())
+	unless (null $ lefts status) $
+		error "Failed to successfully back everything up. Run again later."
+
+usage :: String
+usage = "usage: github-backup [username]"
+
 main :: IO ()
-main = do
-	r <- Git.Config.read =<< getLocalRepo =<< getArgs
-	changeWorkingDirectory $ Git.repoLocation r
-	remotes <- gitHubRemotes r
-	forks <- findForks remotes
-	addForks r forks
-	onGithubBranch r $
-		concat <$> forM (forks ++ remotes) gatherMetaData
-			>>= commitFiles r
+main = getArgs >>= go
+	where
+		go [] = backup =<< Git.Construct.fromCwd
+		go (username:[]) = backupUser username
+		go _= error usage
