diff options
-rw-r--r-- | bridge/core/bridge.go | 6 | ||||
-rw-r--r-- | bridge/core/token.go | 108 | ||||
-rw-r--r-- | commands/bridge_token.go | 26 | ||||
-rw-r--r-- | commands/bridge_token_add.go | 51 | ||||
-rw-r--r-- | commands/bridge_token_rm.go | 15 | ||||
-rw-r--r-- | doc/man/git-bug-bridge-token-add.1 | 8 | ||||
-rw-r--r-- | doc/man/git-bug-bridge-token.1 | 10 | ||||
-rw-r--r-- | doc/md/git-bug_bridge.md | 2 | ||||
-rw-r--r-- | doc/md/git-bug_bridge_token.md | 10 | ||||
-rw-r--r-- | doc/md/git-bug_bridge_token_add.md | 9 | ||||
-rw-r--r-- | doc/md/git-bug_bridge_token_rm.md | 2 | ||||
-rw-r--r-- | entity/id.go | 18 | ||||
-rw-r--r-- | misc/bash_completion/git-bug | 10 | ||||
-rw-r--r-- | misc/powershell_completion/git-bug | 14 | ||||
-rw-r--r-- | misc/zsh_completion/git-bug | 9 | ||||
-rw-r--r-- | repository/config.go | 2 | ||||
-rw-r--r-- | repository/config_git.go | 2 |
17 files changed, 172 insertions, 130 deletions
diff --git a/bridge/core/bridge.go b/bridge/core/bridge.go index ddf041f1..e419ed77 100644 --- a/bridge/core/bridge.go +++ b/bridge/core/bridge.go @@ -71,6 +71,12 @@ func Targets() []string { return result } +// TargetExist return true if the given target has a bridge implementation +func TargetExist(target string) bool { + _, ok := bridgeImpl[target] + return ok +} + // Instantiate a new Bridge for a repo, from the given target and name func NewBridge(repo *cache.RepoCache, target string, name string) (*Bridge, error) { implType, ok := bridgeImpl[target] diff --git a/bridge/core/token.go b/bridge/core/token.go index 9f06e73c..b9d68c0b 100644 --- a/bridge/core/token.go +++ b/bridge/core/token.go @@ -2,14 +2,13 @@ package core import ( "crypto/sha256" + "errors" "fmt" "regexp" - "strconv" + "sort" "strings" "time" - "github.com/araddon/dateparse" - "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/repository" ) @@ -21,9 +20,14 @@ const ( tokenCreateTimeKey = "createtime" ) +var ErrTokenNotExist = errors.New("token doesn't exist") + +func NewErrMultipleMatchToken(matching []entity.Id) *entity.ErrMultipleMatch { + return entity.NewErrMultipleMatch("token", matching) +} + // Token holds an API access token data type Token struct { - ID entity.Id Value string Target string CreateTime time.Time @@ -31,86 +35,96 @@ type Token struct { // NewToken instantiate a new token func NewToken(value, target string) *Token { - token := &Token{ + return &Token{ Value: value, Target: target, CreateTime: time.Now(), } - - token.ID = entity.Id(hashToken(token)) - return token } -func hashToken(token *Token) string { - sum := sha256.Sum256([]byte(token.Value)) - return fmt.Sprintf("%x", sum) +func (t *Token) ID() entity.Id { + sum := sha256.Sum256([]byte(t.Value)) + return entity.Id(fmt.Sprintf("%x", sum)) } // Validate ensure token important fields are valid func (t *Token) Validate() error { - if t.ID == "" { - return fmt.Errorf("missing id") - } if t.Value == "" { return fmt.Errorf("missing value") } if t.Target == "" { return fmt.Errorf("missing target") } - if t.CreateTime.Equal(time.Time{}) { + if t.CreateTime.IsZero() { return fmt.Errorf("missing creation time") } - if _, ok := bridgeImpl[t.Target]; !ok { + if !TargetExist(t.Target) { return fmt.Errorf("unknown target") } return nil } -// LoadToken loads a token from repo config -func LoadToken(repo repository.RepoCommon, id string) (*Token, error) { +// LoadToken loads a token from the repo config +func LoadToken(repo repository.RepoCommon, id entity.Id) (*Token, error) { keyPrefix := fmt.Sprintf("git-bug.token.%s.", id) // read token config pairs - configs, err := repo.GlobalConfig().ReadAll(keyPrefix) + rawconfigs, err := repo.GlobalConfig().ReadAll(keyPrefix) if err != nil { - return nil, err + // Not exactly right due to the limitation of ReadAll() + return nil, ErrTokenNotExist } // trim key prefix - for key, value := range configs { - delete(configs, key) + configs := make(map[string]string) + for key, value := range rawconfigs { newKey := strings.TrimPrefix(key, keyPrefix) configs[newKey] = value } - token := &Token{ID: entity.Id(id)} + token := &Token{} + + token.Value = configs[tokenValueKey] + token.Target = configs[tokenTargetKey] + if createTime, ok := configs[tokenCreateTimeKey]; ok { + if t, err := repository.ParseTimestamp(createTime); err == nil { + token.CreateTime = t + } + } + + return token, nil +} - var ok bool - token.Value, ok = configs[tokenValueKey] - if !ok { - return nil, fmt.Errorf("empty token value") +// LoadTokenPrefix load a token from the repo config with a prefix +func LoadTokenPrefix(repo repository.RepoCommon, prefix string) (*Token, error) { + tokens, err := ListTokens(repo) + if err != nil { + return nil, err } - token.Target, ok = configs[tokenTargetKey] - if !ok { - return nil, fmt.Errorf("empty token key") + // preallocate but empty + matching := make([]entity.Id, 0, 5) + + for _, id := range tokens { + if id.HasPrefix(prefix) { + matching = append(matching, id) + } } - createTime, ok := configs[tokenCreateTimeKey] - if !ok { - return nil, fmt.Errorf("missing createtime key") + if len(matching) > 1 { + return nil, NewErrMultipleMatchToken(matching) } - token.CreateTime, err = dateparse.ParseLocal(createTime) - if err != nil { - return nil, err + if len(matching) == 0 { + return nil, ErrTokenNotExist } - return token, nil + + return LoadToken(repo, matching[0]) } // ListTokens return a map representing the stored tokens in the repo config and global config // along with their type (global: true, local:false) -func ListTokens(repo repository.RepoCommon) ([]string, error) { +func ListTokens(repo repository.RepoCommon) ([]entity.Id, error) { configs, err := repo.GlobalConfig().ReadAll(tokenConfigKeyPrefix + ".") if err != nil { return nil, err @@ -133,36 +147,36 @@ func ListTokens(repo repository.RepoCommon) ([]string, error) { set[res[1]] = nil } - result := make([]string, len(set)) - i := 0 + result := make([]entity.Id, 0, len(set)) for key := range set { - result[i] = key - i++ + result = append(result, entity.Id(key)) } + sort.Sort(entity.Alphabetical(result)) + return result, nil } // StoreToken stores a token in the repo config func StoreToken(repo repository.RepoCommon, token *Token) error { - storeValueKey := fmt.Sprintf("git-bug.token.%s.%s", token.ID.String(), tokenValueKey) + storeValueKey := fmt.Sprintf("git-bug.token.%s.%s", token.ID().String(), tokenValueKey) err := repo.GlobalConfig().StoreString(storeValueKey, token.Value) if err != nil { return err } - storeTargetKey := fmt.Sprintf("git-bug.token.%s.%s", token.ID.String(), tokenTargetKey) + storeTargetKey := fmt.Sprintf("git-bug.token.%s.%s", token.ID().String(), tokenTargetKey) err = repo.GlobalConfig().StoreString(storeTargetKey, token.Target) if err != nil { return err } - createTimeKey := fmt.Sprintf("git-bug.token.%s.%s", token.ID.String(), tokenCreateTimeKey) - return repo.GlobalConfig().StoreString(createTimeKey, strconv.Itoa(int(token.CreateTime.Unix()))) + createTimeKey := fmt.Sprintf("git-bug.token.%s.%s", token.ID().String(), tokenCreateTimeKey) + return repo.GlobalConfig().StoreTimestamp(createTimeKey, token.CreateTime) } // RemoveToken removes a token from the repo config -func RemoveToken(repo repository.RepoCommon, id string) error { +func RemoveToken(repo repository.RepoCommon, id entity.Id) error { keyPrefix := fmt.Sprintf("git-bug.token.%s", id) return repo.GlobalConfig().RemoveAll(keyPrefix) } diff --git a/commands/bridge_token.go b/commands/bridge_token.go index 7467691e..b9e2d49f 100644 --- a/commands/bridge_token.go +++ b/commands/bridge_token.go @@ -6,27 +6,14 @@ import ( "github.com/spf13/cobra" - "github.com/MichaelMure/git-bug/bridge/core" - "github.com/MichaelMure/git-bug/cache" - "github.com/MichaelMure/git-bug/util/colors" - "github.com/MichaelMure/git-bug/util/interrupt" text "github.com/MichaelMure/go-term-text" -) -var ( - bridgeTokenLocal bool - bridgeTokenGlobal bool + "github.com/MichaelMure/git-bug/bridge/core" + "github.com/MichaelMure/git-bug/util/colors" ) func runTokenBridge(cmd *cobra.Command, args []string) error { - backend, err := cache.NewRepoCache(repo) - if err != nil { - return err - } - defer backend.Close() - interrupt.RegisterCleaner(backend.Close) - - tokens, err := core.ListTokens(backend) + tokens, err := core.ListTokens(repo) if err != nil { return err } @@ -43,13 +30,12 @@ func runTokenBridge(cmd *cobra.Command, args []string) error { } func printToken(token *core.Token) { - idFmt := text.LeftPadMaxLine(token.ID.Human(), 7, 0) valueFmt := text.LeftPadMaxLine(token.Value, 15, 0) targetFmt := text.LeftPadMaxLine(token.Target, 7, 0) createTimeFmt := text.LeftPadMaxLine(token.CreateTime.Format(time.RFC822), 20, 0) fmt.Printf("%s %s %s %s\n", - idFmt, + token.ID().Human(), colors.Magenta(targetFmt), valueFmt, createTimeFmt, @@ -58,7 +44,7 @@ func printToken(token *core.Token) { var bridgeTokenCmd = &cobra.Command{ Use: "token", - Short: "List all stored tokens.", + Short: "List all known tokens.", PreRunE: loadRepo, RunE: runTokenBridge, Args: cobra.NoArgs, @@ -66,7 +52,5 @@ var bridgeTokenCmd = &cobra.Command{ func init() { bridgeCmd.AddCommand(bridgeTokenCmd) - bridgeTokenCmd.Flags().BoolVarP(&bridgeTokenLocal, "local", "l", false, "") - bridgeTokenCmd.Flags().BoolVarP(&bridgeTokenGlobal, "global", "g", false, "") bridgeTokenCmd.Flags().SortFlags = false } diff --git a/commands/bridge_token_add.go b/commands/bridge_token_add.go index 58e9f472..d2c697fa 100644 --- a/commands/bridge_token_add.go +++ b/commands/bridge_token_add.go @@ -1,37 +1,74 @@ package commands import ( + "bufio" + "fmt" + "os" + "strings" + + "github.com/mattn/go-isatty" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/MichaelMure/git-bug/bridge" "github.com/MichaelMure/git-bug/bridge/core" ) var ( - bridgeTokenValue string bridgeTokenTarget string ) func runBridgeTokenAdd(cmd *cobra.Command, args []string) error { - token := core.NewToken(bridgeTokenValue, bridgeTokenTarget) + var value string + + if bridgeTokenTarget == "" { + return fmt.Errorf("token target is required") + } + + if !core.TargetExist(bridgeTokenTarget) { + return fmt.Errorf("unknown target") + } + + if len(args) == 1 { + value = args[0] + } else { + // Read from Stdin + if isatty.IsTerminal(os.Stdin.Fd()) { + fmt.Println("Enter the token:") + } + reader := bufio.NewReader(os.Stdin) + raw, err := reader.ReadString('\n') + if err != nil { + return fmt.Errorf("reading from stdin: %v", err) + } + value = strings.TrimSuffix(raw, "\n") + } + + token := core.NewToken(value, bridgeTokenTarget) if err := token.Validate(); err != nil { return errors.Wrap(err, "invalid token") } - return core.StoreToken(repo, token) + err := core.StoreToken(repo, token) + if err != nil { + return err + } + + fmt.Printf("token %s added\n", token.ID()) + return nil } var bridgeTokenAddCmd = &cobra.Command{ Use: "add", - Short: "Create and store a new token", + Short: "Store a new token", PreRunE: loadRepo, RunE: runBridgeTokenAdd, - Args: cobra.NoArgs, + Args: cobra.MaximumNArgs(1), } func init() { bridgeTokenCmd.AddCommand(bridgeTokenAddCmd) - bridgeTokenAddCmd.Flags().StringVarP(&bridgeTokenValue, "value", "v", "", "") - bridgeTokenAddCmd.Flags().StringVarP(&bridgeTokenTarget, "target", "t", "", "") + bridgeTokenAddCmd.Flags().StringVarP(&bridgeTokenTarget, "target", "t", "", + fmt.Sprintf("The target of the bridge. Valid values are [%s]", strings.Join(bridge.Targets(), ","))) bridgeTokenAddCmd.Flags().SortFlags = false } diff --git a/commands/bridge_token_rm.go b/commands/bridge_token_rm.go index 7296f95a..1f4ca4e8 100644 --- a/commands/bridge_token_rm.go +++ b/commands/bridge_token_rm.go @@ -1,13 +1,26 @@ package commands import ( + "fmt" + "github.com/spf13/cobra" "github.com/MichaelMure/git-bug/bridge/core" ) func runBridgeTokenRm(cmd *cobra.Command, args []string) error { - return core.RemoveToken(repo, args[0]) + token, err := core.LoadTokenPrefix(repo, args[0]) + if err != nil { + return err + } + + err = core.RemoveToken(repo, token.ID()) + if err != nil { + return err + } + + fmt.Printf("token %s removed\n", token.ID()) + return nil } var bridgeTokenRmCmd = &cobra.Command{ diff --git a/doc/man/git-bug-bridge-token-add.1 b/doc/man/git-bug-bridge-token-add.1 index 7ce07b1c..9b8a0db8 100644 --- a/doc/man/git-bug-bridge-token-add.1 +++ b/doc/man/git-bug-bridge-token-add.1 @@ -5,7 +5,7 @@ .SH NAME .PP -git\-bug\-bridge\-token\-add \- Create and store a new token +git\-bug\-bridge\-token\-add \- Store a new token .SH SYNOPSIS @@ -15,15 +15,13 @@ git\-bug\-bridge\-token\-add \- Create and store a new token .SH DESCRIPTION .PP -Create and store a new token +Store a new token .SH OPTIONS .PP -\fB\-v\fP, \fB\-\-value\fP="" - -.PP \fB\-t\fP, \fB\-\-target\fP="" + The target of the bridge. Valid values are [github,gitlab,launchpad\-preview] .PP \fB\-h\fP, \fB\-\-help\fP[=false] diff --git a/doc/man/git-bug-bridge-token.1 b/doc/man/git-bug-bridge-token.1 index d97c8e33..d377556f 100644 --- a/doc/man/git-bug-bridge-token.1 +++ b/doc/man/git-bug-bridge-token.1 @@ -5,7 +5,7 @@ .SH NAME .PP -git\-bug\-bridge\-token \- List all stored tokens. +git\-bug\-bridge\-token \- List all known tokens. .SH SYNOPSIS @@ -15,17 +15,11 @@ git\-bug\-bridge\-token \- List all stored tokens. .SH DESCRIPTION .PP -List all stored tokens. +List all known tokens. .SH OPTIONS .PP -\fB\-l\fP, \fB\-\-local\fP[=false] - -.PP -\fB\-g\fP, \fB\-\-global\fP[=false] - -.PP \fB\-h\fP, \fB\-\-help\fP[=false] help for token diff --git a/doc/md/git-bug_bridge.md b/doc/md/git-bug_bridge.md index a965074d..073d23ff 100644 --- a/doc/md/git-bug_bridge.md +++ b/doc/md/git-bug_bridge.md @@ -23,5 +23,5 @@ git-bug bridge [flags] * [git-bug bridge pull](git-bug_bridge_pull.md) - Pull updates. * [git-bug bridge push](git-bug_bridge_push.md) - Push updates. * [git-bug bridge rm](git-bug_bridge_rm.md) - Delete a configured bridge. -* [git-bug bridge token](git-bug_bridge_token.md) - List all stored tokens. +* [git-bug bridge token](git-bug_bridge_token.md) - List all known tokens. diff --git a/doc/md/git-bug_bridge_token.md b/doc/md/git-bug_bridge_token.md index 5e6baa26..e263e291 100644 --- a/doc/md/git-bug_bridge_token.md +++ b/doc/md/git-bug_bridge_token.md @@ -1,10 +1,10 @@ ## git-bug bridge token -List all stored tokens. +List all known tokens. ### Synopsis -List all stored tokens. +List all known tokens. ``` git-bug bridge token [flags] @@ -13,14 +13,12 @@ git-bug bridge token [flags] ### Options ``` - -l, --local - -g, --global - -h, --help help for token + -h, --help help for token ``` ### SEE ALSO * [git-bug bridge](git-bug_bridge.md) - Configure and use bridges to other bug trackers. -* [git-bug bridge token add](git-bug_bridge_token_add.md) - Create and store a new token +* [git-bug bridge token add](git-bug_bridge_token_add.md) - Store a new token * [git-bug bridge token rm](git-bug_bridge_token_rm.md) - Remove token by Id. diff --git a/doc/md/git-bug_bridge_token_add.md b/doc/md/git-bug_bridge_token_add.md index f372a06c..1c0e5bbc 100644 --- a/doc/md/git-bug_bridge_token_add.md +++ b/doc/md/git-bug_bridge_token_add.md @@ -1,10 +1,10 @@ ## git-bug bridge token add -Create and store a new token +Store a new token ### Synopsis -Create and store a new token +Store a new token ``` git-bug bridge token add [flags] @@ -13,12 +13,11 @@ git-bug bridge token add [flags] ### Options ``` - -v, --value string - -t, --target string + -t, --target string The target of the bridge. Valid values are [github,gitlab,launchpad-preview] -h, --help help for add ``` ### SEE ALSO -* [git-bug bridge token](git-bug_bridge_token.md) - List all stored tokens. +* [git-bug bridge token](git-bug_bridge_token.md) - List all known tokens. diff --git a/doc/md/git-bug_bridge_token_rm.md b/doc/md/git-bug_bridge_token_rm.md index 52417a9c..f8c5fcc2 100644 --- a/doc/md/git-bug_bridge_token_rm.md +++ b/doc/md/git-bug_bridge_token_rm.md @@ -18,5 +18,5 @@ git-bug bridge token rm [flags] ### SEE ALSO -* [git-bug bridge token](git-bug_bridge_token.md) - List all stored tokens. +* [git-bug bridge token](git-bug_bridge_token.md) - List all known tokens. diff --git a/entity/id.go b/entity/id.go index 7ff6b223..1b78aacd 100644 --- a/entity/id.go +++ b/entity/id.go @@ -65,3 +65,21 @@ func (i Id) Validate() error { } return nil } + +/* + * Sorting + */ + +type Alphabetical []Id + +func (a Alphabetical) Len() int { + return len(a) +} + +func (a Alphabetical) Less(i, j int) bool { + return a[i] < a[j] +} + +func (a Alphabetical) Swap(i, j int) { + a[i], a[j] = a[j], a[i] +} diff --git a/misc/bash_completion/git-bug b/misc/bash_completion/git-bug index e6a19626..28924d1c 100644 --- a/misc/bash_completion/git-bug +++ b/misc/bash_completion/git-bug @@ -414,10 +414,6 @@ _git-bug_bridge_token_add() flags_with_completion=() flags_completion=() - flags+=("--value=") - two_word_flags+=("--value") - two_word_flags+=("-v") - local_nonpersistent_flags+=("--value=") flags+=("--target=") two_word_flags+=("--target") two_word_flags+=("-t") @@ -464,12 +460,6 @@ _git-bug_bridge_token() flags_with_completion=() flags_completion=() - flags+=("--local") - flags+=("-l") - local_nonpersistent_flags+=("--local") - flags+=("--global") - flags+=("-g") - local_nonpersistent_flags+=("--global") must_have_one_flag=() must_have_one_noun=() diff --git a/misc/powershell_completion/git-bug b/misc/powershell_completion/git-bug index 99265bbb..094fa254 100644 --- a/misc/powershell_completion/git-bug +++ b/misc/powershell_completion/git-bug @@ -52,7 +52,7 @@ Register-ArgumentCompleter -Native -CommandName 'git-bug' -ScriptBlock { [CompletionResult]::new('pull', 'pull', [CompletionResultType]::ParameterValue, 'Pull updates.') [CompletionResult]::new('push', 'push', [CompletionResultType]::ParameterValue, 'Push updates.') [CompletionResult]::new('rm', 'rm', [CompletionResultType]::ParameterValue, 'Delete a configured bridge.') - [CompletionResult]::new('token', 'token', [CompletionResultType]::ParameterValue, 'List all stored tokens.') + [CompletionResult]::new('token', 'token', [CompletionResultType]::ParameterValue, 'List all known tokens.') break } 'git-bug;bridge;configure' { @@ -85,19 +85,13 @@ Register-ArgumentCompleter -Native -CommandName 'git-bug' -ScriptBlock { break } 'git-bug;bridge;token' { - [CompletionResult]::new('-l', 'l', [CompletionResultType]::ParameterName, '') - [CompletionResult]::new('--local', 'local', [CompletionResultType]::ParameterName, '') - [CompletionResult]::new('-g', 'g', [CompletionResultType]::ParameterName, '') - [CompletionResult]::new('--global', 'global', [CompletionResultType]::ParameterName, '') - [CompletionResult]::new('add', 'add', [CompletionResultType]::ParameterValue, 'Create and store a new token') + [CompletionResult]::new('add', 'add', [CompletionResultType]::ParameterValue, 'Store a new token') [CompletionResult]::new('rm', 'rm', [CompletionResultType]::ParameterValue, 'Remove token by Id.') break } 'git-bug;bridge;token;add' { - [CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, '') - [CompletionResult]::new('--value', 'value', [CompletionResultType]::ParameterName, '') - [CompletionResult]::new('-t', 't', [CompletionResultType]::ParameterName, '') - [CompletionResult]::new('--target', 'target', [CompletionResultType]::ParameterName, '') + [CompletionResult]::new('-t', 't', [CompletionResultType]::ParameterName, 'The target of the bridge. Valid values are [github,gitlab,launchpad-preview]') + [CompletionResult]::new('--target', 'target', [CompletionResultType]::ParameterName, 'The target of the bridge. Valid values are [github,gitlab,launchpad-preview]') break } 'git-bug;bridge;token;rm' { diff --git a/misc/zsh_completion/git-bug b/misc/zsh_completion/git-bug index e8c48cb6..7f5f6231 100644 --- a/misc/zsh_completion/git-bug +++ b/misc/zsh_completion/git-bug @@ -118,7 +118,7 @@ function _git-bug_bridge { "pull:Pull updates." "push:Push updates." "rm:Delete a configured bridge." - "token:List all stored tokens." + "token:List all known tokens." ) _describe "command" commands ;; @@ -173,15 +173,13 @@ function _git-bug_bridge_token { local -a commands _arguments -C \ - '(-l --local)'{-l,--local}'[]' \ - '(-g --global)'{-g,--global}'[]' \ "1: :->cmnds" \ "*::arg:->args" case $state in cmnds) commands=( - "add:Create and store a new token" + "add:Store a new token" "rm:Remove token by Id." ) _describe "command" commands @@ -200,8 +198,7 @@ function _git-bug_bridge_token { function _git-bug_bridge_token_add { _arguments \ - '(-v --value)'{-v,--value}'[]:' \ - '(-t --target)'{-t,--target}'[]:' + '(-t --target)'{-t,--target}'[The target of the bridge. Valid values are [github,gitlab,launchpad-preview]]:' } function _git-bug_bridge_token_rm { diff --git a/repository/config.go b/repository/config.go index d72e7b4e..4fa5c69b 100644 --- a/repository/config.go +++ b/repository/config.go @@ -38,7 +38,7 @@ type Config interface { RemoveAll(keyPrefix string) error } -func parseTimestamp(s string) (time.Time, error) { +func ParseTimestamp(s string) (time.Time, error) { timestamp, err := strconv.Atoi(s) if err != nil { return time.Time{}, err diff --git a/repository/config_git.go b/repository/config_git.go index 63ca2457..cff82afb 100644 --- a/repository/config_git.go +++ b/repository/config_git.go @@ -116,7 +116,7 @@ func (gc *gitConfig) ReadTimestamp(key string) (time.Time, error) { if err != nil { return time.Time{}, err } - return parseTimestamp(value) + return ParseTimestamp(value) } func (gc *gitConfig) rmSection(keyPrefix string) error { |