diff --git a/tools/cmd/diff/collect.go b/tools/cmd/diff/collect.go new file mode 100644 index 000000000..c18417384 --- /dev/null +++ b/tools/cmd/diff/collect.go @@ -0,0 +1,48 @@ +// License: GPLv3 Copyright: 2023, Kovid Goyal, + +package diff + +import ( + "fmt" + "os" + "path/filepath" +) + +var _ = fmt.Print +var path_name_map, remote_dirs map[string]string + +type Collection struct { +} + +func create_collection(left, right string) (ans *Collection, err error) { + path_name_map = make(map[string]string, 32) + remote_dirs = make(map[string]string, 32) + ans = &Collection{} + left_stat, err := os.Stat(left) + if err != nil { + return nil, err + } + if left_stat.IsDir() { + err = ans.collect_files(left, right) + if err != nil { + return nil, err + } + } else { + pl, err := filepath.Abs(left) + if err != nil { + return nil, err + } + pr, err := filepath.Abs(right) + if err != nil { + return nil, err + } + path_name_map[pl] = resolve_remote_name(pl, left) + path_name_map[pr] = resolve_remote_name(pr, right) + err = ans.add_change(pl, pr) + if err != nil { + return nil, err + } + } + err = ans.finalize() + return ans, err +} diff --git a/tools/cmd/diff/main.go b/tools/cmd/diff/main.go index 31e4a3058..eda62d2af 100644 --- a/tools/cmd/diff/main.go +++ b/tools/cmd/diff/main.go @@ -4,6 +4,7 @@ package diff import ( "fmt" + "os" "kitty/tools/cli" "kitty/tools/config" @@ -26,6 +27,18 @@ var conf *Config var opts *Options var lp *loop.Loop +func isdir(path string) bool { + if s, err := os.Stat(path); err == nil { + return s.IsDir() + } + return false +} + +func exists(path string) bool { + _, err := os.Stat(path) + return err == nil +} + func main(_ *cli.Command, opts_ *Options, args []string) (rc int, err error) { opts = opts_ conf, err = load_config(opts) @@ -35,7 +48,19 @@ func main(_ *cli.Command, opts_ *Options, args []string) (rc int, err error) { if len(args) != 2 { return 1, fmt.Errorf("You must specify exactly two files/directories to compare") } - left, right := args[0], args[1] + if err = set_diff_command(conf.Diff_cmd); err != nil { + return 1, err + } + left, right := get_remote_file(args[0]), get_remote_file(args[1]) + if isdir(left) != isdir(right) { + return 1, fmt.Errorf("The items to be diffed should both be either directories or files. Comparing a directory to a file is not valid.'") + } + if !exists(left) { + return 1, fmt.Errorf("%s does not exist", left) + } + if !exists(right) { + return 1, fmt.Errorf("%s does not exist", right) + } lp, err = loop.New() if err != nil { return 1, err diff --git a/tools/cmd/diff/patch.go b/tools/cmd/diff/patch.go new file mode 100644 index 000000000..d4dc8345c --- /dev/null +++ b/tools/cmd/diff/patch.go @@ -0,0 +1,84 @@ +// License: GPLv3 Copyright: 2023, Kovid Goyal, + +package diff + +import ( + "bytes" + "errors" + "fmt" + "kitty/tools/utils" + "kitty/tools/utils/shlex" + "os/exec" + "path/filepath" + "strconv" + "strings" +) + +var _ = fmt.Print + +const GIT_DIFF = `git diff --no-color --no-ext-diff --exit-code -U_CONTEXT_ --no-index --` +const DIFF_DIFF = `diff -p -U _CONTEXT_ --` + +var diff_cmd []string + +var GitExe = (&utils.Once[string]{Run: func() string { + return utils.FindExe("git") +}}).Get + +var DiffExe = (&utils.Once[string]{Run: func() string { + return utils.FindExe("diff") +}}).Get + +func find_differ() error { + if GitExe() != "git" && exec.Command(GitExe(), "--help").Run() == nil { + diff_cmd, _ = shlex.Split(GIT_DIFF) + return nil + } + if DiffExe() != "diff" && exec.Command(DiffExe(), "--help").Run() == nil { + diff_cmd, _ = shlex.Split(DIFF_DIFF) + return nil + } + return fmt.Errorf("Neither the git nor the diff programs were found in PATH") +} + +func set_diff_command(q string) error { + if q == "auto" { + return find_differ() + } + c, err := shlex.Split(q) + if err == nil { + diff_cmd = c + } + return err +} + +func run_diff(file1, file2 string, num_of_context_lines int) (ok, is_different bool, patch string, err error) { + context := strconv.Itoa(num_of_context_lines) + cmd := utils.Map(func(x string) string { + return strings.ReplaceAll(x, "_CONTEXT_", context) + }, diff_cmd) + // we resolve symlinks because git diff does not follow symlinks, while diff + // does. We want consistent behavior, also for integration with git difftool + // we always want symlinks to be followed. + path1, err := filepath.EvalSymlinks(file1) + if err != nil { + return + } + path2, err := filepath.EvalSymlinks(file2) + if err != nil { + return + } + cmd = append(cmd, path1, path2) + c := exec.Command(cmd[0], cmd[1:]...) + stdout, stderr := bytes.Buffer{}, bytes.Buffer{} + c.Stdout, c.Stderr = &stdout, &stderr + err = c.Run() + if err != nil { + var e *exec.ExitError + if errors.As(err, &e) && e.ExitCode() == 1 { + return true, true, stdout.String(), nil + } + return false, false, stderr.String(), err + } + return true, false, stdout.String(), nil +} diff --git a/tools/cmd/hyperlinked_grep/main.go b/tools/cmd/hyperlinked_grep/main.go index 9d97c1314..bb8d5b95d 100644 --- a/tools/cmd/hyperlinked_grep/main.go +++ b/tools/cmd/hyperlinked_grep/main.go @@ -23,15 +23,7 @@ import ( var _ = fmt.Print var RgExe = (&utils.Once[string]{Run: func() string { - ans := utils.Which("rg") - if ans != "" { - return ans - } - ans = utils.Which("rg", utils.DefaultExeSearchPaths()...) - if ans == "" { - ans = "rg" - } - return ans + return utils.FindExe("rg") }}).Get func get_options_for_rg() (expecting_args map[string]bool, alias_map map[string]string, err error) { diff --git a/tools/cmd/ssh/utils.go b/tools/cmd/ssh/utils.go index e33e3eee5..972417de7 100644 --- a/tools/cmd/ssh/utils.go +++ b/tools/cmd/ssh/utils.go @@ -18,15 +18,7 @@ import ( var _ = fmt.Print var SSHExe = (&utils.Once[string]{Run: func() string { - ans := utils.Which("ssh") - if ans != "" { - return ans - } - ans = utils.Which("ssh", utils.DefaultExeSearchPaths()...) - if ans == "" { - ans = "ssh" - } - return ans + return utils.FindExe("ssh") }}).Get var SSHOptions = (&utils.Once[map[string]string]{Run: func() (ssh_options map[string]string) { diff --git a/tools/utils/which.go b/tools/utils/which.go index d49e8d3b5..fcc0e7858 100644 --- a/tools/utils/which.go +++ b/tools/utils/which.go @@ -47,3 +47,15 @@ func Which(cmd string, paths ...string) string { } return "" } + +func FindExe(name string) string { + ans := Which(name) + if ans != "" { + return ans + } + ans = Which(name, DefaultExeSearchPaths()...) + if ans == "" { + ans = name + } + return ans +}