diff --git a/client/client_test.go b/client/client_test.go index 2b12bd9..0a44d03 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -681,8 +681,10 @@ func testRepeatedCommandThenQuery(t *testing.T, tester shellTester) { t.Fatalf("hishtory query has the wrong number of commands=%d, out=%#v", strings.Count(out, "echo mycommand"), out) } - // Run a few more commands + // Run a few more commands including some empty lines that don't get recorded tester.RunInteractiveShell(t, `echo mycommand-30 + + echo mycommand-31 echo mycommand-3`) diff --git a/client/hctx/hctx.go b/client/hctx/hctx.go index 878c4f7..377aeac 100644 --- a/client/hctx/hctx.go +++ b/client/hctx/hctx.go @@ -26,6 +26,8 @@ var ( getLoggerOnce sync.Once ) +// TODO: Can we auto-rotate the log file? + func GetLogger() *log.Logger { getLoggerOnce.Do(func() { homedir, err := os.UserHomeDir() diff --git a/client/lib/lib.go b/client/lib/lib.go index 7524a69..6d6edd0 100644 --- a/client/lib/lib.go +++ b/client/lib/lib.go @@ -145,6 +145,10 @@ func BuildHistoryEntry(ctx *context.Context, args []string) (*data.HistoryEntry, } else { return nil, fmt.Errorf("tried to save a hishtory entry from an unsupported shell=%#v", shell) } + if strings.TrimSpace(entry.Command) == "" { + // Skip recording empty commands where the user just hits enter in their terminal + return nil, nil + } // hostname hostname, err := os.Hostname() @@ -278,7 +282,15 @@ func parseCrossPlatformInt(data string) (int64, error) { } func getLastCommand(history string) (string, error) { - return strings.SplitN(strings.SplitN(strings.TrimSpace(history), " ", 2)[1], " ", 2)[1], nil + split := strings.SplitN(strings.TrimSpace(history), " ", 2) + if len(split) <= 1 { + return "", fmt.Errorf("got unexpected bash history line: %#v, please open a bug", history) + } + split = strings.SplitN(split[1], " ", 2) + if len(split) <= 1 { + return "", fmt.Errorf("got unexpected bash history line: %#v, please open a bug", history) + } + return split[1], nil } func shouldSkipHiddenCommand(ctx *context.Context, historyLine string) (bool, error) { diff --git a/client/lib/lib_test.go b/client/lib/lib_test.go index 8724309..41b53ca 100644 --- a/client/lib/lib_test.go +++ b/client/lib/lib_test.go @@ -92,9 +92,41 @@ func TestBuildHistoryEntry(t *testing.T) { if entry.StartTime.Unix() != 1641774958 { t.Fatalf("history entry has incorrect Unix time in the start time: %v", entry.StartTime.Unix()) } + + // Test building an entry for fish + entry, err = BuildHistoryEntry(hctx.MakeContext(), []string{"unused", "saveHistoryEntry", "fish", "120", "ls /foo\n", "1641774958"}) + shared.Check(t, err) + if entry.ExitCode != 120 { + t.Fatalf("history entry has unexpected exit code: %v", entry.ExitCode) + } + if entry.LocalUsername != user.Username { + t.Fatalf("history entry has unexpected user name: %v", entry.LocalUsername) + } + if !strings.HasPrefix(entry.CurrentWorkingDirectory, "/") && !strings.HasPrefix(entry.CurrentWorkingDirectory, "~/") { + t.Fatalf("history entry has unexpected cwd: %v", entry.CurrentWorkingDirectory) + } + if !strings.HasPrefix(entry.HomeDirectory, "/") { + t.Fatalf("history entry has unexpected home directory: %v", entry.HomeDirectory) + } + if entry.Command != "ls /foo" { + t.Fatalf("history entry has unexpected command: %v", entry.Command) + } + if !strings.HasPrefix(entry.StartTime.Format(time.RFC3339), "2022-01-09T") && !strings.HasPrefix(entry.StartTime.Format(time.RFC3339), "2022-01-10T") { + t.Fatalf("history entry has incorrect date in the start time: %v", entry.StartTime.Format(time.RFC3339)) + } + if entry.StartTime.Unix() != 1641774958 { + t.Fatalf("history entry has incorrect Unix time in the start time: %v", entry.StartTime.Unix()) + } + + // Test building an entry that is empty, and thus not saved + entry, err = BuildHistoryEntry(hctx.MakeContext(), []string{"unused", "saveHistoryEntry", "zsh", "120", " \n", "1641774958"}) + shared.Check(t, err) + if entry != nil { + t.Fatalf("expected history entry to be nil") + } } -func TestBuildHistoryEntryWithRedaction(t *testing.T) { +func TestBuildHistoryEntryWithTimestampStripping(t *testing.T) { defer shared.BackupAndRestoreEnv("HISTTIMEFORMAT")() defer shared.BackupAndRestore(t)() defer shared.RunTestServer()() @@ -231,7 +263,9 @@ func TestGetLastCommand(t *testing.T) { testcases := []struct { input, expectedOutput string }{ + {" 0 ls", "ls"}, {" 33 ls", "ls"}, + {" 33 ls --aaaa foo bar ", "ls --aaaa foo bar"}, {" 2389 [2022-09-28 04:38:32 +0000] echo", "[2022-09-28 04:38:32 +0000] echo"}, } for _, tc := range testcases { diff --git a/hishtory.go b/hishtory.go index 6d857df..7ae2960 100644 --- a/hishtory.go +++ b/hishtory.go @@ -274,7 +274,7 @@ func saveHistoryEntry(ctx *context.Context) { entry, err := lib.BuildHistoryEntry(ctx, os.Args) lib.CheckFatalError(err) if entry == nil { - hctx.GetLogger().Printf("Skipping saving a history entry because we failed to build a history entry (was the command prefixed with a space?)\n") + hctx.GetLogger().Printf("Skipping saving a history entry because we did not build a history entry (was the command prefixed with a space and/or empty?)\n") return } @@ -348,4 +348,3 @@ func export(ctx *context.Context, query string) { } // TODO(feature): Add a session_id column that corresponds to the shell session the command was run in -// TODO: Skip recording of empty commands