Last active
March 11, 2025 16:07
-
-
Save crazytaxii/0428496ac65b8e2d09141aa039b5651c to your computer and use it in GitHub Desktop.
Diff 2 directories.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//go:build linux || darwin | |
package main | |
import ( | |
"crypto/sha256" | |
"encoding/hex" | |
"fmt" | |
"io" | |
"log" | |
"os" | |
"path/filepath" | |
"strings" | |
"syscall" | |
) | |
func main() { | |
args := os.Args | |
if len(args) != 3 { | |
log.Fatalf("Usage: %s directory1 directory2", args[0]) | |
} | |
if err := compare(filepath.Clean(args[1]), filepath.Clean(args[2])); err != nil { | |
log.Fatal(err) | |
} | |
log.Println("same") | |
} | |
func relativePath(path, dir1 string, dir2 string) string { | |
return filepath.Clean(strings.Replace(path, dir1, dir2, 1)) | |
} | |
func isSymlink(info os.FileInfo) bool { | |
return info.Mode()&os.ModeSymlink != 0 | |
} | |
func compare(dir1, dir2 string) error { | |
return filepath.Walk(dir1, func(path string, info os.FileInfo, err error) error { | |
if err != nil { | |
return err | |
} | |
// ignore the root directory | |
if path == dir1 { | |
return nil | |
} | |
_path := relativePath(path, dir1, dir2) // hypothetical file path in dir2 | |
_info, err := os.Lstat(_path) | |
if err != nil { | |
return err | |
} | |
if !compareMetadata(info, _info) { | |
return fmt.Errorf("file %s is different from %s", _path, path) | |
} | |
if info.IsDir() { | |
return nil | |
} | |
if isSymlink(info) { | |
if !isSymlink(_info) { | |
return fmt.Errorf("file %s is different from %s", _path, path) | |
} | |
target, err := os.Readlink(path) | |
if err != nil { | |
return err | |
} | |
_target, err := os.Readlink(_path) | |
if err != nil { | |
return err | |
} | |
if target != _target { | |
return fmt.Errorf("symlink %s is different from %s", _path, path) | |
} | |
return nil | |
} | |
digest1, err := compute(path) | |
if err != nil { | |
return err | |
} | |
digest2, err := compute(_path) | |
if err != nil { | |
return err | |
} | |
if digest1 != digest2 { | |
return fmt.Errorf("file %s is different from %s", _path, path) | |
} | |
return nil | |
}) | |
} | |
// compareMetadata returns true if the metadata of two files are the same | |
func compareMetadata(meta1, meta2 os.FileInfo) bool { | |
stat1, ok := meta1.Sys().(*syscall.Stat_t) | |
if !ok { | |
return false | |
} | |
stat2, ok := meta2.Sys().(*syscall.Stat_t) | |
if !ok { | |
return false | |
} | |
return meta1.Mode() == meta2.Mode() && | |
meta1.Size() == meta2.Size() && | |
meta1.ModTime() == meta2.ModTime() && | |
meta1.IsDir() == meta2.IsDir() && | |
stat1.Uid == stat2.Uid && | |
stat1.Gid == stat2.Gid | |
} | |
// compute returns the sha256 hash of the file | |
func compute(file string) (digest string, err error) { | |
f, err := os.Open(file) | |
if err != nil { | |
return | |
} | |
defer f.Close() | |
hasher := sha256.New() | |
if _, err = io.Copy(hasher, f); err != nil { | |
return | |
} | |
return hex.EncodeToString(hasher.Sum(nil)), nil | |
} |
Author
crazytaxii
commented
Mar 11, 2025
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment