github.com/git-lfs/git-lfs@v2.5.2+incompatible/commands/command_fsck.go (about)

     1  package commands
     2  
     3  import (
     4  	"crypto/sha256"
     5  	"encoding/hex"
     6  	"io"
     7  	"os"
     8  	"path/filepath"
     9  
    10  	"github.com/git-lfs/git-lfs/filepathfilter"
    11  	"github.com/git-lfs/git-lfs/git"
    12  	"github.com/git-lfs/git-lfs/lfs"
    13  	"github.com/spf13/cobra"
    14  )
    15  
    16  var (
    17  	fsckDryRun bool
    18  )
    19  
    20  // TODO(zeroshirts): 'git fsck' reports status (percentage, current#/total) as
    21  // it checks... we should do the same, as we are rehashing potentially gigs and
    22  // gigs of content.
    23  //
    24  // NOTE(zeroshirts): Ideally git would have hooks for fsck such that we could
    25  // chain a lfs-fsck, but I don't think it does.
    26  func fsckCommand(cmd *cobra.Command, args []string) {
    27  	installHooks(false)
    28  	requireInRepo()
    29  
    30  	ref, err := git.CurrentRef()
    31  	if err != nil {
    32  		ExitWithError(err)
    33  	}
    34  
    35  	var corruptOids []string
    36  	gitscanner := lfs.NewGitScanner(func(p *lfs.WrappedPointer, err error) {
    37  		if err == nil {
    38  			var pointerOk bool
    39  			pointerOk, err = fsckPointer(p.Name, p.Oid)
    40  			if !pointerOk {
    41  				corruptOids = append(corruptOids, p.Oid)
    42  			}
    43  		}
    44  
    45  		if err != nil {
    46  			Panic(err, "Error checking Git LFS files")
    47  		}
    48  	})
    49  
    50  	// If 'lfs.fetchexclude' is set and 'git lfs fsck' is run after the
    51  	// initial fetch (i.e., has elected to fetch a subset of Git LFS
    52  	// objects), the "missing" ones will fail the fsck.
    53  	//
    54  	// Attach a filepathfilter to avoid _only_ the excluded paths.
    55  	gitscanner.Filter = filepathfilter.New(nil, cfg.FetchExcludePaths())
    56  
    57  	if err := gitscanner.ScanRef(ref.Sha, nil); err != nil {
    58  		ExitWithError(err)
    59  	}
    60  
    61  	if err := gitscanner.ScanIndex("HEAD", nil); err != nil {
    62  		ExitWithError(err)
    63  	}
    64  
    65  	gitscanner.Close()
    66  
    67  	if len(corruptOids) == 0 {
    68  		Print("Git LFS fsck OK")
    69  		return
    70  	}
    71  
    72  	if fsckDryRun {
    73  		return
    74  	}
    75  
    76  	badDir := filepath.Join(cfg.LFSStorageDir(), "bad")
    77  	Print("Moving corrupt objects to %s", badDir)
    78  
    79  	if err := os.MkdirAll(badDir, 0755); err != nil {
    80  		ExitWithError(err)
    81  	}
    82  
    83  	for _, oid := range corruptOids {
    84  		badFile := filepath.Join(badDir, oid)
    85  		if err := os.Rename(cfg.Filesystem().ObjectPathname(oid), badFile); err != nil {
    86  			ExitWithError(err)
    87  		}
    88  	}
    89  }
    90  
    91  func fsckPointer(name, oid string) (bool, error) {
    92  	path := cfg.Filesystem().ObjectPathname(oid)
    93  
    94  	Debug("Examining %v (%v)", name, path)
    95  
    96  	f, err := os.Open(path)
    97  	if pErr, pOk := err.(*os.PathError); pOk {
    98  		Print("Object %s (%s) could not be checked: %s", name, oid, pErr.Err)
    99  		return false, nil
   100  	}
   101  
   102  	if err != nil {
   103  		return false, err
   104  	}
   105  
   106  	oidHash := sha256.New()
   107  	_, err = io.Copy(oidHash, f)
   108  	f.Close()
   109  	if err != nil {
   110  		return false, err
   111  	}
   112  
   113  	recalculatedOid := hex.EncodeToString(oidHash.Sum(nil))
   114  	if recalculatedOid == oid {
   115  		return true, nil
   116  	}
   117  
   118  	Print("Object %s (%s) is corrupt", name, oid)
   119  	return false, nil
   120  }
   121  
   122  func init() {
   123  	RegisterCommand("fsck", fsckCommand, func(cmd *cobra.Command) {
   124  		cmd.Flags().BoolVarP(&fsckDryRun, "dry-run", "d", false, "List corrupt objects without deleting them.")
   125  	})
   126  }