github-backup 1.20141204 → 1.20141222
raw patch · 18 files changed
+319/−114 lines, 18 filesdep ~basedep ~githubdep ~networknew-component:exe:gitriddance
Dependency ranges changed: base, github, network
Files
- Git/Branch.hs +3/−0
- Git/DiffTreeItem.hs +24/−0
- Git/UpdateIndex.hs +13/−2
- Git/Version.hs +7/−26
- Github/EnumRepos.hs +63/−0
- Github/GetAuth.hs +24/−0
- Makefile +4/−3
- Utility/DottedVersion.hs +36/−0
- Utility/Path.hs +2/−1
- Utility/Process.hs +8/−5
- Utility/Tmp.hs +8/−8
- debian/changelog +11/−0
- debian/control +4/−1
- debian/copyright +1/−1
- github-backup.cabal +19/−1
- github-backup.hs +3/−66
- gitriddance.1 +26/−0
- gitriddance.hs +63/−0
Git/Branch.hs view
@@ -43,6 +43,9 @@ | null l = Nothing | otherwise = Just $ Git.Ref l +currentSha :: Repo -> IO (Maybe Git.Sha)+currentSha r = maybe (pure Nothing) (`Git.Ref.sha` r) =<< current r+ {- Checks if the second branch has any commits not present on the first - branch. -} changed :: Branch -> Branch -> Repo -> IO Bool
+ Git/DiffTreeItem.hs view
@@ -0,0 +1,24 @@+{- git diff-tree item+ -+ - Copyright 2012 Joey Hess <joey@kitenet.net>+ -+ - Licensed under the GNU GPL version 3 or higher.+ -}++module Git.DiffTreeItem (+ DiffTreeItem(..),+) where++import System.Posix.Types++import Git.FilePath+import Git.Types++data DiffTreeItem = DiffTreeItem+ { srcmode :: FileMode+ , dstmode :: FileMode+ , srcsha :: Sha -- nullSha if file was added+ , dstsha :: Sha -- nullSha if file was deleted+ , status :: String+ , file :: TopFilePath+ } deriving Show
Git/UpdateIndex.hs view
@@ -19,7 +19,8 @@ updateIndexLine, stageFile, unstageFile,- stageSymlink+ stageSymlink,+ stageDiffTreeItem, ) where import Common@@ -28,6 +29,7 @@ import Git.Command import Git.FilePath import Git.Sha+import qualified Git.DiffTreeItem as Diff {- Streamers are passed a callback and should feed it lines in the form - read by update-index, and generated by ls-tree. -}@@ -95,8 +97,11 @@ unstageFile :: FilePath -> Repo -> IO Streamer unstageFile file repo = do p <- toTopFilePath file repo- return $ pureStreamer $ "0 " ++ fromRef nullSha ++ "\t" ++ indexPath p+ return $ unstageFile' p +unstageFile' :: TopFilePath -> Streamer+unstageFile' p = pureStreamer $ "0 " ++ fromRef nullSha ++ "\t" ++ indexPath p+ {- A streamer that adds a symlink to the index. -} stageSymlink :: FilePath -> Sha -> Repo -> IO Streamer stageSymlink file sha repo = do@@ -105,6 +110,12 @@ <*> pure SymlinkBlob <*> toTopFilePath file repo return $ pureStreamer line++{- A streamer that applies a DiffTreeItem to the index. -}+stageDiffTreeItem :: Diff.DiffTreeItem -> Streamer+stageDiffTreeItem d = case toBlobType (Diff.dstmode d) of+ Nothing -> unstageFile' (Diff.file d)+ Just t -> pureStreamer $ updateIndexLine (Diff.dstsha d) t (Diff.file d) indexPath :: TopFilePath -> InternalGitPath indexPath = toInternalGitPath . getTopFilePath
Git/Version.hs view
@@ -5,18 +5,16 @@ - Licensed under the GNU GPL version 3 or higher. -} -module Git.Version where+module Git.Version (+ installed,+ normalize,+ GitVersion,+) where import Common--data GitVersion = GitVersion String Integer- deriving (Eq)--instance Ord GitVersion where- compare (GitVersion _ x) (GitVersion _ y) = compare x y+import Utility.DottedVersion -instance Show GitVersion where- show (GitVersion s _) = s+type GitVersion = DottedVersion installed :: IO GitVersion installed = normalize . extract <$> readProcess "git" ["--version"]@@ -24,20 +22,3 @@ 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
+ Github/EnumRepos.hs view
@@ -0,0 +1,63 @@+module Github.EnumRepos where++import qualified Github.Repos as Github+import Data.List+import Data.List.Utils+import Data.Maybe++import Utility.PartialPrelude+import qualified Git+import qualified Git.Types++-- A github user and repo.+data GithubUserRepo = GithubUserRepo String String+ deriving (Eq, Show, Read, Ord)++class ToGithubUserRepo a where+ toGithubUserRepo :: a -> GithubUserRepo++instance ToGithubUserRepo Github.Repo where+ toGithubUserRepo r = GithubUserRepo + (Github.githubOwnerLogin $ Github.repoOwner r)+ (Github.repoName r)++instance ToGithubUserRepo Github.RepoRef where+ toGithubUserRepo (Github.RepoRef owner name) = + GithubUserRepo (Github.githubOwnerLogin owner) name++gitHubRepos :: Git.Repo -> [Git.Repo]+gitHubRepos = fst . unzip . gitHubPairs++gitHubRemotes :: Git.Repo -> [GithubUserRepo]+gitHubRemotes = snd . unzip . gitHubPairs++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,+ GithubUserRepo (bits !! 0)+ (dropdotgit $ bits !! 1))+ | otherwise = Nothing+ where+ rest = drop (length prefix) u+ bits = filter (not . null) $ split "/" rest+ 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]+gitHubUrlPrefixes = + [ "git@github.com:"+ , "git://github.com/"+ , "https://github.com/"+ , "http://github.com/"+ , "ssh://git@github.com/~/"+ ]+
+ Github/GetAuth.hs view
@@ -0,0 +1,24 @@+{-# LANGUAGE CPP #-}++module Github.GetAuth where++import Utility.Env++#if MIN_VERSION_github(0,9,0)+import qualified Github.Auth as Github+#else+import qualified Github.Issues as Github+#endif+import Data.Text.Encoding (encodeUtf8)+import qualified Data.Text as T++getAuth :: IO (Maybe Github.GithubAuth)+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+ where+ tobs = encodeUtf8 . T.pack
Makefile view
@@ -4,6 +4,7 @@ build: Build/SysConfig.hs $(CABAL) build ln -sf dist/build/github-backup/github-backup github-backup+ ln -sf dist/build/gitriddance/gitriddance gitriddance @$(MAKE) tags >/dev/null 2>&1 & Build/SysConfig.hs: configure.hs Build/TestConfig.hs Build/Configure.hs@@ -12,12 +13,12 @@ install: build install -d $(DESTDIR)$(PREFIX)/bin- install github-backup $(DESTDIR)$(PREFIX)/bin+ install github-backup gitriddance $(DESTDIR)$(PREFIX)/bin install -d $(DESTDIR)$(PREFIX)/share/man/man1- install -m 0644 github-backup.1 $(DESTDIR)$(PREFIX)/share/man/man1+ install -m 0644 github-backup.1 gitriddance.1 $(DESTDIR)$(PREFIX)/share/man/man1 clean:- rm -rf github-backup dist configure Build/SysConfig.hs Setup tags+ rm -rf github-backup gitriddance dist configure Build/SysConfig.hs Setup tags find -name \*.o -exec rm {} \; find -name \*.hi -exec rm {} \;
+ Utility/DottedVersion.hs view
@@ -0,0 +1,36 @@+{- dotted versions, such as 1.0.1+ -+ - Copyright 2011-2014 Joey Hess <joey@kitenet.net>+ -+ - License: BSD-2-clause+ -}++module Utility.DottedVersion where++import Common++data DottedVersion = DottedVersion String Integer+ deriving (Eq)++instance Ord DottedVersion where+ compare (DottedVersion _ x) (DottedVersion _ y) = compare x y++instance Show DottedVersion where+ show (DottedVersion s _) = s++{- To compare dotted versions like 1.7.7 and 1.8, they are normalized to+ - a somewhat arbitrary integer representation. -}+normalize :: String -> DottedVersion+normalize v = DottedVersion 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
Utility/Path.hs view
@@ -267,7 +267,8 @@ - sane FilePath. - - All spaces and punctuation and other wacky stuff are replaced- - with '_', except for '.' "../" will thus turn into ".._", which is safe.+ - with '_', except for '.'+ - "../" will thus turn into ".._", which is safe. -} sanitizeFilePath :: String -> FilePath sanitizeFilePath = map sanitize
Utility/Process.hs view
@@ -13,6 +13,7 @@ CreateProcess(..), StdHandle(..), readProcess,+ readProcess', readProcessEnv, writeReadProcessEnv, forceSuccessProcess,@@ -66,16 +67,18 @@ readProcess cmd args = readProcessEnv cmd args Nothing readProcessEnv :: FilePath -> [String] -> Maybe [(String, String)] -> IO String-readProcessEnv cmd args environ =- withHandle StdoutHandle createProcessSuccess p $ \h -> do- output <- hGetContentsStrict h- hClose h- return output+readProcessEnv cmd args environ = readProcess' p where p = (proc cmd args) { std_out = CreatePipe , env = environ }++readProcess' :: CreateProcess -> IO String+readProcess' p = withHandle StdoutHandle createProcessSuccess p $ \h -> do+ output <- hGetContentsStrict h+ hClose h+ return output {- Runs an action to write to a process on its stdin, - returns its output, and also allows specifying the environment.
Utility/Tmp.hs view
@@ -24,8 +24,8 @@ {- 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 = bracket setup cleanup use+viaTmp :: (MonadMask m, MonadIO m) => (FilePath -> String -> m ()) -> FilePath -> String -> m ()+viaTmp a file content = bracketIO setup cleanup use where (dir, base) = splitFileName file template = base ++ ".tmp"@@ -36,9 +36,9 @@ _ <- tryIO $ hClose h tryIO $ removeFile tmpfile use (tmpfile, h) = do- hClose h+ liftIO $ hClose h a tmpfile content- rename tmpfile file+ liftIO $ rename 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. -}@@ -61,15 +61,15 @@ {- 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 :: (MonadMask m, MonadIO m) => Template -> (FilePath -> m a) -> m a withTmpDir template a = do- tmpdir <- catchDefaultIO "." getTemporaryDirectory+ tmpdir <- liftIO $ 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+withTmpDirIn :: (MonadMask m, MonadIO m) => FilePath -> Template -> (FilePath -> m a) -> m a+withTmpDirIn tmpdir template = bracketIO create remove where remove d = whenM (doesDirectoryExist d) $ do #if mingw32_HOST_OS
debian/changelog view
@@ -1,3 +1,14 @@+github-backup (1.20141222) unstable; urgency=medium++ * Added gitriddance(1), a utility to close all issues and pull requests,+ for repos that don't want to be bothered with GitHub's proprietary+ issue tracker.+ * gitriddance depends on github 0.13.1, which has bug fixes+ for posting comments.+ * Various updates to internal git and utility libraries shared with git-annex.++ -- Joey Hess <id@joeyh.name> Mon, 22 Dec 2014 15:30:08 -0400+ github-backup (1.20141204) unstable; urgency=high * Fix broken argument parser for the username|organization parameter.
debian/control view
@@ -5,7 +5,7 @@ debhelper (>= 9), ghc, git,- libghc-github-dev (>= 0.7.2),+ libghc-github-dev (>= 0.13.1), libghc-missingh-dev, libghc-hslogger-dev, libghc-pretty-show-dev,@@ -28,3 +28,6 @@ GitHub. It backs up everything GitHub publishes about the repository, including other forks, issues, comments, wikis, milestones, pull requests, and watchers.+ .+ Also includes gitriddance, which can be used to close all open issues and+ pull requests.
debian/copyright view
@@ -2,7 +2,7 @@ Source: native package Files: *-Copyright: © 2010-2013 Joey Hess <joey@kitenet.net>+Copyright: © 2010-2014 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
github-backup.cabal view
@@ -1,5 +1,5 @@ Name: github-backup-Version: 1.20141204+Version: 1.20141222 Cabal-Version: >= 1.8 License: GPL Maintainer: Joey Hess <joey@kitenet.net>@@ -16,6 +16,9 @@ Github. It backs up everything Github knows about the repository, including other forks, issues, comments, milestones, pull requests, and watchers. + Also includes gitriddance, which can be used to close all open issues and+ pull requests.+ Flag network-uri Description: Get Network.URI from the network-uri package Default: True@@ -28,7 +31,22 @@ IfElse, pretty-show, text, process, optparse-applicative, github >= 0.7.2, base >= 4.5, base < 5+ + if (! os(windows))+ Build-Depends: unix+ + if flag(network-uri)+ Build-Depends: network-uri (>= 2.6), network (>= 2.6)+ else+ Build-Depends: network (< 2.6), network (>= 2.0) +Executable gitriddance+ Main-Is: gitriddance.hs+ GHC-Options: -Wall+ Build-Depends: github >= 0.13.1, base >= 4.5, base < 5, text, filepath,+ MissingH, exceptions, transformers, bytestring, hslogger, process,+ containers, unix-compat, IfElse, directory, mtl+ if (! os(windows)) Build-Depends: unix
github-backup.hs view
@@ -13,8 +13,6 @@ 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 Options.Applicative@@ -43,27 +41,13 @@ import qualified Git.Ref import qualified Git.Branch import qualified Git.UpdateIndex+import Github.GetAuth+import Github.EnumRepos import Git.HashObject import Git.FilePath import Git.CatFile import Utility.Env --- A github user and repo.-data GithubUserRepo = GithubUserRepo String String- deriving (Eq, Show, Read, Ord)--class ToGithubUserRepo a where- toGithubUserRepo :: a -> GithubUserRepo--instance ToGithubUserRepo Github.Repo where- toGithubUserRepo r = GithubUserRepo - (Github.githubOwnerLogin $ Github.repoOwner r)- (Github.repoName r)--instance ToGithubUserRepo Github.RepoRef where- toGithubUserRepo (Github.RepoRef owner name) = - GithubUserRepo (Github.githubOwnerLogin owner) name- repoUrl :: GithubUserRepo -> String repoUrl (GithubUserRepo user remote) = "git://github.com/" ++ user ++ "/" ++ remote ++ ".git"@@ -295,42 +279,6 @@ 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 gitRepo--gitHubRemotes :: Backup [GithubUserRepo]-gitHubRemotes = snd . unzip . gitHubPairs <$> getState gitRepo--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,- GithubUserRepo (bits !! 0)- (dropdotgit $ bits !! 1))- | otherwise = Nothing- where- rest = drop (length prefix) u- bits = filter (not . null) $ split "/" rest- 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]-gitHubUrlPrefixes = - [ "git@github.com:"- , "git://github.com/"- , "https://github.com/"- , "http://github.com/"- , "ssh://git@github.com/~/"- ]- {- Commits all files in the workDir into the github branch, and deletes the - workDir. -@@ -421,7 +369,7 @@ "github_" ++ user ++ "_" ++ repo ++ ".wiki" addFork :: ToGithubUserRepo a => a -> Backup ()-addFork forksource = unlessM (elem fork <$> gitHubRemotes) $ do+addFork forksource = unlessM (elem fork . gitHubRemotes <$> getState gitRepo) $ do liftIO $ putStrLn $ "New fork: " ++ repoUrl fork void $ addRemote (remoteFor fork) (repoUrl fork) gitRepo' <- inRepo $ Git.Config.reRead@@ -536,17 +484,6 @@ endState :: Backup () endState = liftIO . maybe noop catFileStop =<< getState catFileHandle--getAuth :: IO (Maybe Github.GithubAuth)-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- where- tobs = encodeUtf8 . T.pack genBackupState :: Git.Repo -> IO BackupState genBackupState repo = newState =<< Git.Config.read repo
+ gitriddance.1 view
@@ -0,0 +1,26 @@+.\" -*- nroff -*-+.TH gitriddance 1 "Commands"+.SH NAME+gitriddance \- closes all open issues and pull requests+.SH SYNOPSIS+.B gitriddance [comment]+.SH DESCRIPTION+.I gitriddance+closes all open issues and pull requests on GitHub. This is useful for+projects that have their own issue trackers, patch submission systems etc,+rather than relying on GitHub's, which many of us find to be clumsy,+slow, proprietary, and encouraging of drive-by pull requests of poor quality.+.PP+It should be run in a git repository that was cloned from GitHub. It+looks at the origin remote to find the repository on GitHub. All open+issues and pull requests will have a comment posted to them, and be closed.+.PP+The text of the comment is either passed as a command-line parameter,+or can be configured by setting core.gitriddance. For example:+.IP+git config core.gitriddance "Please submit patches to http://ikiwiki.info/todo/"+.PP+In order for gitriddance to log into GitHub, you need to set +the GITHUB_USER and GITHUB_PASSWORD environment variables.+.SH AUTHOR +Joey Hess <joey@kitenet.net>
+ gitriddance.hs view
@@ -0,0 +1,63 @@+{- gitriddance - close all open issues and pull requests+ -+ - Copyright 2014 Joey Hess <joey@kitenet.net>+ -+ - Licensed under the GNU GPL version 3 or higher.+ -}++{-# LANGUAGE CPP #-}++module Main where++import qualified Github.Repos as Github+#if MIN_VERSION_github(0,9,0)+import qualified Github.Auth as Github+#endif+import qualified Github.Issues as Github+import qualified Github.Issues.Comments as Github+import System.Environment++import Common+import qualified Git+import qualified Git.Construct+import qualified Git.Config+import Github.GetAuth+import Github.EnumRepos++main :: IO ()+main = do+ auth <- fromMaybe (error "Must set GITHUB_USER and GITHUB_PASSWORD")+ <$> getAuth+ r <- maybe (error "not in a git repository") Git.Config.read+ =<< Git.Construct.fromCwd+ msg <- maybe (getMsg r) id <$> (headMaybe <$> getArgs)+ case gitHubRemotes (onlyOriginRemote r) of+ [] -> error "origin does not seem to be a github repository"+ [origin] -> closeall auth origin msg+ _ -> error "somehow found multiple origin repos; this should be impossible!"++getMsg :: Git.Repo -> String+getMsg r = fromMaybe (error "core.gitriddance needs to be set to a message to use when closing issues/pull requests (or pass the message on the command line)")+ (Git.Config.getMaybe "core.gitriddance" r)++{- Limit to only having the origin remote; we don't want to affect any+ - other remotes that might be on github. -}+onlyOriginRemote :: Git.Repo -> Git.Repo+onlyOriginRemote r = r { Git.remotes = filter isorigin (Git.remotes r) }+ where+ isorigin rmt = Git.remoteName rmt == Just "origin"++closeall :: Github.GithubAuth -> GithubUserRepo -> String -> IO ()+closeall auth (GithubUserRepo user repo) msg =+ either (oops "getting issue list") (mapM_ close)+ =<< Github.issuesForRepo' (Just auth) user repo [Github.Open]+ where+ oops action err = error $ "failed " ++ action ++ ": " ++ show err+ close issue = do+ let i = Github.issueNumber issue+ putStrLn $ "closing issue: " ++ Github.issueTitle issue+ either (oops "posting comment") (const $ return ())+ =<< Github.createComment auth user repo i msg+ either (oops "closing issue/pull") (const $ return ())+ =<< Github.editIssue auth user repo i+ (Github.editOfIssue { Github.editIssueState = Just "closed" } )