git-lfs/commands/command_dedup.go
Chris Darroch ad532efdec command/command_dedup.go: exit if extentions exist
The dedup command should not proceed if any Git LFS
extensions are configured, because these typically
will change the contents of the file (e.g., by
compression) which is written to the local LFS object
storage tree from what the user expects to see in
their working tree, and so the working tree files
should not be cloned (via filesystem reflinks) from
the object storage files.
2020-02-27 22:12:36 -08:00

137 lines
3.7 KiB
Go

package commands
import (
"os"
"path/filepath"
"sync/atomic"
"github.com/git-lfs/git-lfs/config"
"github.com/git-lfs/git-lfs/errors"
"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 (
dedupFlags = struct {
test bool
}{}
dedupStats = &struct {
totalProcessedCount int64
totalProcessedSize int64
}{}
)
func dedupTestCommand(*cobra.Command, []string) {
requireInRepo()
if supported, err := tools.CheckCloneFileSupported(cfg.TempDir()); err != nil || !supported {
if err == nil {
err = errors.New("Unknown reason.")
}
Exit("This system does not support deduplication. %s", err)
}
if len(cfg.Extensions()) > 0 {
Exit("This platform supports file de-duplication, however, Git LFS extensions are configured and therefore de-duplication can not be used.")
}
Print("OK: This platform and repository support file de-duplication.")
}
func dedupCommand(cmd *cobra.Command, args []string) {
if dedupFlags.test {
dedupTestCommand(cmd, args)
return
}
requireInRepo()
if gitDir, err := git.GitDir(); err != nil {
ExitWithError(err)
} else if supported, err := tools.CheckCloneFileSupported(gitDir); err != nil || !supported {
Exit("This system does not support deduplication.")
}
if len(cfg.Extensions()) > 0 {
Exit("This platform supports file de-duplication, however, Git LFS extensions are configured and therefore de-duplication can not be used.")
}
if dirty, err := git.IsWorkingCopyDirty(); err != nil {
ExitWithError(err)
} else if dirty {
Exit("Working tree is dirty. Please commit or reset your change.")
}
// We assume working tree is clean.
gitScanner := lfs.NewGitScanner(config.New(), func(p *lfs.WrappedPointer, err error) {
if err != nil {
Exit("Could not scan for Git LFS tree: %s", err)
return
}
if success, err := dedup(p); err != nil {
Error("Skipped: %s (Size: %d)\n %s", p.Name, p.Size, err)
} else if !success {
Error("Skipped: %s (Size: %d)", p.Name, p.Size)
} else if success {
Print("Success: %s (Size: %d)", p.Name, p.Size)
atomic.AddInt64(&dedupStats.totalProcessedCount, 1)
atomic.AddInt64(&dedupStats.totalProcessedSize, p.Size)
}
})
defer gitScanner.Close()
if err := gitScanner.ScanTree("HEAD"); err != nil {
ExitWithError(err)
}
Print("\n\nSuccessfully finished.\n"+
" De-duplicated size: %d bytes\n"+
" count: %d",
dedupStats.totalProcessedSize,
dedupStats.totalProcessedCount)
}
// dedup executes
// Precondition: working tree MUST clean. We can replace working tree files from mediafile safely.
func dedup(p *lfs.WrappedPointer) (success bool, err error) {
// PRECONDITION, check ofs object exists or skip this file.
if !cfg.LFSObjectExists(p.Oid, p.Size) { // Not exists,
// Basically, this is not happens because executing 'git status' in `git.IsWorkingCopyDirty()` recover it.
return false, errors.New("mediafile is not exist")
}
// DO de-dup
// Gather original state
originalStat, err := os.Stat(p.Name)
if err != nil {
return false, err
}
// Do clone
srcFile := cfg.Filesystem().ObjectPathname(p.Oid)
dstFile := filepath.Join(cfg.LocalWorkingDir(), p.Name)
// Clone the file. This overwrites the destination if it exists.
if ok, err := tools.CloneFileByPath(dstFile, srcFile); err != nil {
return false, err
} else if !ok {
return false, errors.Errorf("unknown clone file error")
}
// Recover original state
if err := os.Chmod(dstFile, originalStat.Mode()); err != nil {
return false, err
}
return true, nil
}
func init() {
RegisterCommand("dedup", dedupCommand, func(cmd *cobra.Command) {
cmd.Flags().BoolVarP(&dedupFlags.test, "test", "t", false, "test")
})
}