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 deltas << diff_in(work, path, kind, from_bytes, to_bytes)!76 }77 return deltas78}79808182pub fn diff_bytes(path string, kind string, from_bytes []u8, to_bytes []u8) !Delta {83 work := temp_workspace()!84 defer {85 os.rmdir_all(work) or {}86 }87 return diff_in(work, path, kind, from_bytes, to_bytes)!88}8990fn diff_in(work string, path string, kind string, from_bytes []u8, to_bytes []u8) !Delta {91 mut delta := Delta{92 path: path93 kind: kind94 }95 if looks_binary(from_bytes) || looks_binary(to_bytes) {96 delta.binary = true97 delta.patch = binary_notice98 } else {99 delta.patch = run_diff(work, path, from_bytes, to_bytes)!100 delta.added, delta.removed = count_lines(delta.patch)101 }102 return delta103}104105pub fn index_files(files []FileEntry) map[string]FileEntry {106 mut out := map[string]FileEntry{}107 for file in files {108 out[file.path] = file109 }110 return out111}112113fn bytes_of(store &storage.Store, tree Tree, entry FileEntry) ![]u8 {114 if tree.root == '' {115 return store.get_object(entry.hash)!116 }117 return os.read_bytes(os.join_path(tree.root, entry.path))!118}119120fn looks_binary(data []u8) bool {121 limit := if data.len < 8000 { data.len } else { 8000 }122 for i in 0 .. limit {123 if data[i] == 0 {124 return true125 }126 }127 return false128}129130fn count_lines(patch string) (int, int) {131 mut added := 0132 mut removed := 0133 for line in patch.split_into_lines() {134 if line.starts_with('+') && !line.starts_with('+++') {135 added++136 } else if line.starts_with('-') && !line.starts_with('---') {137 removed++138 }139 }140 return added, removed141}142143fn temp_workspace() !string {144 dir := os.join_path(os.temp_dir(), 'koh-diff-${os.getpid()}-${time.now().unix_nano()}')145 os.mkdir_all(dir, os.MkdirParams{})!146 return dir147}148149fn run_diff(work string, path string, from_bytes []u8, to_bytes []u8) !string {150 from_path := os.join_path(work, 'before')151 to_path := os.join_path(work, 'after')152 os.write_file_array(from_path, from_bytes)!153 os.write_file_array(to_path, to_bytes)!154 command := 'diff -u -L ${shell_quote('a/' + path)} -L ${shell_quote('b/' + path)} ${shell_quote(from_path)} ${shell_quote(to_path)}'155 result := os.execute(command)156 if result.exit_code < 0 {157 return error('cannot run diff(1)')158 }159 return result.output160}161162fn shell_quote(text string) string {163 return "'" + text.replace("'", "'\\''") + "'"164}