Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,17 @@ This project was developed with the assistance of ChatGPT and GitHub Copilot.
- **Directory-Aware History**
Commands are stored with their execution directory context, allowing you to view history specific to directories.

- **Directory Path Updates**
- **Directory Path Updates**
When you move or rename directories, you can update all related history entries:
- Updates both exact path matches and subdirectory paths
- Preserves your command history context when reorganizing your filesystem
- Handles relative paths automatically

- **Disk Space Safety Policy**
- Warns when disk space falls below 10MB
- Blocks writes when disk space falls below 1MB (to prevent SQLite corruption)
- Handles edge cases gracefully (symlinks, deleted directories, in-memory databases)

- **Shell Context Tracking**
Each command is stored with its execution context:
- Hostname of the machine
Expand Down
22 changes: 17 additions & 5 deletions cmd/histree-core/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"bytes"
"errors"
"flag"
"fmt"
"io"
Expand Down Expand Up @@ -64,16 +65,27 @@ func main() {
os.Exit(1)
}
if err := handleAdd(db, *currentDir, *hostname, *processID, *exitCode); err != nil {
if errors.Is(err, histree.ErrInsufficientDiskSpace) {
fmt.Fprintf(os.Stderr, "Warning: %v\n", err)
return
}

fmt.Fprintf(os.Stderr, "Failed to add entry: %v\n", err)
os.Exit(1)
}

// Check for low disk space warning after successful write
if warning := db.CheckDiskSpaceWarning(); warning != nil {
fmt.Fprintf(os.Stderr, "Warning: Low disk space (%s remaining). History recording may fail soon.\n",
histree.FormatBytes(warning.AvailableBytes))
}

case "get":
if err := handleGet(db, *limit, *currentDir, histree.OutputFormat(*format)); err != nil {
fmt.Fprintf(os.Stderr, "Failed to get entries: %v\n", err)
os.Exit(1)
}

case "update-path":
if *oldPath == "" || *newPath == "" {
fmt.Fprintf(os.Stderr, "Error: both -old-path and -new-path parameters are required for update-path action\n")
Expand Down Expand Up @@ -137,25 +149,25 @@ func handleUpdatePath(db *histree.DB, oldPath, newPath string) error {
}
oldPath = absOldPath
}

if !filepath.IsAbs(newPath) {
absNewPath, err := filepath.Abs(newPath)
if err != nil {
return fmt.Errorf("failed to convert new path to absolute path: %w", err)
}
newPath = absNewPath
}

// Clean the paths to ensure consistent format
oldPath = filepath.Clean(oldPath)
newPath = filepath.Clean(newPath)

// Update the paths in the database
count, err := db.UpdatePaths(oldPath, newPath)
if err != nil {
return err
}

fmt.Printf("Updated %d entries: %s -> %s\n", count, oldPath, newPath)
return nil
}
42 changes: 38 additions & 4 deletions cmd/histree-core/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"bytes"
"errors"
"os"
"strings"
"testing"
Expand Down Expand Up @@ -119,6 +120,39 @@ func TestGetEntries(t *testing.T) {
}
}

func TestAddEntryErrorsWhenNoDiskSpace(t *testing.T) {
db, cleanup := setupTestDB(t)
defer cleanup()

histree.SetDiskSpaceChecker(func(string) (bool, error) {
return false, nil
})
defer histree.SetDiskSpaceChecker(nil)

entry := histree.HistoryEntry{
Command: "should-not-be-inserted",
Directory: "/home/user",
Timestamp: time.Now().UTC(),
ExitCode: 0,
Hostname: "test-host",
ProcessID: 12345,
}

err := db.AddEntry(&entry)
if !errors.Is(err, histree.ErrInsufficientDiskSpace) {
t.Fatalf("expected ErrInsufficientDiskSpace, got %v", err)
}

var count int
if err := db.QueryRow("SELECT COUNT(*) FROM history").Scan(&count); err != nil {
t.Fatalf("failed to count entries: %v", err)
}

if count != 0 {
t.Fatalf("expected 0 entries when disk full, got %d", count)
}
}

// TestFormatVerboseWithTimezone tests that the FormatVerbose output
// correctly converts UTC timestamps to local timezone
func TestFormatVerboseWithTimezone(t *testing.T) {
Expand Down Expand Up @@ -217,7 +251,7 @@ func TestUpdatePaths(t *testing.T) {
// Define test paths
oldPath := "/home/user/oldpath"
newPath := "/home/user/newpath"

// Create test entries with different paths
entries := []histree.HistoryEntry{
{
Expand Down Expand Up @@ -282,9 +316,9 @@ func TestUpdatePaths(t *testing.T) {

// Check the expected path changes
expectedDirs := []string{
newPath, // oldPath should now be newPath
newPath + "/subdir", // oldPath/subdir should now be newPath/subdir
"/tmp", // Unrelated path should remain unchanged
newPath, // oldPath should now be newPath
newPath + "/subdir", // oldPath/subdir should now be newPath/subdir
"/tmp", // Unrelated path should remain unchanged
}

if len(updatedDirs) != len(expectedDirs) {
Expand Down
Loading