github-backup 1.20131006 → 1.20131101
raw patch · 7 files changed
+93/−32 lines, 7 files
Files
- Makefile +6/−1
- README.md +15/−5
- debian/changelog +14/−0
- debian/control +1/−0
- github-backup.1 +7/−1
- github-backup.cabal +1/−1
- github-backup.hs +49/−24
Makefile view
@@ -4,6 +4,7 @@ build: Build/SysConfig.hs $(CABAL) build ln -sf dist/build/github-backup/github-backup github-backup+ @$(MAKE) tags >/dev/null 2>&1 & Build/SysConfig.hs: configure.hs Build/TestConfig.hs Build/Configure.hs if [ "$(CABAL)" = ./Setup ]; then ghc --make Setup; fi@@ -16,7 +17,7 @@ install -m 0644 github-backup.1 $(DESTDIR)$(PREFIX)/share/man/man1 clean:- rm -rf github-backup dist configure Build/SysConfig.hs Setup+ rm -rf github-backup dist configure Build/SysConfig.hs Setup tags find -name \*.o -exec rm {} \; find -name \*.hi -exec rm {} \; @@ -24,3 +25,7 @@ hackage: clean ./make-sdist.sh @cabal upload dist/*.tar.gz++# hothasktags chokes on some template haskell etc, so ignore errors+tags:+ find . | grep -v /.git/ | grep -v /tmp/ | grep -v /dist/ | grep -v /doc/ | egrep '\.hs$$' | xargs hothasktags > tags 2>/dev/null
README.md view
@@ -23,7 +23,9 @@ from GitHub to back up that repository. Or, run `github-backup username` to clone and back up all of a GitHub- user's repositories. (Also works for organization names.)+ user's repositories, as well as all the repositories watched by that+ user. + (Also works for organization names.) ## Why backup GitHub repositories @@ -33,6 +35,10 @@ keeping your data in the cloud *and* relying on the cloud to back it up is foolish. +* In case someone takes down a repository that you were interested in.+ If you run github-backup with your username, it will back up all + the repositories you have watched.+ * So you can keep working on your repository while on a plane, or on a remote beach or mountaintop. Just like Linus intended. @@ -60,7 +66,7 @@ information from GitHub. In particular, social network stuff, like users who are following you, is not backed up. -github-backup does not log into git, so it cannot backup private+github-backup does not log into GitHub, so it cannot backup private repositories. github-backup will find and backup forks of a repository, and all forks@@ -82,9 +88,13 @@ 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.+API to some small number of requests per hour when used without+authentication. To avoid this limit, you can set `GITHUB_USER` and+`GITHUB_PASSWORD` in the environment and it will log in when making +(most) API requests.++Anyway, 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
debian/changelog view
@@ -1,3 +1,17 @@+github-backup (1.20131101) unstable; urgency=low++ * Now also backs up the repos a user is watching, when run with a user's+ name. Useful if you want to back up repositories that you have not forked;+ just watch them and run github-backup.+ * Can now log in to github, to avoid increasingly small API rate limits.+ Set GITHUB_USER and GITHUB_PASSWORD environment to enable.+ Note that a few api calls don't use authentication; see+ https://github.com/fpco/github/issues/40+ * Build-Depend on git. Closes: #728481+ * Don't include tmp directory in files stored in the github branch.++ -- Joey Hess <joeyh@debian.org> Fri, 01 Nov 2013 18:00:16 -0400+ github-backup (1.20131006) unstable; urgency=low * Ported to Windows.
debian/control view
@@ -4,6 +4,7 @@ Build-Depends: debhelper (>= 9), ghc,+ git, libghc-github-dev (>= 0.5.1), libghc-missingh-dev, libghc-hslogger-dev,
github-backup.1 view
@@ -12,7 +12,13 @@ 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. (Also works to pass+out, and back up, all that user's repositories, as well as all the+repositories that user is watching. (Also works to pass the name of an organization using GitHub.)+.PP+By default it runs without logging in to GitHub. To log in, set+GITHUB_USER and GITHUB_PASSWORD environment variables. However note that+this only works around API rate limiting, it does not allow private+repositories to be downloaded. .SH AUTHOR Joey Hess <joey@kitenet.net>
github-backup.cabal view
@@ -1,5 +1,5 @@ Name: github-backup-Version: 1.20131006+Version: 1.20131101 Cabal-Version: >= 1.6 License: GPL Maintainer: Joey Hess <joey@kitenet.net>
github-backup.hs view
@@ -1,6 +1,6 @@ {- github-backup -- - Copyright 2012 Joey Hess <joey@kitenet.net>+ - Copyright 2012-2013 Joey Hess <joey@kitenet.net> - - Licensed under the GNU GPL version 3 or higher. -}@@ -12,9 +12,11 @@ import qualified Data.Map as M import qualified Data.Set as S+import qualified Data.Text as T+import Data.Text.Encoding (encodeUtf8) import Data.Either import Data.Monoid-import System.Environment+import System.Environment (getArgs) import Control.Exception (try, SomeException) import Text.Show.Pretty import "mtl" Control.Monad.State.Strict@@ -40,6 +42,7 @@ import qualified Git.UpdateIndex import Git.HashObject import Git.FilePath+import Utility.Env -- A github user and repo. data GithubUserRepo = GithubUserRepo String String@@ -78,6 +81,7 @@ { failedRequests :: S.Set Request , retriedRequests :: S.Set Request , gitRepo :: Git.Repo+ , gitHubAuth :: Maybe Github.GithubAuth } {- Our monad. -}@@ -139,36 +143,37 @@ bad = error $ "internal error: bad api call: " ++ name userrepoStore :: Storer-userrepoStore = simpleHelper Github.userRepo $ \req r -> do+userrepoStore = simpleHelper (noAuth 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"+watchersStore = simpleHelper (noAuth Github.watchersFor) $+ storeSorted "watchers" pullrequestsStore :: Storer-pullrequestsStore = simpleHelper Github.pullRequestsFor $+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 ->+pullrequestStore = numHelper Github.pullRequest' $ \n -> store ("pullrequest" </> show n) milestonesStore :: Storer-milestonesStore = simpleHelper Github.Issues.Milestones.milestones $+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 (\u r y ->- Github.issuesForRepo u r (y <> [Github.Open])+issuesStore = withHelper (\a u r y ->+ Github.issuesForRepo' a u r (y <> [Github.Open]) >>= either (return . Left)- (\xs -> Github.issuesForRepo u r+ (\xs -> Github.issuesForRepo' a u r (y <> [Github.OnlyClosed]) >>= either (return . Left) (\ys -> return (Right (xs <> ys)))))@@ -181,13 +186,13 @@ runRequest (RequestNum "issuecomments" repo n) issuecommentsStore :: Storer-issuecommentsStore = numHelper Github.Issues.Comments.comments $ \n ->+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+forksStore = simpleHelper (noAuth Github.forksFor) $ \req fs -> do storeSorted "forks" req fs mapM_ (traverse . toGithubUserRepo) fs where@@ -197,25 +202,31 @@ 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 ApiCall v = Maybe Github.GithubAuth -> String -> String -> IO (Either Github.Error v)+type ApiWith v b = Maybe Github.GithubAuth -> String -> String -> b -> IO (Either Github.Error v) type ApiNum v = ApiWith v Int type Handler v = Request -> v -> Backup () type Helper = Request -> Backup () +noAuth :: (String -> String -> IO a) -> Maybe Github.GithubAuth -> String -> String -> IO a+noAuth a _auth user repo = a user repo+ simpleHelper :: ApiCall v -> Handler v -> Helper-simpleHelper call handle req@(RequestSimple _ (GithubUserRepo user repo)) =- either (failedRequest req) (handle req) =<< liftIO (call user repo)+simpleHelper call handle req@(RequestSimple _ (GithubUserRepo user repo)) = do+ auth <- getState gitHubAuth+ either (failedRequest req) (handle req) =<< liftIO (call auth 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 call b handle req@(RequestSimple _ (GithubUserRepo user repo)) = do+ auth <- getState gitHubAuth+ either (failedRequest req) (handle req) =<< liftIO (call auth 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 call handle req@(RequestNum _ (GithubUserRepo user repo) num) = do+ auth <- getState gitHubAuth+ either (failedRequest req) (handle num req) =<< liftIO (call auth user repo num) numHelper _ _ r = badRequest r badRequest :: Request -> a@@ -306,7 +317,7 @@ fs <- filter (not . dirCruft) <$> dirContentsRecursive dir forM_ fs $ \f -> do sha <- hashFile h f- path <- toTopFilePath f r+ let path = asTopFilePath (relPathDirToFile dir f) streamer $ Git.UpdateIndex.updateIndexLine sha Git.Types.FileBlob path @@ -464,12 +475,25 @@ [ "Run again later." ] -newState :: Git.Repo -> BackupState-newState = BackupState S.empty S.empty+newState :: Git.Repo -> IO BackupState+newState r = BackupState+ <$> pure S.empty+ <*> pure S.empty+ <*> pure r+ <*> getauth+ where+ getauth = do+ user <- getEnv "GITHUB_USER"+ password <- getEnv "GITHUB_PASSWORD"+ return $ case (user, password) of+ (Just u, Just p) -> Just $ + Github.GithubBasicAuth (tobs u) (tobs p)+ _ -> Nothing+ tobs = encodeUtf8 . T.pack 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+backupRepo (Just repo) = evalStateT (runBackup go) =<< newState =<< Git.Config.read repo where go = do retriedfailed <- retry@@ -485,6 +509,7 @@ backupName name = do l <- sequence [ Github.userRepos name Github.All+ , Github.reposWatchedBy name , Github.organizationRepos name ] let repos = concat $ rights l