From 342a02a8432b797dce0bdbb944e6292a80cf3d0d Mon Sep 17 00:00:00 2001 From: David Dworken Date: Sun, 13 Nov 2022 18:26:23 -0800 Subject: [PATCH 01/13] Add initial cobra implementation for config-* --- README.md | 2 +- client/client_test.go | 8 +- client/cmd/configAdd.go | 51 +++++++ client/cmd/configDelete.go | 73 ++++++++++ client/cmd/configGet.go | 86 ++++++++++++ client/cmd/configSet.go | 92 +++++++++++++ client/cmd/root.go | 51 +++++++ .../testCustomColumns-query-isAction=false | 20 +-- .../testCustomColumns-query-isAction=true | 2 +- .../lib/goldens/testCustomColumns-tquery-bash | 48 +++---- ...tCustomColumns-tquery-bash-isAction-darwin | 2 +- ...stCustomColumns-tquery-bash-isAction-linux | 2 +- .../lib/goldens/testCustomColumns-tquery-zsh | 2 +- ...stCustomColumns-tquery-zsh-isAction-darwin | 2 +- ...estCustomColumns-tquery-zsh-isAction-linux | 2 +- go.mod | 3 +- go.sum | 4 + hishtory.go | 128 +----------------- 18 files changed, 410 insertions(+), 168 deletions(-) create mode 100644 client/cmd/configAdd.go create mode 100644 client/cmd/configDelete.go create mode 100644 client/cmd/configGet.go create mode 100644 client/cmd/configSet.go create mode 100644 client/cmd/root.go diff --git a/README.md b/README.md index 3368f81..092756e 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ hishtory config-set displayed-columns CWD Command You can create custom column definitions that are populated from arbitrary commands. For example, if you want to create a new column named `git_remote` that contains the git remote if the cwd is in a git directory, you can run: ``` -hishtory config-add custom-column git_remote '(git remote -v 2>/dev/null | grep origin 1>/dev/null ) && git remote get-url origin || true' +hishtory config-add custom-columns git_remote '(git remote -v 2>/dev/null | grep origin 1>/dev/null ) && git remote get-url origin || true' hishtory config-add displayed-columns git_remote ``` diff --git a/client/client_test.go b/client/client_test.go index fcaadb8..ecfb241 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -1641,17 +1641,17 @@ func testConfigGetSet(t *testing.T, tester shellTester) { // Config-get and set for enable-control-r out := tester.RunInteractiveShell(t, `hishtory config-get enable-control-r`) - if out != "true" { + if out != "true\n" { t.Fatalf("unexpected config-get output: %#v", out) } tester.RunInteractiveShell(t, `hishtory config-set enable-control-r false`) out = tester.RunInteractiveShell(t, `hishtory config-get enable-control-r`) - if out != "false" { + if out != "false\n" { t.Fatalf("unexpected config-get output: %#v", out) } tester.RunInteractiveShell(t, `hishtory config-set enable-control-r true`) out = tester.RunInteractiveShell(t, `hishtory config-get enable-control-r`) - if out != "true" { + if out != "true\n" { t.Fatalf("unexpected config-get output: %#v", out) } @@ -2073,7 +2073,7 @@ echo baz`) compareGoldens(t, out, "testCustomColumns-initHistory") // Configure a custom column - tester.RunInteractiveShell(t, `hishtory config-add custom-column git_remote '(git remote -v 2>/dev/null | grep origin 1>/dev/null ) && git remote get-url origin || true'`) + tester.RunInteractiveShell(t, `hishtory config-add custom-columns git_remote '(git remote -v 2>/dev/null | grep origin 1>/dev/null ) && git remote get-url origin || true'`) // Run a few commands, some of which will have a git_remote out = tester.RunInteractiveShell(t, `echo foo diff --git a/client/cmd/configAdd.go b/client/cmd/configAdd.go new file mode 100644 index 0000000..24d892f --- /dev/null +++ b/client/cmd/configAdd.go @@ -0,0 +1,51 @@ +package cmd + +import ( + "github.com/ddworken/hishtory/client/hctx" + "github.com/ddworken/hishtory/client/lib" + "github.com/spf13/cobra" +) + +var configAddCmd = &cobra.Command{ + Use: "config-add", + Short: "Add a config option", + Run: func(cmd *cobra.Command, args []string) { + cmd.Help() + }, +} + +var addCustomColumnsCmd = &cobra.Command{ + Use: "custom-columns", + Short: "Add a custom column", + Args: cobra.ExactArgs(2), + Run: func(cmd *cobra.Command, args []string) { + columnName := args[0] + command := args[1] + ctx := hctx.MakeContext() + config := hctx.GetConf(ctx) + if config.CustomColumns == nil { + config.CustomColumns = make([]hctx.CustomColumnDefinition, 0) + } + config.CustomColumns = append(config.CustomColumns, hctx.CustomColumnDefinition{ColumnName: columnName, ColumnCommand: command}) + lib.CheckFatalError(hctx.SetConfig(config)) + }, +} + +var addDisplayedColumnsCmd = &cobra.Command{ + Use: "displayed-columns", + Short: "Add a column to be displayed", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + ctx := hctx.MakeContext() + config := hctx.GetConf(ctx) + vals := args + config.DisplayedColumns = append(config.DisplayedColumns, vals...) + lib.CheckFatalError(hctx.SetConfig(config)) + }, +} + +func init() { + rootCmd.AddCommand(configAddCmd) + configAddCmd.AddCommand(addCustomColumnsCmd) + configAddCmd.AddCommand(addDisplayedColumnsCmd) +} diff --git a/client/cmd/configDelete.go b/client/cmd/configDelete.go new file mode 100644 index 0000000..7f701c6 --- /dev/null +++ b/client/cmd/configDelete.go @@ -0,0 +1,73 @@ +package cmd + +import ( + "log" + + "github.com/ddworken/hishtory/client/hctx" + "github.com/ddworken/hishtory/client/lib" + "github.com/spf13/cobra" +) + +var configDeleteCmd = &cobra.Command{ + Use: "config-delete", + Short: "Delete a config option", + Run: func(cmd *cobra.Command, args []string) { + cmd.Help() + }, +} + +var deleteCustomColumnsCmd = &cobra.Command{ + Use: "custom-columns", + Short: "Delete a custom column", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + ctx := hctx.MakeContext() + config := hctx.GetConf(ctx) + columnName := args[0] + if config.CustomColumns == nil { + log.Fatalf("Did not find a column with name %#v to delete (current columns = %#v)", columnName, config.CustomColumns) + } + newColumns := make([]hctx.CustomColumnDefinition, 0) + deletedColumns := false + for _, c := range config.CustomColumns { + if c.ColumnName != columnName { + newColumns = append(newColumns, c) + deletedColumns = true + } + } + if !deletedColumns { + log.Fatalf("Did not find a column with name %#v to delete (current columns = %#v)", columnName, config.CustomColumns) + } + config.CustomColumns = newColumns + lib.CheckFatalError(hctx.SetConfig(config)) + }, +} +var deleteDisplayedColumnCommand = &cobra.Command{ + Use: "displayed-columns", + Short: "Delete a displayed column", + Args: cobra.MinimumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + ctx := hctx.MakeContext() + config := hctx.GetConf(ctx) + deletedColumns := args + newColumns := make([]string, 0) + for _, c := range config.DisplayedColumns { + isDeleted := false + for _, d := range deletedColumns { + if c == d { + isDeleted = true + } + } + if !isDeleted { + newColumns = append(newColumns, c) + } + } + config.DisplayedColumns = newColumns + lib.CheckFatalError(hctx.SetConfig(config)) + }, +} + +func init() { + rootCmd.AddCommand(configDeleteCmd) + configDeleteCmd.AddCommand(deleteCustomColumnsCmd) +} diff --git a/client/cmd/configGet.go b/client/cmd/configGet.go new file mode 100644 index 0000000..033f336 --- /dev/null +++ b/client/cmd/configGet.go @@ -0,0 +1,86 @@ +package cmd + +import ( + "fmt" + "strings" + + "github.com/ddworken/hishtory/client/hctx" + "github.com/spf13/cobra" +) + +var configGetCmd = &cobra.Command{ + Use: "config-get", + Short: "Get the value of a config option", + Run: func(cmd *cobra.Command, args []string) { + cmd.Help() + }, +} + +var getEnableControlRCmd = &cobra.Command{ + Use: "enable-control-r", + Short: "Whether hishtory replaces your shell's default control-r", + Run: func(cmd *cobra.Command, args []string) { + ctx := hctx.MakeContext() + config := hctx.GetConf(ctx) + fmt.Println(config.ControlRSearchEnabled) + }, +} + +var getFilterDuplicateCommandsCmd = &cobra.Command{ + Use: "filter-duplicate-commands", + Short: "Whether hishtory filters out duplicate commands when displaying your history", + Run: func(cmd *cobra.Command, args []string) { + ctx := hctx.MakeContext() + config := hctx.GetConf(ctx) + fmt.Println(config.FilterDuplicateCommands) + }, +} + +var getDisplayedColumnsCmd = &cobra.Command{ + Use: "displayed-columns", + Short: "The list of columns that hishtory displays", + Run: func(cmd *cobra.Command, args []string) { + ctx := hctx.MakeContext() + config := hctx.GetConf(ctx) + for _, col := range config.DisplayedColumns { + if strings.Contains(col, " ") { + fmt.Printf("%q ", col) + } else { + fmt.Print(col + " ") + } + } + fmt.Print("\n") + }, +} + +var getTimestampFormatCmd = &cobra.Command{ + Use: "timestamp-format", + Short: "The go format string to use for formatting the timestamp", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + ctx := hctx.MakeContext() + config := hctx.GetConf(ctx) + fmt.Println(config.TimestampFormat) + }, +} + +var getCustomColumnsCmd = &cobra.Command{ + Use: "custom-columns", + Short: "The list of custom columns that hishtory is tracking", + Run: func(cmd *cobra.Command, args []string) { + ctx := hctx.MakeContext() + config := hctx.GetConf(ctx) + for _, cc := range config.CustomColumns { + fmt.Println(cc.ColumnName + ": " + cc.ColumnCommand) + } + }, +} + +func init() { + rootCmd.AddCommand(configGetCmd) + configGetCmd.AddCommand(getEnableControlRCmd) + configGetCmd.AddCommand(getFilterDuplicateCommandsCmd) + configGetCmd.AddCommand(getDisplayedColumnsCmd) + configGetCmd.AddCommand(getTimestampFormatCmd) + configGetCmd.AddCommand(getCustomColumnsCmd) +} diff --git a/client/cmd/configSet.go b/client/cmd/configSet.go new file mode 100644 index 0000000..1a181b7 --- /dev/null +++ b/client/cmd/configSet.go @@ -0,0 +1,92 @@ +package cmd + +import ( + "log" + + "github.com/ddworken/hishtory/client/hctx" + "github.com/ddworken/hishtory/client/lib" + "github.com/spf13/cobra" +) + +var configSetCmd = &cobra.Command{ + Use: "config-set", + Short: "Set the value of a config option", + Run: func(cmd *cobra.Command, args []string) { + cmd.Help() + }, +} + +var setEnableControlRCmd = &cobra.Command{ + Use: "enable-control-r", + Short: "Whether hishtory replaces your shell's default control-r", + Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), + ValidArgs: []string{"true", "false"}, + Run: func(cmd *cobra.Command, args []string) { + val := args[0] + if val != "true" && val != "false" { + log.Fatalf("Unexpected config value %s, must be one of: true, false", val) + } + ctx := hctx.MakeContext() + config := hctx.GetConf(ctx) + config.ControlRSearchEnabled = (val == "true") + lib.CheckFatalError(hctx.SetConfig(config)) + }, +} + +var setFilterDuplicateCommandsCmd = &cobra.Command{ + Use: "filter-duplicate-commands", + Short: "Whether hishtory filters out duplicate commands when displaying your history", + Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), + ValidArgs: []string{"true", "false"}, + Run: func(cmd *cobra.Command, args []string) { + val := args[0] + if val != "true" && val != "false" { + log.Fatalf("Unexpected config value %s, must be one of: true, false", val) + } + ctx := hctx.MakeContext() + config := hctx.GetConf(ctx) + config.FilterDuplicateCommands = (val == "true") + lib.CheckFatalError(hctx.SetConfig(config)) + }, +} + +var setDisplayedColumnsCmd = &cobra.Command{ + Use: "displayed-columns", + Short: "The list of columns that hishtory displays", + Args: cobra.MinimumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + ctx := hctx.MakeContext() + config := hctx.GetConf(ctx) + config.DisplayedColumns = args + lib.CheckFatalError(hctx.SetConfig(config)) + }, +} + +var setTimestampFormatCmd = &cobra.Command{ + Use: "timestamp-format", + Short: "The go format string to use for formatting the timestamp", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + ctx := hctx.MakeContext() + config := hctx.GetConf(ctx) + config.TimestampFormat = args[0] + lib.CheckFatalError(hctx.SetConfig(config)) + }, +} + +var setCustomColumnsCmd = &cobra.Command{ + Use: "custom-columns", + Short: "The list of custom columns that hishtory is tracking", + Run: func(cmd *cobra.Command, args []string) { + log.Fatalf("Please use config-add and config-delete to interact with custom-columns") + }, +} + +func init() { + rootCmd.AddCommand(configSetCmd) + configSetCmd.AddCommand(setEnableControlRCmd) + configSetCmd.AddCommand(setFilterDuplicateCommandsCmd) + configSetCmd.AddCommand(setDisplayedColumnsCmd) + configSetCmd.AddCommand(setTimestampFormatCmd) + configSetCmd.AddCommand(setCustomColumnsCmd) +} diff --git a/client/cmd/root.go b/client/cmd/root.go new file mode 100644 index 0000000..8255a2a --- /dev/null +++ b/client/cmd/root.go @@ -0,0 +1,51 @@ +/* +Copyright © 2022 NAME HERE + +*/ +package cmd + +import ( + "os" + + "github.com/spf13/cobra" +) + + + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "client", + Short: "A brief description of your application", + Long: `A longer description that spans multiple lines and likely contains +examples and usage of using your application. For example: + +Cobra is a CLI library for Go that empowers applications. +This application is a tool to generate the needed files +to quickly create a Cobra application.`, + // Uncomment the following line if your bare application + // has an action associated with it: + // Run: func(cmd *cobra.Command, args []string) { }, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} + +func init() { + // Here you will define your flags and configuration settings. + // Cobra supports persistent flags, which, if defined here, + // will be global for your application. + + // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.client.yaml)") + + // Cobra also supports local flags, which will only run + // when this action is called directly. + rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} + + diff --git a/client/lib/goldens/testCustomColumns-query-isAction=false b/client/lib/goldens/testCustomColumns-query-isAction=false index 8ceba6d..c5f0427 100644 --- a/client/lib/goldens/testCustomColumns-query-isAction=false +++ b/client/lib/goldens/testCustomColumns-query-isAction=false @@ -1,10 +1,10 @@ -Exit Code git_remote Command -0 git@github.com:ddworken/hishtory.git hishtory config-set displayed-columns 'Exit Code' git_remote Command -0 echo bar -0 cd / -0 git@github.com:ddworken/hishtory.git echo foo -0 git@github.com:ddworken/hishtory.git hishtory config-add custom-column git_remote '(git remote -v 2>/dev/null | grep origin 1>/dev/null ) && git remote get-url origin || true' -0 echo baz -0 cd / -0 echo $FOOBAR world -0 export FOOBAR='hello' +Exit Code git_remote Command +0 git@github.com:ddworken/hishtory.git hishtory config-set displayed-columns 'Exit Code' git_remote Command +0 echo bar +0 cd / +0 git@github.com:ddworken/hishtory.git echo foo +0 git@github.com:ddworken/hishtory.git hishtory config-add custom-columns git_remote '(git remote -v 2>/dev/null | grep origin 1>/dev/null ) && git remote get-url origin || true' +0 echo baz +0 cd / +0 echo $FOOBAR world +0 export FOOBAR='hello' diff --git a/client/lib/goldens/testCustomColumns-query-isAction=true b/client/lib/goldens/testCustomColumns-query-isAction=true index f27ffea..ac6c291 100644 --- a/client/lib/goldens/testCustomColumns-query-isAction=true +++ b/client/lib/goldens/testCustomColumns-query-isAction=true @@ -3,7 +3,7 @@ Exit Code git_remote Command 0 echo bar 0 cd / 0 https://github.com/ddworken/hishtory echo foo -0 https://github.com/ddworken/hishtory hishtory config-add custom-column git_remote '(git remote -v 2>/dev/null | grep origin 1>/dev/null ) && git remote get-url origin || true' +0 https://github.com/ddworken/hishtory hishtory config-add custom-columns git_remote '(git remote -v 2>/dev/null | grep origin 1>/dev/null ) && git remote get-url origin || true' 0 echo baz 0 cd / 0 echo $FOOBAR world diff --git a/client/lib/goldens/testCustomColumns-tquery-bash b/client/lib/goldens/testCustomColumns-tquery-bash index e1718e7..e0b133d 100644 --- a/client/lib/goldens/testCustomColumns-tquery-bash +++ b/client/lib/goldens/testCustomColumns-tquery-bash @@ -5,27 +5,27 @@ bash-5.2$ hishtory tquery -pipefail Search Query: > -pipefail -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Exit Code git_remote Command │ -│─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│ -│ 0 git@github.com:ddworken/hishtory.git hishtory config-set displayed-columns 'Exit Code' git_remote Command │ -│ 0 echo bar │ -│ 0 cd / │ -│ 0 git@github.com:ddworken/hishtory.git echo foo │ -│ 0 git@github.com:ddworken/hishtory.git hishtory config-add custom-column git_remote '(git remote -v 2>/dev/null | grep origin 1>/dev/null ) && git remote get-url origin || … │ -│ 0 echo baz │ -│ 0 cd / │ -│ 0 echo $FOOBAR world │ -│ 0 export FOOBAR='hello' │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ \ No newline at end of file +┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Exit Code git_remote Command │ +│──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│ +│ 0 git@github.com:ddworken/hishtory.git hishtory config-set displayed-columns 'Exit Code' git_remote Command │ +│ 0 echo bar │ +│ 0 cd / │ +│ 0 git@github.com:ddworken/hishtory.git echo foo │ +│ 0 git@github.com:ddworken/hishtory.git hishtory config-add custom-columns git_remote '(git remote -v 2>/dev/null | grep origin 1>/dev/null ) && git remote get-url origin || … │ +│ 0 echo baz │ +│ 0 cd / │ +│ 0 echo $FOOBAR world │ +│ 0 export FOOBAR='hello' │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ \ No newline at end of file diff --git a/client/lib/goldens/testCustomColumns-tquery-bash-isAction-darwin b/client/lib/goldens/testCustomColumns-tquery-bash-isAction-darwin index bdb37ea..b0950bd 100644 --- a/client/lib/goldens/testCustomColumns-tquery-bash-isAction-darwin +++ b/client/lib/goldens/testCustomColumns-tquery-bash-isAction-darwin @@ -12,7 +12,7 @@ Search Query: > -pipefail │ 0 echo bar │ │ 0 cd / │ │ 0 https://github.com/ddworken/hishtory echo foo │ -│ 0 https://github.com/ddworken/hishtory hishtory config-add custom-column git_remote '(git remote -v 2>/dev/null | grep origin 1>/dev/null ) && git remote get-url origin || … │ +│ 0 https://github.com/ddworken/hishtory hishtory config-add custom-columns git_remote '(git remote -v 2>/dev/null | grep origin 1>/dev/null ) && git remote get-url origin || … │ │ 0 echo baz │ │ 0 cd / │ │ 0 echo $FOOBAR world │ diff --git a/client/lib/goldens/testCustomColumns-tquery-bash-isAction-linux b/client/lib/goldens/testCustomColumns-tquery-bash-isAction-linux index 7b13c1f..601f1a8 100644 --- a/client/lib/goldens/testCustomColumns-tquery-bash-isAction-linux +++ b/client/lib/goldens/testCustomColumns-tquery-bash-isAction-linux @@ -12,7 +12,7 @@ Search Query: > -pipefail │ 0 echo bar │ │ 0 cd / │ │ 0 https://github.com/ddworken/hishtory echo foo │ -│ 0 https://github.com/ddworken/hishtory hishtory config-add custom-column git_remote '(git remote -v 2>/dev/null | grep origin 1>/dev/null ) && git remote get-url origin || … │ +│ 0 https://github.com/ddworken/hishtory hishtory config-add custom-columns git_remote '(git remote -v 2>/dev/null | grep origin 1>/dev/null ) && git remote get-url origin || … │ │ 0 echo baz │ │ 0 cd / │ │ 0 echo $FOOBAR world │ diff --git a/client/lib/goldens/testCustomColumns-tquery-zsh b/client/lib/goldens/testCustomColumns-tquery-zsh index 90c1128..a64ddad 100644 --- a/client/lib/goldens/testCustomColumns-tquery-zsh +++ b/client/lib/goldens/testCustomColumns-tquery-zsh @@ -11,7 +11,7 @@ Search Query: > -pipefail │ 0 echo bar │ │ 0 cd / │ │ 0 git@github.com:ddworken/hishtory.git echo foo │ -│ 0 git@github.com:ddworken/hishtory.git hishtory config-add custom-column git_remote '(git remote -v 2>/dev/null | grep origin 1>/dev/null ) && git remote get-url origin || … │ +│ 0 git@github.com:ddworken/hishtory.git hishtory config-add custom-columns git_remote '(git remote -v 2>/dev/null | grep origin 1>/dev/null ) && git remote get-url origin || … │ │ 0 echo baz │ │ 0 cd / │ │ 0 echo $FOOBAR world │ diff --git a/client/lib/goldens/testCustomColumns-tquery-zsh-isAction-darwin b/client/lib/goldens/testCustomColumns-tquery-zsh-isAction-darwin index 8d032a7..5d88da3 100644 --- a/client/lib/goldens/testCustomColumns-tquery-zsh-isAction-darwin +++ b/client/lib/goldens/testCustomColumns-tquery-zsh-isAction-darwin @@ -11,7 +11,7 @@ Search Query: > -pipefail │ 0 echo bar │ │ 0 cd / │ │ 0 https://github.com/ddworken/hishtory echo foo │ -│ 0 https://github.com/ddworken/hishtory hishtory config-add custom-column git_remote '(git remote -v 2>/dev/null | grep origin 1>/dev/null ) && git remote get-url origin || … │ +│ 0 https://github.com/ddworken/hishtory hishtory config-add custom-columns git_remote '(git remote -v 2>/dev/null | grep origin 1>/dev/null ) && git remote get-url origin || … │ │ 0 echo baz │ │ 0 cd / │ │ 0 echo $FOOBAR world │ diff --git a/client/lib/goldens/testCustomColumns-tquery-zsh-isAction-linux b/client/lib/goldens/testCustomColumns-tquery-zsh-isAction-linux index 59cd668..bbacba1 100644 --- a/client/lib/goldens/testCustomColumns-tquery-zsh-isAction-linux +++ b/client/lib/goldens/testCustomColumns-tquery-zsh-isAction-linux @@ -11,7 +11,7 @@ Search Query: > -pipefail │ 0 echo bar │ │ 0 cd / │ │ 0 https://github.com/ddworken/hishtory echo foo │ -│ 0 https://github.com/ddworken/hishtory hishtory config-add custom-column git_remote '(git remote -v 2>/dev/null | grep origin 1>/dev/null ) && git remote get-url origin || … │ +│ 0 https://github.com/ddworken/hishtory hishtory config-add custom-columns git_remote '(git remote -v 2>/dev/null | grep origin 1>/dev/null ) && git remote get-url origin || … │ │ 0 echo baz │ │ 0 cd / │ │ 0 echo $FOOBAR world │ diff --git a/go.mod b/go.mod index 8ce2bea..788c506 100644 --- a/go.mod +++ b/go.mod @@ -38,6 +38,7 @@ require ( github.com/PaesslerAG/gval v1.0.0 // indirect github.com/PaesslerAG/jsonpath v0.1.1 // indirect github.com/ThalesIgnite/crypto11 v1.2.5 // indirect + github.com/alecthomas/kong v0.7.1 // indirect github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 // indirect github.com/alibabacloud-go/cr-20160607 v1.0.1 // indirect github.com/alibabacloud-go/cr-20181201 v1.0.10 // indirect @@ -211,7 +212,7 @@ require ( github.com/soheilhy/cmux v0.1.5 // indirect github.com/spf13/afero v1.8.2 // indirect github.com/spf13/cast v1.5.0 // indirect - github.com/spf13/cobra v1.6.0 // indirect + github.com/spf13/cobra v1.6.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/viper v1.13.0 // indirect diff --git a/go.sum b/go.sum index 4975d1d..e74e074 100644 --- a/go.sum +++ b/go.sum @@ -226,6 +226,8 @@ github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/ github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE= +github.com/alecthomas/kong v0.7.1 h1:azoTh0IOfwlAX3qN9sHWTxACE2oV8Bg2gAwBsMwDQY4= +github.com/alecthomas/kong v0.7.1/go.mod h1:n1iCIO2xS46oE8ZfYCNDqdR0b0wZNrXAIAqro/2132U= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -1955,6 +1957,8 @@ github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= github.com/spf13/cobra v1.6.0 h1:42a0n6jwCot1pUmomAp4T7DeMD+20LFv4Q54pxLf2LI= github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= +github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= diff --git a/hishtory.go b/hishtory.go index e689d59..e5b2ca1 100644 --- a/hishtory.go +++ b/hishtory.go @@ -10,6 +10,7 @@ import ( "strings" "time" + "github.com/ddworken/hishtory/client/cmd" "github.com/ddworken/hishtory/client/data" "github.com/ddworken/hishtory/client/hctx" "github.com/ddworken/hishtory/client/lib" @@ -135,131 +136,14 @@ func main() { if err != nil { log.Fatalf("Failed to update hishtory: %v", err) } - case "config-get": - ctx := hctx.MakeContext() - config := hctx.GetConf(ctx) - key := os.Args[2] - switch key { - case "enable-control-r": - fmt.Printf("%v", config.ControlRSearchEnabled) - case "filter-duplicate-commands": - fmt.Printf("%v", config.FilterDuplicateCommands) - case "displayed-columns": - for _, col := range config.DisplayedColumns { - if strings.Contains(col, " ") { - fmt.Printf("%q ", col) - } else { - fmt.Print(col + " ") - } - } - fmt.Print("\n") - case "custom-columns": - for _, cc := range config.CustomColumns { - fmt.Println(cc.ColumnName + ": " + cc.ColumnCommand) - } - default: - log.Fatalf("Unrecognized config key: %s", key) - } case "config-set": - ctx := hctx.MakeContext() - config := hctx.GetConf(ctx) - key := os.Args[2] - switch key { - case "enable-control-r": - val := os.Args[3] - if val != "true" && val != "false" { - log.Fatalf("Unexpected config value %s, must be one of: true, false", val) - } - config.ControlRSearchEnabled = (val == "true") - lib.CheckFatalError(hctx.SetConfig(config)) - case "filter-duplicate-commands": - val := os.Args[3] - if val != "true" && val != "false" { - log.Fatalf("Unexpected config value %s, must be one of: true, false", val) - } - config.FilterDuplicateCommands = (val == "true") - lib.CheckFatalError(hctx.SetConfig(config)) - case "displayed-columns": - vals := os.Args[3:] - config.DisplayedColumns = vals - lib.CheckFatalError(hctx.SetConfig(config)) - case "timestamp-format": - val := os.Args[3] - config.TimestampFormat = val - lib.CheckFatalError(hctx.SetConfig(config)) - case "custom-columns": - log.Fatalf("Please use config-add and config-delete to interact with custom-columns") - default: - log.Fatalf("Unrecognized config key: %s", key) - } + fallthrough + case "config-get": + fallthrough case "config-add": - ctx := hctx.MakeContext() - config := hctx.GetConf(ctx) - key := os.Args[2] - switch key { - case "custom-column": - fallthrough - case "custom-columns": - columnName := os.Args[3] - command := os.Args[4] - ctx := hctx.MakeContext() - config := hctx.GetConf(ctx) - if config.CustomColumns == nil { - config.CustomColumns = make([]hctx.CustomColumnDefinition, 0) - } - config.CustomColumns = append(config.CustomColumns, hctx.CustomColumnDefinition{ColumnName: columnName, ColumnCommand: command}) - lib.CheckFatalError(hctx.SetConfig(config)) - case "displayed-columns": - vals := os.Args[3:] - config.DisplayedColumns = append(config.DisplayedColumns, vals...) - lib.CheckFatalError(hctx.SetConfig(config)) - default: - log.Fatalf("Unrecognized config key: %s", key) - } + fallthrough case "config-delete": - ctx := hctx.MakeContext() - config := hctx.GetConf(ctx) - key := os.Args[2] - switch key { - case "custom-columns": - columnName := os.Args[2] - ctx := hctx.MakeContext() - config := hctx.GetConf(ctx) - if config.CustomColumns == nil { - return - } - newColumns := make([]hctx.CustomColumnDefinition, 0) - deletedColumns := false - for _, c := range config.CustomColumns { - if c.ColumnName != columnName { - newColumns = append(newColumns, c) - deletedColumns = true - } - } - if !deletedColumns { - log.Fatalf("Did not find a column with name %#v to delete (current columns = %#v)", columnName, config.CustomColumns) - } - config.CustomColumns = newColumns - lib.CheckFatalError(hctx.SetConfig(config)) - case "displayed-columns": - deletedColumns := os.Args[3:] - newColumns := make([]string, 0) - for _, c := range config.DisplayedColumns { - isDeleted := false - for _, d := range deletedColumns { - if c == d { - isDeleted = true - } - } - if !isDeleted { - newColumns = append(newColumns, c) - } - } - config.DisplayedColumns = newColumns - lib.CheckFatalError(hctx.SetConfig(config)) - default: - log.Fatalf("Unrecognized config key: %s", key) - } + cmd.Execute() case "reupload": // Purposefully undocumented since this command is generally not necessary to run lib.CheckFatalError(lib.Reupload(hctx.MakeContext())) From c59de420080d8a133f0e05bf47d81ce1a59d4243 Mon Sep 17 00:00:00 2001 From: David Dworken Date: Mon, 14 Nov 2022 19:26:56 -0800 Subject: [PATCH 02/13] More refactoring to use cobra --- client/cmd/enableDisable.go | 30 +++++++++ client/cmd/import.go | 27 +++++++++ client/cmd/init.go | 56 +++++++++++++++++ client/cmd/install.go | 37 ++++++++++++ client/cmd/status.go | 44 ++++++++++++++ client/cmd/uninstall.go | 32 ++++++++++ client/cmd/update.go | 19 ++++++ client/lib/lib.go | 40 +++++++----- client/lib/lib_test.go | 8 +-- client/lib/tui.go | 4 +- hishtory.go | 117 ++++-------------------------------- scripts/client-ldflags | 2 +- 12 files changed, 287 insertions(+), 129 deletions(-) create mode 100644 client/cmd/enableDisable.go create mode 100644 client/cmd/import.go create mode 100644 client/cmd/init.go create mode 100644 client/cmd/install.go create mode 100644 client/cmd/status.go create mode 100644 client/cmd/uninstall.go create mode 100644 client/cmd/update.go diff --git a/client/cmd/enableDisable.go b/client/cmd/enableDisable.go new file mode 100644 index 0000000..36e6e02 --- /dev/null +++ b/client/cmd/enableDisable.go @@ -0,0 +1,30 @@ +package cmd + +import ( + "github.com/ddworken/hishtory/client/hctx" + "github.com/ddworken/hishtory/client/lib" + "github.com/spf13/cobra" +) + +var enableCmd = &cobra.Command{ + Use: "enable", + Short: "Enable hiSHtory recording", + Run: func(cmd *cobra.Command, args []string) { + ctx := hctx.MakeContext() + lib.CheckFatalError(lib.Enable(ctx)) + }, +} + +var disableCmd = &cobra.Command{ + Use: "disable", + Short: "Disable hiSHtory recording", + Run: func(cmd *cobra.Command, args []string) { + ctx := hctx.MakeContext() + lib.CheckFatalError(lib.Disable(ctx)) + }, +} + +func init() { + rootCmd.AddCommand(enableCmd) + rootCmd.AddCommand(disableCmd) +} diff --git a/client/cmd/import.go b/client/cmd/import.go new file mode 100644 index 0000000..7657c8b --- /dev/null +++ b/client/cmd/import.go @@ -0,0 +1,27 @@ +package cmd + +import ( + "fmt" + + "github.com/ddworken/hishtory/client/hctx" + "github.com/ddworken/hishtory/client/lib" + "github.com/spf13/cobra" +) + +var importCmd = &cobra.Command{ + Use: "import", + Short: "Re-import history entries from your existing shell history", + Long: "Note that you must pipe commands to be imported in via stdin. For example `history | hishtory import`.", + Run: func(cmd *cobra.Command, args []string) { + ctx := hctx.MakeContext() + numImported, err := lib.ImportHistory(ctx, true, true) + lib.CheckFatalError(err) + if numImported > 0 { + fmt.Printf("Imported %v history entries from your existing shell history\n", numImported) + } + }, +} + +func init() { + rootCmd.AddCommand(importCmd) +} diff --git a/client/cmd/init.go b/client/cmd/init.go new file mode 100644 index 0000000..8a15727 --- /dev/null +++ b/client/cmd/init.go @@ -0,0 +1,56 @@ +package cmd + +import ( + "bufio" + "fmt" + "os" + "strings" + + "github.com/ddworken/hishtory/client/hctx" + "github.com/ddworken/hishtory/client/lib" + "github.com/spf13/cobra" +) + +var offline *bool + +var initCmd = &cobra.Command{ + Use: "init", + Short: "Re-initialize hiSHtory with a specified secret key", + Args: cobra.MaximumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + cmd.Flag("offline").Value.String() + db, err := hctx.OpenLocalSqliteDb() + lib.CheckFatalError(err) + data, err := lib.Search(nil, db, "", 10) + lib.CheckFatalError(err) + if len(data) > 0 { + fmt.Printf("Your current hishtory profile has saved history entries, are you sure you want to run `init` and reset?\nNote: This won't clear any imported history entries from your existing shell\n[y/N]") + reader := bufio.NewReader(os.Stdin) + resp, err := reader.ReadString('\n') + lib.CheckFatalError(err) + if strings.TrimSpace(resp) != "y" { + fmt.Printf("Aborting init per user response of %#v\n", strings.TrimSpace(resp)) + return + } + } + secretKey := "" + if len(args) > 0 { + secretKey = args[0] + } + lib.CheckFatalError(lib.Setup(secretKey, *offline)) + if os.Getenv("HISHTORY_SKIP_INIT_IMPORT") == "" { + fmt.Println("Importing existing shell history...") + ctx := hctx.MakeContext() + numImported, err := lib.ImportHistory(ctx, false, false) + lib.CheckFatalError(err) + if numImported > 0 { + fmt.Printf("Imported %v history entries from your existing shell history\n", numImported) + } + } + }, +} + +func init() { + rootCmd.AddCommand(initCmd) + offline = initCmd.Flags().Bool("offline", false, "Install hiSHtory in offline mode wiht all syncing capabilities disabled") +} diff --git a/client/cmd/install.go b/client/cmd/install.go new file mode 100644 index 0000000..e31b1e3 --- /dev/null +++ b/client/cmd/install.go @@ -0,0 +1,37 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/ddworken/hishtory/client/hctx" + "github.com/ddworken/hishtory/client/lib" + "github.com/spf13/cobra" +) + +var installCmd = &cobra.Command{ + Use: "install", + Short: "Copy this binary to ~/.hishtory/ and configure your shell to use it for recording your shell history", + Run: func(cmd *cobra.Command, args []string) { + lib.CheckFatalError(lib.Install()) + if os.Getenv("HISHTORY_SKIP_INIT_IMPORT") == "" { + db, err := hctx.OpenLocalSqliteDb() + lib.CheckFatalError(err) + data, err := lib.Search(nil, db, "", 10) + lib.CheckFatalError(err) + if len(data) < 10 { + fmt.Println("Importing existing shell history...") + ctx := hctx.MakeContext() + numImported, err := lib.ImportHistory(ctx, false, false) + lib.CheckFatalError(err) + if numImported > 0 { + fmt.Printf("Imported %v history entries from your existing shell history\n", numImported) + } + } + } + }, +} + +func init() { + rootCmd.AddCommand(installCmd) +} diff --git a/client/cmd/status.go b/client/cmd/status.go new file mode 100644 index 0000000..7a06c8f --- /dev/null +++ b/client/cmd/status.go @@ -0,0 +1,44 @@ +package cmd + +import ( + "fmt" + + "github.com/ddworken/hishtory/client/data" + "github.com/ddworken/hishtory/client/hctx" + "github.com/ddworken/hishtory/client/lib" + "github.com/spf13/cobra" +) + +var verbose *bool + +var statusCmd = &cobra.Command{ + Use: "status", + Short: "Get the hishtory status", + Run: func(cmd *cobra.Command, args []string) { + ctx := hctx.MakeContext() + config := hctx.GetConf(ctx) + fmt.Printf("hiSHtory: v0.%s\nEnabled: %v\n", lib.Version, config.IsEnabled) + fmt.Printf("Secret Key: %s\n", config.UserSecret) + if *verbose { + fmt.Printf("User ID: %s\n", data.UserId(config.UserSecret)) + fmt.Printf("Device ID: %s\n", config.DeviceId) + printDumpStatus(config) + } + fmt.Printf("Commit Hash: %s\n", lib.GitCommit) + }, +} + +func printDumpStatus(config hctx.ClientConfig) { + dumpRequests, err := lib.GetDumpRequests(config) + lib.CheckFatalError(err) + fmt.Printf("Dump Requests: ") + for _, d := range dumpRequests { + fmt.Printf("%#v, ", *d) + } + fmt.Print("\n") +} + +func init() { + rootCmd.AddCommand(statusCmd) + verbose = statusCmd.Flags().BoolP("verbose", "v", false, "Display verbose hiSHtory information") +} diff --git a/client/cmd/uninstall.go b/client/cmd/uninstall.go new file mode 100644 index 0000000..9bfcb02 --- /dev/null +++ b/client/cmd/uninstall.go @@ -0,0 +1,32 @@ +package cmd + +import ( + "bufio" + "fmt" + "os" + "strings" + + "github.com/ddworken/hishtory/client/hctx" + "github.com/ddworken/hishtory/client/lib" + "github.com/spf13/cobra" +) + +var uninstallCmd = &cobra.Command{ + Use: "uninstall", + Short: "Completely uninstall hiSHtory and remove your shell history", + Run: func(cmd *cobra.Command, args []string) { + fmt.Printf("Are you sure you want to uninstall hiSHtory and delete all locally saved history data [y/N]") + reader := bufio.NewReader(os.Stdin) + resp, err := reader.ReadString('\n') + lib.CheckFatalError(err) + if strings.TrimSpace(resp) != "y" { + fmt.Printf("Aborting uninstall per user response of %#v\n", strings.TrimSpace(resp)) + return + } + lib.CheckFatalError(lib.Uninstall(hctx.MakeContext())) + }, +} + +func init() { + rootCmd.AddCommand(uninstallCmd) +} diff --git a/client/cmd/update.go b/client/cmd/update.go new file mode 100644 index 0000000..2974758 --- /dev/null +++ b/client/cmd/update.go @@ -0,0 +1,19 @@ +package cmd + +import ( + "github.com/ddworken/hishtory/client/hctx" + "github.com/ddworken/hishtory/client/lib" + "github.com/spf13/cobra" +) + +var updateCmd = &cobra.Command{ + Use: "update", + Short: "Securely update hishtory to the latest version", + Run: func(cmd *cobra.Command, args []string) { + lib.CheckFatalError(lib.Update(hctx.MakeContext())) + }, +} + +func init() { + rootCmd.AddCommand(updateCmd) +} diff --git a/client/lib/lib.go b/client/lib/lib.go index 2202a1a..33dd9a8 100644 --- a/client/lib/lib.go +++ b/client/lib/lib.go @@ -48,6 +48,7 @@ var ConfigZshContents string var ConfigFishContents string var Version string = "Unknown" +var GitCommit string = "Unknown" // 256KB ought to be enough for any reasonable cmd var maxSupportedLineLengthForImport = 256_000 @@ -333,18 +334,9 @@ func shouldSkipHiddenCommand(ctx *context.Context, historyLine string) (bool, er return false, nil } -func Setup(args []string) error { - userSecret := uuid.Must(uuid.NewRandom()).String() - isOffline := false - if len(args) > 2 && args[2] != "" { - if args[2] == "--offline" { - isOffline = true - } else { - if args[2][0] == '-' { - return fmt.Errorf("refusing to set user secret to %#v since it looks like a flag", args[2]) - } - userSecret = args[2] - } +func Setup(userSecret string, isOffline bool) error { + if userSecret == "" { + userSecret = uuid.Must(uuid.NewRandom()).String() } fmt.Println("Setting secret hishtory key to " + string(userSecret)) @@ -691,8 +683,8 @@ func Install() error { } _, err = hctx.GetConfig() if err != nil { - // No config, so set up a new installation - return Setup(os.Args) + // No config, so set up a new installation with a new key and in online mode + return Setup("", false) } return nil } @@ -1470,12 +1462,12 @@ func ProcessDeletionRequests(ctx *context.Context) error { return nil } -func GetBanner(ctx *context.Context, gitCommit string) ([]byte, error) { +func GetBanner(ctx *context.Context) ([]byte, error) { config := hctx.GetConf(ctx) if config.IsOffline { return []byte{}, nil } - url := "/api/v1/banner?commit_hash=" + gitCommit + "&user_id=" + data.UserId(config.UserSecret) + "&device_id=" + config.DeviceId + "&version=" + Version + "&forced_banner=" + os.Getenv("FORCED_BANNER") + url := "/api/v1/banner?commit_hash=" + GitCommit + "&user_id=" + data.UserId(config.UserSecret) + "&device_id=" + config.DeviceId + "&version=" + Version + "&forced_banner=" + os.Getenv("FORCED_BANNER") return ApiGet(url) } @@ -1703,3 +1695,19 @@ func Uninstall(ctx *context.Context) error { fmt.Println("Successfully uninstalled hishtory, please restart your terminal...") return nil } + +func GetDumpRequests(config hctx.ClientConfig) ([]*shared.DumpRequest, error) { + if config.IsOffline { + return make([]*shared.DumpRequest, 0), nil + } + resp, err := ApiGet("/api/v1/get-dump-requests?user_id=" + data.UserId(config.UserSecret) + "&device_id=" + config.DeviceId) + if IsOfflineError(err) { + return []*shared.DumpRequest{}, nil + } + if err != nil { + return nil, err + } + var dumpRequests []*shared.DumpRequest + err = json.Unmarshal(resp, &dumpRequests) + return dumpRequests, err +} diff --git a/client/lib/lib_test.go b/client/lib/lib_test.go index a20ef45..4e0964a 100644 --- a/client/lib/lib_test.go +++ b/client/lib/lib_test.go @@ -23,7 +23,7 @@ func TestSetup(t *testing.T) { if _, err := os.Stat(path.Join(homedir, data.HISHTORY_PATH, data.CONFIG_PATH)); err == nil { t.Fatalf("hishtory secret file already exists!") } - testutils.Check(t, Setup([]string{})) + testutils.Check(t, Setup("", false)) if _, err := os.Stat(path.Join(homedir, data.HISHTORY_PATH, data.CONFIG_PATH)); err != nil { t.Fatalf("hishtory secret file does not exist after Setup()!") } @@ -47,7 +47,7 @@ func TestSetupOffline(t *testing.T) { if _, err := os.Stat(path.Join(homedir, data.HISHTORY_PATH, data.CONFIG_PATH)); err == nil { t.Fatalf("hishtory secret file already exists!") } - testutils.Check(t, Setup([]string{"", "", "--offline"})) + testutils.Check(t, Setup("", true)) if _, err := os.Stat(path.Join(homedir, data.HISHTORY_PATH, data.CONFIG_PATH)); err != nil { t.Fatalf("hishtory secret file does not exist after Setup()!") } @@ -65,7 +65,7 @@ func TestSetupOffline(t *testing.T) { func TestBuildHistoryEntry(t *testing.T) { defer testutils.BackupAndRestore(t)() defer testutils.RunTestServer()() - testutils.Check(t, Setup([]string{})) + testutils.Check(t, Setup("", false)) // Test building an actual entry for bash entry, err := BuildHistoryEntry(hctx.MakeContext(), []string{"unused", "saveHistoryEntry", "bash", "120", " 123 ls /foo ", "1641774958"}) @@ -158,7 +158,7 @@ func TestBuildHistoryEntryWithTimestampStripping(t *testing.T) { defer testutils.BackupAndRestoreEnv("HISTTIMEFORMAT")() defer testutils.BackupAndRestore(t)() defer testutils.RunTestServer()() - testutils.Check(t, Setup([]string{})) + testutils.Check(t, Setup("", false)) testcases := []struct { input, histtimeformat, expectedCommand string diff --git a/client/lib/tui.go b/client/lib/tui.go index 1b71675..fa4104e 100644 --- a/client/lib/tui.go +++ b/client/lib/tui.go @@ -408,7 +408,7 @@ func makeTable(ctx *context.Context, rows []table.Row) (table.Model, error) { return t, nil } -func TuiQuery(ctx *context.Context, gitCommit, initialQuery string) error { +func TuiQuery(ctx *context.Context, initialQuery string) error { lipgloss.SetColorProfile(termenv.ANSI) rows, numEntries, err := getRows(ctx, hctx.GetConf(ctx).DisplayedColumns, initialQuery, PADDED_NUM_ENTRIES) if err != nil { @@ -435,7 +435,7 @@ func TuiQuery(ctx *context.Context, gitCommit, initialQuery string) error { }() // Async: Check for any banner from the server go func() { - banner, err := GetBanner(ctx, gitCommit) + banner, err := GetBanner(ctx) if err != nil { if IsOfflineError(err) { p.Send(offlineMsg{}) diff --git a/hishtory.go b/hishtory.go index e5b2ca1..d335fea 100644 --- a/hishtory.go +++ b/hishtory.go @@ -1,11 +1,9 @@ package main import ( - "bufio" "context" "encoding/json" "fmt" - "log" "os" "strings" "time" @@ -17,8 +15,6 @@ import ( "github.com/ddworken/hishtory/shared" ) -var GitCommit string = "Unknown" - func main() { if len(os.Args) == 1 { fmt.Println("Must specify a command! Do you mean `hishtory query`?") @@ -35,7 +31,7 @@ func main() { query(ctx, strings.Join(os.Args[2:], " ")) case "tquery": ctx := hctx.MakeContext() - lib.CheckFatalError(lib.TuiQuery(ctx, GitCommit, strings.Join(os.Args[2:], " "))) + lib.CheckFatalError(lib.TuiQuery(ctx, strings.Join(os.Args[2:], " "))) case "export": ctx := hctx.MakeContext() lib.CheckFatalError(lib.ProcessDeletionRequests(ctx)) @@ -54,88 +50,23 @@ func main() { } lib.CheckFatalError(lib.Redact(ctx, query, force)) case "init": - db, err := hctx.OpenLocalSqliteDb() - lib.CheckFatalError(err) - data, err := lib.Search(nil, db, "", 10) - lib.CheckFatalError(err) - if len(data) > 0 { - fmt.Printf("Your current hishtory profile has saved history entries, are you sure you want to run `init` and reset?\nNote: This won't clear any imported history entries from your existing shell\n[y/N]") - reader := bufio.NewReader(os.Stdin) - resp, err := reader.ReadString('\n') - lib.CheckFatalError(err) - if strings.TrimSpace(resp) != "y" { - fmt.Printf("Aborting init per user response of %#v\n", strings.TrimSpace(resp)) - return - } - } - lib.CheckFatalError(lib.Setup(os.Args)) - if os.Getenv("HISHTORY_SKIP_INIT_IMPORT") == "" { - fmt.Println("Importing existing shell history...") - ctx := hctx.MakeContext() - numImported, err := lib.ImportHistory(ctx, false, false) - lib.CheckFatalError(err) - if numImported > 0 { - fmt.Printf("Imported %v history entries from your existing shell history\n", numImported) - } - } + fallthrough case "install": - lib.CheckFatalError(lib.Install()) - if os.Getenv("HISHTORY_SKIP_INIT_IMPORT") == "" { - db, err := hctx.OpenLocalSqliteDb() - lib.CheckFatalError(err) - data, err := lib.Search(nil, db, "", 10) - lib.CheckFatalError(err) - if len(data) < 10 { - fmt.Println("Importing existing shell history...") - ctx := hctx.MakeContext() - numImported, err := lib.ImportHistory(ctx, false, false) - lib.CheckFatalError(err) - if numImported > 0 { - fmt.Printf("Imported %v history entries from your existing shell history\n", numImported) - } - } - } + fallthrough case "uninstall": - fmt.Printf("Are you sure you want to uninstall hiSHtory and delete all locally saved history data [y/N]") - reader := bufio.NewReader(os.Stdin) - resp, err := reader.ReadString('\n') - lib.CheckFatalError(err) - if strings.TrimSpace(resp) != "y" { - fmt.Printf("Aborting uninstall per user response of %#v\n", strings.TrimSpace(resp)) - return - } - lib.CheckFatalError(lib.Uninstall(hctx.MakeContext())) + fallthrough case "import": - ctx := hctx.MakeContext() - numImported, err := lib.ImportHistory(ctx, true, true) - lib.CheckFatalError(err) - if numImported > 0 { - fmt.Printf("Imported %v history entries from your existing shell history\n", numImported) - } + fallthrough case "enable": - ctx := hctx.MakeContext() - lib.CheckFatalError(lib.Enable(ctx)) + fallthrough case "disable": - ctx := hctx.MakeContext() - lib.CheckFatalError(lib.Disable(ctx)) + fallthrough case "version": fallthrough case "status": - ctx := hctx.MakeContext() - config := hctx.GetConf(ctx) - fmt.Printf("hiSHtory: v0.%s\nEnabled: %v\n", lib.Version, config.IsEnabled) - fmt.Printf("Secret Key: %s\n", config.UserSecret) - if len(os.Args) == 3 && os.Args[2] == "-v" { - fmt.Printf("User ID: %s\n", data.UserId(config.UserSecret)) - fmt.Printf("Device ID: %s\n", config.DeviceId) - printDumpStatus(config) - } - fmt.Printf("Commit Hash: %s\n", GitCommit) + fallthrough case "update": - err := lib.Update(hctx.MakeContext()) - if err != nil { - log.Fatalf("Failed to update hishtory: %v", err) - } + fallthrough case "config-set": fallthrough case "config-get": @@ -181,32 +112,6 @@ Supported commands: } } -func printDumpStatus(config hctx.ClientConfig) { - dumpRequests, err := getDumpRequests(config) - lib.CheckFatalError(err) - fmt.Printf("Dump Requests: ") - for _, d := range dumpRequests { - fmt.Printf("%#v, ", *d) - } - fmt.Print("\n") -} - -func getDumpRequests(config hctx.ClientConfig) ([]*shared.DumpRequest, error) { - if config.IsOffline { - return make([]*shared.DumpRequest, 0), nil - } - resp, err := lib.ApiGet("/api/v1/get-dump-requests?user_id=" + data.UserId(config.UserSecret) + "&device_id=" + config.DeviceId) - if lib.IsOfflineError(err) { - return []*shared.DumpRequest{}, nil - } - if err != nil { - return nil, err - } - var dumpRequests []*shared.DumpRequest - err = json.Unmarshal(resp, &dumpRequests) - return dumpRequests, err -} - func query(ctx *context.Context, query string) { db := hctx.GetDb(ctx) err := lib.RetrieveAdditionalEntriesFromRemote(ctx) @@ -225,7 +130,7 @@ func query(ctx *context.Context, query string) { } func displayBannerIfSet(ctx *context.Context) error { - respBody, err := lib.GetBanner(ctx, GitCommit) + respBody, err := lib.GetBanner(ctx) if lib.IsOfflineError(err) { return nil } @@ -313,7 +218,7 @@ func saveHistoryEntry(ctx *context.Context) { } // Check if there is a pending dump request and reply to it if so - dumpRequests, err := getDumpRequests(config) + dumpRequests, err := lib.GetDumpRequests(config) if err != nil { if lib.IsOfflineError(err) { // It is fine to just ignore this, the next command will retry the API and eventually we will respond to any pending dump requests diff --git a/scripts/client-ldflags b/scripts/client-ldflags index 4097ec9..9cbea64 100755 --- a/scripts/client-ldflags +++ b/scripts/client-ldflags @@ -1,4 +1,4 @@ #!/usr/bin/env bash GIT_HASH=$(git rev-parse HEAD) -echo "-X main.GitCommit=$GIT_HASH -X github.com/ddworken/hishtory/client/lib.Version=`cat VERSION` -w -extldflags \"-static\"" +echo "-X github.com/ddworken/hishtory/client/lib.GitCommit=$GIT_HASH -X github.com/ddworken/hishtory/client/lib.Version=`cat VERSION` -w -extldflags \"-static\"" From 48e2a41d5c0ec7c123bf861c6c7955be4220b812 Mon Sep 17 00:00:00 2001 From: David Dworken Date: Mon, 14 Nov 2022 19:29:48 -0800 Subject: [PATCH 03/13] Migrate reupload to cobra --- client/cmd/reupload.go | 19 +++++++++++++++++++ hishtory.go | 5 ++--- 2 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 client/cmd/reupload.go diff --git a/client/cmd/reupload.go b/client/cmd/reupload.go new file mode 100644 index 0000000..08d09d7 --- /dev/null +++ b/client/cmd/reupload.go @@ -0,0 +1,19 @@ +package cmd + +import ( + "github.com/ddworken/hishtory/client/hctx" + "github.com/ddworken/hishtory/client/lib" + "github.com/spf13/cobra" +) + +var reuploadCmd = &cobra.Command{ + Use: "reupload", + Short: "[Debug Only] Reupload your entire hiSHtory to all other devices", + Run: func(cmd *cobra.Command, args []string) { + lib.CheckFatalError(lib.Reupload(hctx.MakeContext())) + }, +} + +func init() { + rootCmd.AddCommand(reuploadCmd) +} diff --git a/hishtory.go b/hishtory.go index d335fea..503566b 100644 --- a/hishtory.go +++ b/hishtory.go @@ -73,11 +73,10 @@ func main() { fallthrough case "config-add": fallthrough + case "reupload": + fallthrough case "config-delete": cmd.Execute() - case "reupload": - // Purposefully undocumented since this command is generally not necessary to run - lib.CheckFatalError(lib.Reupload(hctx.MakeContext())) case "-h": fallthrough case "help": From ecdd22dcdd7fc228aa43411f64c807cd7cb56c98 Mon Sep 17 00:00:00 2001 From: David Dworken Date: Mon, 14 Nov 2022 20:02:16 -0800 Subject: [PATCH 04/13] Everything migrated to cobra, but with some very significant TODOs --- client/cmd/query.go | 108 +++++++++++++ client/cmd/redact.go | 29 ++++ client/cmd/root.go | 30 +--- client/cmd/saveHistoryEntry.go | 138 +++++++++++++++++ client/cmd/status.go | 2 +- hishtory.go | 269 +-------------------------------- 6 files changed, 287 insertions(+), 289 deletions(-) create mode 100644 client/cmd/query.go create mode 100644 client/cmd/redact.go create mode 100644 client/cmd/saveHistoryEntry.go diff --git a/client/cmd/query.go b/client/cmd/query.go new file mode 100644 index 0000000..627f9d4 --- /dev/null +++ b/client/cmd/query.go @@ -0,0 +1,108 @@ +package cmd + +import ( + "context" + "fmt" + "os" + "strings" + + "github.com/ddworken/hishtory/client/hctx" + "github.com/ddworken/hishtory/client/lib" + "github.com/spf13/cobra" +) + +var EXAMPLE_QUERIES string = `Example queries: +'hishtory SUBCOMMAND apt-get' # Find shell commands containing 'apt-get' +'hishtory SUBCOMMAND apt-get install' # Find shell commands containing 'apt-get' and 'install' +'hishtory SUBCOMMAND curl cwd:/tmp/' # Find shell commands containing 'curl' run in '/tmp/' +'hishtory SUBCOMMAND curl user:david' # Find shell commands containing 'curl' run by 'david' +'hishtory SUBCOMMAND curl host:x1' # Find shell commands containing 'curl' run on 'x1' +'hishtory SUBCOMMAND exit_code:1' # Find shell commands that exited with status code 1 +'hishtory SUBCOMMAND before:2022-02-01' # Find shell commands run before 2022-02-01 +` + +var queryCmd = &cobra.Command{ + Use: "query", + Short: "Query your shell history", + Long: strings.ReplaceAll(EXAMPLE_QUERIES, "SUBCOMMAND", "query"), + Run: func(cmd *cobra.Command, args []string) { + ctx := hctx.MakeContext() + lib.CheckFatalError(lib.ProcessDeletionRequests(ctx)) + query(ctx, strings.Join(args, " ")) + }, +} + +var tqueryCmd = &cobra.Command{ + Use: "tquery", + Short: "Interactively query your shell history", + Long: strings.ReplaceAll(EXAMPLE_QUERIES, "SUBCOMMAND", "tquery"), + Run: func(cmd *cobra.Command, args []string) { + ctx := hctx.MakeContext() + lib.CheckFatalError(lib.TuiQuery(ctx, strings.Join(args, " "))) + }, +} + +var exportCmd = &cobra.Command{ + Use: "export", + Short: "Export your shell history", + Long: strings.ReplaceAll(EXAMPLE_QUERIES, "SUBCOMMAND", "export"), + Run: func(cmd *cobra.Command, args []string) { + ctx := hctx.MakeContext() + lib.CheckFatalError(lib.ProcessDeletionRequests(ctx)) + export(ctx, strings.Join(os.Args[2:], " ")) + }, +} + +func export(ctx *context.Context, query string) { + db := hctx.GetDb(ctx) + err := lib.RetrieveAdditionalEntriesFromRemote(ctx) + if err != nil { + if lib.IsOfflineError(err) { + fmt.Println("Warning: hishtory is offline so this may be missing recent results from your other machines!") + } else { + lib.CheckFatalError(err) + } + } + data, err := lib.Search(ctx, db, query, 0) + lib.CheckFatalError(err) + for i := len(data) - 1; i >= 0; i-- { + fmt.Println(data[i].Command) + } +} + +func query(ctx *context.Context, query string) { + db := hctx.GetDb(ctx) + err := lib.RetrieveAdditionalEntriesFromRemote(ctx) + if err != nil { + if lib.IsOfflineError(err) { + fmt.Println("Warning: hishtory is offline so this may be missing recent results from your other machines!") + } else { + lib.CheckFatalError(err) + } + } + lib.CheckFatalError(displayBannerIfSet(ctx)) + numResults := 25 + data, err := lib.Search(ctx, db, query, numResults*5) + lib.CheckFatalError(err) + lib.CheckFatalError(lib.DisplayResults(ctx, data, numResults)) +} + +func displayBannerIfSet(ctx *context.Context) error { + respBody, err := lib.GetBanner(ctx) + if lib.IsOfflineError(err) { + return nil + } + if err != nil { + return err + } + if len(respBody) > 0 { + fmt.Println(string(respBody)) + } + return nil +} + +func init() { + rootCmd.AddCommand(queryCmd) + rootCmd.AddCommand(tqueryCmd) + rootCmd.AddCommand(exportCmd) +} diff --git a/client/cmd/redact.go b/client/cmd/redact.go new file mode 100644 index 0000000..c67d5a2 --- /dev/null +++ b/client/cmd/redact.go @@ -0,0 +1,29 @@ +package cmd + +import ( + "strings" + + "github.com/ddworken/hishtory/client/hctx" + "github.com/ddworken/hishtory/client/lib" + "github.com/spf13/cobra" +) + +var force *bool + +var redactCmd = &cobra.Command{ + Use: "redact", + Short: "Query for matching commands and remove them from your shell history", + Long: "This removes history entries on the current machine and on all remote machines. Supports the same query format as 'hishtory query'.", + Run: func(cmd *cobra.Command, args []string) { + ctx := hctx.MakeContext() + lib.CheckFatalError(lib.RetrieveAdditionalEntriesFromRemote(ctx)) + lib.CheckFatalError(lib.ProcessDeletionRequests(ctx)) + query := strings.Join(args, " ") + lib.CheckFatalError(lib.Redact(ctx, query, *force)) + }, +} + +func init() { + rootCmd.AddCommand(redactCmd) + force = redactCmd.Flags().Bool("force", false, "Force redaction with no confirmation prompting") +} diff --git a/client/cmd/root.go b/client/cmd/root.go index 8255a2a..e2441a3 100644 --- a/client/cmd/root.go +++ b/client/cmd/root.go @@ -1,6 +1,5 @@ /* Copyright © 2022 NAME HERE - */ package cmd @@ -10,21 +9,10 @@ import ( "github.com/spf13/cobra" ) - - // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ - Use: "client", - Short: "A brief description of your application", - Long: `A longer description that spans multiple lines and likely contains -examples and usage of using your application. For example: - -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, - // Uncomment the following line if your bare application - // has an action associated with it: - // Run: func(cmd *cobra.Command, args []string) { }, + Use: "hiSHtory", + Short: "A better shell history", } // Execute adds all child commands to the root command and sets flags appropriately. @@ -36,16 +24,4 @@ func Execute() { } } -func init() { - // Here you will define your flags and configuration settings. - // Cobra supports persistent flags, which, if defined here, - // will be global for your application. - - // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.client.yaml)") - - // Cobra also supports local flags, which will only run - // when this action is called directly. - rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") -} - - +func init() {} diff --git a/client/cmd/saveHistoryEntry.go b/client/cmd/saveHistoryEntry.go new file mode 100644 index 0000000..5767d60 --- /dev/null +++ b/client/cmd/saveHistoryEntry.go @@ -0,0 +1,138 @@ +package cmd + +import ( + "context" + "encoding/json" + "fmt" + "os" + "time" + + "github.com/ddworken/hishtory/client/data" + "github.com/ddworken/hishtory/client/hctx" + "github.com/ddworken/hishtory/client/lib" + "github.com/ddworken/hishtory/shared" + "github.com/spf13/cobra" +) + +var saveHistoryEntryCmd = &cobra.Command{ + Use: "saveHistoryEntry", + // TODO: hide this from the help info? + Run: func(cmd *cobra.Command, args []string) { + ctx := hctx.MakeContext() + lib.CheckFatalError(maybeUploadSkippedHistoryEntries(ctx)) + saveHistoryEntry(ctx) + }, +} + +func maybeUploadSkippedHistoryEntries(ctx *context.Context) error { + config := hctx.GetConf(ctx) + if !config.HaveMissedUploads { + return nil + } + if config.IsOffline { + return nil + } + + // Upload the missing entries + db := hctx.GetDb(ctx) + query := fmt.Sprintf("after:%s", time.Unix(config.MissedUploadTimestamp, 0).Format("2006-01-02")) + entries, err := lib.Search(ctx, db, query, 0) + if err != nil { + return fmt.Errorf("failed to retrieve history entries that haven't been uploaded yet: %v", err) + } + hctx.GetLogger().Infof("Uploading %d history entries that previously failed to upload (query=%#v)\n", len(entries), query) + jsonValue, err := lib.EncryptAndMarshal(config, entries) + if err != nil { + return err + } + _, err = lib.ApiPost("/api/v1/submit?source_device_id="+config.DeviceId, "application/json", jsonValue) + if err != nil { + // Failed to upload the history entry, so we must still be offline. So just return nil and we'll try again later. + return nil + } + + // Mark down that we persisted it + config.HaveMissedUploads = false + config.MissedUploadTimestamp = 0 + err = hctx.SetConfig(config) + if err != nil { + return fmt.Errorf("failed to mark a history entry as uploaded: %v", err) + } + return nil +} + +func saveHistoryEntry(ctx *context.Context) { + config := hctx.GetConf(ctx) + if !config.IsEnabled { + hctx.GetLogger().Infof("Skipping saving a history entry because hishtory is disabled\n") + return + } + entry, err := lib.BuildHistoryEntry(ctx, os.Args) + lib.CheckFatalError(err) + if entry == nil { + hctx.GetLogger().Infof("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 + } + + // Persist it locally + db := hctx.GetDb(ctx) + err = lib.ReliableDbCreate(db, *entry) + lib.CheckFatalError(err) + + // Persist it remotely + if !config.IsOffline { + jsonValue, err := lib.EncryptAndMarshal(config, []*data.HistoryEntry{entry}) + lib.CheckFatalError(err) + _, err = lib.ApiPost("/api/v1/submit?source_device_id="+config.DeviceId, "application/json", jsonValue) + if err != nil { + if lib.IsOfflineError(err) { + hctx.GetLogger().Infof("Failed to remotely persist hishtory entry because we failed to connect to the remote server! This is likely because the device is offline, but also could be because the remote server is having reliability issues. Original error: %v", err) + if !config.HaveMissedUploads { + config.HaveMissedUploads = true + config.MissedUploadTimestamp = time.Now().Unix() + lib.CheckFatalError(hctx.SetConfig(config)) + } + } else { + lib.CheckFatalError(err) + } + } + } + + // Check if there is a pending dump request and reply to it if so + dumpRequests, err := lib.GetDumpRequests(config) + if err != nil { + if lib.IsOfflineError(err) { + // It is fine to just ignore this, the next command will retry the API and eventually we will respond to any pending dump requests + dumpRequests = []*shared.DumpRequest{} + hctx.GetLogger().Infof("Failed to check for dump requests because we failed to connect to the remote server!") + } else { + lib.CheckFatalError(err) + } + } + if len(dumpRequests) > 0 { + lib.CheckFatalError(lib.RetrieveAdditionalEntriesFromRemote(ctx)) + entries, err := lib.Search(ctx, db, "", 0) + lib.CheckFatalError(err) + var encEntries []*shared.EncHistoryEntry + for _, entry := range entries { + enc, err := data.EncryptHistoryEntry(config.UserSecret, *entry) + lib.CheckFatalError(err) + encEntries = append(encEntries, &enc) + } + reqBody, err := json.Marshal(encEntries) + lib.CheckFatalError(err) + for _, dumpRequest := range dumpRequests { + if !config.IsOffline { + _, err := lib.ApiPost("/api/v1/submit-dump?user_id="+dumpRequest.UserId+"&requesting_device_id="+dumpRequest.RequestingDeviceId+"&source_device_id="+config.DeviceId, "application/json", reqBody) + lib.CheckFatalError(err) + } + } + } + + // Handle deletion requests + lib.CheckFatalError(lib.ProcessDeletionRequests(ctx)) +} + +func init() { + rootCmd.AddCommand(saveHistoryEntryCmd) +} diff --git a/client/cmd/status.go b/client/cmd/status.go index 7a06c8f..9169e13 100644 --- a/client/cmd/status.go +++ b/client/cmd/status.go @@ -13,7 +13,7 @@ var verbose *bool var statusCmd = &cobra.Command{ Use: "status", - Short: "Get the hishtory status", + Short: "View status info including the secret key which is needed to sync shell history from another machine", Run: func(cmd *cobra.Command, args []string) { ctx := hctx.MakeContext() config := hctx.GetConf(ctx) diff --git a/hishtory.go b/hishtory.go index 503566b..725bd97 100644 --- a/hishtory.go +++ b/hishtory.go @@ -1,271 +1,18 @@ package main import ( - "context" - "encoding/json" - "fmt" - "os" - "strings" - "time" - "github.com/ddworken/hishtory/client/cmd" - "github.com/ddworken/hishtory/client/data" - "github.com/ddworken/hishtory/client/hctx" - "github.com/ddworken/hishtory/client/lib" - "github.com/ddworken/hishtory/shared" ) func main() { - if len(os.Args) == 1 { - fmt.Println("Must specify a command! Do you mean `hishtory query`?") - return - } - switch os.Args[1] { - case "saveHistoryEntry": - ctx := hctx.MakeContext() - lib.CheckFatalError(maybeUploadSkippedHistoryEntries(ctx)) - saveHistoryEntry(ctx) - case "query": - ctx := hctx.MakeContext() - lib.CheckFatalError(lib.ProcessDeletionRequests(ctx)) - query(ctx, strings.Join(os.Args[2:], " ")) - case "tquery": - ctx := hctx.MakeContext() - lib.CheckFatalError(lib.TuiQuery(ctx, strings.Join(os.Args[2:], " "))) - case "export": - ctx := hctx.MakeContext() - lib.CheckFatalError(lib.ProcessDeletionRequests(ctx)) - export(ctx, strings.Join(os.Args[2:], " ")) - case "redact": - fallthrough - case "delete": - ctx := hctx.MakeContext() - lib.CheckFatalError(lib.RetrieveAdditionalEntriesFromRemote(ctx)) - lib.CheckFatalError(lib.ProcessDeletionRequests(ctx)) - query := strings.Join(os.Args[2:], " ") - force := false - if os.Args[2] == "--force" { - query = strings.Join(os.Args[3:], " ") - force = true - } - lib.CheckFatalError(lib.Redact(ctx, query, force)) - case "init": - fallthrough - case "install": - fallthrough - case "uninstall": - fallthrough - case "import": - fallthrough - case "enable": - fallthrough - case "disable": - fallthrough - case "version": - fallthrough - case "status": - fallthrough - case "update": - fallthrough - case "config-set": - fallthrough - case "config-get": - fallthrough - case "config-add": - fallthrough - case "reupload": - fallthrough - case "config-delete": - cmd.Execute() - case "-h": - fallthrough - case "help": - fmt.Print(`hiSHtory: Better shell history - -Supported commands: - 'hishtory query': Query for matching commands and display them in a table. Examples: - 'hishtory query apt-get' # Find shell commands containing 'apt-get' - 'hishtory query apt-get install' # Find shell commands containing 'apt-get' and 'install' - 'hishtory query curl cwd:/tmp/' # Find shell commands containing 'curl' run in '/tmp/' - 'hishtory query curl user:david' # Find shell commands containing 'curl' run by 'david' - 'hishtory query curl host:x1' # Find shell commands containing 'curl' run on 'x1' - 'hishtory query exit_code:1' # Find shell commands that exited with status code 1 - 'hishtory query before:2022-02-01' # Find shell commands run before 2022-02-01 - 'hishtory export': Query for matching commands and display them in list without any other - metadata. Supports the same query format as 'hishtory query'. - 'hishtory redact': Query for matching commands and remove them from your shell history (on the - current machine and on all remote machines). Supports the same query format as 'hishtory query'. - 'hishtory update': Securely update hishtory to the latest version. - 'hishtory disable': Stop recording shell commands - 'hishtory enable': Start recording shell commands - 'hishtory status': View status info including the secret key which is needed to sync shell - history from another machine. - 'hishtory init': Set the secret key to enable syncing shell commands from another - machine with a matching secret key. - 'hishtory config-get', 'hishtory config-set', 'hishtory config-add', 'hishtory config-delete': Edit the config. See the README for details on each of the config options. - 'hishtory uninstall': Permanently uninstall hishtory - 'hishtory help': View this help page - `) - default: - lib.CheckFatalError(fmt.Errorf("unknown command: %s", os.Args[1])) - } -} - -func query(ctx *context.Context, query string) { - db := hctx.GetDb(ctx) - err := lib.RetrieveAdditionalEntriesFromRemote(ctx) - if err != nil { - if lib.IsOfflineError(err) { - fmt.Println("Warning: hishtory is offline so this may be missing recent results from your other machines!") - } else { - lib.CheckFatalError(err) - } - } - lib.CheckFatalError(displayBannerIfSet(ctx)) - numResults := 25 - data, err := lib.Search(ctx, db, query, numResults*5) - lib.CheckFatalError(err) - lib.CheckFatalError(lib.DisplayResults(ctx, data, numResults)) -} - -func displayBannerIfSet(ctx *context.Context) error { - respBody, err := lib.GetBanner(ctx) - if lib.IsOfflineError(err) { - return nil - } - if err != nil { - return err - } - if len(respBody) > 0 { - fmt.Println(string(respBody)) - } - return nil -} - -func maybeUploadSkippedHistoryEntries(ctx *context.Context) error { - config := hctx.GetConf(ctx) - if !config.HaveMissedUploads { - return nil - } - if config.IsOffline { - return nil - } - - // Upload the missing entries - db := hctx.GetDb(ctx) - query := fmt.Sprintf("after:%s", time.Unix(config.MissedUploadTimestamp, 0).Format("2006-01-02")) - entries, err := lib.Search(ctx, db, query, 0) - if err != nil { - return fmt.Errorf("failed to retrieve history entries that haven't been uploaded yet: %v", err) - } - hctx.GetLogger().Infof("Uploading %d history entries that previously failed to upload (query=%#v)\n", len(entries), query) - jsonValue, err := lib.EncryptAndMarshal(config, entries) - if err != nil { - return err - } - _, err = lib.ApiPost("/api/v1/submit?source_device_id="+config.DeviceId, "application/json", jsonValue) - if err != nil { - // Failed to upload the history entry, so we must still be offline. So just return nil and we'll try again later. - return nil - } - - // Mark down that we persisted it - config.HaveMissedUploads = false - config.MissedUploadTimestamp = 0 - err = hctx.SetConfig(config) - if err != nil { - return fmt.Errorf("failed to mark a history entry as uploaded: %v", err) - } - return nil -} - -func saveHistoryEntry(ctx *context.Context) { - config := hctx.GetConf(ctx) - if !config.IsEnabled { - hctx.GetLogger().Infof("Skipping saving a history entry because hishtory is disabled\n") - return - } - entry, err := lib.BuildHistoryEntry(ctx, os.Args) - lib.CheckFatalError(err) - if entry == nil { - hctx.GetLogger().Infof("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 - } - - // Persist it locally - db := hctx.GetDb(ctx) - err = lib.ReliableDbCreate(db, *entry) - lib.CheckFatalError(err) - - // Persist it remotely - if !config.IsOffline { - jsonValue, err := lib.EncryptAndMarshal(config, []*data.HistoryEntry{entry}) - lib.CheckFatalError(err) - _, err = lib.ApiPost("/api/v1/submit?source_device_id="+config.DeviceId, "application/json", jsonValue) - if err != nil { - if lib.IsOfflineError(err) { - hctx.GetLogger().Infof("Failed to remotely persist hishtory entry because we failed to connect to the remote server! This is likely because the device is offline, but also could be because the remote server is having reliability issues. Original error: %v", err) - if !config.HaveMissedUploads { - config.HaveMissedUploads = true - config.MissedUploadTimestamp = time.Now().Unix() - lib.CheckFatalError(hctx.SetConfig(config)) - } - } else { - lib.CheckFatalError(err) - } - } - } - - // Check if there is a pending dump request and reply to it if so - dumpRequests, err := lib.GetDumpRequests(config) - if err != nil { - if lib.IsOfflineError(err) { - // It is fine to just ignore this, the next command will retry the API and eventually we will respond to any pending dump requests - dumpRequests = []*shared.DumpRequest{} - hctx.GetLogger().Infof("Failed to check for dump requests because we failed to connect to the remote server!") - } else { - lib.CheckFatalError(err) - } - } - if len(dumpRequests) > 0 { - lib.CheckFatalError(lib.RetrieveAdditionalEntriesFromRemote(ctx)) - entries, err := lib.Search(ctx, db, "", 0) - lib.CheckFatalError(err) - var encEntries []*shared.EncHistoryEntry - for _, entry := range entries { - enc, err := data.EncryptHistoryEntry(config.UserSecret, *entry) - lib.CheckFatalError(err) - encEntries = append(encEntries, &enc) - } - reqBody, err := json.Marshal(encEntries) - lib.CheckFatalError(err) - for _, dumpRequest := range dumpRequests { - if !config.IsOffline { - _, err := lib.ApiPost("/api/v1/submit-dump?user_id="+dumpRequest.UserId+"&requesting_device_id="+dumpRequest.RequestingDeviceId+"&source_device_id="+config.DeviceId, "application/json", reqBody) - lib.CheckFatalError(err) - } - } - } - - // Handle deletion requests - lib.CheckFatalError(lib.ProcessDeletionRequests(ctx)) -} - -func export(ctx *context.Context, query string) { - db := hctx.GetDb(ctx) - err := lib.RetrieveAdditionalEntriesFromRemote(ctx) - if err != nil { - if lib.IsOfflineError(err) { - fmt.Println("Warning: hishtory is offline so this may be missing recent results from your other machines!") - } else { - lib.CheckFatalError(err) - } - } - data, err := lib.Search(ctx, db, query, 0) - lib.CheckFatalError(err) - for i := len(data) - 1; i >= 0; i-- { - fmt.Println(data[i].Command) - } + cmd.Execute() } // TODO(feature): Add a session_id column that corresponds to the shell session the command was run in + +/* +Remaining things: +* Support exclusions in searches +* Figure out how to hide certain things from the help doc +* Figure out how to reorder the docs +*/ From 27bbe97cb25aa35c7157c19ece1b0b483dde7990 Mon Sep 17 00:00:00 2001 From: David Dworken Date: Mon, 14 Nov 2022 20:18:22 -0800 Subject: [PATCH 05/13] Better organize the cobra generated help page --- client/cmd/configAdd.go | 5 +++-- client/cmd/configDelete.go | 5 +++-- client/cmd/configGet.go | 7 +++++-- client/cmd/configSet.go | 5 +++-- client/cmd/enableDisable.go | 10 ++++++---- client/cmd/import.go | 7 ++++--- client/cmd/init.go | 7 ++++--- client/cmd/install.go | 5 +++-- client/cmd/query.go | 23 ++++++++++++++--------- client/cmd/redact.go | 10 +++++++--- client/cmd/reupload.go | 5 +++-- client/cmd/root.go | 6 +++++- client/cmd/saveHistoryEntry.go | 5 +++-- client/cmd/status.go | 2 +- hishtory.go | 1 + 15 files changed, 65 insertions(+), 38 deletions(-) diff --git a/client/cmd/configAdd.go b/client/cmd/configAdd.go index 24d892f..45ff04b 100644 --- a/client/cmd/configAdd.go +++ b/client/cmd/configAdd.go @@ -7,8 +7,9 @@ import ( ) var configAddCmd = &cobra.Command{ - Use: "config-add", - Short: "Add a config option", + Use: "config-add", + Short: "Add a config option", + GroupID: GROUP_ID_CONFIG, Run: func(cmd *cobra.Command, args []string) { cmd.Help() }, diff --git a/client/cmd/configDelete.go b/client/cmd/configDelete.go index 7f701c6..dfda07f 100644 --- a/client/cmd/configDelete.go +++ b/client/cmd/configDelete.go @@ -9,8 +9,9 @@ import ( ) var configDeleteCmd = &cobra.Command{ - Use: "config-delete", - Short: "Delete a config option", + Use: "config-delete", + Short: "Delete a config option", + GroupID: GROUP_ID_CONFIG, Run: func(cmd *cobra.Command, args []string) { cmd.Help() }, diff --git a/client/cmd/configGet.go b/client/cmd/configGet.go index 033f336..efefda9 100644 --- a/client/cmd/configGet.go +++ b/client/cmd/configGet.go @@ -8,9 +8,12 @@ import ( "github.com/spf13/cobra" ) +var GROUP_ID_CONFIG string = "group_id_config" + var configGetCmd = &cobra.Command{ - Use: "config-get", - Short: "Get the value of a config option", + Use: "config-get", + Short: "Get the value of a config option", + GroupID: GROUP_ID_CONFIG, Run: func(cmd *cobra.Command, args []string) { cmd.Help() }, diff --git a/client/cmd/configSet.go b/client/cmd/configSet.go index 1a181b7..d08b19f 100644 --- a/client/cmd/configSet.go +++ b/client/cmd/configSet.go @@ -9,8 +9,9 @@ import ( ) var configSetCmd = &cobra.Command{ - Use: "config-set", - Short: "Set the value of a config option", + Use: "config-set", + Short: "Set the value of a config option", + GroupID: GROUP_ID_CONFIG, Run: func(cmd *cobra.Command, args []string) { cmd.Help() }, diff --git a/client/cmd/enableDisable.go b/client/cmd/enableDisable.go index 36e6e02..76fdfb7 100644 --- a/client/cmd/enableDisable.go +++ b/client/cmd/enableDisable.go @@ -7,8 +7,9 @@ import ( ) var enableCmd = &cobra.Command{ - Use: "enable", - Short: "Enable hiSHtory recording", + Use: "enable", + Short: "Enable hiSHtory recording", + GroupID: GROUP_ID_CONFIG, Run: func(cmd *cobra.Command, args []string) { ctx := hctx.MakeContext() lib.CheckFatalError(lib.Enable(ctx)) @@ -16,8 +17,9 @@ var enableCmd = &cobra.Command{ } var disableCmd = &cobra.Command{ - Use: "disable", - Short: "Disable hiSHtory recording", + Use: "disable", + Short: "Disable hiSHtory recording", + GroupID: GROUP_ID_CONFIG, Run: func(cmd *cobra.Command, args []string) { ctx := hctx.MakeContext() lib.CheckFatalError(lib.Disable(ctx)) diff --git a/client/cmd/import.go b/client/cmd/import.go index 7657c8b..18d120a 100644 --- a/client/cmd/import.go +++ b/client/cmd/import.go @@ -9,9 +9,10 @@ import ( ) var importCmd = &cobra.Command{ - Use: "import", - Short: "Re-import history entries from your existing shell history", - Long: "Note that you must pipe commands to be imported in via stdin. For example `history | hishtory import`.", + Use: "import", + Hidden: true, + Short: "Re-import history entries from your existing shell history", + Long: "Note that you must pipe commands to be imported in via stdin. For example `history | hishtory import`.", Run: func(cmd *cobra.Command, args []string) { ctx := hctx.MakeContext() numImported, err := lib.ImportHistory(ctx, true, true) diff --git a/client/cmd/init.go b/client/cmd/init.go index 8a15727..96908b9 100644 --- a/client/cmd/init.go +++ b/client/cmd/init.go @@ -14,9 +14,10 @@ import ( var offline *bool var initCmd = &cobra.Command{ - Use: "init", - Short: "Re-initialize hiSHtory with a specified secret key", - Args: cobra.MaximumNArgs(1), + Use: "init", + Short: "Re-initialize hiSHtory with a specified secret key", + GroupID: GROUP_ID_CONFIG, + Args: cobra.MaximumNArgs(1), Run: func(cmd *cobra.Command, args []string) { cmd.Flag("offline").Value.String() db, err := hctx.OpenLocalSqliteDb() diff --git a/client/cmd/install.go b/client/cmd/install.go index e31b1e3..9857b2f 100644 --- a/client/cmd/install.go +++ b/client/cmd/install.go @@ -10,8 +10,9 @@ import ( ) var installCmd = &cobra.Command{ - Use: "install", - Short: "Copy this binary to ~/.hishtory/ and configure your shell to use it for recording your shell history", + Use: "install", + Hidden: true, + Short: "Copy this binary to ~/.hishtory/ and configure your shell to use it for recording your shell history", Run: func(cmd *cobra.Command, args []string) { lib.CheckFatalError(lib.Install()) if os.Getenv("HISHTORY_SKIP_INIT_IMPORT") == "" { diff --git a/client/cmd/query.go b/client/cmd/query.go index 627f9d4..97dd1bc 100644 --- a/client/cmd/query.go +++ b/client/cmd/query.go @@ -21,10 +21,13 @@ var EXAMPLE_QUERIES string = `Example queries: 'hishtory SUBCOMMAND before:2022-02-01' # Find shell commands run before 2022-02-01 ` +var GROUP_ID_QUERYING string = "group_id:querying" + var queryCmd = &cobra.Command{ - Use: "query", - Short: "Query your shell history", - Long: strings.ReplaceAll(EXAMPLE_QUERIES, "SUBCOMMAND", "query"), + Use: "query", + Short: "Query your shell history and display the results in an ASCII art table", + GroupID: GROUP_ID_QUERYING, + Long: strings.ReplaceAll(EXAMPLE_QUERIES, "SUBCOMMAND", "query"), Run: func(cmd *cobra.Command, args []string) { ctx := hctx.MakeContext() lib.CheckFatalError(lib.ProcessDeletionRequests(ctx)) @@ -33,9 +36,10 @@ var queryCmd = &cobra.Command{ } var tqueryCmd = &cobra.Command{ - Use: "tquery", - Short: "Interactively query your shell history", - Long: strings.ReplaceAll(EXAMPLE_QUERIES, "SUBCOMMAND", "tquery"), + Use: "tquery", + Short: "Interactively query your shell history in a TUI interface", + GroupID: GROUP_ID_QUERYING, + Long: strings.ReplaceAll(EXAMPLE_QUERIES, "SUBCOMMAND", "tquery"), Run: func(cmd *cobra.Command, args []string) { ctx := hctx.MakeContext() lib.CheckFatalError(lib.TuiQuery(ctx, strings.Join(args, " "))) @@ -43,9 +47,10 @@ var tqueryCmd = &cobra.Command{ } var exportCmd = &cobra.Command{ - Use: "export", - Short: "Export your shell history", - Long: strings.ReplaceAll(EXAMPLE_QUERIES, "SUBCOMMAND", "export"), + Use: "export", + Short: "Export your shell history and display just the raw commands", + GroupID: GROUP_ID_QUERYING, + Long: strings.ReplaceAll(EXAMPLE_QUERIES, "SUBCOMMAND", "export"), Run: func(cmd *cobra.Command, args []string) { ctx := hctx.MakeContext() lib.CheckFatalError(lib.ProcessDeletionRequests(ctx)) diff --git a/client/cmd/redact.go b/client/cmd/redact.go index c67d5a2..d78643c 100644 --- a/client/cmd/redact.go +++ b/client/cmd/redact.go @@ -10,10 +10,14 @@ import ( var force *bool +var GROUP_ID_MANAGEMENT string = "group_id_management" + var redactCmd = &cobra.Command{ - Use: "redact", - Short: "Query for matching commands and remove them from your shell history", - Long: "This removes history entries on the current machine and on all remote machines. Supports the same query format as 'hishtory query'.", + Use: "redact", + Aliases: []string{"delete"}, + Short: "Query for matching commands and remove them from your shell history", + Long: "This removes history entries on the current machine and on all remote machines. Supports the same query format as 'hishtory query'.", + GroupID: GROUP_ID_MANAGEMENT, Run: func(cmd *cobra.Command, args []string) { ctx := hctx.MakeContext() lib.CheckFatalError(lib.RetrieveAdditionalEntriesFromRemote(ctx)) diff --git a/client/cmd/reupload.go b/client/cmd/reupload.go index 08d09d7..88af658 100644 --- a/client/cmd/reupload.go +++ b/client/cmd/reupload.go @@ -7,8 +7,9 @@ import ( ) var reuploadCmd = &cobra.Command{ - Use: "reupload", - Short: "[Debug Only] Reupload your entire hiSHtory to all other devices", + Use: "reupload", + Hidden: true, + Short: "[Debug Only] Reupload your entire hiSHtory to all other devices", Run: func(cmd *cobra.Command, args []string) { lib.CheckFatalError(lib.Reupload(hctx.MakeContext())) }, diff --git a/client/cmd/root.go b/client/cmd/root.go index e2441a3..0bacc9e 100644 --- a/client/cmd/root.go +++ b/client/cmd/root.go @@ -24,4 +24,8 @@ func Execute() { } } -func init() {} +func init() { + rootCmd.AddGroup(&cobra.Group{ID: GROUP_ID_QUERYING, Title: "History Searching"}) + rootCmd.AddGroup(&cobra.Group{ID: GROUP_ID_MANAGEMENT, Title: "History Management"}) + rootCmd.AddGroup(&cobra.Group{ID: GROUP_ID_CONFIG, Title: "Configuration"}) +} diff --git a/client/cmd/saveHistoryEntry.go b/client/cmd/saveHistoryEntry.go index 5767d60..f31e7f5 100644 --- a/client/cmd/saveHistoryEntry.go +++ b/client/cmd/saveHistoryEntry.go @@ -15,8 +15,9 @@ import ( ) var saveHistoryEntryCmd = &cobra.Command{ - Use: "saveHistoryEntry", - // TODO: hide this from the help info? + Use: "saveHistoryEntry", + Hidden: true, + Short: "[Internal-only] The command used to save history entries", Run: func(cmd *cobra.Command, args []string) { ctx := hctx.MakeContext() lib.CheckFatalError(maybeUploadSkippedHistoryEntries(ctx)) diff --git a/client/cmd/status.go b/client/cmd/status.go index 9169e13..1dd42fd 100644 --- a/client/cmd/status.go +++ b/client/cmd/status.go @@ -13,7 +13,7 @@ var verbose *bool var statusCmd = &cobra.Command{ Use: "status", - Short: "View status info including the secret key which is needed to sync shell history from another machine", + Short: "View status info including the secret key which is needed to sync shell history from another machine", Run: func(cmd *cobra.Command, args []string) { ctx := hctx.MakeContext() config := hctx.GetConf(ctx) diff --git a/hishtory.go b/hishtory.go index 725bd97..c218966 100644 --- a/hishtory.go +++ b/hishtory.go @@ -15,4 +15,5 @@ Remaining things: * Support exclusions in searches * Figure out how to hide certain things from the help doc * Figure out how to reorder the docs +* Acutally migrate saveHistoryEntry to cobra */ From 6ef9bb00d5ec7d8d900deaf0e63112b57ddddfca Mon Sep 17 00:00:00 2001 From: David Dworken Date: Mon, 14 Nov 2022 20:20:55 -0800 Subject: [PATCH 06/13] Add version to cobra help page --- client/cmd/root.go | 2 ++ hishtory.go | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/cmd/root.go b/client/cmd/root.go index 0bacc9e..9260389 100644 --- a/client/cmd/root.go +++ b/client/cmd/root.go @@ -6,6 +6,7 @@ package cmd import ( "os" + "github.com/ddworken/hishtory/client/lib" "github.com/spf13/cobra" ) @@ -28,4 +29,5 @@ func init() { rootCmd.AddGroup(&cobra.Group{ID: GROUP_ID_QUERYING, Title: "History Searching"}) rootCmd.AddGroup(&cobra.Group{ID: GROUP_ID_MANAGEMENT, Title: "History Management"}) rootCmd.AddGroup(&cobra.Group{ID: GROUP_ID_CONFIG, Title: "Configuration"}) + rootCmd.Version = "v0." + lib.Version } diff --git a/hishtory.go b/hishtory.go index c218966..243adf8 100644 --- a/hishtory.go +++ b/hishtory.go @@ -13,7 +13,5 @@ func main() { /* Remaining things: * Support exclusions in searches -* Figure out how to hide certain things from the help doc -* Figure out how to reorder the docs * Acutally migrate saveHistoryEntry to cobra */ From 06674942399b7d19025c3e8553e73f940dd36db0 Mon Sep 17 00:00:00 2001 From: David Dworken Date: Mon, 14 Nov 2022 20:31:20 -0800 Subject: [PATCH 07/13] Disable flag parsing for the querying functions so they can use exclusions + refactor redact to remove the --force flag --- client/client_test.go | 22 +++++++++++----------- client/cmd/query.go | 27 +++++++++++++++------------ client/cmd/redact.go | 17 ++++++++--------- hishtory.go | 1 - 4 files changed, 34 insertions(+), 33 deletions(-) diff --git a/client/client_test.go b/client/client_test.go index ecfb241..7ebfaaa 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -1531,27 +1531,27 @@ ls /tmp`, randomCmdUuid, randomCmdUuid) } // Redact foo - out = tester.RunInteractiveShell(t, `hishtory redact --force foo`) + out = tester.RunInteractiveShell(t, `HISHTORY_REDACT_FORCE=1 hishtory redact foo`) if out != "Permanently deleting 2 entries\n" { t.Fatalf("hishtory redact gave unexpected output=%#v", out) } // Check that the commands are redacted out = tester.RunInteractiveShell(t, `hishtory export | grep -v pipefail`) - expectedOutput = fmt.Sprintf("echo %s-bas\nls /tmp\nhishtory redact --force foo\n", randomCmdUuid) + expectedOutput = fmt.Sprintf("echo %s-bas\nls /tmp\nHISHTORY_REDACT_FORCE=1 hishtory redact foo\n", randomCmdUuid) if diff := cmp.Diff(expectedOutput, out); diff != "" { t.Fatalf("hishtory export mismatch (-expected +got):\n%s\nout=%#v", diff, out) } // Redact s - out = tester.RunInteractiveShell(t, `hishtory redact --force s`) + out = tester.RunInteractiveShell(t, `HISHTORY_REDACT_FORCE=1 hishtory redact s`) if out != "Permanently deleting 10 entries\n" && out != "Permanently deleting 11 entries\n" { t.Fatalf("hishtory redact gave unexpected output=%#v", out) } // Check that the commands are redacted out = tester.RunInteractiveShell(t, `hishtory export | grep -v pipefail`) - expectedOutput = "hishtory redact --force s\n" + expectedOutput = "HISHTORY_REDACT_FORCE=1 hishtory redact s\n" if diff := cmp.Diff(expectedOutput, out); diff != "" { t.Fatalf("hishtory export mismatch (-expected +got):\n%s\nout=%#v", diff, out) } @@ -1559,12 +1559,12 @@ ls /tmp`, randomCmdUuid, randomCmdUuid) // Record another command tester.RunInteractiveShell(t, `echo hello`) out = tester.RunInteractiveShell(t, `hishtory export | grep -v pipefail`) - expectedOutput = "hishtory redact --force s\necho hello\n" + expectedOutput = "HISHTORY_REDACT_FORCE=1 hishtory redact s\necho hello\n" if diff := cmp.Diff(expectedOutput, out); diff != "" { t.Fatalf("hishtory export mismatch (-expected +got):\n%s\nout=%#v", diff, out) } - // Redact it without --force + // Redact it without HISHTORY_REDACT_FORCE out, err := tester.RunInteractiveShellRelaxed(t, `yes | hishtory redact hello`) testutils.Check(t, err) if out != "This will permanently delete 1 entries, are you sure? [y/N]" { @@ -1573,7 +1573,7 @@ ls /tmp`, randomCmdUuid, randomCmdUuid) // And check it was redacted out = tester.RunInteractiveShell(t, `hishtory export | grep -v pipefail`) - expectedOutput = "hishtory redact --force s\nyes | hishtory redact hello\n" + expectedOutput = "HISHTORY_REDACT_FORCE=1 hishtory redact s\nyes | hishtory redact hello\n" if diff := cmp.Diff(expectedOutput, out); diff != "" { t.Fatalf("hishtory export mismatch (-expected +got):\n%s\nout=%#v", diff, out) } @@ -1614,14 +1614,14 @@ ls /tmp`, randomCmdUuid, randomCmdUuid) // Restore the first client, and redact some commands restoreInstall2 := testutils.BackupAndRestoreWithId(t, "-2") restoreInstall1() - out = tester.RunInteractiveShell(t, `hishtory redact --force `+randomCmdUuid) + out = tester.RunInteractiveShell(t, `HISHTORY_REDACT_FORCE=1 hishtory redact `+randomCmdUuid) if out != "Permanently deleting 2 entries\n" { t.Fatalf("hishtory redact gave unexpected output=%#v", out) } // Confirm that client1 doesn't have the commands out = tester.RunInteractiveShell(t, `hishtory export | grep -v pipefail`) - expectedOutput = fmt.Sprintf("echo foo\nls /tmp\nhishtory redact --force %s\n", randomCmdUuid) + expectedOutput = fmt.Sprintf("echo foo\nls /tmp\nHISHTORY_REDACT_FORCE=1 hishtory redact %s\n", randomCmdUuid) if diff := cmp.Diff(expectedOutput, out); diff != "" { t.Fatalf("hishtory export mismatch (-expected +got):\n%s\nout=%#v", diff, out) } @@ -2413,7 +2413,7 @@ func fuzzTest(t *testing.T, tester shellTester, input string) { testutils.Check(t, err) } if op.redactQuery != "" { - _, err := tester.RunInteractiveShellRelaxed(t, `hishtory redact --force `+op.redactQuery) + _, err := tester.RunInteractiveShellRelaxed(t, `HISHTORY_REDACT_FORCE=1 hishtory redact `+op.redactQuery) testutils.Check(t, err) } @@ -2436,7 +2436,7 @@ func fuzzTest(t *testing.T, tester shellTester, input string) { filteredLines = append(filteredLines, line) } val = strings.Join(filteredLines, "\n") - val += `hishtory redact --force ` + op.redactQuery + "\n" + val += `HISHTORY_REDACT_FORCE=1 hishtory redact ` + op.redactQuery + "\n" } keyToCommands[op.device.key] = val diff --git a/client/cmd/query.go b/client/cmd/query.go index 97dd1bc..33e8f1a 100644 --- a/client/cmd/query.go +++ b/client/cmd/query.go @@ -24,10 +24,11 @@ var EXAMPLE_QUERIES string = `Example queries: var GROUP_ID_QUERYING string = "group_id:querying" var queryCmd = &cobra.Command{ - Use: "query", - Short: "Query your shell history and display the results in an ASCII art table", - GroupID: GROUP_ID_QUERYING, - Long: strings.ReplaceAll(EXAMPLE_QUERIES, "SUBCOMMAND", "query"), + Use: "query", + Short: "Query your shell history and display the results in an ASCII art table", + GroupID: GROUP_ID_QUERYING, + Long: strings.ReplaceAll(EXAMPLE_QUERIES, "SUBCOMMAND", "query"), + DisableFlagParsing: true, Run: func(cmd *cobra.Command, args []string) { ctx := hctx.MakeContext() lib.CheckFatalError(lib.ProcessDeletionRequests(ctx)) @@ -36,10 +37,11 @@ var queryCmd = &cobra.Command{ } var tqueryCmd = &cobra.Command{ - Use: "tquery", - Short: "Interactively query your shell history in a TUI interface", - GroupID: GROUP_ID_QUERYING, - Long: strings.ReplaceAll(EXAMPLE_QUERIES, "SUBCOMMAND", "tquery"), + Use: "tquery", + Short: "Interactively query your shell history in a TUI interface", + GroupID: GROUP_ID_QUERYING, + Long: strings.ReplaceAll(EXAMPLE_QUERIES, "SUBCOMMAND", "tquery"), + DisableFlagParsing: true, Run: func(cmd *cobra.Command, args []string) { ctx := hctx.MakeContext() lib.CheckFatalError(lib.TuiQuery(ctx, strings.Join(args, " "))) @@ -47,10 +49,11 @@ var tqueryCmd = &cobra.Command{ } var exportCmd = &cobra.Command{ - Use: "export", - Short: "Export your shell history and display just the raw commands", - GroupID: GROUP_ID_QUERYING, - Long: strings.ReplaceAll(EXAMPLE_QUERIES, "SUBCOMMAND", "export"), + Use: "export", + Short: "Export your shell history and display just the raw commands", + GroupID: GROUP_ID_QUERYING, + Long: strings.ReplaceAll(EXAMPLE_QUERIES, "SUBCOMMAND", "export"), + DisableFlagParsing: true, Run: func(cmd *cobra.Command, args []string) { ctx := hctx.MakeContext() lib.CheckFatalError(lib.ProcessDeletionRequests(ctx)) diff --git a/client/cmd/redact.go b/client/cmd/redact.go index d78643c..46f2b73 100644 --- a/client/cmd/redact.go +++ b/client/cmd/redact.go @@ -1,6 +1,7 @@ package cmd import ( + "os" "strings" "github.com/ddworken/hishtory/client/hctx" @@ -8,26 +9,24 @@ import ( "github.com/spf13/cobra" ) -var force *bool - var GROUP_ID_MANAGEMENT string = "group_id_management" var redactCmd = &cobra.Command{ - Use: "redact", - Aliases: []string{"delete"}, - Short: "Query for matching commands and remove them from your shell history", - Long: "This removes history entries on the current machine and on all remote machines. Supports the same query format as 'hishtory query'.", - GroupID: GROUP_ID_MANAGEMENT, + Use: "redact", + Aliases: []string{"delete"}, + Short: "Query for matching commands and remove them from your shell history", + Long: "This removes history entries on the current machine and on all remote machines. Supports the same query format as 'hishtory query'.", + GroupID: GROUP_ID_MANAGEMENT, + DisableFlagParsing: true, Run: func(cmd *cobra.Command, args []string) { ctx := hctx.MakeContext() lib.CheckFatalError(lib.RetrieveAdditionalEntriesFromRemote(ctx)) lib.CheckFatalError(lib.ProcessDeletionRequests(ctx)) query := strings.Join(args, " ") - lib.CheckFatalError(lib.Redact(ctx, query, *force)) + lib.CheckFatalError(lib.Redact(ctx, query, os.Getenv("HISHTORY_REDACT_FORCE") != "")) }, } func init() { rootCmd.AddCommand(redactCmd) - force = redactCmd.Flags().Bool("force", false, "Force redaction with no confirmation prompting") } diff --git a/hishtory.go b/hishtory.go index 243adf8..5bc81c7 100644 --- a/hishtory.go +++ b/hishtory.go @@ -12,6 +12,5 @@ func main() { /* Remaining things: -* Support exclusions in searches * Acutally migrate saveHistoryEntry to cobra */ From be2cde72c264ee65c5f41f9946e4e8aa9e2ee4b3 Mon Sep 17 00:00:00 2001 From: David Dworken Date: Mon, 14 Nov 2022 20:34:36 -0800 Subject: [PATCH 08/13] Appears to be a functional migration to cobra --- client/cmd/query.go | 3 +-- client/cmd/saveHistoryEntry.go | 1 + hishtory.go | 7 +------ 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/client/cmd/query.go b/client/cmd/query.go index 33e8f1a..d6a9596 100644 --- a/client/cmd/query.go +++ b/client/cmd/query.go @@ -3,7 +3,6 @@ package cmd import ( "context" "fmt" - "os" "strings" "github.com/ddworken/hishtory/client/hctx" @@ -57,7 +56,7 @@ var exportCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { ctx := hctx.MakeContext() lib.CheckFatalError(lib.ProcessDeletionRequests(ctx)) - export(ctx, strings.Join(os.Args[2:], " ")) + export(ctx, strings.Join(args, " ")) }, } diff --git a/client/cmd/saveHistoryEntry.go b/client/cmd/saveHistoryEntry.go index f31e7f5..c9fed2a 100644 --- a/client/cmd/saveHistoryEntry.go +++ b/client/cmd/saveHistoryEntry.go @@ -18,6 +18,7 @@ var saveHistoryEntryCmd = &cobra.Command{ Use: "saveHistoryEntry", Hidden: true, Short: "[Internal-only] The command used to save history entries", + DisableFlagParsing: true, Run: func(cmd *cobra.Command, args []string) { ctx := hctx.MakeContext() lib.CheckFatalError(maybeUploadSkippedHistoryEntries(ctx)) diff --git a/hishtory.go b/hishtory.go index 5bc81c7..b2a95c3 100644 --- a/hishtory.go +++ b/hishtory.go @@ -8,9 +8,4 @@ func main() { cmd.Execute() } -// TODO(feature): Add a session_id column that corresponds to the shell session the command was run in - -/* -Remaining things: -* Acutally migrate saveHistoryEntry to cobra -*/ +// TODO(feature): Add a session_id column that corresponds to the shell session the command was run in \ No newline at end of file From fe6394d1b5e0c81723a277687823402f805807ad Mon Sep 17 00:00:00 2001 From: David Dworken Date: Mon, 14 Nov 2022 20:55:10 -0800 Subject: [PATCH 09/13] Some test fixes for the cobra integration --- client/client_test.go | 10 +++++-- client/cmd/configDelete.go | 1 + client/cmd/init.go | 57 -------------------------------------- client/cmd/install.go | 47 ++++++++++++++++++++++++++++++- client/cmd/root.go | 2 +- client/lib/lib.go | 6 ++-- hishtory.go | 2 +- 7 files changed, 59 insertions(+), 66 deletions(-) delete mode 100644 client/cmd/init.go diff --git a/client/client_test.go b/client/client_test.go index 7ebfaaa..111624a 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -1206,6 +1206,10 @@ echo other`) } func testInstallViaPythonScript(t *testing.T, tester shellTester) { + if !testutils.IsOnline() { + t.Skip("skipping because we're currently offline") + } + // Set up defer testutils.BackupAndRestore(t)() defer testutils.BackupAndRestoreEnv("HISHTORY_TEST")() @@ -1314,7 +1318,7 @@ func testHelpCommand(t *testing.T, tester shellTester) { // Test the help command out := tester.RunInteractiveShell(t, `hishtory help`) - if !strings.HasPrefix(out, "hiSHtory: Better shell history\n\nSupported commands:\n") { + if !strings.HasPrefix(out, "hiSHtory: Better shell history") { t.Fatalf("expected hishtory help to contain intro, actual=%#v", out) } out2 := tester.RunInteractiveShell(t, `hishtory -h`) @@ -1708,7 +1712,7 @@ func testHandleUpgradedFeatures(t *testing.T, tester shellTester) { // And check that hishtory says it is false by default out := tester.RunInteractiveShell(t, `hishtory config-get enable-control-r`) - if out != "false" { + if out != "false\n" { t.Fatalf("unexpected config-get output: %#v", out) } @@ -1718,7 +1722,7 @@ func testHandleUpgradedFeatures(t *testing.T, tester shellTester) { // Now it should be enabled out = tester.RunInteractiveShell(t, `hishtory config-get enable-control-r`) - if out != "true" { + if out != "true\n" { t.Fatalf("unexpected config-get output: %#v", out) } } diff --git a/client/cmd/configDelete.go b/client/cmd/configDelete.go index dfda07f..26a9b64 100644 --- a/client/cmd/configDelete.go +++ b/client/cmd/configDelete.go @@ -71,4 +71,5 @@ var deleteDisplayedColumnCommand = &cobra.Command{ func init() { rootCmd.AddCommand(configDeleteCmd) configDeleteCmd.AddCommand(deleteCustomColumnsCmd) + configDeleteCmd.AddCommand(deleteDisplayedColumnCommand) } diff --git a/client/cmd/init.go b/client/cmd/init.go deleted file mode 100644 index 96908b9..0000000 --- a/client/cmd/init.go +++ /dev/null @@ -1,57 +0,0 @@ -package cmd - -import ( - "bufio" - "fmt" - "os" - "strings" - - "github.com/ddworken/hishtory/client/hctx" - "github.com/ddworken/hishtory/client/lib" - "github.com/spf13/cobra" -) - -var offline *bool - -var initCmd = &cobra.Command{ - Use: "init", - Short: "Re-initialize hiSHtory with a specified secret key", - GroupID: GROUP_ID_CONFIG, - Args: cobra.MaximumNArgs(1), - Run: func(cmd *cobra.Command, args []string) { - cmd.Flag("offline").Value.String() - db, err := hctx.OpenLocalSqliteDb() - lib.CheckFatalError(err) - data, err := lib.Search(nil, db, "", 10) - lib.CheckFatalError(err) - if len(data) > 0 { - fmt.Printf("Your current hishtory profile has saved history entries, are you sure you want to run `init` and reset?\nNote: This won't clear any imported history entries from your existing shell\n[y/N]") - reader := bufio.NewReader(os.Stdin) - resp, err := reader.ReadString('\n') - lib.CheckFatalError(err) - if strings.TrimSpace(resp) != "y" { - fmt.Printf("Aborting init per user response of %#v\n", strings.TrimSpace(resp)) - return - } - } - secretKey := "" - if len(args) > 0 { - secretKey = args[0] - } - lib.CheckFatalError(lib.Setup(secretKey, *offline)) - if os.Getenv("HISHTORY_SKIP_INIT_IMPORT") == "" { - fmt.Println("Importing existing shell history...") - ctx := hctx.MakeContext() - numImported, err := lib.ImportHistory(ctx, false, false) - lib.CheckFatalError(err) - if numImported > 0 { - fmt.Printf("Imported %v history entries from your existing shell history\n", numImported) - } - } - }, -} - -func init() { - rootCmd.AddCommand(initCmd) - offline = initCmd.Flags().Bool("offline", false, "Install hiSHtory in offline mode wiht all syncing capabilities disabled") -} diff --git a/client/cmd/install.go b/client/cmd/install.go index 9857b2f..bec838a 100644 --- a/client/cmd/install.go +++ b/client/cmd/install.go @@ -1,20 +1,25 @@ package cmd import ( + "bufio" "fmt" "os" + "strings" "github.com/ddworken/hishtory/client/hctx" "github.com/ddworken/hishtory/client/lib" "github.com/spf13/cobra" ) +var offlineInit *bool +var offlineInstall *bool + var installCmd = &cobra.Command{ Use: "install", Hidden: true, Short: "Copy this binary to ~/.hishtory/ and configure your shell to use it for recording your shell history", Run: func(cmd *cobra.Command, args []string) { - lib.CheckFatalError(lib.Install()) + lib.CheckFatalError(lib.Install(*offlineInstall)) if os.Getenv("HISHTORY_SKIP_INIT_IMPORT") == "" { db, err := hctx.OpenLocalSqliteDb() lib.CheckFatalError(err) @@ -33,6 +38,46 @@ var installCmd = &cobra.Command{ }, } +var initCmd = &cobra.Command{ + Use: "init", + Short: "Re-initialize hiSHtory with a specified secret key", + GroupID: GROUP_ID_CONFIG, + Args: cobra.MaximumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + db, err := hctx.OpenLocalSqliteDb() + lib.CheckFatalError(err) + data, err := lib.Search(nil, db, "", 10) + lib.CheckFatalError(err) + if len(data) > 0 { + fmt.Printf("Your current hishtory profile has saved history entries, are you sure you want to run `init` and reset?\nNote: This won't clear any imported history entries from your existing shell\n[y/N]") + reader := bufio.NewReader(os.Stdin) + resp, err := reader.ReadString('\n') + lib.CheckFatalError(err) + if strings.TrimSpace(resp) != "y" { + fmt.Printf("Aborting init per user response of %#v\n", strings.TrimSpace(resp)) + return + } + } + secretKey := "" + if len(args) > 0 { + secretKey = args[0] + } + lib.CheckFatalError(lib.Setup(secretKey, *offlineInit)) + if os.Getenv("HISHTORY_SKIP_INIT_IMPORT") == "" { + fmt.Println("Importing existing shell history...") + ctx := hctx.MakeContext() + numImported, err := lib.ImportHistory(ctx, false, false) + lib.CheckFatalError(err) + if numImported > 0 { + fmt.Printf("Imported %v history entries from your existing shell history\n", numImported) + } + } + }, +} + func init() { rootCmd.AddCommand(installCmd) + rootCmd.AddCommand(initCmd) + offlineInit = initCmd.Flags().Bool("offline", false, "Install hiSHtory in offline mode wiht all syncing capabilities disabled") + offlineInstall = installCmd.Flags().Bool("offline", false, "Install hiSHtory in offline mode wiht all syncing capabilities disabled") } diff --git a/client/cmd/root.go b/client/cmd/root.go index 9260389..762ab94 100644 --- a/client/cmd/root.go +++ b/client/cmd/root.go @@ -13,7 +13,7 @@ import ( // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "hiSHtory", - Short: "A better shell history", + Short: "hiSHtory: Better shell history", } // Execute adds all child commands to the root command and sets flags appropriately. diff --git a/client/lib/lib.go b/client/lib/lib.go index 33dd9a8..24f7f98 100644 --- a/client/lib/lib.go +++ b/client/lib/lib.go @@ -652,7 +652,7 @@ func readFileToArray(path string) ([]string, error) { return lines, nil } -func Install() error { +func Install(offline bool) error { homedir, err := os.UserHomeDir() if err != nil { return fmt.Errorf("failed to get user's home directory: %v", err) @@ -683,8 +683,8 @@ func Install() error { } _, err = hctx.GetConfig() if err != nil { - // No config, so set up a new installation with a new key and in online mode - return Setup("", false) + // No config, so set up a new installation with a new key + return Setup("", offline) } return nil } diff --git a/hishtory.go b/hishtory.go index b2a95c3..66e5534 100644 --- a/hishtory.go +++ b/hishtory.go @@ -8,4 +8,4 @@ func main() { cmd.Execute() } -// TODO(feature): Add a session_id column that corresponds to the shell session the command was run in \ No newline at end of file +// TODO(feature): Add a session_id column that corresponds to the shell session the command was run in From 65405b868f802c3b958d5eed08e96e619ab2078c Mon Sep 17 00:00:00 2001 From: David Dworken Date: Wed, 16 Nov 2022 08:07:02 -0800 Subject: [PATCH 10/13] Remove no-op config-set that isn't needed now that we have good docs from cobra --- client/cmd/configSet.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/client/cmd/configSet.go b/client/cmd/configSet.go index d08b19f..fd9c39f 100644 --- a/client/cmd/configSet.go +++ b/client/cmd/configSet.go @@ -75,19 +75,10 @@ var setTimestampFormatCmd = &cobra.Command{ }, } -var setCustomColumnsCmd = &cobra.Command{ - Use: "custom-columns", - Short: "The list of custom columns that hishtory is tracking", - Run: func(cmd *cobra.Command, args []string) { - log.Fatalf("Please use config-add and config-delete to interact with custom-columns") - }, -} - func init() { rootCmd.AddCommand(configSetCmd) configSetCmd.AddCommand(setEnableControlRCmd) configSetCmd.AddCommand(setFilterDuplicateCommandsCmd) configSetCmd.AddCommand(setDisplayedColumnsCmd) configSetCmd.AddCommand(setTimestampFormatCmd) - configSetCmd.AddCommand(setCustomColumnsCmd) } From ba769ec70031b40faee1551c2a9e20b9a7d0aeb2 Mon Sep 17 00:00:00 2001 From: David Dworken Date: Wed, 16 Nov 2022 08:09:04 -0800 Subject: [PATCH 11/13] Fix mis-alignment in example queries --- client/cmd/query.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/cmd/query.go b/client/cmd/query.go index d6a9596..c00a3a1 100644 --- a/client/cmd/query.go +++ b/client/cmd/query.go @@ -11,7 +11,7 @@ import ( ) var EXAMPLE_QUERIES string = `Example queries: -'hishtory SUBCOMMAND apt-get' # Find shell commands containing 'apt-get' +'hishtory SUBCOMMAND apt-get' # Find shell commands containing 'apt-get' 'hishtory SUBCOMMAND apt-get install' # Find shell commands containing 'apt-get' and 'install' 'hishtory SUBCOMMAND curl cwd:/tmp/' # Find shell commands containing 'curl' run in '/tmp/' 'hishtory SUBCOMMAND curl user:david' # Find shell commands containing 'curl' run by 'david' From 35208680d8569a535ac1166452a49106678b3f98 Mon Sep 17 00:00:00 2001 From: David Dworken Date: Wed, 16 Nov 2022 20:26:44 -0800 Subject: [PATCH 12/13] Improve debug logs in server.go --- backend/server/server.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/server/server.go b/backend/server/server.go index 2898410..b8f97e8 100644 --- a/backend/server/server.go +++ b/backend/server/server.go @@ -82,7 +82,7 @@ func usageStatsHandler(w http.ResponseWriter, r *http.Request) { COUNT(DISTINCT devices.device_id) as num_devices, SUM(usage_data.num_entries_handled) as num_history_entries, MAX(usage_data.last_used) as last_active, - COALESCE(STRING_AGG(DISTINCT usage_data.last_ip, ', ') FILTER (WHERE usage_data.last_ip != 'Unknown'), 'Unknown') as ip_addresses, + COALESCE(STRING_AGG(DISTINCT usage_data.last_ip, ', ') FILTER (WHERE usage_data.last_ip != 'Unknown' AND usage_data.last_ip != 'UnknownIp'), 'Unknown') as ip_addresses, COALESCE(SUM(usage_data.num_queries), 0) as num_queries, COALESCE(MAX(usage_data.last_queried), 'January 1, 1970') as last_queried, STRING_AGG(DISTINCT usage_data.version, ', ') as versions @@ -177,6 +177,7 @@ func apiBootstrapHandler(w http.ResponseWriter, r *http.Request) { tx := GLOBAL_DB.Where("user_id = ?", userId) var historyEntries []*shared.EncHistoryEntry checkGormResult(tx.Find(&historyEntries)) + fmt.Printf("apiBootstrapHandler: Found %d entries\n", len(historyEntries)) resp, err := json.Marshal(historyEntries) if err != nil { panic(err) @@ -216,7 +217,7 @@ func apiQueryHandler(w http.ResponseWriter, r *http.Request) { func getRemoteAddr(r *http.Request) string { addr, ok := r.Header["X-Real-Ip"] if !ok || len(addr) == 0 { - return "Unknown" + return "UnknownIp" } return addr[0] } From 86f9d67aff38f5bb3bd78c3de0d17e341fe2a189 Mon Sep 17 00:00:00 2001 From: David Dworken Date: Wed, 16 Nov 2022 20:28:25 -0800 Subject: [PATCH 13/13] Fix test failures caused by the cobra install command not respecting the secret key Also added a persistLog() function so that I can easily inspect the hishtory logs from test runs. --- client/cmd/install.go | 7 ++++++- client/hctx/hctx.go | 2 +- client/lib/lib.go | 6 +++--- shared/testutils/testutils.go | 24 ++++++++++++++++++++---- 4 files changed, 30 insertions(+), 9 deletions(-) diff --git a/client/cmd/install.go b/client/cmd/install.go index bec838a..9334a85 100644 --- a/client/cmd/install.go +++ b/client/cmd/install.go @@ -18,8 +18,13 @@ var installCmd = &cobra.Command{ Use: "install", Hidden: true, Short: "Copy this binary to ~/.hishtory/ and configure your shell to use it for recording your shell history", + Args: cobra.MaximumNArgs(1), Run: func(cmd *cobra.Command, args []string) { - lib.CheckFatalError(lib.Install(*offlineInstall)) + secretKey := "" + if len(args) > 0 { + secretKey = args[0] + } + lib.CheckFatalError(lib.Install(secretKey, *offlineInstall)) if os.Getenv("HISHTORY_SKIP_INIT_IMPORT") == "" { db, err := hctx.OpenLocalSqliteDb() lib.CheckFatalError(err) diff --git a/client/hctx/hctx.go b/client/hctx/hctx.go index 9635fdb..74c6074 100644 --- a/client/hctx/hctx.go +++ b/client/hctx/hctx.go @@ -78,7 +78,7 @@ func OpenLocalSqliteDb() (*gorm.DB, error) { return nil, err } newLogger := logger.New( - GetLogger(), + GetLogger().WithField("fromSQL", true), logger.Config{ SlowThreshold: 100 * time.Millisecond, LogLevel: logger.Warn, diff --git a/client/lib/lib.go b/client/lib/lib.go index 24f7f98..c0ef071 100644 --- a/client/lib/lib.go +++ b/client/lib/lib.go @@ -652,7 +652,7 @@ func readFileToArray(path string) ([]string, error) { return lines, nil } -func Install(offline bool) error { +func Install(secretKey string, offline bool) error { homedir, err := os.UserHomeDir() if err != nil { return fmt.Errorf("failed to get user's home directory: %v", err) @@ -683,8 +683,8 @@ func Install(offline bool) error { } _, err = hctx.GetConfig() if err != nil { - // No config, so set up a new installation with a new key - return Setup("", offline) + // No config, so set up a new installation + return Setup(secretKey, offline) } return nil } diff --git a/shared/testutils/testutils.go b/shared/testutils/testutils.go index 6ca8731..7166942 100644 --- a/shared/testutils/testutils.go +++ b/shared/testutils/testutils.go @@ -25,10 +25,8 @@ const ( func ResetLocalState(t *testing.T) { homedir, err := os.UserHomeDir() - if err != nil { - t.Fatalf("failed to retrieve homedir: %v", err) - } - + Check(t, err) + persistLog() _ = BackupAndRestoreWithId(t, "-reset-local-state") _ = os.RemoveAll(path.Join(homedir, data.HISHTORY_PATH)) } @@ -90,6 +88,7 @@ func BackupAndRestoreWithId(t *testing.T, id string) func() { t.Fatalf("failed to execute killall hishtory, stdout=%#v: %v", string(stdout), err) } } + persistLog() Check(t, os.RemoveAll(path.Join(homedir, data.HISHTORY_PATH))) Check(t, os.MkdirAll(path.Join(homedir, data.HISHTORY_PATH), os.ModePerm)) for _, file := range renameFiles { @@ -298,3 +297,20 @@ func TestLog(t *testing.T, line string) { Check(t, err) } } + +func persistLog() { + homedir, err := os.UserHomeDir() + checkError(err) + fp := path.Join(homedir, data.HISHTORY_PATH, "hishtory.log") + log, err := os.ReadFile(fp) + if err != nil { + return + } + f, err := os.OpenFile("/tmp/hishtory.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + checkError(err) + defer f.Close() + _, err = f.Write(log) + checkError(err) + _, err = f.WriteString("\n") + checkError(err) +}