Skip to content

Commit 6b1aeb0

Browse files
authored
Merge pull request #136 from markusressel/feature/file-history-enhancements
Feature/file history enhancements
2 parents 0904c27 + 737eaa6 commit 6b1aeb0

17 files changed

Lines changed: 448 additions & 112 deletions

internal/application.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"zfs-file-history/internal/ui"
1212

1313
"github.com/oklog/run"
14-
"github.com/pterm/pterm"
1514
)
1615

1716
func RunApplication(path string) {
@@ -29,7 +28,6 @@ func RunApplication(path string) {
2928
os.Exit(1)
3029
} else {
3130
logging.Info("Done.")
32-
pterm.Info.Printfln("Done.")
3331
os.Exit(0)
3432
}
3533
}
@@ -41,7 +39,6 @@ func addSignalHandlerActor(g *run.Group, cancel context.CancelFunc) {
4139
g.Add(func() error {
4240
<-sig
4341
logging.Info("Received SIGTERM signal, exiting...")
44-
pterm.Info.Printfln("Received SIGTERM signal, exiting...")
4542

4643
return nil
4744
}, func(err error) {

internal/data/snapshot_browser_entry.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ import (
66
)
77

88
type SnapshotBrowserEntry struct {
9-
Snapshot *zfs.Snapshot
10-
DiffState diff_state.DiffState
11-
IsLoading bool
9+
Snapshot *zfs.Snapshot
10+
DiffState diff_state.DiffState
11+
WorkingCopyDiffState diff_state.DiffState
12+
IsLoading bool
1213
}
1314

1415
func (s SnapshotBrowserEntry) TableRowId() string {

internal/ui/actor.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ func AddActor(g *run.Group, ctx context.Context, path string) {
2424
pterm.Warning.Printfln("Error stopping UI: %s", err.Error())
2525
} else {
2626
logging.Debug("UI stopped.")
27-
pterm.Debug.Printfln("Received SIGTERM signal, exiting...")
2827
}
2928
})
3029
}

internal/ui/dialog/column_selection_dialog.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,13 +110,11 @@ func (d *ColumnSelectionDialog) createLayout() {
110110
extraWidth := 2 * (maxColWidth + 4)
111111
staticHeight := 1 + len(d.allColumns) + 2
112112

113-
width, height := CalculateDialogSize(DialogSizeConstraints{
113+
d.layout = createModal(d.title, content, DialogSizeConstraints{
114114
Title: d.title,
115115
ExtraContentWidth: extraWidth,
116116
StaticHeight: staticHeight,
117117
})
118-
119-
d.layout = createModal(d.title, content, width, height)
120118
d.layout.SetInputCapture(d.captureInput)
121119
}
122120

internal/ui/dialog/file_action_dialog.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"slices"
66
"zfs-file-history/internal/data"
7+
"zfs-file-history/internal/data/diff_state"
78
"zfs-file-history/internal/ui/localization"
89
"zfs-file-history/internal/ui/util"
910

@@ -54,7 +55,8 @@ func buildFileDialogOptions(file *data.FileBrowserEntry, diffBinAvailable bool)
5455
})
5556
}
5657

