func (p *FilePreparator) prepareCopy(src, dst string) bool { var err error if p.Logger != nil { p.Logger(p, src, dst, false, false) } p.NumFiles += 1 srci, srcerr := os.Lstat(src) dsti, dsterr := os.Lstat(dst) // // File in source but not in destination // if os.IsNotExist(dsterr) && srcerr == nil { if srci.IsDir() { if p.DocIgnore && ignore.IsIgnored(src) { if p.Verbose { fmt.Printf("Ignoring %s\n", src) } return true } res := p.HandleAction(*NewCreateDir(src, dst, srci)) if !res { return false } f, err := os.Open(src) if err != nil { return p.HandleError(err) } defer f.Close() names, err := f.Readdirnames(-1) if err != nil { return p.HandleError(err) } for _, name := range names { if !p.prepareCopy(filepath.Join(src, name), filepath.Join(dst, name)) { return false } } return true } else { var srchash []byte if !srci.IsDir() { srchash, err = repo.GetHash(src, srci, p.Dedup != nil) if err != nil { return p.HandleError(err) } } res := p.HandleAction(*NewCopyFile(src, dst, srchash, srci)) p.TotalBytes += uint64(size(srci)) return res } } // // [Bidir] File in destination but not in source: reverse copy // if p.Bidir && os.IsNotExist(srcerr) && dsterr == nil { // FIXME: this could probably be simplified into // return p.prepareCopy(dst, src) if dsti.IsDir() { if p.DocIgnore && ignore.IsIgnored(dst) { if p.Verbose { fmt.Printf("Ignoring %s\n", dst) } return true } res := p.HandleAction(*NewCreateDir(dst, src, dsti)) if !res { return false } f, err := os.Open(dst) if err != nil { return p.HandleError(err) } defer f.Close() names, err := f.Readdirnames(-1) if err != nil { return p.HandleError(err) } for _, name := range names { if !p.prepareCopy(filepath.Join(src, name), filepath.Join(dst, name)) { return false } } return true } else { var dsthash []byte if !dsti.IsDir() { dsthash, err = repo.GetHash(dst, dsti, p.Dedup != nil) if err != nil { return p.HandleError(err) } } res := p.HandleAction(*NewCopyAction(dst, src, dsthash, size(dsti), "", false, dsti.Mode(), 0)) p.TotalBytes += uint64(size(dsti)) return res } } // // [Dedup] File in destination but not in source: register in dedup // if p.Dedup != nil && os.IsNotExist(srcerr) && dsterr == nil { hash, err := repo.GetHash(dst, dsti, p.CheckHash) if err != nil { if !p.HandleError(err) { return false } } else if hash != nil { p.Dedup[string(hash)] = append(p.Dedup[string(hash)], dst) } } // // Handle stat() errors // if srcerr != nil { return p.HandleError(srcerr) } if dsterr != nil { return p.HandleError(dsterr) } // // Both source and destination are directories, merge // if srci.IsDir() && dsti.IsDir() { if p.DocIgnore && (ignore.IsIgnored(src) || ignore.IsIgnored(dst)) { if p.Verbose { fmt.Printf("Ignoring %s (source and destination)\n", src) } return true } var srcnames map[string]bool if p.Bidir { srcnames = map[string]bool{} } f, err := os.Open(src) if err != nil { return p.HandleError(err) } defer f.Close() names, err := f.Readdirnames(-1) if err != nil { return p.HandleError(err) } for _, name := range names { if p.Bidir { srcnames[name] = true } if !p.prepareCopy(filepath.Join(src, name), filepath.Join(dst, name)) { return false } } if p.Bidir { f, err := os.Open(dst) if err != nil { return p.HandleError(err) } defer f.Close() dstnames, err := f.Readdirnames(-1) if err != nil { return p.HandleError(err) } for _, name := range dstnames { if srcnames[name] { continue } if !p.prepareCopy(filepath.Join(src, name), filepath.Join(dst, name)) { return false } } } return true } // // Source and destination are regular files // If hash is different, there is a conflict // var srch, dsth []byte if !srci.IsDir() { srch, err = repo.GetHash(src, srci, false) computed := false if err == nil && srch == nil { if p.Logger != nil { p.Logger(p, src, dst, true, false) } srch, err = repo.HashFile(src, srci) computed = true } if err == nil && computed && p.Commit { _, err = repo.CommitFileHash(src, srci, srch, false) } if err != nil { return p.HandleError(err) } } if !dsti.IsDir() { dsth, err = repo.GetHash(dst, dsti, false) computed := false if err == nil && dsth == nil { if p.Logger != nil { p.Logger(p, src, dst, false, true) } dsth, err = repo.HashFile(dst, dsti) computed = true } if err == nil && computed && p.Commit { _, err = repo.CommitFileHash(dst, dsti, dsth, false) } if err != nil { return p.HandleError(err) } } if bytes.Equal(srch, dsth) && srci.Mode()&os.ModeSymlink == dsti.Mode()&os.ModeSymlink { return true } if repo.ConflictFile(src) == "" { dstname := repo.FindConflictFileName(dst, srch) if dstname != "" { p.TotalBytes += uint64(size(srci)) if !p.HandleAction(*NewCopyAction(src, dstname, srch, size(srci), dst, true, srci.Mode(), dsti.Mode())) { return false } } } if p.Bidir && repo.ConflictFile(dst) == "" { srcname := repo.FindConflictFileName(src, dsth) if srcname != "" { p.TotalBytes += uint64(size(dsti)) if !p.HandleAction(*NewCopyAction(dst, srcname, dsth, size(dsti), src, true, dsti.Mode(), srci.Mode())) { return false } } } return true }
func mainStatus(args []string) int { f := flag.NewFlagSet("status", flag.ExitOnError) opt_no_par2 := f.Bool("n", false, "Do not show files missing PAR2 redundency data") opt_show_only_hash := f.Bool("c", false, "Show only unchanged committed files with their hash") opt_no_docignore := f.Bool("no-docignore", false, "Don't treat .docignore files specially") f.Usage = func() { fmt.Print(usageStatus) f.PrintDefaults() } f.Parse(args) dir := f.Arg(0) if dir == "" { dir = "." } rep := repo.GetRepo(dir) status := 0 err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { if err != nil { fmt.Fprintf(os.Stderr, "%s: %v\n", path, err.Error()) status = 1 return err } // Skip directories containing an empty .docignore file if !*opt_no_docignore && ignore.IsIgnored(path) { return filepath.SkipDir } // Skip .dirstore/ at root if filepath.Base(path) == attrs.DirStoreName && filepath.Dir(path) == dir && info.IsDir() { return filepath.SkipDir } else if !info.Mode().IsRegular() { return nil } if *opt_show_only_hash { hash, err := repo.GetHash(path, info, false) if err != nil { fmt.Fprintf(os.Stderr, "%s: %v\n", path, err.Error()) return nil } if hash != nil { fmt.Printf("%s\t%s\n", base58.Encode(hash), path) } } else { var conflict string = "" if repo.ConflictFile(path) != "" { conflict = " c" } else if len(repo.ConflictFileAlternatives(path)) > 0 { conflict = " C" } hashTime, err := repo.GetHashTime(path) if repo.IsNoData(err) { if info.Mode()&os.FileMode(0200) == 0 { fmt.Printf("?%s (ro)\t%s\n", conflict, path) } else { fmt.Printf("?%s\t%s\n", conflict, path) } return nil } else if err != nil { fmt.Fprintf(os.Stderr, "%s: %v\n", path, err.Error()) return nil } var redundency string = "*" if rep != nil { digest, err := repo.GetHash(path, info, true) if err != nil { fmt.Fprintf(os.Stderr, "%s: %v\n", path, err.Error()) return nil } if par2exists, _ := rep.Par2Exists(digest); par2exists { redundency = "" } } if hashTime != info.ModTime() { fmt.Printf("+%s%s\t%s\n", conflict, redundency, path) } else if conflict != "" || (redundency != "" && !*opt_no_par2) { fmt.Printf("%s%s\t%s\n", conflict, redundency, path) } } return nil }) if err != nil { fmt.Fprintf(os.Stderr, "%v", err) os.Exit(1) } return status }
func mainDupes(args []string) int { f := flag.NewFlagSet("dupes", flag.ExitOnError) opt_show_links := f.Bool("l", false, "Show group of files that share the same inode") opt_progress := f.Bool("p", false, "Show progress") opt_hash := f.Bool("c", false, "Check real hash in case the file is updated") opt_dedup := f.Bool("d", false, "Deduplicate files (make links)") f.Usage = func() { fmt.Print(dupesUsage) f.PrintDefaults() } f.Parse(args) srcs := f.Args() if len(srcs) == 0 { srcs = append(srcs, ".") } dupes := map[string]sameFile{} num := 0 errors := 0 for _, src := range srcs { e := repo.Walk(src, func(path string, info os.FileInfo) error { // Skip symlinks if info.Mode()&os.ModeSymlink != 0 { return nil } hash, err := repo.GetHash(path, info, *opt_hash) if err != nil { return err } if hash == nil { return nil } sys, ok := info.Sys().(*syscall.Stat_t) if !ok { sys.Ino = 0 } f := dupes[string(hash)] f.hash = hash f.paths = append(f.paths, path) f.inodes = append(f.inodes, sys.Ino) f.devices = append(f.devices, sys.Dev) dupes[string(hash)] = f num = num + 1 if *opt_progress { fmt.Printf("\r\x1b[2K%d %s\r", num, path) } return nil }, func(path string, info os.FileInfo, err error) bool { fmt.Fprintf(os.Stderr, "%s: %v\n", path, err.Error()) return true }) errors = errors + len(e) } for _, f := range dupes { if len(f.paths) <= 1 { continue } files := map[uint64][]string{} for i, ino := range f.inodes { files[ino] = append(files[ino], f.paths[i]) } if len(files) == 1 && !*opt_show_links { continue } fmt.Println() hash := base58.Encode(f.hash) for _, paths := range files { for _, path := range paths { fmt.Printf("%s\t%d\t%s\n", hash, len(paths), path) } } if len(files) > 1 && *opt_dedup { err := deduplicate(f) if err != nil { fmt.Fprintf(os.Stderr, "%s", err.Error()) errors = errors + 1 } } } if errors > 0 { return 1 } return 0 }