add repair command

This commit is contained in:
Joey Hess 2013-10-23 12:21:59 -04:00
parent 6b7f1baa6a
commit d5eb85acf4
6 changed files with 154 additions and 87 deletions

View file

@ -6,6 +6,7 @@
-}
module Git.RecoverRepository (
runRecovery,
cleanCorruptObjects,
retrieveMissingObjects,
resetLocalBranches,
@ -17,23 +18,23 @@ module Git.RecoverRepository (
import Common
import Git
import Git.Command
import Git.Fsck
import Git.Objects
import Git.Sha
import Git.Types
import qualified Git.Config
import qualified Git.Construct
import Git.Fsck
import qualified Git.Config as Config
import qualified Git.Construct as Construct
import qualified Git.LsTree as LsTree
import qualified Git.LsFiles as LsFiles
import qualified Git.Ref as Ref
import qualified Git.RefLog as RefLog
import qualified Git.UpdateIndex as UpdateIndex
import qualified Git.Branch as Branch
import Utility.Tmp
import Utility.Rsync
import qualified Data.Set as S
import qualified Data.ByteString.Lazy as L
import System.Log.Logger
import Data.Tuple.Utils
{- Given a set of bad objects found by git fsck, removes all
@ -52,7 +53,7 @@ cleanCorruptObjects :: FsckResults -> Repo -> IO MissingObjects
cleanCorruptObjects mmissing r = check mmissing
where
check Nothing = do
notice "git fsck found a problem but no specific broken objects. Perhaps a corrupt pack file?"
putStrLn "git fsck found a problem but no specific broken objects. Perhaps a corrupt pack file?"
ifM (explodePacks r)
( retry S.empty
, return S.empty
@ -60,7 +61,7 @@ cleanCorruptObjects mmissing r = check mmissing
check (Just bad)
| S.null bad = return S.empty
| otherwise = do
notice $ unwords
putStrLn $ unwords
[ "git fsck found"
, show (S.size bad)
, "broken objects."
@ -71,7 +72,7 @@ cleanCorruptObjects mmissing r = check mmissing
then retry bad
else return bad
retry oldbad = do
notice "Re-running git fsck to see if it finds more problems."
putStrLn "Re-running git fsck to see if it finds more problems."
v <- findBroken False r
case v of
Nothing -> error $ unwords
@ -92,7 +93,7 @@ removeLoose r s = do
count <- length <$> filterM doesFileExist fs
if (count > 0)
then do
notice $ unwords
putStrLn $ unwords
[ "removing"
, show count
, "corrupt loose objects"
@ -107,7 +108,7 @@ explodePacks r = do
if null packs
then return False
else do
notice "Unpacking all pack files."
putStrLn "Unpacking all pack files."
mapM_ go packs
return True
where
@ -128,7 +129,7 @@ retrieveMissingObjects missing r
| otherwise = withTmpDir "tmprepo" $ \tmpdir -> do
unlessM (boolSystem "git" [Params "init", File tmpdir]) $
error $ "failed to create temp repository in " ++ tmpdir
tmpr <- Git.Config.read =<< Git.Construct.fromAbsPath tmpdir
tmpr <- Config.read =<< Construct.fromAbsPath tmpdir
stillmissing <- pullremotes tmpr (remotes r) fetchrefstags missing
if S.null stillmissing
then return stillmissing
@ -138,14 +139,14 @@ retrieveMissingObjects missing r
pullremotes tmpr (rmt:rmts) fetchrefs s
| S.null s = return s
| otherwise = do
notice $ "Trying to recover missing objects from remote " ++ repoDescribe rmt
putStrLn $ "Trying to recover missing objects from remote " ++ repoDescribe rmt
ifM (fetchsome rmt fetchrefs tmpr)
( do
void $ copyObjects tmpr r
stillmissing <- findMissing (S.toList s) r
pullremotes tmpr rmts fetchrefs stillmissing
, do
notice $ unwords
putStrLn $ unwords
[ "failed to fetch from remote"
, repoDescribe rmt
, "(will continue without it, but making this remote available may improve recovery)"
@ -360,7 +361,7 @@ rewriteIndex :: MissingObjects -> Repo -> IO [FilePath]
rewriteIndex missing r
| repoIsLocalBare r = return []
| otherwise = do
(indexcontents, cleanup) <- LsFiles.stagedDetails [Git.repoPath r] r
(indexcontents, cleanup) <- LsFiles.stagedDetails [repoPath r] r
let (bad, good) = partition ismissing indexcontents
unless (null bad) $ do
nukeFile (localGitDir r </> "index")
@ -390,5 +391,77 @@ addGoodCommits :: [Sha] -> GoodCommits -> GoodCommits
addGoodCommits shas (GoodCommits s) = GoodCommits $
S.union s (S.fromList shas)
notice :: String -> IO ()
notice = noticeM "RecoverRepository"
displayList :: [String] -> String -> IO ()
displayList items header
| null items = return ()
| otherwise = do
putStrLn header
putStr $ unlines $ map (\i -> "\t" ++ i) truncateditems
where
numitems = length items
truncateditems
| numitems > 10 = take 10 items ++ ["(and " ++ show (numitems - 10) ++ " more)"]
| otherwise = items
{- Put it all together. -}
runRecovery :: Bool -> Repo -> IO Bool
runRecovery forced g = do
putStrLn "Running git fsck ..."
fsckresult <- findBroken False g
missing <- cleanCorruptObjects fsckresult g
stillmissing <- retrieveMissingObjects missing g
if S.null stillmissing
then successfulfinish
else do
putStrLn $ unwords
[ show (S.size stillmissing)
, "missing objects could not be recovered!"
]
if forced
then do
(remotebranches, goodcommits) <- removeTrackingBranches stillmissing emptyGoodCommits g
unless (null remotebranches) $
putStrLn $ unwords
[ "removed"
, show (length remotebranches)
, "remote tracking branches that referred to missing objects"
]
(resetbranches, deletedbranches, _) <- resetLocalBranches stillmissing goodcommits g
displayList (map show resetbranches)
"Reset these local branches to old versions before the missing objects were committed:"
displayList (map show deletedbranches)
"Deleted these local branches, which could not be recovered due to missing objects:"
deindexedfiles <- rewriteIndex stillmissing g
displayList deindexedfiles
"Removed these missing files from the index. You should look at what files are present in your working tree and git add them back to the index when appropriate."
if null resetbranches && null deletedbranches
then successfulfinish
else do
unless (repoIsLocalBare g) $ do
mcurr <- Branch.currentUnsafe g
case mcurr of
Nothing -> return ()
Just curr -> when (any (== curr) (resetbranches ++ deletedbranches)) $ do
putStrLn $ unwords
[ "You currently have"
, show curr
, "checked out. You may have staged changes in the index that can be committed to recover the lost state of this branch!"
]
putStrLn "Successfully recovered repository!"
putStrLn "Please carefully check that the changes mentioned above are ok.."
return True
else do
if repoIsLocalBare g
then do
putStrLn "If you have a clone of this bare repository, you should add it as a remote of this repository, and re-run git-recover-repository."
putStrLn "If there are no clones of this repository, you can instead run git-recover-repository with the --force parameter to force recovery to a possibly usable state."
else putStrLn "To force a recovery to a usable state, run this command again with the --force parameter."
return False
where
successfulfinish = do
mapM_ putStrLn
[ "Successfully recovered repository!"
, "You should run \"git fsck\" to make sure, but it looks like"
, "everything was recovered ok."
]
return True