Skip to content

Commit 0904c27

Browse files
committed
Merge branch 'feature/cleanup-file-specific-history'
2 parents b3bb7d9 + 23c1307 commit 0904c27

10 files changed

Lines changed: 634 additions & 365 deletions

internal/ui/dialog/diff.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package dialog
2+
3+
import (
4+
"os"
5+
"os/exec"
6+
"strings"
7+
)
8+
9+
const (
10+
DiffBinPath = "/usr/bin/diff"
11+
)
12+
13+
// DiffBinExists checks if the diff binary is available on the system.
14+
func DiffBinExists() bool {
15+
_, err := exec.LookPath(DiffBinPath)
16+
return err == nil
17+
}
18+
19+
// RunDiff executes the system diff command on two files and returns the output.
20+
func RunDiff(oldPath, newPath string) (string, error) {
21+
output, err := exec.Command(
22+
DiffBinPath,
23+
"-U", "3",
24+
oldPath,
25+
newPath,
26+
).Output()
27+
if err != nil && err.Error() != "exit status 1" {
28+
return "", err
29+
}
30+
return string(output), nil
31+
}
32+
33+
// IsBinaryFile checks if a file contains null bytes, indicating it is binary.
34+
func IsBinaryFile(path string) bool {
35+
if path == DevNull {
36+
return false
37+
}
38+
f, err := os.Open(path)
39+
if err != nil {
40+
return false
41+
}
42+
defer f.Close()
43+
44+
buf := make([]byte, 512)
45+
n, _ := f.Read(buf)
46+
for i := 0; i < n; i++ {
47+
if buf[i] == 0 {
48+
return true
49+
}
50+
}
51+
return false
52+
}
53+
54+
// FormatDiffText highlights diff additions (green) and deletions (red) with tview color tags.
55+
// If filterHeaders is true, it removes diff unified header lines (--- and +++).
56+
func FormatDiffText(diffText string, filterHeaders bool) string {
57+
diffTextLines := strings.Split(diffText, "\n")
58+
var resultLines []string
59+
for _, line := range diffTextLines {
60+
if filterHeaders {
61+
if len(line) >= 4 && (strings.HasPrefix(line, "---") || strings.HasPrefix(line, "+++")) && (line[3] == ' ' || line[3] == '\t') {
62+
continue
63+
}
64+
}
65+
if strings.HasPrefix(line, "+") {
66+
resultLines = append(resultLines, `[green]`+line+`[white]`)
67+
} else if strings.HasPrefix(line, "-") {
68+
resultLines = append(resultLines, `[red]`+line+`[white]`)
69+
} else {
70+
resultLines = append(resultLines, line)
71+
}
72+
}
73+
return strings.Join(resultLines, "\n")
74+
}

internal/ui/dialog/diff_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package dialog
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"testing"
7+
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestIsBinaryFile(t *testing.T) {
12+
tempDir := t.TempDir()
13+
14+
// Text file
15+
textFile := filepath.Join(tempDir, "text.txt")
16+
err := os.WriteFile(textFile, []byte("Hello world, this is a plain text file."), 0644)
17+
assert.NoError(t, err)
18+
assert.False(t, IsBinaryFile(textFile))
19+
20+
// Binary file (contains null byte)
21+
binFile := filepath.Join(tempDir, "binary.bin")
22+
err = os.WriteFile(binFile, []byte{0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x00, 0x57, 0x6f, 0x72, 0x6c, 0x64}, 0644)
23+
assert.NoError(t, err)
24+
assert.True(t, IsBinaryFile(binFile))
25+
26+
// Non-existent file
27+
assert.False(t, IsBinaryFile(filepath.Join(tempDir, "does-not-exist")))
28+
assert.False(t, IsBinaryFile(DevNull))
29+
}
30+
31+
func TestFormatDiffText(t *testing.T) {
32+
diffInput := `--- old.txt
33+
+++ new.txt
34+
@@ -1,3 +1,3 @@
35+
-Hello
36+
+World
37+
Unchanged`
38+
39+
// Test without filtering headers
40+
outNoFilter := FormatDiffText(diffInput, false)
41+
assert.Contains(t, outNoFilter, "[red]-Hello[white]")
42+
assert.Contains(t, outNoFilter, "[green]+World[white]")
43+
assert.Contains(t, outNoFilter, "--- old.txt")
44+
assert.Contains(t, outNoFilter, "+++ new.txt")
45+
46+
// Test with filtering headers
47+
outWithFilter := FormatDiffText(diffInput, true)
48+
assert.Contains(t, outWithFilter, "[red]-Hello[white]")
49+
assert.Contains(t, outWithFilter, "[green]+World[white]")
50+
assert.NotContains(t, outWithFilter, "--- old.txt")
51+
assert.NotContains(t, outWithFilter, "+++ new.txt")
52+
}

internal/ui/dialog/file_action_dialog.go

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package dialog
22

33
import (
44
"fmt"
5-
"os/exec"
65
"slices"
76
"zfs-file-history/internal/data"
87
"zfs-file-history/internal/ui/localization"
@@ -95,11 +94,3 @@ func buildFileDialogOptions(file *data.FileBrowserEntry, diffBinAvailable bool)
9594

9695
return ensureDialogCloseIsLast(dialogOptions)
9796
}
98-
99-
func DiffBinExists() bool {
100-
_, err := exec.LookPath(DiffBinPath)
101-
if err != nil {
102-
return false
103-
}
104-
return true
105-
}

internal/ui/dialog/file_diff_dialog.go

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package dialog
22

33
import (
4-
"os/exec"
5-
"strings"
64
"zfs-file-history/internal/data"
75
"zfs-file-history/internal/ui/util"
86

@@ -11,8 +9,6 @@ import (
119
)
1210

1311
const (
14-
DiffBinPath = "/usr/bin/diff"
15-
1612
FileDiffDialogPage util.Page = "FileDiffDialog"
1713
)
1814

@@ -43,28 +39,19 @@ func (d *FileDiffDialog) createLayout() {
4339
realFilePath := d.file.RealFile.Path
4440
snapshotFilePath := d.snapshot.Snapshot.GetSnapshotPath(d.file.RealFile.Path)
4541

46-
output, err := exec.Command(
47-
DiffBinPath,
48-
"-U", "3",
49-
snapshotFilePath,
50-
realFilePath,
51-
).Output()
52-
diffText := string(output)
53-
if err != nil && err.Error() != "exit status 1" {
54-
diffText = "error calculating diff: " + err.Error()
55-
}
56-
57-
diffTextLines := strings.Split(diffText, "\n")
58-
for i := 0; i < len(diffTextLines); i++ {
59-
line := diffTextLines[i]
60-
if strings.HasPrefix(line, "+") {
61-
diffTextLines[i] = `[green]` + line + `[white]`
62-
}
63-
if strings.HasPrefix(line, "-") {
64-
diffTextLines[i] = `[red]` + line + `[white]`
42+
var diffText string
43+
isBinary := IsBinaryFile(snapshotFilePath) || IsBinaryFile(realFilePath)
44+
if isBinary {
45+
diffText = "Binary files differ, content preview not available."
46+
} else {
47+
var err error
48+
diffText, err = RunDiff(snapshotFilePath, realFilePath)
49+
if err != nil {
50+
diffText = "error calculating diff: " + err.Error()
51+
} else {
52+
diffText = FormatDiffText(diffText, false)
6553
}
6654
}
67-
diffText = strings.Join(diffTextLines, "\n")
6855

6956
textDescriptionView := tview.NewTextView().
7057
SetDynamicColors(true).

0 commit comments

Comments
 (0)