1234567891011module snapshot1213import os14import time15import src.storage1617pub struct Tree {18pub:19 root string 20 files []FileEntry21}2223pub struct Delta {24pub mut:25 path string26 kind string 27 added int28 removed int29 binary bool30 patch string31}3233pub const binary_notice = 'Binary files differ'343536pub fn diff_trees(store &storage.Store, from Tree, to Tree) ![]Delta {37 from_map := index_files(from.files)38 to_map := index_files(to.files)39 mut paths := []string{}40 for path in from_map.keys() {41 paths << path42 }43 for path in to_map.keys() {44 if path !in from_map {45 paths << path46 }47 }48 paths.sort()49 mut deltas := []Delta{}50 work := temp_workspace()!51 defer {52 os.rmdir_all(work) or {}53 }54 for path in paths {55 has_before := path in from_map56 has_after := path in to_map57 if has_before && has_after && from_map[path].hash == to_map[path].hash {58 continue59 }60 kind := if !has_before {61 'added'62 } else if !has_after {63 'removed'64 } else {65 'modified'66 }67 mut from_bytes := []u8{}68 if has_before {69 from_bytes = bytes_of(store, from, from_map[path])!70 }71 mut to_bytes := []u8{}72 if has_after {73 to_bytes = bytes_of(store, to, to_map[path])!74 }75 mut delta := Delta{76 path: path77 kind: kind78 }79 if looks_binary(from_bytes) || looks_binary(to_bytes) {80 delta.binary = true81 delta.patch = binary_notice82 } else {83 delta.patch = run_diff(work, path, from_bytes, to_bytes)!84 delta.added, delta.removed = count_lines(delta.patch)85 }86 deltas << delta87 }88 return deltas89}9091pub fn index_files(files []FileEntry) map[string]FileEntry {92 mut out := map[string]FileEntry{}93 for file in files {94 out[file.path] = file95 }96 return out97}9899fn bytes_of(store &storage.Store, tree Tree, entry FileEntry) ![]u8 {100 if tree.root == '' {101 return store.get_object(entry.hash)!102 }103 return os.read_bytes(os.join_path(tree.root, entry.path))!104}105106fn looks_binary(data []u8) bool {107 limit := if data.len < 8000 { data.len } else { 8000 }108 for i in 0 .. limit {109 if data[i] == 0 {110 return true111 }112 }113 return false114}115116fn count_lines(patch string) (int, int) {117 mut added := 0118 mut removed := 0119 for line in patch.split_into_lines() {120 if line.starts_with('+') && !line.starts_with('+++') {121 added++122 } else if line.starts_with('-') && !line.starts_with('---') {123 removed++124 }125 }126 return added, removed127}128129fn temp_workspace() !string {130 dir := os.join_path(os.temp_dir(), 'koh-diff-${os.getpid()}-${time.now().unix_nano()}')131 os.mkdir_all(dir, os.MkdirParams{})!132 return dir133}134135fn run_diff(work string, path string, from_bytes []u8, to_bytes []u8) !string {136 from_path := os.join_path(work, 'before')137 to_path := os.join_path(work, 'after')138 os.write_file_array(from_path, from_bytes)!139 os.write_file_array(to_path, to_bytes)!140 command := 'diff -u -L ${shell_quote('a/' + path)} -L ${shell_quote('b/' + path)} ${shell_quote(from_path)} ${shell_quote(to_path)}'141 result := os.execute(command)142 if result.exit_code < 0 {143 return error('cannot run diff(1)')144 }145 return result.output146}147148fn shell_quote(text string) string {149 return "'" + text.replace("'", "'\\''") + "'"150}