git-lfs/commands/command_fsck.go
brian m. carlson 9a51ea0f46
Pass environment variables into object scanner
We're going to need the environment variables in the object scanner, so
pass the appropriate Environment instance down into the object scanner.
Use an interface to avoid an import loop between the git and config
packages.

Note that the environment is not yet used, but will be in a future
commit.
2019-08-15 17:39:32 +00:00

128 lines
2.8 KiB
Go

package commands
import (
"crypto/sha256"
"encoding/hex"
"io"
"os"
"path/filepath"
"github.com/git-lfs/git-lfs/filepathfilter"
"github.com/git-lfs/git-lfs/git"
"github.com/git-lfs/git-lfs/lfs"
"github.com/git-lfs/git-lfs/tools"
"github.com/spf13/cobra"
)
var (
fsckDryRun bool
)
// TODO(zeroshirts): 'git fsck' reports status (percentage, current#/total) as
// it checks... we should do the same, as we are rehashing potentially gigs and
// gigs of content.
//
// NOTE(zeroshirts): Ideally git would have hooks for fsck such that we could
// chain a lfs-fsck, but I don't think it does.
func fsckCommand(cmd *cobra.Command, args []string) {
installHooks(false)
requireInRepo()
ref, err := git.CurrentRef()
if err != nil {
ExitWithError(err)
}
var corruptOids []string
gitscanner := lfs.NewGitScanner(cfg, func(p *lfs.WrappedPointer, err error) {
if err == nil {
var pointerOk bool
pointerOk, err = fsckPointer(p.Name, p.Oid)
if !pointerOk {
corruptOids = append(corruptOids, p.Oid)
}
}
if err != nil {
Panic(err, "Error checking Git LFS files")
}
})
// If 'lfs.fetchexclude' is set and 'git lfs fsck' is run after the
// initial fetch (i.e., has elected to fetch a subset of Git LFS
// objects), the "missing" ones will fail the fsck.
//
// Attach a filepathfilter to avoid _only_ the excluded paths.
gitscanner.Filter = filepathfilter.New(nil, cfg.FetchExcludePaths())
if err := gitscanner.ScanRef(ref.Sha, nil); err != nil {
ExitWithError(err)
}
if err := gitscanner.ScanIndex("HEAD", nil); err != nil {
ExitWithError(err)
}
gitscanner.Close()
if len(corruptOids) == 0 {
Print("Git LFS fsck OK")
return
}
if fsckDryRun {
return
}
badDir := filepath.Join(cfg.LFSStorageDir(), "bad")
Print("Moving corrupt objects to %s", badDir)
if err := tools.MkdirAll(badDir, cfg); err != nil {
ExitWithError(err)
}
for _, oid := range corruptOids {
badFile := filepath.Join(badDir, oid)
if err := os.Rename(cfg.Filesystem().ObjectPathname(oid), badFile); err != nil {
ExitWithError(err)
}
}
}
func fsckPointer(name, oid string) (bool, error) {
path := cfg.Filesystem().ObjectPathname(oid)
Debug("Examining %v (%v)", name, path)
f, err := os.Open(path)
if pErr, pOk := err.(*os.PathError); pOk {
Print("Object %s (%s) could not be checked: %s", name, oid, pErr.Err)
return false, nil
}
if err != nil {
return false, err
}
oidHash := sha256.New()
_, err = io.Copy(oidHash, f)
f.Close()
if err != nil {
return false, err
}
recalculatedOid := hex.EncodeToString(oidHash.Sum(nil))
if recalculatedOid == oid {
return true, nil
}
Print("Object %s (%s) is corrupt", name, oid)
return false, nil
}
func init() {
RegisterCommand("fsck", fsckCommand, func(cmd *cobra.Command) {
cmd.Flags().BoolVarP(&fsckDryRun, "dry-run", "d", false, "List corrupt objects without deleting them.")
})
}