mirror of
https://github.com/ddworken/hishtory.git
synced 2024-11-22 08:14:02 +01:00
Merge pull request #36 from ddworken/cobra
Migrate to cobra for CLI parsing
This commit is contained in:
commit
666d9c6e81
@ -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
|
||||
```
|
||||
</details>
|
||||
|
@ -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]
|
||||
}
|
||||
|
@ -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`)
|
||||
@ -1531,27 +1535,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 +1563,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 +1577,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 +1618,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)
|
||||
}
|
||||
@ -1641,17 +1645,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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -2073,7 +2077,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
|
||||
@ -2413,7 +2417,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 +2440,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
|
||||
|
||||
|
52
client/cmd/configAdd.go
Normal file
52
client/cmd/configAdd.go
Normal file
@ -0,0 +1,52 @@
|
||||
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",
|
||||
GroupID: GROUP_ID_CONFIG,
|
||||
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)
|
||||
}
|
75
client/cmd/configDelete.go
Normal file
75
client/cmd/configDelete.go
Normal file
@ -0,0 +1,75 @@
|
||||
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",
|
||||
GroupID: GROUP_ID_CONFIG,
|
||||
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)
|
||||
configDeleteCmd.AddCommand(deleteDisplayedColumnCommand)
|
||||
}
|
89
client/cmd/configGet.go
Normal file
89
client/cmd/configGet.go
Normal file
@ -0,0 +1,89 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/ddworken/hishtory/client/hctx"
|
||||
"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",
|
||||
GroupID: GROUP_ID_CONFIG,
|
||||
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)
|
||||
}
|
84
client/cmd/configSet.go
Normal file
84
client/cmd/configSet.go
Normal file
@ -0,0 +1,84 @@
|
||||
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",
|
||||
GroupID: GROUP_ID_CONFIG,
|
||||
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))
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(configSetCmd)
|
||||
configSetCmd.AddCommand(setEnableControlRCmd)
|
||||
configSetCmd.AddCommand(setFilterDuplicateCommandsCmd)
|
||||
configSetCmd.AddCommand(setDisplayedColumnsCmd)
|
||||
configSetCmd.AddCommand(setTimestampFormatCmd)
|
||||
}
|
32
client/cmd/enableDisable.go
Normal file
32
client/cmd/enableDisable.go
Normal file
@ -0,0 +1,32 @@
|
||||
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",
|
||||
GroupID: GROUP_ID_CONFIG,
|
||||
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",
|
||||
GroupID: GROUP_ID_CONFIG,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ctx := hctx.MakeContext()
|
||||
lib.CheckFatalError(lib.Disable(ctx))
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(enableCmd)
|
||||
rootCmd.AddCommand(disableCmd)
|
||||
}
|
28
client/cmd/import.go
Normal file
28
client/cmd/import.go
Normal file
@ -0,0 +1,28 @@
|
||||
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",
|
||||
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)
|
||||
lib.CheckFatalError(err)
|
||||
if numImported > 0 {
|
||||
fmt.Printf("Imported %v history entries from your existing shell history\n", numImported)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(importCmd)
|
||||
}
|
88
client/cmd/install.go
Normal file
88
client/cmd/install.go
Normal file
@ -0,0 +1,88 @@
|
||||
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",
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
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)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
115
client/cmd/query.go
Normal file
115
client/cmd/query.go
Normal file
@ -0,0 +1,115 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"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 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"),
|
||||
DisableFlagParsing: true,
|
||||
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 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, " ")))
|
||||
},
|
||||
}
|
||||
|
||||
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"),
|
||||
DisableFlagParsing: true,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ctx := hctx.MakeContext()
|
||||
lib.CheckFatalError(lib.ProcessDeletionRequests(ctx))
|
||||
export(ctx, strings.Join(args, " "))
|
||||
},
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
32
client/cmd/redact.go
Normal file
32
client/cmd/redact.go
Normal file
@ -0,0 +1,32 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/ddworken/hishtory/client/hctx"
|
||||
"github.com/ddworken/hishtory/client/lib"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
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,
|
||||
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, os.Getenv("HISHTORY_REDACT_FORCE") != ""))
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(redactCmd)
|
||||
}
|
20
client/cmd/reupload.go
Normal file
20
client/cmd/reupload.go
Normal file
@ -0,0 +1,20 @@
|
||||
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",
|
||||
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()))
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(reuploadCmd)
|
||||
}
|
33
client/cmd/root.go
Normal file
33
client/cmd/root.go
Normal file
@ -0,0 +1,33 @@
|
||||
/*
|
||||
Copyright © 2022 NAME HERE <EMAIL ADDRESS>
|
||||
*/
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/ddworken/hishtory/client/lib"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// rootCmd represents the base command when called without any subcommands
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "hiSHtory",
|
||||
Short: "hiSHtory: Better shell history",
|
||||
}
|
||||
|
||||
// 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() {
|
||||
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
|
||||
}
|
140
client/cmd/saveHistoryEntry.go
Normal file
140
client/cmd/saveHistoryEntry.go
Normal file
@ -0,0 +1,140 @@
|
||||
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",
|
||||
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))
|
||||
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)
|
||||
}
|
44
client/cmd/status.go
Normal file
44
client/cmd/status.go
Normal file
@ -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: "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)
|
||||
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")
|
||||
}
|
32
client/cmd/uninstall.go
Normal file
32
client/cmd/uninstall.go
Normal file
@ -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)
|
||||
}
|
19
client/cmd/update.go
Normal file
19
client/cmd/update.go
Normal file
@ -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)
|
||||
}
|
@ -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,
|
||||
|
@ -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'
|
||||
|
@ -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
|
||||
|
@ -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' │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 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' │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
@ -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 │
|
||||
|
@ -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 │
|
||||
|
@ -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 │
|
||||
|
@ -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 │
|
||||
|
@ -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 │
|
||||
|
@ -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))
|
||||
|
||||
@ -660,7 +652,7 @@ func readFileToArray(path string) ([]string, error) {
|
||||
return lines, nil
|
||||
}
|
||||
|
||||
func Install() 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)
|
||||
@ -692,7 +684,7 @@ func Install() error {
|
||||
_, err = hctx.GetConfig()
|
||||
if err != nil {
|
||||
// No config, so set up a new installation
|
||||
return Setup(os.Args)
|
||||
return Setup(secretKey, offline)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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{})
|
||||
|
3
go.mod
3
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
|
||||
|
4
go.sum
4
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=
|
||||
|
476
hishtory.go
476
hishtory.go
@ -1,483 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"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/ddworken/hishtory/client/cmd"
|
||||
)
|
||||
|
||||
var GitCommit string = "Unknown"
|
||||
|
||||
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, GitCommit, 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":
|
||||
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)
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
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()))
|
||||
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)
|
||||
}
|
||||
case "enable":
|
||||
ctx := hctx.MakeContext()
|
||||
lib.CheckFatalError(lib.Enable(ctx))
|
||||
case "disable":
|
||||
ctx := hctx.MakeContext()
|
||||
lib.CheckFatalError(lib.Disable(ctx))
|
||||
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)
|
||||
case "update":
|
||||
err := lib.Update(hctx.MakeContext())
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
case "reupload":
|
||||
// Purposefully undocumented since this command is generally not necessary to run
|
||||
lib.CheckFatalError(lib.Reupload(hctx.MakeContext()))
|
||||
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 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)
|
||||
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, GitCommit)
|
||||
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 := 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
|
||||
|
@ -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\""
|
||||
|
@ -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)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user