57-
if file.HasSnapshot() {
58+
canRestore := file.HasSnapshot() || (file.DiffState != diff_state.Equal && file.DiffState != diff_state.Unknown)
59+
if canRestore {
5860
if file.Type == data.Directory {
5961
dialogOptions = slices.Insert(dialogOptions, 0, &DialogOption{
6062
Id: FileDialogRestoreFileActionId,

internal/ui/dialog/file_action_dialog_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,10 @@ func TestBuildFileDialogOptions_DirectoryWithSnapshot(t *testing.T) {
8585

8686
func TestBuildFileDialogOptions_OnlyRealFile(t *testing.T) {
8787
entry := &data.FileBrowserEntry{
88-
Name: "real-only.txt",
89-
RealFile: &data.RealFile{Name: "real-only.txt"},
90-
Type: data.File,
88+
Name: "real-only.txt",
89+
RealFile: &data.RealFile{Name: "real-only.txt"},
90+
Type: data.File,
91+
DiffState: diff_state.Unknown,
9192
}
9293

9394
options := buildFileDialogOptions(entry, true)

internal/ui/dialog/file_diff_dialog.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,11 @@ func (d *FileDiffDialog) createLayout() {
6969
dialogContent.AddItem(closeTextView, 1, 0, false)
7070
dialogContent.SetBorderPadding(0, 0, 1, 1)
7171

72-
width, height := CalculateDialogSize(DialogSizeConstraints{
72+
dialog := createModal(dialogTitle, dialogContent, DialogSizeConstraints{
7373
Title: dialogTitle,
7474
ExtraContentWidth: 74,
7575
StaticHeight: 18, // Sane content height for scrollable diff text
7676
})
77-
78-
dialog := createModal(dialogTitle, dialogContent, width, height)
7977
dialog.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
8078
if event.Key() == tcell.KeyEscape {
8179
d.Close()

internal/ui/dialog/file_history_overlay.go

Lines changed: 103 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -195,19 +195,41 @@ func (o *FileHistoryOverlay) createTableCells(row int, columns []*table.Column,
195195
text = entry.Snapshot.Name
196196
case historyColumnDiff:
197197
align = tview.AlignCenter
198-
switch entry.DiffState {
199-
case diff_state.Added:
200-
text = "Added"
201-
color = theme.Colors.SnapshotBrowser.Table.State.LocalOnly
202-
case diff_state.Deleted:
203-
text = "Deleted"
204-
color = theme.Colors.SnapshotBrowser.Table.State.SnapshotOnly
205-
case diff_state.Modified:
206-
text = "Modified"
207-
color = theme.Colors.SnapshotBrowser.Table.State.Modified
208-
default:
209-
text = "Unknown"
210-
color = tcell.ColorGray
198+
if o.currentDiffMode == diffModeWorkingCopy {
199+
switch entry.WorkingCopyDiffState {
200+
case diff_state.Deleted:
201+
text = "Present"
202+
color = theme.Colors.FileBrowser.Table.State.Added
203+
case diff_state.Added:
204+
text = "Absent"
205+
color = theme.Colors.FileBrowser.Table.State.Deleted
206+
case diff_state.Modified:
207+
text = "Modified"
208+
color = theme.Colors.FileBrowser.Table.State.Modified
209+
default:
210+
text = "Identical"
211+
color = theme.Colors.FileBrowser.Table.State.Equal
212+
}
213+
} else {
214+
isOldest := len(o.historyEntries) > 0 && o.historyEntries[len(o.historyEntries)-1] == entry
215+
switch entry.DiffState {
216+
case diff_state.Added:
217+
if isOldest {
218+
text = "Initial"
219+
} else {
220+
text = "Added"
221+
}
222+
color = theme.Colors.FileBrowser.Table.State.Added
223+
case diff_state.Deleted:
224+
text = "Deleted"
225+
color = theme.Colors.FileBrowser.Table.State.Deleted
226+
case diff_state.Modified:
227+
text = "Modified"
228+
color = theme.Colors.FileBrowser.Table.State.Modified
229+
default:
230+
text = "Equal"
231+
color = theme.Colors.FileBrowser.Table.State.Equal
232+
}
211233
}
212234
case historyColumnDate:
213235
text = entry.Snapshot.Properties.CreationDate.Format(theme.Style.Format.DateTime)
@@ -229,17 +251,28 @@ func (o *FileHistoryOverlay) createTableCells(row int, columns []*table.Column,
229251
}
230252

231253
func (o *FileHistoryOverlay) determineStatusColor(entry *data.SnapshotBrowserEntry) tcell.Color {
232-
switch entry.DiffState {
233-
case diff_state.Equal:
234-
return theme.Colors.SnapshotBrowser.Table.State.Equal
235-
case diff_state.Deleted:
236-
return theme.Colors.SnapshotBrowser.Table.State.SnapshotOnly
237-
case diff_state.Added:
238-
return theme.Colors.SnapshotBrowser.Table.State.LocalOnly
239-
case diff_state.Modified:
240-
return theme.Colors.SnapshotBrowser.Table.State.Modified
241-
default:
242-
return theme.Colors.SnapshotBrowser.Table.State.Unknown
254+
if o.currentDiffMode == diffModeWorkingCopy {
255+
switch entry.WorkingCopyDiffState {
256+
case diff_state.Deleted:
257+
return theme.Colors.FileBrowser.Table.State.Added
258+
case diff_state.Added:
259+
return theme.Colors.FileBrowser.Table.State.Deleted
260+
case diff_state.Modified:
261+
return theme.Colors.FileBrowser.Table.State.Modified
262+
default:
263+
return theme.Colors.FileBrowser.Table.State.Equal
264+
}
265+
} else {
266+
switch entry.DiffState {
267+
case diff_state.Added:
268+
return theme.Colors.FileBrowser.Table.State.Added
269+
case diff_state.Deleted:
270+
return theme.Colors.FileBrowser.Table.State.Deleted
271+
case diff_state.Modified:
272+
return theme.Colors.FileBrowser.Table.State.Modified
273+
default:
274+
return theme.Colors.FileBrowser.Table.State.Equal
275+
}
243276
}
244277
}
245278

@@ -306,6 +339,8 @@ func (o *FileHistoryOverlay) createLayout() *tview.Flex {
306339
AddItem(dialogContentRowWrapper, width, 1, true).
307340
AddItem(nil, 0, 1, false)
308341

342+
MakeFlexResizing(dialogContentColumnWrapper, dialogContentRowWrapper, dialogFrame, 99999, 80, 99999, 15)
343+
309344
return dialogContentColumnWrapper
310345
}
311346

@@ -418,6 +453,7 @@ func (o *FileHistoryOverlay) toggleDiffMode() {
418453
o.updateModeView()
419454
o.updateShortcuts()
420455
o.updateDiff()
456+
o.tableContainer.SetData(o.historyEntries)
421457
}
422458

423459
func (o *FileHistoryOverlay) renderDiffTextSync(text string) {
@@ -649,11 +685,30 @@ func computeHistoryDiffText(oldPath, newPath string, diffMode diffMode, prevSnap
649685
return "Binary files differ, content preview not available."
650686
}
651687

652-
if diffMode == diffModeWorkingCopy {
653-
_, err := os.Lstat(oldPath)
688+
// Resolve missing/deleted files to DevNull for comparison
689+
if oldPath != DevNull {
690+
stat, err := os.Lstat(oldPath)
654691
if os.IsNotExist(err) {
655-
return "Working copy file does not exist (deleted)."
692+
oldPath = DevNull
693+
} else if err == nil && stat.IsDir() {
694+
return "Directory content comparison not available."
695+
}
696+
}
697+
if newPath != DevNull {
698+
stat, err := os.Lstat(newPath)
699+
if os.IsNotExist(err) {
700+
newPath = DevNull
701+
} else if err == nil && stat.IsDir() {
702+
return "Directory content comparison not available."
656703
}
704+
}
705+
706+
// If both are missing, there's no diff content to show
707+
if oldPath == DevNull && newPath == DevNull {
708+
return ""
709+
}
710+
711+
if diffMode == diffModeWorkingCopy {
657712
output, err := RunDiff(oldPath, newPath)
658713
if err != nil {
659714
return "Error calculating diff: " + err.Error()
@@ -663,12 +718,10 @@ func computeHistoryDiffText(oldPath, newPath string, diffMode diffMode, prevSnap
663718

664719
// diffModePredecessor
665720
if prevSnapshot == nil {
666-
stat, err := os.Lstat(newPath)
667-
if err != nil {
668-
return "Snapshot file does not exist."
669-
}
670-
if stat.IsDir() {
671-
return "Directory content comparison not available."
721+
// Since prevSnapshot is nil, oldPath is DevNull.
722+
// If newPath is also DevNull (e.g. not found), return empty
723+
if newPath == DevNull {
724+
return ""
672725
}
673726
data, err := os.ReadFile(newPath)
674727
if err != nil {
@@ -707,13 +760,23 @@ func (o *FileHistoryOverlay) restoreSelectedVersion() {
707760
return
708761
}
709762

710-
snapshotPath := entry.Snapshot.GetSnapshotPath(o.file.GetRealPath())
711-
stat, err := os.Lstat(snapshotPath)
712-
if err != nil {
713-
logging.Error("Could not stat snapshot file %s: %s", snapshotPath, err.Error())
714-
errDialog := NewErrorDialog(o.application, "Restore Failed", err)
715-
ShowDialogOnPages(o.application, o.pages, errDialog, nil)
716-
return
763+
var stat os.FileInfo
764+
var snapshotPath string
765+
766+
if entry.DiffState == diff_state.Deleted {
767+
// File is deleted/absent in the selected snapshot.
768+
snapshotPath = ""
769+
stat = nil
770+
} else {
771+
snapshotPath = entry.Snapshot.GetSnapshotPath(o.file.GetRealPath())
772+
var err error
773+
stat, err = os.Lstat(snapshotPath)
774+
if err != nil {
775+
logging.Error("Could not stat snapshot file %s: %s", snapshotPath, err.Error())
776+
errDialog := NewErrorDialog(o.application, "Restore Failed", err)
777+
ShowDialogOnPages(o.application, o.pages, errDialog, nil)
778+
return
779+
}
717780
}
718781

719782
snapFile := &data.SnapshotFile{

0 commit comments

Comments
 (0)