aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gopkg.lock49
-rw-r--r--Gopkg.toml4
-rw-r--r--Makefile16
-rw-r--r--README.md2
-rw-r--r--bridge/bridges.go12
-rw-r--r--bridge/core/bridge.go6
-rw-r--r--bridge/core/export.go11
-rw-r--r--bridge/core/import.go14
-rw-r--r--bridge/core/token.go182
-rw-r--r--bridge/github/github.go4
-rw-r--r--bridge/github/import_query.go4
-rw-r--r--bridge/github/iterator.go101
-rw-r--r--bridge/gitlab/export_test.go5
-rw-r--r--bridge/gitlab/gitlab.go4
-rw-r--r--bridge/launchpad/launchpad.go4
-rw-r--r--bug/bug.go15
-rw-r--r--bug/bug_actions_test.go8
-rw-r--r--bug/bug_test.go6
-rw-r--r--bug/clocks.go4
-rw-r--r--bug/interface.go4
-rw-r--r--commands/bridge_auth.go53
-rw-r--r--commands/bridge_auth_add.go74
-rw-r--r--commands/bridge_auth_rm.go36
-rw-r--r--commands/bridge_auth_show.go37
-rw-r--r--doc/man/git-bug-bridge-auth-add-token.133
-rw-r--r--doc/man/git-bug-bridge-auth-rm.129
-rw-r--r--doc/man/git-bug-bridge-auth-show.129
-rw-r--r--doc/man/git-bug-bridge-auth.129
-rw-r--r--doc/man/git-bug-bridge.12
-rw-r--r--doc/md/git-bug_bridge.md1
-rw-r--r--doc/md/git-bug_bridge_auth.md25
-rw-r--r--doc/md/git-bug_bridge_auth_add-token.md23
-rw-r--r--doc/md/git-bug_bridge_auth_rm.md22
-rw-r--r--doc/md/git-bug_bridge_auth_show.md22
-rw-r--r--entity/id.go18
-rw-r--r--misc/bash_completion/git-bug88
-rw-r--r--misc/powershell_completion/git-bug18
-rw-r--r--misc/zsh_completion/git-bug49
-rw-r--r--repository/config.go2
-rw-r--r--repository/config_git.go2
-rw-r--r--repository/git.go20
-rw-r--r--repository/mock_repo.go4
-rw-r--r--repository/repo.go14
-rw-r--r--termui/bug_table.go60
-rw-r--r--termui/input_popup.go6
-rw-r--r--termui/label_select.go16
-rw-r--r--termui/msg_popup.go6
-rw-r--r--termui/show_bug.go24
-rw-r--r--termui/termui.go10
-rw-r--r--vendor/github.com/awesome-gocui/gocui/.gitignore (renamed from vendor/github.com/MichaelMure/gocui/.gitignore)0
-rw-r--r--vendor/github.com/awesome-gocui/gocui/AUTHORS (renamed from vendor/github.com/MichaelMure/gocui/AUTHORS)0
-rw-r--r--vendor/github.com/awesome-gocui/gocui/CODE_OF_CONDUCT.md76
-rw-r--r--vendor/github.com/awesome-gocui/gocui/CONTRIBUTING.md33
-rw-r--r--vendor/github.com/awesome-gocui/gocui/LICENSE (renamed from vendor/github.com/MichaelMure/gocui/LICENSE)0
-rw-r--r--vendor/github.com/awesome-gocui/gocui/README.md (renamed from vendor/github.com/MichaelMure/gocui/README.md)49
-rw-r--r--vendor/github.com/awesome-gocui/gocui/attribute.go (renamed from vendor/github.com/MichaelMure/gocui/attribute.go)2
-rw-r--r--vendor/github.com/awesome-gocui/gocui/doc.go (renamed from vendor/github.com/MichaelMure/gocui/doc.go)6
-rw-r--r--vendor/github.com/awesome-gocui/gocui/edit.go (renamed from vendor/github.com/MichaelMure/gocui/edit.go)158
-rw-r--r--vendor/github.com/awesome-gocui/gocui/escape.go (renamed from vendor/github.com/MichaelMure/gocui/escape.go)2
-rw-r--r--vendor/github.com/awesome-gocui/gocui/go.mod9
-rw-r--r--vendor/github.com/awesome-gocui/gocui/go.sum6
-rw-r--r--vendor/github.com/awesome-gocui/gocui/gui.go (renamed from vendor/github.com/MichaelMure/gocui/gui.go)266
-rw-r--r--vendor/github.com/awesome-gocui/gocui/gui_others.go60
-rw-r--r--vendor/github.com/awesome-gocui/gocui/gui_windows.go53
-rw-r--r--vendor/github.com/awesome-gocui/gocui/keybinding.go (renamed from vendor/github.com/MichaelMure/gocui/keybinding.go)168
-rw-r--r--vendor/github.com/awesome-gocui/gocui/loader.go46
-rw-r--r--vendor/github.com/awesome-gocui/gocui/view.go (renamed from vendor/github.com/MichaelMure/gocui/view.go)474
-rw-r--r--vendor/github.com/awesome-gocui/termbox-go/AUTHORS (renamed from vendor/github.com/nsf/termbox-go/AUTHORS)0
-rw-r--r--vendor/github.com/awesome-gocui/termbox-go/LICENSE (renamed from vendor/github.com/nsf/termbox-go/LICENSE)0
-rw-r--r--vendor/github.com/awesome-gocui/termbox-go/README.md (renamed from vendor/github.com/nsf/termbox-go/README.md)6
-rw-r--r--vendor/github.com/awesome-gocui/termbox-go/api.go (renamed from vendor/github.com/nsf/termbox-go/api.go)31
-rw-r--r--vendor/github.com/awesome-gocui/termbox-go/api_common.go (renamed from vendor/github.com/nsf/termbox-go/api_common.go)0
-rw-r--r--vendor/github.com/awesome-gocui/termbox-go/api_windows.go (renamed from vendor/github.com/nsf/termbox-go/api_windows.go)12
-rwxr-xr-xvendor/github.com/awesome-gocui/termbox-go/collect_terminfo.py (renamed from vendor/github.com/nsf/termbox-go/collect_terminfo.py)0
-rw-r--r--vendor/github.com/awesome-gocui/termbox-go/escwait.go (renamed from vendor/github.com/nsf/termbox-go/escwait.go)0
-rw-r--r--vendor/github.com/awesome-gocui/termbox-go/escwait_darwin.go (renamed from vendor/github.com/nsf/termbox-go/escwait_darwin.go)0
-rw-r--r--vendor/github.com/awesome-gocui/termbox-go/syscalls.go (renamed from vendor/github.com/nsf/termbox-go/syscalls.go)0
-rw-r--r--vendor/github.com/awesome-gocui/termbox-go/syscalls_darwin.go (renamed from vendor/github.com/nsf/termbox-go/syscalls_darwin.go)0
-rw-r--r--vendor/github.com/awesome-gocui/termbox-go/syscalls_darwin_amd64.go (renamed from vendor/github.com/nsf/termbox-go/syscalls_darwin_amd64.go)0
-rw-r--r--vendor/github.com/awesome-gocui/termbox-go/syscalls_dragonfly.go (renamed from vendor/github.com/nsf/termbox-go/syscalls_dragonfly.go)0
-rw-r--r--vendor/github.com/awesome-gocui/termbox-go/syscalls_freebsd.go (renamed from vendor/github.com/nsf/termbox-go/syscalls_freebsd.go)0
-rw-r--r--vendor/github.com/awesome-gocui/termbox-go/syscalls_linux.go (renamed from vendor/github.com/nsf/termbox-go/syscalls_linux.go)0
-rw-r--r--vendor/github.com/awesome-gocui/termbox-go/syscalls_netbsd.go (renamed from vendor/github.com/nsf/termbox-go/syscalls_netbsd.go)0
-rw-r--r--vendor/github.com/awesome-gocui/termbox-go/syscalls_openbsd.go (renamed from vendor/github.com/nsf/termbox-go/syscalls_openbsd.go)0
-rw-r--r--vendor/github.com/awesome-gocui/termbox-go/syscalls_windows.go (renamed from vendor/github.com/nsf/termbox-go/syscalls_windows.go)0
-rw-r--r--vendor/github.com/awesome-gocui/termbox-go/termbox.go (renamed from vendor/github.com/nsf/termbox-go/termbox.go)0
-rw-r--r--vendor/github.com/awesome-gocui/termbox-go/termbox_common.go (renamed from vendor/github.com/nsf/termbox-go/termbox_common.go)0
-rw-r--r--vendor/github.com/awesome-gocui/termbox-go/termbox_windows.go (renamed from vendor/github.com/nsf/termbox-go/termbox_windows.go)45
-rw-r--r--vendor/github.com/awesome-gocui/termbox-go/terminfo.go (renamed from vendor/github.com/nsf/termbox-go/terminfo.go)0
-rw-r--r--vendor/github.com/awesome-gocui/termbox-go/terminfo_builtin.go (renamed from vendor/github.com/nsf/termbox-go/terminfo_builtin.go)0
-rw-r--r--vendor/github.com/go-errors/errors/.travis.yml5
-rw-r--r--vendor/github.com/go-errors/errors/LICENSE.MIT7
-rw-r--r--vendor/github.com/go-errors/errors/README.md66
-rw-r--r--vendor/github.com/go-errors/errors/cover.out89
-rw-r--r--vendor/github.com/go-errors/errors/error.go217
-rw-r--r--vendor/github.com/go-errors/errors/parse_panic.go127
-rw-r--r--vendor/github.com/go-errors/errors/stackframe.go102
-rw-r--r--vendor/github.com/xanzy/go-gitlab/README.md4
-rw-r--r--vendor/github.com/xanzy/go-gitlab/branches.go5
-rw-r--r--vendor/github.com/xanzy/go-gitlab/environments.go2
-rw-r--r--vendor/github.com/xanzy/go-gitlab/event_types.go61
-rw-r--r--vendor/github.com/xanzy/go-gitlab/gitlab.go1
-rw-r--r--vendor/github.com/xanzy/go-gitlab/groups.go43
-rw-r--r--vendor/github.com/xanzy/go-gitlab/merge_request_approvals.go4
-rw-r--r--vendor/github.com/xanzy/go-gitlab/pipeline_schedules.go8
-rw-r--r--vendor/github.com/xanzy/go-gitlab/pipelines.go17
-rw-r--r--vendor/github.com/xanzy/go-gitlab/repository_files.go2
-rw-r--r--vendor/github.com/xanzy/go-gitlab/services.go92
-rw-r--r--vendor/github.com/xanzy/go-gitlab/settings.go512
109 files changed, 3453 insertions, 620 deletions
diff --git a/Gopkg.lock b/Gopkg.lock
index c731a1ec..e4ecfcb9 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -33,14 +33,6 @@
version = "v0.2.1"
[[projects]]
- branch = "master"
- digest = "1:38a84d9b4cf50b3e8eb2b54f218413ac163076e3a7763afe5fa15a4eb15fbda6"
- name = "github.com/MichaelMure/gocui"
- packages = ["."]
- pruneopts = "UT"
- revision = "d753c235dd8582d55e99bbb7f7fe453fb3fd3a19"
-
-[[projects]]
digest = "1:897d91c431ce469d35a5e6030e60e617dccd9a0e95bdffa6a80594f5c5800d29"
name = "github.com/agnivade/levenshtein"
packages = ["."]
@@ -57,6 +49,22 @@
revision = "0fb0a474d195a3449cf412ae0176faa193f0ef0b"
[[projects]]
+ branch = "master"
+ digest = "1:96d56c73765f6ba0dbccf953502342da2c4f0d4280a5aef4e4e3eea9e6674ba1"
+ name = "github.com/awesome-gocui/gocui"
+ packages = ["."]
+ pruneopts = "UT"
+ revision = "c9d3c2bec453a8d648228640c79cc769bbc78df8"
+
+[[projects]]
+ branch = "master"
+ digest = "1:237f3e0692e330851b8b5a53117cfcea1beba09aec7668d34db91b809879c296"
+ name = "github.com/awesome-gocui/termbox-go"
+ packages = ["."]
+ pruneopts = "UT"
+ revision = "c0aef3d18bcc218a92e318293310ca0191f29654"
+
+[[projects]]
digest = "1:b6d886569181ec96ca83d529f4d6ba0cbf92ace7bb6f633f90c5f34d9bba7aab"
name = "github.com/blang/semver"
packages = ["."]
@@ -113,6 +121,14 @@
version = "v1.7.0"
[[projects]]
+ digest = "1:aacef5f5e45685f2aeda5534d0a750dee6859de7e9088cdd06192787bb01ae6d"
+ name = "github.com/go-errors/errors"
+ packages = ["."]
+ pruneopts = "UT"
+ revision = "a6af135bd4e28680facf08a3d206b454abc877a4"
+ version = "v1.0.1"
+
+[[projects]]
digest = "1:97df918963298c287643883209a2c3f642e6593379f97ab400c2a2e219ab647d"
name = "github.com/golang/protobuf"
packages = ["proto"]
@@ -204,14 +220,6 @@
revision = "fa473d140ef3c6adf42d6b391fe76707f1f243c8"
[[projects]]
- branch = "master"
- digest = "1:c9b6e36dbd23f8403a04493376916ca5dad8c01b2da5ae0a05e6a468eb0b6f24"
- name = "github.com/nsf/termbox-go"
- packages = ["."]
- pruneopts = "UT"
- revision = "5c94acc5e6eb520f1bcd183974e01171cc4c23b3"
-
-[[projects]]
digest = "1:7413525ee648f20b4181be7fe8103d0cb98be9e141926a03ee082dc207061e4e"
name = "github.com/phayes/freeport"
packages = ["."]
@@ -361,12 +369,12 @@
version = "v1.0.0"
[[projects]]
- digest = "1:766db8705204fd893db77ff5fde228362fbceac616b87ccb9976518095aac8ce"
+ digest = "1:a58711c8b908d88e28007ddebf529f40f4a9d34efe7ba729244d737f46a756ca"
name = "github.com/xanzy/go-gitlab"
packages = ["."]
pruneopts = "UT"
- revision = "87a6b9db49fa4bd6efeaeec450b0c5661f94fcb5"
- version = "v0.21.0"
+ revision = "457d4d018eaa1fad8e6c63502cebcd11ba60164e"
+ version = "v0.22.0"
[[projects]]
branch = "master"
@@ -481,12 +489,13 @@
"github.com/99designs/gqlgen/graphql/introspection",
"github.com/99designs/gqlgen/handler",
"github.com/MichaelMure/go-term-text",
- "github.com/MichaelMure/gocui",
"github.com/araddon/dateparse",
+ "github.com/awesome-gocui/gocui",
"github.com/blang/semver",
"github.com/cheekybits/genny/generic",
"github.com/dustin/go-humanize",
"github.com/fatih/color",
+ "github.com/go-errors/errors",
"github.com/gorilla/mux",
"github.com/icrowley/fake",
"github.com/phayes/freeport",
diff --git a/Gopkg.toml b/Gopkg.toml
index 4a88cbff..58b9d663 100644
--- a/Gopkg.toml
+++ b/Gopkg.toml
@@ -61,7 +61,7 @@
version = "0.9.2"
[[constraint]]
- name = "github.com/MichaelMure/gocui"
+ name = "github.com/awesome-gocui/gocui"
branch = "master"
[[override]]
@@ -74,7 +74,7 @@
[[constraint]]
name = "github.com/xanzy/go-gitlab"
- version = "0.21.0"
+ version = "0.22.0"
[[constraint]]
branch = "master"
diff --git a/Makefile b/Makefile
index f4e76f4c..9d0e24ec 100644
--- a/Makefile
+++ b/Makefile
@@ -3,6 +3,12 @@ all: build
GIT_COMMIT:=$(shell git rev-list -1 HEAD)
GIT_LAST_TAG:=$(shell git describe --abbrev=0 --tags)
GIT_EXACT_TAG:=$(shell git name-rev --name-only --tags HEAD)
+UNAME_S := $(shell uname -s)
+XARGS:=xargs -r
+ifeq ($(UNAME_S),Darwin)
+ XARGS:=xargs
+endif
+
COMMANDS_PATH:=github.com/MichaelMure/git-bug/commands
LDFLAGS:=-X ${COMMANDS_PATH}.GitCommit=${GIT_COMMIT} \
-X ${COMMANDS_PATH}.GitLastTag=${GIT_LAST_TAG} \
@@ -33,16 +39,16 @@ debug-webui:
go build -ldflags "$(LDFLAGS)" -tags=debugwebui
clean-local-bugs:
- git for-each-ref refs/bugs/ | cut -f 2 | xargs -r -n 1 git update-ref -d
- git for-each-ref refs/remotes/origin/bugs/ | cut -f 2 | xargs -r -n 1 git update-ref -d
+ git for-each-ref refs/bugs/ | cut -f 2 | $(XARGS) -n 1 git update-ref -d
+ git for-each-ref refs/remotes/origin/bugs/ | cut -f 2 | $(XARGS) -n 1 git update-ref -d
rm -f .git/git-bug/bug-cache
clean-remote-bugs:
- git ls-remote origin "refs/bugs/*" | cut -f 2 | xargs -r git push origin -d
+ git ls-remote origin "refs/bugs/*" | cut -f 2 | $(XARGS) git push origin -d
clean-local-identities:
- git for-each-ref refs/identities/ | cut -f 2 | xargs -r -n 1 git update-ref -d
- git for-each-ref refs/remotes/origin/identities/ | cut -f 2 | xargs -r -n 1 git update-ref -d
+ git for-each-ref refs/identities/ | cut -f 2 | $(XARGS) -n 1 git update-ref -d
+ git for-each-ref refs/remotes/origin/identities/ | cut -f 2 | $(XARGS) -n 1 git update-ref -d
rm -f .git/git-bug/identity-cache
.PHONY: build install test pack-webui debug-webui clean-local-bugs clean-remote-bugs
diff --git a/README.md b/README.md
index 42501ebc..6f105bbf 100644
--- a/README.md
+++ b/README.md
@@ -125,7 +125,7 @@ The web UI interact with the backend through a GraphQL API. The schema is availa
| | Github | Gitlab | Launchpad |
| --- | --- | --- | --- |
| **incremental**<br/>(can import more than once) | :heavy_check_mark: | :heavy_check_mark: | :x: |
-| **with resume**<br/>(download only new data) | :x: | :x: | :x: |
+| **with resume**<br/>(download only new data) | :heavy_check_mark: | :heavy_check_mark: | :x: |
| **identities** | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| identities update | :x: | :x: | :x: |
| **bug** | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
diff --git a/bridge/bridges.go b/bridge/bridges.go
index dcb35af1..9bbf3941 100644
--- a/bridge/bridges.go
+++ b/bridge/bridges.go
@@ -3,13 +3,19 @@ package bridge
import (
"github.com/MichaelMure/git-bug/bridge/core"
- _ "github.com/MichaelMure/git-bug/bridge/github"
- _ "github.com/MichaelMure/git-bug/bridge/gitlab"
- _ "github.com/MichaelMure/git-bug/bridge/launchpad"
+ "github.com/MichaelMure/git-bug/bridge/github"
+ "github.com/MichaelMure/git-bug/bridge/gitlab"
+ "github.com/MichaelMure/git-bug/bridge/launchpad"
"github.com/MichaelMure/git-bug/cache"
"github.com/MichaelMure/git-bug/repository"
)
+func init() {
+ core.Register(&github.Github{})
+ core.Register(&gitlab.Gitlab{})
+ core.Register(&launchpad.Launchpad{})
+}
+
// Targets return all known bridge implementation target
func Targets() []string {
return core.Targets()
diff --git a/bridge/core/bridge.go b/bridge/core/bridge.go
index e90476eb..a3133b9c 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/export.go b/bridge/core/export.go
index 558b3d78..0f45404c 100644
--- a/bridge/core/export.go
+++ b/bridge/core/export.go
@@ -10,13 +10,24 @@ type ExportEvent int
const (
_ ExportEvent = iota
+
+ // Bug has been exported on the remote tracker
ExportEventBug
+ // Comment has been exported on the remote tracker
ExportEventComment
+ // Comment has been edited on the remote tracker
ExportEventCommentEdition
+ // Bug's status has been changed on on the remote tracker
ExportEventStatusChange
+ // Bug's title has been changed on the remote tracker
ExportEventTitleEdition
+ // Bug's labels have been changed on the remote tracker
ExportEventLabelChange
+
+ // Nothing changed on the bug
ExportEventNothing
+
+ // Error happened during export
ExportEventError
)
diff --git a/bridge/core/import.go b/bridge/core/import.go
index cff30f61..e4771d2c 100644
--- a/bridge/core/import.go
+++ b/bridge/core/import.go
@@ -10,14 +10,26 @@ type ImportEvent int
const (
_ ImportEvent = iota
+
+ // Bug has been created
ImportEventBug
+ // Comment has been created
ImportEventComment
+ // Comment has been edited
ImportEventCommentEdition
+ // Bug's status has changed
ImportEventStatusChange
+ // Bug's title has changed
ImportEventTitleEdition
+ // Bug's labels changed
ImportEventLabelChange
- ImportEventIdentity
+ // Nothing happened on a Bug
ImportEventNothing
+
+ // Identity has been created
+ ImportEventIdentity
+
+ // Error happened during import
ImportEventError
)
diff --git a/bridge/core/token.go b/bridge/core/token.go
new file mode 100644
index 00000000..2ceabca2
--- /dev/null
+++ b/bridge/core/token.go
@@ -0,0 +1,182 @@
+package core
+
+import (
+ "crypto/sha256"
+ "errors"
+ "fmt"
+ "regexp"
+ "sort"
+ "strings"
+ "time"
+
+ "github.com/MichaelMure/git-bug/entity"
+ "github.com/MichaelMure/git-bug/repository"
+)
+
+const (
+ tokenConfigKeyPrefix = "git-bug.token"
+ tokenValueKey = "value"
+ tokenTargetKey = "target"
+ 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 {
+ Value string
+ Target string
+ CreateTime time.Time
+}
+
+// NewToken instantiate a new token
+func NewToken(value, target string) *Token {
+ return &Token{
+ Value: value,
+ Target: target,
+ CreateTime: time.Now(),
+ }
+}
+
+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.Value == "" {
+ return fmt.Errorf("missing value")
+ }
+ if t.Target == "" {
+ return fmt.Errorf("missing target")
+ }
+ if t.CreateTime.IsZero() || t.CreateTime.Equal(time.Time{}) {
+ return fmt.Errorf("missing creation time")
+ }
+ if !TargetExist(t.Target) {
+ return fmt.Errorf("unknown target")
+ }
+ return nil
+}
+
+// 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
+ rawconfigs, err := repo.GlobalConfig().ReadAll(keyPrefix)
+ if err != nil {
+ // Not exactly right due to the limitation of ReadAll()
+ return nil, ErrTokenNotExist
+ }
+
+ // trim key prefix
+ configs := make(map[string]string)
+ for key, value := range rawconfigs {
+ newKey := strings.TrimPrefix(key, keyPrefix)
+ configs[newKey] = value
+ }
+
+ 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
+}
+
+// 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
+ }
+
+ // preallocate but empty
+ matching := make([]entity.Id, 0, 5)
+
+ for _, id := range tokens {
+ if id.HasPrefix(prefix) {
+ matching = append(matching, id)
+ }
+ }
+
+ if len(matching) > 1 {
+ return nil, NewErrMultipleMatchToken(matching)
+ }
+
+ if len(matching) == 0 {
+ return nil, ErrTokenNotExist
+ }
+
+ 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) ([]entity.Id, error) {
+ configs, err := repo.GlobalConfig().ReadAll(tokenConfigKeyPrefix + ".")
+ if err != nil {
+ return nil, err
+ }
+
+ re, err := regexp.Compile(tokenConfigKeyPrefix + `.([^.]+)`)
+ if err != nil {
+ panic(err)
+ }
+
+ set := make(map[string]interface{})
+
+ for key := range configs {
+ res := re.FindStringSubmatch(key)
+
+ if res == nil {
+ continue
+ }
+
+ set[res[1]] = nil
+ }
+
+ result := make([]entity.Id, 0, len(set))
+ for key := range set {
+ 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)
+ err := repo.GlobalConfig().StoreString(storeValueKey, token.Value)
+ if err != nil {
+ return err
+ }
+
+ 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().StoreTimestamp(createTimeKey, token.CreateTime)
+}
+
+// RemoveToken removes a token from the repo config
+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/bridge/github/github.go b/bridge/github/github.go
index 176bdd84..e4fb03dd 100644
--- a/bridge/github/github.go
+++ b/bridge/github/github.go
@@ -10,10 +10,6 @@ import (
"github.com/MichaelMure/git-bug/bridge/core"
)
-func init() {
- core.Register(&Github{})
-}
-
type Github struct{}
func (*Github) Target() string {
diff --git a/bridge/github/import_query.go b/bridge/github/import_query.go
index 62d3227b..f5cad299 100644
--- a/bridge/github/import_query.go
+++ b/bridge/github/import_query.go
@@ -102,13 +102,13 @@ type issueTimeline struct {
Body githubv4.String
Url githubv4.URI
- Timeline struct {
+ TimelineItems struct {
Edges []struct {
Cursor githubv4.String
Node timelineItem
}
PageInfo pageInfo
- } `graphql:"timeline(first: $timelineFirst, after: $timelineAfter)"`
+ } `graphql:"timelineItems(first: $timelineFirst, after: $timelineAfter)"`
UserContentEdits struct {
Nodes []userContentEdit
diff --git a/bridge/github/iterator.go b/bridge/github/iterator.go
index a97ed036..d1d7900f 100644
--- a/bridge/github/iterator.go
+++ b/bridge/github/iterator.go
@@ -138,9 +138,9 @@ func (i *iterator) initCommentEditQueryVariables() {
func (i *iterator) reverseTimelineEditNodes() {
node := i.timeline.query.Repository.Issues.Nodes[0]
reverseEdits(node.UserContentEdits.Nodes)
- for index, ce := range node.Timeline.Edges {
- if ce.Node.Typename == "IssueComment" && len(node.Timeline.Edges) != 0 {
- reverseEdits(node.Timeline.Edges[index].Node.IssueComment.UserContentEdits.Nodes)
+ for index, ce := range node.TimelineItems.Edges {
+ if ce.Node.Typename == "IssueComment" && len(node.TimelineItems.Edges) != 0 {
+ reverseEdits(node.TimelineItems.Edges[index].Node.IssueComment.UserContentEdits.Nodes)
}
}
}
@@ -159,7 +159,8 @@ func (i *iterator) queryIssue() bool {
return false
}
- if len(i.timeline.query.Repository.Issues.Nodes) == 0 {
+ issues := i.timeline.query.Repository.Issues.Nodes
+ if len(issues) == 0 {
return false
}
@@ -178,29 +179,35 @@ func (i *iterator) NextIssue() bool {
if i.timeline.variables["issueAfter"] == (*githubv4.String)(nil) {
nextIssue := i.queryIssue()
// prevent from infinite loop by setting a non nil cursor
- i.timeline.variables["issueAfter"] = i.timeline.query.Repository.Issues.PageInfo.EndCursor
+ issues := i.timeline.query.Repository.Issues
+ i.timeline.variables["issueAfter"] = issues.PageInfo.EndCursor
return nextIssue
}
- if !i.timeline.query.Repository.Issues.PageInfo.HasNextPage {
+ issues := i.timeline.query.Repository.Issues
+ if !issues.PageInfo.HasNextPage {
return false
}
// if we have more issues, query them
i.timeline.variables["timelineAfter"] = (*githubv4.String)(nil)
- i.timeline.variables["issueAfter"] = i.timeline.query.Repository.Issues.PageInfo.EndCursor
i.timeline.index = -1
+ timelineEndCursor := issues.Nodes[0].TimelineItems.PageInfo.EndCursor
// store cursor for future use
- i.timeline.lastEndCursor = i.timeline.query.Repository.Issues.Nodes[0].Timeline.PageInfo.EndCursor
+ i.timeline.lastEndCursor = timelineEndCursor
// query issue block
- return i.queryIssue()
+ nextIssue := i.queryIssue()
+ i.timeline.variables["issueAfter"] = issues.PageInfo.EndCursor
+
+ return nextIssue
}
// IssueValue return the actual issue value
func (i *iterator) IssueValue() issueTimeline {
- return i.timeline.query.Repository.Issues.Nodes[0]
+ issues := i.timeline.query.Repository.Issues
+ return issues.Nodes[0]
}
// NextTimelineItem return true if there is a next timeline item and increments the index by one.
@@ -214,23 +221,25 @@ func (i *iterator) NextTimelineItem() bool {
return false
}
- if len(i.timeline.query.Repository.Issues.Nodes[0].Timeline.Edges) == 0 {
+ timelineItems := i.timeline.query.Repository.Issues.Nodes[0].TimelineItems
+ // after NextIssue call it's good to check wether we have some timelineItems items or not
+ if len(timelineItems.Edges) == 0 {
return false
}
- if i.timeline.index < len(i.timeline.query.Repository.Issues.Nodes[0].Timeline.Edges)-1 {
+ if i.timeline.index < len(timelineItems.Edges)-1 {
i.timeline.index++
return true
}
- if !i.timeline.query.Repository.Issues.Nodes[0].Timeline.PageInfo.HasNextPage {
+ if !timelineItems.PageInfo.HasNextPage {
return false
}
- i.timeline.lastEndCursor = i.timeline.query.Repository.Issues.Nodes[0].Timeline.PageInfo.EndCursor
+ i.timeline.lastEndCursor = timelineItems.PageInfo.EndCursor
// more timelines, query them
- i.timeline.variables["timelineAfter"] = i.timeline.query.Repository.Issues.Nodes[0].Timeline.PageInfo.EndCursor
+ i.timeline.variables["timelineAfter"] = timelineItems.PageInfo.EndCursor
ctx, cancel := context.WithTimeout(i.ctx, defaultTimeout)
defer cancel()
@@ -240,6 +249,12 @@ func (i *iterator) NextTimelineItem() bool {
return false
}
+ timelineItems = i.timeline.query.Repository.Issues.Nodes[0].TimelineItems
+ // (in case github returns something weird) just for safety: better return a false than a panic
+ if len(timelineItems.Edges) == 0 {
+ return false
+ }
+
i.reverseTimelineEditNodes()
i.timeline.index = 0
return true
@@ -247,7 +262,8 @@ func (i *iterator) NextTimelineItem() bool {
// TimelineItemValue return the actual timeline item value
func (i *iterator) TimelineItemValue() timelineItem {
- return i.timeline.query.Repository.Issues.Nodes[0].Timeline.Edges[i.timeline.index].Node
+ timelineItems := i.timeline.query.Repository.Issues.Nodes[0].TimelineItems
+ return timelineItems.Edges[i.timeline.index].Node
}
func (i *iterator) queryIssueEdit() bool {
@@ -260,11 +276,12 @@ func (i *iterator) queryIssueEdit() bool {
return false
}
+ issueEdits := i.issueEdit.query.Repository.Issues.Nodes[0].UserContentEdits
// reverse issue edits because github
- reverseEdits(i.issueEdit.query.Repository.Issues.Nodes[0].UserContentEdits.Nodes)
+ reverseEdits(issueEdits.Nodes)
// this is not supposed to happen
- if len(i.issueEdit.query.Repository.Issues.Nodes[0].UserContentEdits.Nodes) == 0 {
+ if len(issueEdits.Nodes) == 0 {
i.timeline.issueEdit.index = -1
return false
}
@@ -297,22 +314,24 @@ func (i *iterator) NextIssueEdit() bool {
// this mean we looped over all available issue edits in the timeline.
// now we have to use i.issueEditQuery
if i.timeline.issueEdit.index == -2 {
- if i.issueEdit.index < len(i.issueEdit.query.Repository.Issues.Nodes[0].UserContentEdits.Nodes)-1 {
+ issueEdits := i.issueEdit.query.Repository.Issues.Nodes[0].UserContentEdits
+ if i.issueEdit.index < len(issueEdits.Nodes)-1 {
i.issueEdit.index++
return i.nextValidIssueEdit()
}
- if !i.issueEdit.query.Repository.Issues.Nodes[0].UserContentEdits.PageInfo.HasPreviousPage {
+ if !issueEdits.PageInfo.HasPreviousPage {
i.timeline.issueEdit.index = -1
i.issueEdit.index = -1
return false
}
// if there is more edits, query them
- i.issueEdit.variables["issueEditBefore"] = i.issueEdit.query.Repository.Issues.Nodes[0].UserContentEdits.PageInfo.StartCursor
+ i.issueEdit.variables["issueEditBefore"] = issueEdits.PageInfo.StartCursor
return i.queryIssueEdit()
}
+ issueEdits := i.timeline.query.Repository.Issues.Nodes[0].UserContentEdits
// if there is no edit, the UserContentEdits given by github is empty. That
// means that the original message is given by the issue message.
//
@@ -323,24 +342,24 @@ func (i *iterator) NextIssueEdit() bool {
// the tricky part: for an issue older than the UserContentEdits API, github
// doesn't have the previous message version anymore and give an edition
// with .Diff == nil. We have to filter them.
- if len(i.timeline.query.Repository.Issues.Nodes[0].UserContentEdits.Nodes) == 0 {
+ if len(issueEdits.Nodes) == 0 {
return false
}
// loop over them timeline comment edits
- if i.timeline.issueEdit.index < len(i.timeline.query.Repository.Issues.Nodes[0].UserContentEdits.Nodes)-1 {
+ if i.timeline.issueEdit.index < len(issueEdits.Nodes)-1 {
i.timeline.issueEdit.index++
return i.nextValidIssueEdit()
}
- if !i.timeline.query.Repository.Issues.Nodes[0].UserContentEdits.PageInfo.HasPreviousPage {
+ if !issueEdits.PageInfo.HasPreviousPage {
i.timeline.issueEdit.index = -1
return false
}
// if there is more edits, query them
i.initIssueEditQueryVariables()
- i.issueEdit.variables["issueEditBefore"] = i.timeline.query.Repository.Issues.Nodes[0].UserContentEdits.PageInfo.StartCursor
+ i.issueEdit.variables["issueEditBefore"] = issueEdits.PageInfo.StartCursor
return i.queryIssueEdit()
}
@@ -348,11 +367,13 @@ func (i *iterator) NextIssueEdit() bool {
func (i *iterator) IssueEditValue() userContentEdit {
// if we are using issue edit query
if i.timeline.issueEdit.index == -2 {
- return i.issueEdit.query.Repository.Issues.Nodes[0].UserContentEdits.Nodes[i.issueEdit.index]
+ issueEdits := i.issueEdit.query.Repository.Issues.Nodes[0].UserContentEdits
+ return issueEdits.Nodes[i.issueEdit.index]
}
+ issueEdits := i.timeline.query.Repository.Issues.Nodes[0].UserContentEdits
// else get it from timeline issue edit query
- return i.timeline.query.Repository.Issues.Nodes[0].UserContentEdits.Nodes[i.timeline.issueEdit.index]
+ return issueEdits.Nodes[i.timeline.issueEdit.index]
}
func (i *iterator) queryCommentEdit() bool {
@@ -364,13 +385,14 @@ func (i *iterator) queryCommentEdit() bool {
return false
}
+ commentEdits := i.commentEdit.query.Repository.Issues.Nodes[0].Timeline.Nodes[0].IssueComment.UserContentEdits
// this is not supposed to happen
- if len(i.commentEdit.query.Repository.Issues.Nodes[0].Timeline.Nodes[0].IssueComment.UserContentEdits.Nodes) == 0 {
+ if len(commentEdits.Nodes) == 0 {
i.timeline.commentEdit.index = -1
return false
}
- reverseEdits(i.commentEdit.query.Repository.Issues.Nodes[0].Timeline.Nodes[0].IssueComment.UserContentEdits.Nodes)
+ reverseEdits(commentEdits.Nodes)
i.commentEdit.index = 0
i.timeline.commentEdit.index = -2
@@ -398,35 +420,36 @@ func (i *iterator) NextCommentEdit() bool {
// same as NextIssueEdit
if i.timeline.commentEdit.index == -2 {
-
- if i.commentEdit.index < len(i.commentEdit.query.Repository.Issues.Nodes[0].Timeline.Nodes[0].IssueComment.UserContentEdits.Nodes)-1 {
+ commentEdits := i.commentEdit.query.Repository.Issues.Nodes[0].Timeline.Nodes[0].IssueComment.UserContentEdits
+ if i.commentEdit.index < len(commentEdits.Nodes)-1 {
i.commentEdit.index++
return i.nextValidCommentEdit()
}
- if !i.commentEdit.query.Repository.Issues.Nodes[0].Timeline.Nodes[0].IssueComment.UserContentEdits.PageInfo.HasPreviousPage {
+ if !commentEdits.PageInfo.HasPreviousPage {
i.timeline.commentEdit.index = -1
i.commentEdit.index = -1
return false
}
// if there is more comment edits, query them
- i.commentEdit.variables["commentEditBefore"] = i.commentEdit.query.Repository.Issues.Nodes[0].Timeline.Nodes[0].IssueComment.UserContentEdits.PageInfo.StartCursor
+ i.commentEdit.variables["commentEditBefore"] = commentEdits.PageInfo.StartCursor
return i.queryCommentEdit()
}
+ commentEdits := i.timeline.query.Repository.Issues.Nodes[0].TimelineItems.Edges[i.timeline.index].Node.IssueComment
// if there is no comment edits
- if len(i.timeline.query.Repository.Issues.Nodes[0].Timeline.Edges[i.timeline.index].Node.IssueComment.UserContentEdits.Nodes) == 0 {
+ if len(commentEdits.UserContentEdits.Nodes) == 0 {
return false
}
// loop over them timeline comment edits
- if i.timeline.commentEdit.index < len(i.timeline.query.Repository.Issues.Nodes[0].Timeline.Edges[i.timeline.index].Node.IssueComment.UserContentEdits.Nodes)-1 {
+ if i.timeline.commentEdit.index < len(commentEdits.UserContentEdits.Nodes)-1 {
i.timeline.commentEdit.index++
return i.nextValidCommentEdit()
}
- if !i.timeline.query.Repository.Issues.Nodes[0].Timeline.Edges[i.timeline.index].Node.IssueComment.UserContentEdits.PageInfo.HasPreviousPage {
+ if !commentEdits.UserContentEdits.PageInfo.HasPreviousPage {
i.timeline.commentEdit.index = -1
return false
}
@@ -435,10 +458,10 @@ func (i *iterator) NextCommentEdit() bool {
if i.timeline.index == 0 {
i.commentEdit.variables["timelineAfter"] = i.timeline.lastEndCursor
} else {
- i.commentEdit.variables["timelineAfter"] = i.timeline.query.Repository.Issues.Nodes[0].Timeline.Edges[i.timeline.index-1].Cursor
+ i.commentEdit.variables["timelineAfter"] = i.timeline.query.Repository.Issues.Nodes[0].TimelineItems.Edges[i.timeline.index-1].Cursor
}
- i.commentEdit.variables["commentEditBefore"] = i.timeline.query.Repository.Issues.Nodes[0].Timeline.Edges[i.timeline.index].Node.IssueComment.UserContentEdits.PageInfo.StartCursor
+ i.commentEdit.variables["commentEditBefore"] = commentEdits.UserContentEdits.PageInfo.StartCursor
return i.queryCommentEdit()
}
@@ -449,7 +472,7 @@ func (i *iterator) CommentEditValue() userContentEdit {
return i.commentEdit.query.Repository.Issues.Nodes[0].Timeline.Nodes[0].IssueComment.UserContentEdits.Nodes[i.commentEdit.index]
}
- return i.timeline.query.Repository.Issues.Nodes[0].Timeline.Edges[i.timeline.index].Node.IssueComment.UserContentEdits.Nodes[i.timeline.commentEdit.index]
+ return i.timeline.query.Repository.Issues.Nodes[0].TimelineItems.Edges[i.timeline.index].Node.IssueComment.UserContentEdits.Nodes[i.timeline.commentEdit.index]
}
func reverseEdits(edits []userContentEdit) {
diff --git a/bridge/gitlab/export_test.go b/bridge/gitlab/export_test.go
index 46c8c494..26b47bfb 100644
--- a/bridge/gitlab/export_test.go
+++ b/bridge/gitlab/export_test.go
@@ -284,8 +284,11 @@ func createRepository(ctx context.Context, name, token string) (int, error) {
},
gitlab.WithContext(ctx),
)
+ if err != nil {
+ return 0, err
+ }
- return project.ID, err
+ return project.ID, nil
}
// delete repository need a token with scope 'delete_repo'
diff --git a/bridge/gitlab/gitlab.go b/bridge/gitlab/gitlab.go
index 7e5c37cc..d976d813 100644
--- a/bridge/gitlab/gitlab.go
+++ b/bridge/gitlab/gitlab.go
@@ -23,10 +23,6 @@ const (
defaultTimeout = 60 * time.Second
)
-func init() {
- core.Register(&Gitlab{})
-}
-
type Gitlab struct{}
func (*Gitlab) Target() string {
diff --git a/bridge/launchpad/launchpad.go b/bridge/launchpad/launchpad.go
index 1fd9edc2..030d9169 100644
--- a/bridge/launchpad/launchpad.go
+++ b/bridge/launchpad/launchpad.go
@@ -5,10 +5,6 @@ import (
"github.com/MichaelMure/git-bug/bridge/core"
)
-func init() {
- core.Register(&Launchpad{})
-}
-
type Launchpad struct{}
func (*Launchpad) Target() string {
diff --git a/bug/bug.go b/bug/bug.go
index eb0337a4..ca817dc1 100644
--- a/bug/bug.go
+++ b/bug/bug.go
@@ -160,7 +160,7 @@ func readBug(repo repository.ClockedRepo, ref string) (*Bug, error) {
rootFound = true
}
if strings.HasPrefix(entry.Name, createClockEntryPrefix) {
- n, err := fmt.Sscanf(string(entry.Name), createClockEntryPattern, &createTime)
+ n, err := fmt.Sscanf(entry.Name, createClockEntryPattern, &createTime)
if err != nil {
return nil, errors.Wrap(err, "can't read create lamport time")
}
@@ -169,7 +169,7 @@ func readBug(repo repository.ClockedRepo, ref string) (*Bug, error) {
}
}
if strings.HasPrefix(entry.Name, editClockEntryPrefix) {
- n, err := fmt.Sscanf(string(entry.Name), editClockEntryPattern, &editTime)
+ n, err := fmt.Sscanf(entry.Name, editClockEntryPattern, &editTime)
if err != nil {
return nil, errors.Wrap(err, "can't read edit lamport time")
}
@@ -197,10 +197,10 @@ func readBug(repo repository.ClockedRepo, ref string) (*Bug, error) {
}
// Update the clocks
- if err := repo.CreateWitness(bug.createTime); err != nil {
+ if err := repo.WitnessCreate(bug.createTime); err != nil {
return nil, errors.Wrap(err, "failed to update create lamport clock")
}
- if err := repo.EditWitness(bug.editTime); err != nil {
+ if err := repo.WitnessEdit(bug.editTime); err != nil {
return nil, errors.Wrap(err, "failed to update edit lamport clock")
}
@@ -350,11 +350,6 @@ func (bug *Bug) Append(op Operation) {
bug.staging.Append(op)
}
-// HasPendingOp tell if the bug need to be committed
-func (bug *Bug) HasPendingOp() bool {
- return !bug.staging.IsEmpty()
-}
-
// Commit write the staging area in Git and move the operations to the packs
func (bug *Bug) Commit(repo repository.ClockedRepo) error {
@@ -592,6 +587,8 @@ func (bug *Bug) Merge(repo repository.Repo, other Interface) (bool, error) {
bug.lastCommit = hash
}
+ bug.packs = newPacks
+
// Update the git ref
err = repo.UpdateRef(bugsRefPattern+bug.id.String(), bug.lastCommit)
if err != nil {
diff --git a/bug/bug_actions_test.go b/bug/bug_actions_test.go
index 4bc58aea..38dddce2 100644
--- a/bug/bug_actions_test.go
+++ b/bug/bug_actions_test.go
@@ -4,6 +4,7 @@ import (
"testing"
"time"
+ "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/MichaelMure/git-bug/identity"
@@ -18,8 +19,10 @@ func TestPushPull(t *testing.T) {
bug1, _, err := Create(reneA, time.Now().Unix(), "bug1", "message")
require.NoError(t, err)
+ assert.True(t, bug1.NeedCommit())
err = bug1.Commit(repoA)
require.NoError(t, err)
+ assert.False(t, bug1.NeedCommit())
// distribute the identity
_, err = identity.Push(repoA, "origin")
@@ -91,8 +94,10 @@ func _RebaseTheirs(t testing.TB) {
bug1, _, err := Create(reneA, time.Now().Unix(), "bug1", "message")
require.NoError(t, err)
+ assert.True(t, bug1.NeedCommit())
err = bug1.Commit(repoA)
require.NoError(t, err)
+ assert.False(t, bug1.NeedCommit())
// distribute the identity
_, err = identity.Push(repoA, "origin")
@@ -111,18 +116,21 @@ func _RebaseTheirs(t testing.TB) {
bug2, err := ReadLocalBug(repoB, bug1.Id())
require.NoError(t, err)
+ assert.False(t, bug2.NeedCommit())
reneB, err := identity.ReadLocal(repoA, reneA.Id())
require.NoError(t, err)
_, err = AddComment(bug2, reneB, time.Now().Unix(), "message2")
require.NoError(t, err)
+ assert.True(t, bug2.NeedCommit())
_, err = AddComment(bug2, reneB, time.Now().Unix(), "message3")
require.NoError(t, err)
_, err = AddComment(bug2, reneB, time.Now().Unix(), "message4")
require.NoError(t, err)
err = bug2.Commit(repoB)
require.NoError(t, err)
+ assert.False(t, bug2.NeedCommit())
// B --> remote
_, err = Push(repoB, "origin")
diff --git a/bug/bug_test.go b/bug/bug_test.go
index 4e8a9440..35e8a395 100644
--- a/bug/bug_test.go
+++ b/bug/bug_test.go
@@ -78,8 +78,11 @@ func TestBugCommitLoad(t *testing.T) {
repo := repository.NewMockRepoForTest()
+ assert.True(t, bug1.NeedCommit())
+
err := bug1.Commit(repo)
assert.Nil(t, err)
+ assert.False(t, bug1.NeedCommit())
bug2, err := ReadLocalBug(repo, bug1.Id())
assert.NoError(t, err)
@@ -90,8 +93,11 @@ func TestBugCommitLoad(t *testing.T) {
bug1.Append(setTitleOp)
bug1.Append(addCommentOp)
+ assert.True(t, bug1.NeedCommit())
+
err = bug1.Commit(repo)
assert.Nil(t, err)
+ assert.False(t, bug1.NeedCommit())
bug3, err := ReadLocalBug(repo, bug1.Id())
assert.NoError(t, err)
diff --git a/bug/clocks.go b/bug/clocks.go
index bb3d81f0..52d23544 100644
--- a/bug/clocks.go
+++ b/bug/clocks.go
@@ -12,12 +12,12 @@ func Witnesser(repo repository.ClockedRepo) error {
return b.Err
}
- err := repo.CreateWitness(b.Bug.createTime)
+ err := repo.WitnessCreate(b.Bug.createTime)
if err != nil {
return err
}
- err = repo.EditWitness(b.Bug.editTime)
+ err = repo.WitnessEdit(b.Bug.editTime)
if err != nil {
return err
}
diff --git a/bug/interface.go b/bug/interface.go
index 8266e99e..796ee569 100644
--- a/bug/interface.go
+++ b/bug/interface.go
@@ -16,8 +16,8 @@ type Interface interface {
// Append an operation into the staging area, to be committed later
Append(op Operation)
- // Append an operation into the staging area, to be committed later
- HasPendingOp() bool
+ // Indicate that the in-memory state changed and need to be commit in the repository
+ NeedCommit() bool
// Commit write the staging area in Git and move the operations to the packs
Commit(repo repository.ClockedRepo) error
diff --git a/commands/bridge_auth.go b/commands/bridge_auth.go
new file mode 100644
index 00000000..e7fce1bd
--- /dev/null
+++ b/commands/bridge_auth.go
@@ -0,0 +1,53 @@
+package commands
+
+import (
+ "fmt"
+
+ "github.com/spf13/cobra"
+
+ text "github.com/MichaelMure/go-term-text"
+
+ "github.com/MichaelMure/git-bug/bridge/core"
+ "github.com/MichaelMure/git-bug/util/colors"
+)
+
+func runBridgeAuth(cmd *cobra.Command, args []string) error {
+ tokens, err := core.ListTokens(repo)
+ if err != nil {
+ return err
+ }
+
+ for _, token := range tokens {
+ token, err := core.LoadToken(repo, token)
+ if err != nil {
+ return err
+ }
+ printToken(token)
+ }
+
+ return nil
+}
+
+func printToken(token *core.Token) {
+ targetFmt := text.LeftPadMaxLine(token.Target, 10, 0)
+
+ fmt.Printf("%s %s %s %s\n",
+ colors.Cyan(token.ID().Human()),
+ colors.Yellow(targetFmt),
+ colors.Magenta("token"),
+ token.Value,
+ )
+}
+
+var bridgeAuthCmd = &cobra.Command{
+ Use: "auth",
+ Short: "List all known bridge authentication credentials.",
+ PreRunE: loadRepo,
+ RunE: runBridgeAuth,
+ Args: cobra.NoArgs,
+}
+
+func init() {
+ bridgeCmd.AddCommand(bridgeAuthCmd)
+ bridgeAuthCmd.Flags().SortFlags = false
+}
diff --git a/commands/bridge_auth_add.go b/commands/bridge_auth_add.go
new file mode 100644
index 00000000..ae2c4dbc
--- /dev/null
+++ b/commands/bridge_auth_add.go
@@ -0,0 +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 (
+ bridgeAuthAddTokenTarget string
+)
+
+func runBridgeTokenAdd(cmd *cobra.Command, args []string) error {
+ var value string
+
+ if bridgeAuthAddTokenTarget == "" {
+ return fmt.Errorf("auth target is required")
+ }
+
+ if !core.TargetExist(bridgeAuthAddTokenTarget) {
+ 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, bridgeAuthAddTokenTarget)
+ if err := token.Validate(); err != nil {
+ return errors.Wrap(err, "invalid token")
+ }
+
+ err := core.StoreToken(repo, token)
+ if err != nil {
+ return err
+ }
+
+ fmt.Printf("token %s added\n", token.ID())
+ return nil
+}
+
+var bridgeAuthAddTokenCmd = &cobra.Command{
+ Use: "add-token [<token>]",
+ Short: "Store a new token",
+ PreRunE: loadRepo,
+ RunE: runBridgeTokenAdd,
+ Args: cobra.MaximumNArgs(1),
+}
+
+func init() {
+ bridgeAuthCmd.AddCommand(bridgeAuthAddTokenCmd)
+ bridgeAuthAddTokenCmd.Flags().StringVarP(&bridgeAuthAddTokenTarget, "target", "t", "",
+ fmt.Sprintf("The target of the bridge. Valid values are [%s]", strings.Join(bridge.Targets(), ",")))
+ bridgeAuthAddTokenCmd.Flags().SortFlags = false
+}
diff --git a/commands/bridge_auth_rm.go b/commands/bridge_auth_rm.go
new file mode 100644
index 00000000..b0b4d437
--- /dev/null
+++ b/commands/bridge_auth_rm.go
@@ -0,0 +1,36 @@
+package commands
+
+import (
+ "fmt"
+
+ "github.com/spf13/cobra"
+
+ "github.com/MichaelMure/git-bug/bridge/core"
+)
+
+func runBridgeAuthRm(cmd *cobra.Command, args []string) error {
+ 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 bridgeAuthRmCmd = &cobra.Command{
+ Use: "rm <id>",
+ Short: "Remove a credential.",
+ PreRunE: loadRepo,
+ RunE: runBridgeAuthRm,
+ Args: cobra.ExactArgs(1),
+}
+
+func init() {
+ bridgeAuthCmd.AddCommand(bridgeAuthRmCmd)
+}
diff --git a/commands/bridge_auth_show.go b/commands/bridge_auth_show.go
new file mode 100644
index 00000000..94141b93
--- /dev/null
+++ b/commands/bridge_auth_show.go
@@ -0,0 +1,37 @@
+package commands
+
+import (
+ "fmt"
+ "time"
+
+ "github.com/spf13/cobra"
+
+ "github.com/MichaelMure/git-bug/bridge/core"
+)
+
+func runBridgeAuthShow(cmd *cobra.Command, args []string) error {
+ token, err := core.LoadTokenPrefix(repo, args[0])
+ if err != nil {
+ return err
+ }
+
+ fmt.Printf("Id: %s\n", token.ID())
+ fmt.Printf("Target: %s\n", token.Target)
+ fmt.Printf("Type: token\n")
+ fmt.Printf("Value: %s\n", token.Value)
+ fmt.Printf("Creation: %s\n", token.CreateTime.Format(time.RFC822))
+
+ return nil
+}
+
+var bridgeAuthShowCmd = &cobra.Command{
+ Use: "show",
+ Short: "Display an authentication credential.",
+ PreRunE: loadRepo,
+ RunE: runBridgeAuthShow,
+ Args: cobra.ExactArgs(1),
+}
+
+func init() {
+ bridgeAuthCmd.AddCommand(bridgeAuthShowCmd)
+}
diff --git a/doc/man/git-bug-bridge-auth-add-token.1 b/doc/man/git-bug-bridge-auth-add-token.1
new file mode 100644
index 00000000..a76ed793
--- /dev/null
+++ b/doc/man/git-bug-bridge-auth-add-token.1
@@ -0,0 +1,33 @@
+.TH "GIT-BUG" "1" "Apr 2019" "Generated from git-bug's source code" ""
+.nh
+.ad l
+
+
+.SH NAME
+.PP
+git\-bug\-bridge\-auth\-add\-token \- Store a new token
+
+
+.SH SYNOPSIS
+.PP
+\fBgit\-bug bridge auth add\-token [<token>] [flags]\fP
+
+
+.SH DESCRIPTION
+.PP
+Store a new token
+
+
+.SH OPTIONS
+.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]
+ help for add\-token
+
+
+.SH SEE ALSO
+.PP
+\fBgit\-bug\-bridge\-auth(1)\fP
diff --git a/doc/man/git-bug-bridge-auth-rm.1 b/doc/man/git-bug-bridge-auth-rm.1
new file mode 100644
index 00000000..b0222b72
--- /dev/null
+++ b/doc/man/git-bug-bridge-auth-rm.1
@@ -0,0 +1,29 @@
+.TH "GIT-BUG" "1" "Apr 2019" "Generated from git-bug's source code" ""
+.nh
+.ad l
+
+
+.SH NAME
+.PP
+git\-bug\-bridge\-auth\-rm \- Remove a credential.
+
+
+.SH SYNOPSIS
+.PP
+\fBgit\-bug bridge auth rm <id> [flags]\fP
+
+
+.SH DESCRIPTION
+.PP
+Remove a credential.
+
+
+.SH OPTIONS
+.PP
+\fB\-h\fP, \fB\-\-help\fP[=false]
+ help for rm
+
+
+.SH SEE ALSO
+.PP
+\fBgit\-bug\-bridge\-auth(1)\fP
diff --git a/doc/man/git-bug-bridge-auth-show.1 b/doc/man/git-bug-bridge-auth-show.1
new file mode 100644
index 00000000..6e0d345c
--- /dev/null
+++ b/doc/man/git-bug-bridge-auth-show.1
@@ -0,0 +1,29 @@
+.TH "GIT-BUG" "1" "Apr 2019" "Generated from git-bug's source code" ""
+.nh
+.ad l
+
+
+.SH NAME
+.PP
+git\-bug\-bridge\-auth\-show \- Display an authentication credential.
+
+
+.SH SYNOPSIS
+.PP
+\fBgit\-bug bridge auth show [flags]\fP
+
+
+.SH DESCRIPTION
+.PP
+Display an authentication credential.
+
+
+.SH OPTIONS
+.PP
+\fB\-h\fP, \fB\-\-help\fP[=false]
+ help for show
+
+
+.SH SEE ALSO
+.PP
+\fBgit\-bug\-bridge\-auth(1)\fP
diff --git a/doc/man/git-bug-bridge-auth.1 b/doc/man/git-bug-bridge-auth.1
new file mode 100644
index 00000000..0e400c41
--- /dev/null
+++ b/doc/man/git-bug-bridge-auth.1
@@ -0,0 +1,29 @@
+.TH "GIT-BUG" "1" "Apr 2019" "Generated from git-bug's source code" ""
+.nh
+.ad l
+
+
+.SH NAME
+.PP
+git\-bug\-bridge\-auth \- List all known bridge authentication credentials.
+
+
+.SH SYNOPSIS
+.PP
+\fBgit\-bug bridge auth [flags]\fP
+
+
+.SH DESCRIPTION
+.PP
+List all known bridge authentication credentials.
+
+
+.SH OPTIONS
+.PP
+\fB\-h\fP, \fB\-\-help\fP[=false]
+ help for auth
+
+
+.SH SEE ALSO
+.PP
+\fBgit\-bug\-bridge(1)\fP, \fBgit\-bug\-bridge\-auth\-add\-token(1)\fP, \fBgit\-bug\-bridge\-auth\-rm(1)\fP, \fBgit\-bug\-bridge\-auth\-show(1)\fP
diff --git a/doc/man/git-bug-bridge.1 b/doc/man/git-bug-bridge.1
index dfede4e0..8e885f10 100644
--- a/doc/man/git-bug-bridge.1
+++ b/doc/man/git-bug-bridge.1
@@ -26,4 +26,4 @@ Configure and use bridges to other bug trackers.
.SH SEE ALSO
.PP
-\fBgit\-bug(1)\fP, \fBgit\-bug\-bridge\-configure(1)\fP, \fBgit\-bug\-bridge\-pull(1)\fP, \fBgit\-bug\-bridge\-push(1)\fP, \fBgit\-bug\-bridge\-rm(1)\fP
+\fBgit\-bug(1)\fP, \fBgit\-bug\-bridge\-auth(1)\fP, \fBgit\-bug\-bridge\-configure(1)\fP, \fBgit\-bug\-bridge\-pull(1)\fP, \fBgit\-bug\-bridge\-push(1)\fP, \fBgit\-bug\-bridge\-rm(1)\fP
diff --git a/doc/md/git-bug_bridge.md b/doc/md/git-bug_bridge.md
index dfd61e29..3ddb9892 100644
--- a/doc/md/git-bug_bridge.md
+++ b/doc/md/git-bug_bridge.md
@@ -19,6 +19,7 @@ git-bug bridge [flags]
### SEE ALSO
* [git-bug](git-bug.md) - A bug tracker embedded in Git.
+* [git-bug bridge auth](git-bug_bridge_auth.md) - List all known bridge authentication credentials.
* [git-bug bridge configure](git-bug_bridge_configure.md) - Configure a new bridge.
* [git-bug bridge pull](git-bug_bridge_pull.md) - Pull updates.
* [git-bug bridge push](git-bug_bridge_push.md) - Push updates.
diff --git a/doc/md/git-bug_bridge_auth.md b/doc/md/git-bug_bridge_auth.md
new file mode 100644
index 00000000..e953f0ec
--- /dev/null
+++ b/doc/md/git-bug_bridge_auth.md
@@ -0,0 +1,25 @@
+## git-bug bridge auth
+
+List all known bridge authentication credentials.
+
+### Synopsis
+
+List all known bridge authentication credentials.
+
+```
+git-bug bridge auth [flags]
+```
+
+### Options
+
+```
+ -h, --help help for auth
+```
+
+### SEE ALSO
+
+* [git-bug bridge](git-bug_bridge.md) - Configure and use bridges to other bug trackers.
+* [git-bug bridge auth add-token](git-bug_bridge_auth_add-token.md) - Store a new token
+* [git-bug bridge auth rm](git-bug_bridge_auth_rm.md) - Remove a credential.
+* [git-bug bridge auth show](git-bug_bridge_auth_show.md) - Display an authentication credential.
+
diff --git a/doc/md/git-bug_bridge_auth_add-token.md b/doc/md/git-bug_bridge_auth_add-token.md
new file mode 100644
index 00000000..7067c3ca
--- /dev/null
+++ b/doc/md/git-bug_bridge_auth_add-token.md
@@ -0,0 +1,23 @@
+## git-bug bridge auth add-token
+
+Store a new token
+
+### Synopsis
+
+Store a new token
+
+```
+git-bug bridge auth add-token [<token>] [flags]
+```
+
+### Options
+
+```
+ -t, --target string The target of the bridge. Valid values are [github,gitlab,launchpad-preview]
+ -h, --help help for add-token
+```
+
+### SEE ALSO
+
+* [git-bug bridge auth](git-bug_bridge_auth.md) - List all known bridge authentication credentials.
+
diff --git a/doc/md/git-bug_bridge_auth_rm.md b/doc/md/git-bug_bridge_auth_rm.md
new file mode 100644
index 00000000..059aa43d
--- /dev/null
+++ b/doc/md/git-bug_bridge_auth_rm.md
@@ -0,0 +1,22 @@
+## git-bug bridge auth rm
+
+Remove a credential.
+
+### Synopsis
+
+Remove a credential.
+
+```
+git-bug bridge auth rm <id> [flags]
+```
+
+### Options
+
+```
+ -h, --help help for rm
+```
+
+### SEE ALSO
+
+* [git-bug bridge auth](git-bug_bridge_auth.md) - List all known bridge authentication credentials.
+
diff --git a/doc/md/git-bug_bridge_auth_show.md b/doc/md/git-bug_bridge_auth_show.md
new file mode 100644
index 00000000..5da3820f
--- /dev/null
+++ b/doc/md/git-bug_bridge_auth_show.md
@@ -0,0 +1,22 @@
+## git-bug bridge auth show
+
+Display an authentication credential.
+
+### Synopsis
+
+Display an authentication credential.
+
+```
+git-bug bridge auth show [flags]
+```
+
+### Options
+
+```
+ -h, --help help for show
+```
+
+### SEE ALSO
+
+* [git-bug bridge auth](git-bug_bridge_auth.md) - List all known bridge authentication credentials.
+
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 2088fd4d..9dc3ac87 100644
--- a/misc/bash_completion/git-bug
+++ b/misc/bash_completion/git-bug
@@ -287,6 +287,93 @@ _git-bug_add()
noun_aliases=()
}
+_git-bug_bridge_auth_add-token()
+{
+ last_command="git-bug_bridge_auth_add-token"
+
+ command_aliases=()
+
+ commands=()
+
+ flags=()
+ two_word_flags=()
+ local_nonpersistent_flags=()
+ flags_with_completion=()
+ flags_completion=()
+
+ flags+=("--target=")
+ two_word_flags+=("--target")
+ two_word_flags+=("-t")
+ local_nonpersistent_flags+=("--target=")
+
+ must_have_one_flag=()
+ must_have_one_noun=()
+ noun_aliases=()
+}
+
+_git-bug_bridge_auth_rm()
+{
+ last_command="git-bug_bridge_auth_rm"
+
+ command_aliases=()
+
+ commands=()
+
+ flags=()
+ two_word_flags=()
+ local_nonpersistent_flags=()
+ flags_with_completion=()
+ flags_completion=()
+
+
+ must_have_one_flag=()
+ must_have_one_noun=()
+ noun_aliases=()
+}
+
+_git-bug_bridge_auth_show()
+{
+ last_command="git-bug_bridge_auth_show"
+
+ command_aliases=()
+
+ commands=()
+
+ flags=()
+ two_word_flags=()
+ local_nonpersistent_flags=()
+ flags_with_completion=()
+ flags_completion=()
+
+
+ must_have_one_flag=()
+ must_have_one_noun=()
+ noun_aliases=()
+}
+
+_git-bug_bridge_auth()
+{
+ last_command="git-bug_bridge_auth"
+
+ command_aliases=()
+
+ commands=()
+ commands+=("add-token")
+ commands+=("rm")
+ commands+=("show")
+
+ flags=()
+ two_word_flags=()
+ local_nonpersistent_flags=()
+ flags_with_completion=()
+ flags_completion=()
+
+
+ must_have_one_flag=()
+ must_have_one_noun=()
+ noun_aliases=()
+}
+
_git-bug_bridge_configure()
{
last_command="git-bug_bridge_configure"
@@ -407,6 +494,7 @@ _git-bug_bridge()
command_aliases=()
commands=()
+ commands+=("auth")
commands+=("configure")
commands+=("pull")
commands+=("push")
diff --git a/misc/powershell_completion/git-bug b/misc/powershell_completion/git-bug
index 34037531..5f043932 100644
--- a/misc/powershell_completion/git-bug
+++ b/misc/powershell_completion/git-bug
@@ -48,12 +48,30 @@ Register-ArgumentCompleter -Native -CommandName 'git-bug' -ScriptBlock {
break
}
'git-bug;bridge' {
+ [CompletionResult]::new('auth', 'auth', [CompletionResultType]::ParameterValue, 'List all known bridge authentication credentials.')
[CompletionResult]::new('configure', 'configure', [CompletionResultType]::ParameterValue, 'Configure a new bridge.')
[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.')
break
}
+ 'git-bug;bridge;auth' {
+ [CompletionResult]::new('add-token', 'add-token', [CompletionResultType]::ParameterValue, 'Store a new token')
+ [CompletionResult]::new('rm', 'rm', [CompletionResultType]::ParameterValue, 'Remove a credential.')
+ [CompletionResult]::new('show', 'show', [CompletionResultType]::ParameterValue, 'Display an authentication credential.')
+ break
+ }
+ 'git-bug;bridge;auth;add-token' {
+ [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;auth;rm' {
+ break
+ }
+ 'git-bug;bridge;auth;show' {
+ break
+ }
'git-bug;bridge;configure' {
[CompletionResult]::new('-n', 'n', [CompletionResultType]::ParameterName, 'A distinctive name to identify the bridge')
[CompletionResult]::new('--name', 'name', [CompletionResultType]::ParameterName, 'A distinctive name to identify the bridge')
diff --git a/misc/zsh_completion/git-bug b/misc/zsh_completion/git-bug
index 9951bab9..230061dd 100644
--- a/misc/zsh_completion/git-bug
+++ b/misc/zsh_completion/git-bug
@@ -114,6 +114,7 @@ function _git-bug_bridge {
case $state in
cmnds)
commands=(
+ "auth:List all known bridge authentication credentials."
"configure:Configure a new bridge."
"pull:Pull updates."
"push:Push updates."
@@ -124,6 +125,9 @@ function _git-bug_bridge {
esac
case "$words[1]" in
+ auth)
+ _git-bug_bridge_auth
+ ;;
configure)
_git-bug_bridge_configure
;;
@@ -139,6 +143,51 @@ function _git-bug_bridge {
esac
}
+
+function _git-bug_bridge_auth {
+ local -a commands
+
+ _arguments -C \
+ "1: :->cmnds" \
+ "*::arg:->args"
+
+ case $state in
+ cmnds)
+ commands=(
+ "add-token:Store a new token"
+ "rm:Remove a credential."
+ "show:Display an authentication credential."
+ )
+ _describe "command" commands
+ ;;
+ esac
+
+ case "$words[1]" in
+ add-token)
+ _git-bug_bridge_auth_add-token
+ ;;
+ rm)
+ _git-bug_bridge_auth_rm
+ ;;
+ show)
+ _git-bug_bridge_auth_show
+ ;;
+ esac
+}
+
+function _git-bug_bridge_auth_add-token {
+ _arguments \
+ '(-t --target)'{-t,--target}'[The target of the bridge. Valid values are [github,gitlab,launchpad-preview]]:'
+}
+
+function _git-bug_bridge_auth_rm {
+ _arguments
+}
+
+function _git-bug_bridge_auth_show {
+ _arguments
+}
+
function _git-bug_bridge_configure {
_arguments \
'(-n --name)'{-n,--name}'[A distinctive name to identify the bridge]:' \
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 {
diff --git a/repository/git.go b/repository/git.go
index 2b00d1f2..d4560805 100644
--- a/repository/git.go
+++ b/repository/git.go
@@ -15,11 +15,15 @@ import (
"github.com/MichaelMure/git-bug/util/lamport"
)
-const createClockFile = "/git-bug/create-clock"
-const editClockFile = "/git-bug/edit-clock"
+const (
+ createClockFile = "/git-bug/create-clock"
+ editClockFile = "/git-bug/edit-clock"
+)
-// ErrNotARepo is the error returned when the git repo root wan't be found
-var ErrNotARepo = errors.New("not a git repository")
+var (
+ // ErrNotARepo is the error returned when the git repo root wan't be found
+ ErrNotARepo = errors.New("not a git repository")
+)
var _ ClockedRepo = &GitRepo{}
@@ -457,14 +461,14 @@ func (repo *GitRepo) EditTimeIncrement() (lamport.Time, error) {
return repo.editClock.Increment()
}
-// CreateWitness witness another create time and increment the corresponding clock
+// WitnessCreate witness another create time and increment the corresponding clock
// if needed.
-func (repo *GitRepo) CreateWitness(time lamport.Time) error {
+func (repo *GitRepo) WitnessCreate(time lamport.Time) error {
return repo.createClock.Witness(time)
}
-// EditWitness witness another edition time and increment the corresponding clock
+// WitnessEdit witness another edition time and increment the corresponding clock
// if needed.
-func (repo *GitRepo) EditWitness(time lamport.Time) error {
+func (repo *GitRepo) WitnessEdit(time lamport.Time) error {
return repo.editClock.Witness(time)
}
diff --git a/repository/mock_repo.go b/repository/mock_repo.go
index 26c02ede..88c5a132 100644
--- a/repository/mock_repo.go
+++ b/repository/mock_repo.go
@@ -236,12 +236,12 @@ func (r *mockRepoForTest) EditTimeIncrement() (lamport.Time, error) {
return r.editClock.Increment(), nil
}
-func (r *mockRepoForTest) CreateWitness(time lamport.Time) error {
+func (r *mockRepoForTest) WitnessCreate(time lamport.Time) error {
r.createClock.Witness(time)
return nil
}
-func (r *mockRepoForTest) EditWitness(time lamport.Time) error {
+func (r *mockRepoForTest) WitnessEdit(time lamport.Time) error {
r.editClock.Witness(time)
return nil
}
diff --git a/repository/repo.go b/repository/repo.go
index 7d655bde..71bd7a8e 100644
--- a/repository/repo.go
+++ b/repository/repo.go
@@ -10,8 +10,10 @@ import (
"github.com/MichaelMure/git-bug/util/lamport"
)
-var ErrNoConfigEntry = errors.New("no config entry for the given key")
-var ErrMultipleConfigEntry = errors.New("multiple config entry for the given key")
+var (
+ ErrNoConfigEntry = errors.New("no config entry for the given key")
+ ErrMultipleConfigEntry = errors.New("multiple config entry for the given key")
+)
// RepoCommon represent the common function the we want all the repo to implement
type RepoCommon interface {
@@ -109,13 +111,13 @@ type ClockedRepo interface {
// EditTimeIncrement increment the edit clock and return the new value.
EditTimeIncrement() (lamport.Time, error)
- // CreateWitness witness another create time and increment the corresponding
+ // WitnessCreate witness another create time and increment the corresponding
// clock if needed.
- CreateWitness(time lamport.Time) error
+ WitnessCreate(time lamport.Time) error
- // EditWitness witness another edition time and increment the corresponding
+ // WitnessEdit witness another edition time and increment the corresponding
// clock if needed.
- EditWitness(time lamport.Time) error
+ WitnessEdit(time lamport.Time) error
}
// Witnesser is a function that will initialize the clocks of a repo
diff --git a/termui/bug_table.go b/termui/bug_table.go
index c432c94a..41aa4e83 100644
--- a/termui/bug_table.go
+++ b/termui/bug_table.go
@@ -7,7 +7,7 @@ import (
"time"
"github.com/MichaelMure/go-term-text"
- "github.com/MichaelMure/gocui"
+ "github.com/awesome-gocui/gocui"
"github.com/dustin/go-humanize"
"github.com/MichaelMure/git-bug/cache"
@@ -56,10 +56,10 @@ func (bt *bugTable) layout(g *gocui.Gui) error {
return nil
}
- v, err := g.SetView(bugTableHeaderView, -1, -1, maxX, 3)
+ v, err := g.SetView(bugTableHeaderView, -1, -1, maxX, 3, 0)
if err != nil {
- if err != gocui.ErrUnknownView {
+ if !gocui.IsUnknownView(err) {
return err
}
@@ -69,21 +69,16 @@ func (bt *bugTable) layout(g *gocui.Gui) error {
v.Clear()
bt.renderHeader(v, maxX)
- v, err = g.SetView(bugTableView, -1, 1, maxX, maxY-3)
+ v, err = g.SetView(bugTableView, -1, 1, maxX, maxY-3, 0)
if err != nil {
- if err != gocui.ErrUnknownView {
+ if !gocui.IsUnknownView(err) {
return err
}
v.Frame = false
- v.Highlight = true
v.SelBgColor = gocui.ColorWhite
v.SelFgColor = gocui.ColorBlack
-
- // restore the cursor
- // window is too small to set the cursor properly, ignoring the error
- _ = v.SetCursor(0, bt.selectCursor)
}
_, viewHeight := v.Size()
@@ -100,10 +95,10 @@ func (bt *bugTable) layout(g *gocui.Gui) error {
v.Clear()
bt.render(v, maxX)
- v, err = g.SetView(bugTableFooterView, -1, maxY-4, maxX, maxY)
+ v, err = g.SetView(bugTableFooterView, -1, maxY-4, maxX, maxY, 0)
if err != nil {
- if err != gocui.ErrUnknownView {
+ if !gocui.IsUnknownView(err) {
return err
}
@@ -113,10 +108,10 @@ func (bt *bugTable) layout(g *gocui.Gui) error {
v.Clear()
bt.renderFooter(v, maxX)
- v, err = g.SetView(bugTableInstructionView, -1, maxY-2, maxX, maxY)
+ v, err = g.SetView(bugTableInstructionView, -1, maxY-2, maxX, maxY, 0)
if err != nil {
- if err != gocui.ErrUnknownView {
+ if !gocui.IsUnknownView(err) {
return err
}
@@ -216,16 +211,16 @@ func (bt *bugTable) keybindings(g *gocui.Gui) error {
}
func (bt *bugTable) disable(g *gocui.Gui) error {
- if err := g.DeleteView(bugTableView); err != nil && err != gocui.ErrUnknownView {
+ if err := g.DeleteView(bugTableView); err != nil && !gocui.IsUnknownView(err) {
return err
}
- if err := g.DeleteView(bugTableHeaderView); err != nil && err != gocui.ErrUnknownView {
+ if err := g.DeleteView(bugTableHeaderView); err != nil && !gocui.IsUnknownView(err) {
return err
}
- if err := g.DeleteView(bugTableFooterView); err != nil && err != gocui.ErrUnknownView {
+ if err := g.DeleteView(bugTableFooterView); err != nil && !gocui.IsUnknownView(err) {
return err
}
- if err := g.DeleteView(bugTableInstructionView); err != nil && err != gocui.ErrUnknownView {
+ if err := g.DeleteView(bugTableInstructionView); err != nil && !gocui.IsUnknownView(err) {
return err
}
return nil
@@ -339,6 +334,8 @@ func (bt *bugTable) render(v *gocui.View, maxX int) {
lastEdit,
)
}
+
+ _ = v.SetHighlight(bt.selectCursor, true)
}
func (bt *bugTable) renderHeader(v *gocui.View, maxX int) {
@@ -360,10 +357,8 @@ func (bt *bugTable) renderFooter(v *gocui.View, maxX int) {
}
func (bt *bugTable) cursorDown(g *gocui.Gui, v *gocui.View) error {
- _, y := v.Cursor()
-
// If we are at the bottom of the page, switch to the next one.
- if y+1 > bt.getTableLength()-1 {
+ if bt.selectCursor+1 > bt.getTableLength()-1 {
_, max := v.Size()
if bt.pageCursor+max >= len(bt.allIds) {
@@ -372,24 +367,18 @@ func (bt *bugTable) cursorDown(g *gocui.Gui, v *gocui.View) error {
bt.pageCursor += max
bt.selectCursor = 0
- _ = v.SetCursor(0, bt.selectCursor)
return bt.doPaginate(max)
}
- y = minInt(y+1, bt.getTableLength()-1)
- // window is too small to set the cursor properly, ignoring the error
- _ = v.SetCursor(0, y)
- bt.selectCursor = y
+ bt.selectCursor = minInt(bt.selectCursor+1, bt.getTableLength()-1)
return nil
}
func (bt *bugTable) cursorUp(g *gocui.Gui, v *gocui.View) error {
- _, y := v.Cursor()
-
// If we are at the top of the page, switch to the previous one.
- if y-1 < 0 {
+ if bt.selectCursor-1 < 0 {
_, max := v.Size()
if bt.pageCursor == 0 {
@@ -398,27 +387,21 @@ func (bt *bugTable) cursorUp(g *gocui.Gui, v *gocui.View) error {
bt.pageCursor = maxInt(0, bt.pageCursor-max)
bt.selectCursor = max - 1
- _ = v.SetCursor(0, bt.selectCursor)
return bt.doPaginate(max)
}
- y = maxInt(y-1, 0)
- // window is too small to set the cursor properly, ignoring the error
- _ = v.SetCursor(0, y)
- bt.selectCursor = y
+ bt.selectCursor = maxInt(bt.selectCursor-1, 0)
return nil
}
func (bt *bugTable) cursorClamp(v *gocui.View) error {
- _, y := v.Cursor()
+ y := bt.selectCursor
y = minInt(y, bt.getTableLength()-1)
y = maxInt(y, 0)
- // window is too small to set the cursor properly, ignoring the error
- _ = v.SetCursor(0, y)
bt.selectCursor = y
return nil
@@ -453,8 +436,7 @@ func (bt *bugTable) newBug(g *gocui.Gui, v *gocui.View) error {
}
func (bt *bugTable) openBug(g *gocui.Gui, v *gocui.View) error {
- _, y := v.Cursor()
- id := bt.excerpts[y].Id
+ id := bt.excerpts[bt.selectCursor].Id
b, err := bt.repo.ResolveBug(id)
if err != nil {
return err
diff --git a/termui/input_popup.go b/termui/input_popup.go
index 3576ba2f..3ff9b5bf 100644
--- a/termui/input_popup.go
+++ b/termui/input_popup.go
@@ -3,7 +3,7 @@ package termui
import (
"io/ioutil"
- "github.com/MichaelMure/gocui"
+ "github.com/awesome-gocui/gocui"
)
const inputPopupView = "inputPopupView"
@@ -46,9 +46,9 @@ func (ip *inputPopup) layout(g *gocui.Gui) error {
x0 := (maxX - width) / 2
y0 := (maxY - height) / 2
- v, err := g.SetView(inputPopupView, x0, y0, x0+width, y0+height)
+ v, err := g.SetView(inputPopupView, x0, y0, x0+width, y0+height, 0)
if err != nil {
- if err != gocui.ErrUnknownView {
+ if !gocui.IsUnknownView(err) {
return err
}
diff --git a/termui/label_select.go b/termui/label_select.go
index 39edbdb1..a05d9c8b 100644
--- a/termui/label_select.go
+++ b/termui/label_select.go
@@ -4,7 +4,7 @@ import (
"fmt"
"strings"
- "github.com/MichaelMure/gocui"
+ "github.com/awesome-gocui/gocui"
"github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/cache"
@@ -106,9 +106,9 @@ func (ls *labelSelect) layout(g *gocui.Gui) error {
x0 := 1
y0 := 0 - ls.scroll
- v, err := g.SetView(labelSelectView, x0, 0, x0+width, maxY-2)
+ v, err := g.SetView(labelSelectView, x0, 0, x0+width, maxY-2, 0)
if err != nil {
- if err != gocui.ErrUnknownView {
+ if !gocui.IsUnknownView(err) {
return err
}
@@ -117,8 +117,8 @@ func (ls *labelSelect) layout(g *gocui.Gui) error {
for i, label := range ls.labels {
viewname := fmt.Sprintf("view%d", i)
- v, err := g.SetView(viewname, x0+2, y0, x0+width-2, y0+2)
- if err != nil && err != gocui.ErrUnknownView {
+ v, err := g.SetView(viewname, x0+2, y0, x0+width-2, y0+2, 0)
+ if err != nil && !gocui.IsUnknownView(err) {
return err
}
ls.childViews = append(ls.childViews, viewname)
@@ -137,10 +137,10 @@ func (ls *labelSelect) layout(g *gocui.Gui) error {
y0 += 2
}
- v, err = g.SetView(labelSelectInstructionsView, -1, maxY-2, maxX, maxY)
+ v, err = g.SetView(labelSelectInstructionsView, -1, maxY-2, maxX, maxY, 0)
ls.childViews = append(ls.childViews, labelSelectInstructionsView)
if err != nil {
- if err != gocui.ErrUnknownView {
+ if !gocui.IsUnknownView(err) {
return err
}
v.Frame = false
@@ -159,7 +159,7 @@ func (ls *labelSelect) layout(g *gocui.Gui) error {
func (ls *labelSelect) disable(g *gocui.Gui) error {
for _, view := range ls.childViews {
- if err := g.DeleteView(view); err != nil && err != gocui.ErrUnknownView {
+ if err := g.DeleteView(view); err != nil && !gocui.IsUnknownView(err) {
return err
}
}
diff --git a/termui/msg_popup.go b/termui/msg_popup.go
index 99180c99..236c16a2 100644
--- a/termui/msg_popup.go
+++ b/termui/msg_popup.go
@@ -4,7 +4,7 @@ import (
"fmt"
"github.com/MichaelMure/go-term-text"
- "github.com/MichaelMure/gocui"
+ "github.com/awesome-gocui/gocui"
)
const msgPopupView = "msgPopupView"
@@ -50,9 +50,9 @@ func (ep *msgPopup) layout(g *gocui.Gui) error {
x0 := (maxX - width) / 2
y0 := (maxY - height) / 2
- v, err := g.SetView(msgPopupView, x0, y0, x0+width, y0+height)
+ v, err := g.SetView(msgPopupView, x0, y0, x0+width, y0+height, 0)
if err != nil {
- if err != gocui.ErrUnknownView {
+ if !gocui.IsUnknownView(err) {
return err
}
diff --git a/termui/show_bug.go b/termui/show_bug.go
index 50478b8f..6c7163ac 100644
--- a/termui/show_bug.go
+++ b/termui/show_bug.go
@@ -6,7 +6,7 @@ import (
"strings"
"github.com/MichaelMure/go-term-text"
- "github.com/MichaelMure/gocui"
+ "github.com/awesome-gocui/gocui"
"github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/cache"
@@ -49,10 +49,10 @@ func (sb *showBug) layout(g *gocui.Gui) error {
maxX, maxY := g.Size()
sb.childViews = nil
- v, err := g.SetView(showBugView, 0, 0, maxX*2/3, maxY-2)
+ v, err := g.SetView(showBugView, 0, 0, maxX*2/3, maxY-2, 0)
if err != nil {
- if err != gocui.ErrUnknownView {
+ if !gocui.IsUnknownView(err) {
return err
}
@@ -66,10 +66,10 @@ func (sb *showBug) layout(g *gocui.Gui) error {
return err
}
- v, err = g.SetView(showBugSidebarView, maxX*2/3+1, 0, maxX-1, maxY-2)
+ v, err = g.SetView(showBugSidebarView, maxX*2/3+1, 0, maxX-1, maxY-2, 0)
if err != nil {
- if err != gocui.ErrUnknownView {
+ if !gocui.IsUnknownView(err) {
return err
}
@@ -83,10 +83,10 @@ func (sb *showBug) layout(g *gocui.Gui) error {
return err
}
- v, err = g.SetView(showBugInstructionView, -1, maxY-2, maxX, maxY)
+ v, err = g.SetView(showBugInstructionView, -1, maxY-2, maxX, maxY, 0)
if err != nil {
- if err != gocui.ErrUnknownView {
+ if !gocui.IsUnknownView(err) {
return err
}
@@ -190,7 +190,7 @@ func (sb *showBug) keybindings(g *gocui.Gui) error {
func (sb *showBug) disable(g *gocui.Gui) error {
for _, view := range sb.childViews {
- if err := g.DeleteView(view); err != nil && err != gocui.ErrUnknownView {
+ if err := g.DeleteView(view); err != nil && !gocui.IsUnknownView(err) {
return err
}
}
@@ -383,9 +383,9 @@ func emptyMessagePlaceholder() string {
}
func (sb *showBug) createOpView(g *gocui.Gui, name string, x0 int, y0 int, maxX int, height int, selectable bool) (*gocui.View, error) {
- v, err := g.SetView(name, x0, y0, maxX, y0+height+1)
+ v, err := g.SetView(name, x0, y0, maxX, y0+height+1, 0)
- if err != nil && err != gocui.ErrUnknownView {
+ if err != nil && !gocui.IsUnknownView(err) {
return nil, err
}
@@ -403,9 +403,9 @@ func (sb *showBug) createOpView(g *gocui.Gui, name string, x0 int, y0 int, maxX
}
func (sb *showBug) createSideView(g *gocui.Gui, name string, x0 int, y0 int, maxX int, height int) (*gocui.View, error) {
- v, err := g.SetView(name, x0, y0, maxX, y0+height+1)
+ v, err := g.SetView(name, x0, y0, maxX, y0+height+1, 0)
- if err != nil && err != gocui.ErrUnknownView {
+ if err != nil && !gocui.IsUnknownView(err) {
return nil, err
}
diff --git a/termui/termui.go b/termui/termui.go
index 8aece020..67f91280 100644
--- a/termui/termui.go
+++ b/termui/termui.go
@@ -2,9 +2,13 @@
package termui
import (
- "github.com/MichaelMure/gocui"
+ "fmt"
+
+ "github.com/awesome-gocui/gocui"
"github.com/pkg/errors"
+ errors2 "github.com/go-errors/errors"
+
"github.com/MichaelMure/git-bug/cache"
"github.com/MichaelMure/git-bug/entity"
"github.com/MichaelMure/git-bug/input"
@@ -63,15 +67,15 @@ func Run(cache *cache.RepoCache) error {
err := <-ui.gError
if err != nil && err != gocui.ErrQuit {
+ fmt.Println(err.(*errors2.Error).ErrorStack())
return err
}
-
return nil
}
func initGui(action func(ui *termUI) error) {
- g, err := gocui.NewGui(gocui.Output256)
+ g, err := gocui.NewGui(gocui.Output256, false)
if err != nil {
ui.gError <- err
diff --git a/vendor/github.com/MichaelMure/gocui/.gitignore b/vendor/github.com/awesome-gocui/gocui/.gitignore
index 1377554e..1377554e 100644
--- a/vendor/github.com/MichaelMure/gocui/.gitignore
+++ b/vendor/github.com/awesome-gocui/gocui/.gitignore
diff --git a/vendor/github.com/MichaelMure/gocui/AUTHORS b/vendor/github.com/awesome-gocui/gocui/AUTHORS
index 43ec4cec..43ec4cec 100644
--- a/vendor/github.com/MichaelMure/gocui/AUTHORS
+++ b/vendor/github.com/awesome-gocui/gocui/AUTHORS
diff --git a/vendor/github.com/awesome-gocui/gocui/CODE_OF_CONDUCT.md b/vendor/github.com/awesome-gocui/gocui/CODE_OF_CONDUCT.md
new file mode 100644
index 00000000..1bdac055
--- /dev/null
+++ b/vendor/github.com/awesome-gocui/gocui/CODE_OF_CONDUCT.md
@@ -0,0 +1,76 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, sex characteristics, gender identity and expression,
+level of experience, education, socio-economic status, nationality, personal
+appearance, race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+ advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project team at mkopenga@gmail.com. All
+complaints will be reviewed and investigated and will result in a response that
+is deemed necessary and appropriate to the circumstances. The project team is
+obligated to maintain confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
+available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see
+https://www.contributor-covenant.org/faq
diff --git a/vendor/github.com/awesome-gocui/gocui/CONTRIBUTING.md b/vendor/github.com/awesome-gocui/gocui/CONTRIBUTING.md
new file mode 100644
index 00000000..b93e45b2
--- /dev/null
+++ b/vendor/github.com/awesome-gocui/gocui/CONTRIBUTING.md
@@ -0,0 +1,33 @@
+# Contributing
+
+Everyone is welcome to help make gocui better!
+
+When contributing to this repository, please first discuss the change you wish
+to make via issue, email, or any other method with the owners of this repository
+before making a change.
+
+## So all code changes happen through Pull Requests
+Pull requests are the best way to propose changes to the codebase. We actively
+welcome your pull requests:
+
+1. Fork the repo and create your branch from `master` with a name like `feature/contributors-guide`.
+2. If you've added code that should be tested, add tests.
+3. If you've added code that need documentation, update the documentation.
+4. Make sure your code follows the [effective go](https://golang.org/doc/effective_go.html) guidelines as much as possible.
+5. Be sure to test your modifications.
+6. Make sure your branch is up to date with the master branch.
+7. Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
+8. Create that pull request!
+
+## Code of conduct
+Please note by participating in this project, you agree to abide by the [code of conduct].
+
+[code of conduct]: https://github.com/awesome-gocui/gocui/blob/master/CODE-OF-CONDUCT.md
+
+## Any contributions you make will be under the license indicated in the [license](LICENSE.md)
+In short, when you submit code changes, your submissions are understood to be
+under the same license as the rest of project. Feel free to contact the maintainers if that's a concern.
+
+## Report bugs using Github's [issues](https://github.com/awesome-gocui/gocui/issues)
+We use GitHub issues to track public bugs. Report a bug by [opening a new
+issue](https://github.com/awesome-gocui/gocui/issues/new); it's that easy! \ No newline at end of file
diff --git a/vendor/github.com/MichaelMure/gocui/LICENSE b/vendor/github.com/awesome-gocui/gocui/LICENSE
index 8cb28215..8cb28215 100644
--- a/vendor/github.com/MichaelMure/gocui/LICENSE
+++ b/vendor/github.com/awesome-gocui/gocui/LICENSE
diff --git a/vendor/github.com/MichaelMure/gocui/README.md b/vendor/github.com/awesome-gocui/gocui/README.md
index d7b55a3b..be212c58 100644
--- a/vendor/github.com/MichaelMure/gocui/README.md
+++ b/vendor/github.com/awesome-gocui/gocui/README.md
@@ -1,8 +1,13 @@
# GOCUI - Go Console User Interface
+[![CircleCI](https://circleci.com/gh/awesome-gocui/gocui/tree/master.svg?style=svg)](https://circleci.com/gh/awesome-gocui/gocui/tree/master)
+[![CodeCov](https://codecov.io/gh/awesome-gocui/gocui/branch/master/graph/badge.svg)](https://codecov.io/gh/awesome-gocui/gocui)
+[![Go Report Card](https://goreportcard.com/badge/github.com/awesome-gocui/gocui)](https://goreportcard.com/report/github.com/awesome-gocui/gocui)
+[![GolangCI](https://golangci.com/badges/github.com/awesome-gocui/gocui.svg)](https://golangci.com/badges/github.com/awesome-gocui/gocui.svg)
+[![GoDoc](https://godoc.org/github.com/awesome-gocui/gocui?status.svg)](https://godoc.org/github.com/awesome-gocui/gocui)
+![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/awesome-gocui/gocui.svg)
-[![GoDoc](https://godoc.org/github.com/jroimartin/gocui?status.svg)](https://godoc.org/github.com/jroimartin/gocui)
-
-Minimalist Go package aimed at creating Console User Interfaces.
+Minimalist Go package aimed at creating Console User Interfaces.
+A community fork based on the amazing work of [jroimartin](https://github.com/jroimartin/gocui)
## Features
@@ -13,15 +18,29 @@ Minimalist Go package aimed at creating Console User Interfaces.
* Global and view-level keybindings.
* Mouse support.
* Colored text.
-* Customizable edition mode.
+* Customizable editing mode.
* Easy to build reusable widgets, complex layouts...
+## About fork
+
+This fork has many improvements over the original work from [jroimartin](https://github.com/jroimartin/gocui).
+
+* Better wide character support
+* Support for 1 Line height views
+* Better support for running in docker container
+* Customize frame colors
+* Improved code comments and quality
+* Many small improvements
+* Change Visibility of views
+
+For information about this org see: [awesome-gocui/about](https://github.com/awesome-gocui/about).
+
## Installation
Execute:
```
-$ go get github.com/jroimartin/gocui
+$ go get github.com/awesome-gocui/gocui
```
## Documentation
@@ -29,13 +48,14 @@ $ go get github.com/jroimartin/gocui
Execute:
```
-$ go doc github.com/jroimartin/gocui
+$ go doc github.com/awesome-gocui/gocui
```
-Or visit [godoc.org](https://godoc.org/github.com/jroimartin/gocui) to read it
+Or visit [godoc.org](https://godoc.org/github.com/awesome-gocui/gocui) to read it
online.
## Example
+See the [_example](./_example/) folder for more examples
```go
package main
@@ -44,11 +64,11 @@ import (
"fmt"
"log"
- "github.com/jroimartin/gocui"
+ "github.com/awesome-gocui/gocui"
)
func main() {
- g, err := gocui.NewGui(gocui.OutputNormal)
+ g, err := gocui.NewGui(gocui.OutputNormal, false)
if err != nil {
log.Panicln(err)
}
@@ -60,18 +80,21 @@ func main() {
log.Panicln(err)
}
- if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
+ if err := g.MainLoop(); err != nil && !gocui.IsQuit(err) {
log.Panicln(err)
}
}
func layout(g *gocui.Gui) error {
maxX, maxY := g.Size()
- if v, err := g.SetView("hello", maxX/2-7, maxY/2, maxX/2+7, maxY/2+2); err != nil {
- if err != gocui.ErrUnknownView {
+ if v, err := g.SetView("hello", maxX/2-7, maxY/2, maxX/2+7, maxY/2+2, 0); err != nil {
+ if !gocui.IsUnknownView(err) {
return err
}
fmt.Fprintln(v, "Hello world!")
+ if _, err := g.SetCurrentView("hello"); err != nil {
+ return err
+ }
}
return nil
}
@@ -106,5 +129,7 @@ func quit(g *gocui.Gui, v *gocui.View) error {
* [fac](https://github.com/mkchoi212/fac): git merge conflict resolver
* [jsonui](https://github.com/gulyasm/jsonui): Interactive JSON explorer for your terminal.
* [cointop](https://github.com/miguelmota/cointop): Interactive terminal based UI application for tracking cryptocurrencies.
+* [lazygit](https://github.com/jesseduffield/lazygit): simple terminal UI for git commands.
+* [lazydocker](https://github.com/jesseduffield/lazydocker): The lazier way to manage everything docker.
Note: if your project is not listed here, let us know! :)
diff --git a/vendor/github.com/MichaelMure/gocui/attribute.go b/vendor/github.com/awesome-gocui/gocui/attribute.go
index bad758a1..3d986a71 100644
--- a/vendor/github.com/MichaelMure/gocui/attribute.go
+++ b/vendor/github.com/awesome-gocui/gocui/attribute.go
@@ -4,7 +4,7 @@
package gocui
-import "github.com/nsf/termbox-go"
+import "github.com/awesome-gocui/termbox-go"
// Attribute represents a terminal attribute, like color, font style, etc. They
// can be combined using bitwise OR (|). Note that it is not possible to
diff --git a/vendor/github.com/MichaelMure/gocui/doc.go b/vendor/github.com/awesome-gocui/gocui/doc.go
index fe128afb..ca7113fa 100644
--- a/vendor/github.com/MichaelMure/gocui/doc.go
+++ b/vendor/github.com/awesome-gocui/gocui/doc.go
@@ -16,7 +16,7 @@ Create a new GUI:
// Set GUI managers and key bindings
// ...
- if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
+ if err := g.MainLoop(); err != nil && !gocui.IsQuit(err) {
// handle error
}
@@ -38,7 +38,7 @@ their content. The same is valid for reading.
Create and initialize a view with absolute coordinates:
if v, err := g.SetView("viewname", 2, 2, 22, 7); err != nil {
- if err != gocui.ErrUnknownView {
+ if !gocui.IsUnknownView(err) {
// handle error
}
fmt.Fprintln(v, "This is a new view")
@@ -84,7 +84,7 @@ use *Gui.Update(). For example:
return nil
})
-By default, gocui provides a basic edition mode. This mode can be extended
+By default, gocui provides a basic editing mode. This mode can be extended
and customized creating a new Editor and assigning it to *View.Editor:
type Editor interface {
diff --git a/vendor/github.com/MichaelMure/gocui/edit.go b/vendor/github.com/awesome-gocui/gocui/edit.go
index de86580f..b5630df3 100644
--- a/vendor/github.com/MichaelMure/gocui/edit.go
+++ b/vendor/github.com/awesome-gocui/gocui/edit.go
@@ -5,7 +5,7 @@
package gocui
import (
- "errors"
+ "github.com/go-errors/errors"
"github.com/mattn/go-runewidth"
)
@@ -53,13 +53,64 @@ func simpleEditor(v *View, key Key, ch rune, mod Modifier) {
v.MoveCursor(-1, 0, false)
case key == KeyArrowRight:
v.MoveCursor(1, 0, false)
+ case key == KeyTab:
+ v.EditWrite('\t')
+ case key == KeySpace:
+ v.EditWrite(' ')
+ case key == KeyInsert:
+ v.Overwrite = !v.Overwrite
+ default:
+ v.EditWrite(ch)
}
}
// EditWrite writes a rune at the cursor position.
func (v *View) EditWrite(ch rune) {
+ w := runewidth.RuneWidth(ch)
v.writeRune(v.cx, v.cy, ch)
- v.MoveCursor(runewidth.RuneWidth(ch), 0, true)
+ v.moveCursor(w, 0, true)
+}
+
+// EditDeleteToStartOfLine is the equivalent of pressing ctrl+U in your terminal, it deletes to the start of the line. Or if you are already at the start of the line, it deletes the newline character
+func (v *View) EditDeleteToStartOfLine() {
+ x, _ := v.Cursor()
+ if x == 0 {
+ v.EditDelete(true)
+ } else {
+ // delete characters until we are the start of the line
+ for x > 0 {
+ v.EditDelete(true)
+ x, _ = v.Cursor()
+ }
+ }
+}
+
+// EditGotoToStartOfLine takes you to the start of the current line
+func (v *View) EditGotoToStartOfLine() {
+ x, _ := v.Cursor()
+ for x > 0 {
+ v.MoveCursor(-1, 0, false)
+ x, _ = v.Cursor()
+ }
+}
+
+// EditGotoToEndOfLine takes you to the end of the line
+func (v *View) EditGotoToEndOfLine() {
+ _, y := v.Cursor()
+ _ = v.SetCursor(0, y+1)
+ x, newY := v.Cursor()
+ if newY == y {
+ // we must be on the last line, so lets move to the very end
+ prevX := -1
+ for prevX != x {
+ prevX = x
+ v.MoveCursor(1, 0, false)
+ x, _ = v.Cursor()
+ }
+ } else {
+ // most left so now we're at the end of the original line
+ v.MoveCursor(-1, 0, false)
+ }
}
// EditDelete deletes a rune at the cursor position. back determines the
@@ -93,12 +144,12 @@ func (v *View) EditDelete(back bool) {
v.MoveCursor(-1, 0, true)
}
} else { // wrapped line
- ch, _ := v.deleteRune(len(v.viewLines[y-1].line)-1, v.cy-1)
- v.MoveCursor(0-runewidth.RuneWidth(ch), 0, true)
+ n, _ := v.deleteRune(len(v.viewLines[y-1].line)-1, v.cy-1)
+ v.MoveCursor(-n, 0, true)
}
} else { // middle/end of the line
- ch, _ := v.deleteRune(v.cx-1, v.cy)
- v.MoveCursor(0-runewidth.RuneWidth(ch), 0, true)
+ n, _ := v.deleteRune(v.cx-1, v.cy)
+ v.MoveCursor(-n, 0, true)
}
} else {
if x == len(v.viewLines[y].line) { // end of the line
@@ -113,42 +164,81 @@ func (v *View) EditDelete(back bool) {
func (v *View) EditNewLine() {
v.breakLine(v.cx, v.cy)
v.ox = 0
+ v.cy = v.cy + 1
v.cx = 0
- v.MoveCursor(0, 1, true)
}
// MoveCursor moves the cursor taking into account the width of the line/view,
// displacing the origin if necessary.
func (v *View) MoveCursor(dx, dy int, writeMode bool) {
+ ox, oy := v.cx+v.ox, v.cy+v.oy
+ x, y := ox+dx, oy+dy
+
+ if y < 0 || y >= len(v.viewLines) {
+ v.moveCursor(dx, dy, writeMode)
+ return
+ }
+
+ // Removing newline.
+ if x < 0 {
+ var prevLen int
+ if y-1 >= 0 && y-1 < len(v.viewLines) {
+ prevLen = lineWidth(v.viewLines[y-1].line)
+ }
+
+ v.MoveCursor(prevLen, -1, writeMode)
+ return
+ }
+
+ line := v.viewLines[y].line
+ var col int
+ var prevCol int
+ for i := range line {
+ prevCol = col
+ col += runewidth.RuneWidth(line[i].chr)
+ if dx > 0 {
+ if x <= col {
+ x = col
+ break
+ }
+ continue
+ }
+
+ if x < col {
+ x = prevCol
+ break
+ }
+ }
+
+ v.moveCursor(x-ox, y-oy, writeMode)
+}
+
+func (v *View) moveCursor(dx, dy int, writeMode bool) {
maxX, maxY := v.Size()
cx, cy := v.cx+dx, v.cy+dy
x, y := v.ox+cx, v.oy+cy
var curLineWidth, prevLineWidth int
// get the width of the current line
- if writeMode {
- if v.Wrap {
- curLineWidth = maxX - 1
- } else {
- curLineWidth = maxInt
- }
- } else {
+ curLineWidth = maxInt
+ if v.Wrap {
+ curLineWidth = maxX - 1
+ }
+
+ if !writeMode {
+ curLineWidth = 0
if y >= 0 && y < len(v.viewLines) {
- curLineWidth = len(v.viewLines[y].line)
+ curLineWidth = lineWidth(v.viewLines[y].line)
if v.Wrap && curLineWidth >= maxX {
curLineWidth = maxX - 1
}
- } else {
- curLineWidth = 0
}
}
// get the width of the previous line
+ prevLineWidth = 0
if y-1 >= 0 && y-1 < len(v.viewLines) {
- prevLineWidth = len(v.viewLines[y-1].line)
- } else {
- prevLineWidth = 0
+ prevLineWidth = lineWidth(v.viewLines[y-1].line)
}
-
// adjust cursor's x position and view's x origin
if x > curLineWidth { // move to next line
if dx > 0 { // horizontal movement
@@ -194,10 +284,9 @@ func (v *View) MoveCursor(dx, dy int, writeMode bool) {
if !v.Wrap { // set origin so the EOL is visible
nox := prevLineWidth - maxX + 1
if nox < 0 {
- v.ox = 0
- } else {
- v.ox = nox
+ nox = 0
}
+ v.ox = nox
}
v.cx = prevLineWidth
} else {
@@ -279,10 +368,11 @@ func (v *View) writeRune(x, y int, ch rune) error {
// deleteRune removes a rune from the view's internal buffer, at the
// position corresponding to the point (x, y).
-func (v *View) deleteRune(x, y int) (ch rune, err error) {
+// returns the amount of columns that where removed.
+func (v *View) deleteRune(x, y int) (int, error) {
v.tainted = true
- x, y, err = v.realPosition(x, y)
+ x, y, err := v.realPosition(x, y)
if err != nil {
return 0, err
}
@@ -290,9 +380,19 @@ func (v *View) deleteRune(x, y int) (ch rune, err error) {
if x < 0 || y < 0 || y >= len(v.lines) || x >= len(v.lines[y]) {
return 0, errors.New("invalid point")
}
- chx := v.lines[y][x]
- v.lines[y] = append(v.lines[y][:x], v.lines[y][x+1:]...)
- return chx.chr, nil
+
+ var tw int
+ for i := range v.lines[y] {
+ w := runewidth.RuneWidth(v.lines[y][i].chr)
+ tw += w
+ if tw > x {
+ v.lines[y] = append(v.lines[y][:i], v.lines[y][i+1:]...)
+ return w, nil
+ }
+
+ }
+
+ return 0, nil
}
// mergeLines merges the lines "y" and "y+1" if possible.
diff --git a/vendor/github.com/MichaelMure/gocui/escape.go b/vendor/github.com/awesome-gocui/gocui/escape.go
index ec31bbe0..c88309b0 100644
--- a/vendor/github.com/MichaelMure/gocui/escape.go
+++ b/vendor/github.com/awesome-gocui/gocui/escape.go
@@ -5,7 +5,7 @@
package gocui
import (
- "errors"
+ "github.com/go-errors/errors"
"strconv"
)
diff --git a/vendor/github.com/awesome-gocui/gocui/go.mod b/vendor/github.com/awesome-gocui/gocui/go.mod
new file mode 100644
index 00000000..5791b4e4
--- /dev/null
+++ b/vendor/github.com/awesome-gocui/gocui/go.mod
@@ -0,0 +1,9 @@
+module github.com/awesome-gocui/gocui
+
+go 1.12
+
+require (
+ github.com/awesome-gocui/termbox-go v0.0.0-20190427202837-c0aef3d18bcc
+ github.com/go-errors/errors v1.0.1
+ github.com/mattn/go-runewidth v0.0.4
+)
diff --git a/vendor/github.com/awesome-gocui/gocui/go.sum b/vendor/github.com/awesome-gocui/gocui/go.sum
new file mode 100644
index 00000000..25f1c037
--- /dev/null
+++ b/vendor/github.com/awesome-gocui/gocui/go.sum
@@ -0,0 +1,6 @@
+github.com/awesome-gocui/termbox-go v0.0.0-20190427202837-c0aef3d18bcc h1:wGNpKcHU8Aadr9yOzsT3GEsFLS7HQu8HxQIomnekqf0=
+github.com/awesome-gocui/termbox-go v0.0.0-20190427202837-c0aef3d18bcc/go.mod h1:tOy3o5Nf1bA17mnK4W41gD7PS3u4Cv0P0pqFcoWMy8s=
+github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
+github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
+github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
+github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
diff --git a/vendor/github.com/MichaelMure/gocui/gui.go b/vendor/github.com/awesome-gocui/gocui/gui.go
index 9499d3c3..6fe0d5d8 100644
--- a/vendor/github.com/MichaelMure/gocui/gui.go
+++ b/vendor/github.com/awesome-gocui/gocui/gui.go
@@ -5,21 +5,36 @@
package gocui
import (
- "errors"
+ standardErrors "errors"
+ "runtime"
- "github.com/nsf/termbox-go"
+ "github.com/go-errors/errors"
+
+ "github.com/awesome-gocui/termbox-go"
)
+// OutputMode represents the terminal's output mode (8 or 256 colors).
+type OutputMode termbox.OutputMode
+
var (
- // ErrQuit is used to decide if the MainLoop finished successfully.
- ErrQuit = errors.New("quit")
+ // ErrAlreadyBlacklisted is returned when the keybinding is already blacklisted.
+ ErrAlreadyBlacklisted = standardErrors.New("keybind already blacklisted")
+
+ // ErrBlacklisted is returned when the keybinding being parsed / used is blacklisted.
+ ErrBlacklisted = standardErrors.New("keybind blacklisted")
+
+ // ErrNotBlacklisted is returned when a keybinding being whitelisted is not blacklisted.
+ ErrNotBlacklisted = standardErrors.New("keybind not blacklisted")
+
+ // ErrNoSuchKeybind is returned when the keybinding being parsed does not exist.
+ ErrNoSuchKeybind = standardErrors.New("no such keybind")
// ErrUnknownView allows to assert if a View must be initialized.
- ErrUnknownView = errors.New("unknown view")
-)
+ ErrUnknownView = standardErrors.New("unknown view")
-// OutputMode represents the terminal's output mode (8 or 256 colors).
-type OutputMode termbox.OutputMode
+ // ErrQuit is used to decide if the MainLoop finished successfully.
+ ErrQuit = standardErrors.New("quit")
+)
const (
// OutputNormal provides 8-colors terminal mode.
@@ -27,6 +42,12 @@ const (
// Output256 provides 256-colors terminal mode.
Output256 = OutputMode(termbox.Output256)
+
+ // OutputGrayScale provides greyscale terminal mode.
+ OutputGrayScale = OutputMode(termbox.OutputGrayscale)
+
+ // Output216 provides greyscale terminal mode.
+ Output216 = OutputMode(termbox.Output216)
)
// Gui represents the whole User Interface, including the views, layouts
@@ -40,14 +61,16 @@ type Gui struct {
keybindings []*keybinding
maxX, maxY int
outputMode OutputMode
+ stop chan struct{}
+ blacklist []Key
// BgColor and FgColor allow to configure the background and foreground
// colors of the GUI.
- BgColor, FgColor Attribute
+ BgColor, FgColor, FrameColor Attribute
// SelBgColor and SelFgColor allow to configure the background and
// foreground colors of the frame of the current view.
- SelBgColor, SelFgColor Attribute
+ SelBgColor, SelFgColor, SelFrameColor Attribute
// If Highlight is true, Sel{Bg,Fg}Colors will be used to draw the
// frame of the current view.
@@ -66,11 +89,16 @@ type Gui struct {
// If ASCII is true then use ASCII instead of unicode to draw the
// interface. Using ASCII is more portable.
ASCII bool
+
+ // SupportOverlaps is true when we allow for view edges to overlap with other
+ // view edges
+ SupportOverlaps bool
}
// NewGui returns a new Gui object with a given output mode.
-func NewGui(mode OutputMode) (*Gui, error) {
- if err := termbox.Init(); err != nil {
+func NewGui(mode OutputMode, supportOverlaps bool) (*Gui, error) {
+ err := termbox.Init()
+ if err != nil {
return nil, err
}
@@ -79,20 +107,36 @@ func NewGui(mode OutputMode) (*Gui, error) {
g.outputMode = mode
termbox.SetOutputMode(termbox.OutputMode(mode))
+ g.stop = make(chan struct{})
+
g.tbEvents = make(chan termbox.Event, 20)
g.userEvents = make(chan userEvent, 20)
- g.maxX, g.maxY = termbox.Size()
+ if runtime.GOOS != "windows" {
+ g.maxX, g.maxY, err = g.getTermWindowSize()
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ g.maxX, g.maxY = termbox.Size()
+ }
g.BgColor, g.FgColor = ColorDefault, ColorDefault
g.SelBgColor, g.SelFgColor = ColorDefault, ColorDefault
+ // SupportOverlaps is true when we allow for view edges to overlap with other
+ // view edges
+ g.SupportOverlaps = supportOverlaps
+
return g, nil
}
// Close finalizes the library. It should be called after a successful
// initialization and when gocui is not needed anymore.
func (g *Gui) Close() {
+ go func() {
+ g.stop <- struct{}{}
+ }()
termbox.Close()
}
@@ -127,8 +171,8 @@ func (g *Gui) Rune(x, y int) (rune, error) {
// already exists, its dimensions are updated; otherwise, the error
// ErrUnknownView is returned, which allows to assert if the View must
// be initialized. It checks if the position is valid.
-func (g *Gui) SetView(name string, x0, y0, x1, y1 int) (*View, error) {
- if x0 >= x1 || y0 >= y1 {
+func (g *Gui) SetView(name string, x0, y0, x1, y1 int, overlaps byte) (*View, error) {
+ if x0 >= x1 {
return nil, errors.New("invalid dimensions")
}
if name == "" {
@@ -147,8 +191,20 @@ func (g *Gui) SetView(name string, x0, y0, x1, y1 int) (*View, error) {
v := newView(name, x0, y0, x1, y1, g.outputMode)
v.BgColor, v.FgColor = g.BgColor, g.FgColor
v.SelBgColor, v.SelFgColor = g.SelBgColor, g.SelFgColor
+ v.Overlaps = overlaps
g.views = append(g.views, v)
- return v, ErrUnknownView
+ return v, errors.Wrap(ErrUnknownView, 0)
+}
+
+// SetViewBeneath sets a view stacked beneath another view
+func (g *Gui) SetViewBeneath(name string, aboveViewName string, height int) (*View, error) {
+ aboveView, err := g.View(aboveViewName)
+ if err != nil {
+ return nil, err
+ }
+
+ viewTop := aboveView.y1 + 1
+ return g.SetView(name, aboveView.x0, viewTop, aboveView.x1, viewTop+height-1, 0)
}
// SetViewOnTop sets the given view on top of the existing ones.
@@ -160,7 +216,7 @@ func (g *Gui) SetViewOnTop(name string) (*View, error) {
return v, nil
}
}
- return nil, ErrUnknownView
+ return nil, errors.Wrap(ErrUnknownView, 0)
}
// SetViewOnBottom sets the given view on bottom of the existing ones.
@@ -172,7 +228,7 @@ func (g *Gui) SetViewOnBottom(name string) (*View, error) {
return v, nil
}
}
- return nil, ErrUnknownView
+ return nil, errors.Wrap(ErrUnknownView, 0)
}
// Views returns all the views in the GUI.
@@ -188,7 +244,7 @@ func (g *Gui) View(name string) (*View, error) {
return v, nil
}
}
- return nil, ErrUnknownView
+ return nil, errors.Wrap(ErrUnknownView, 0)
}
// ViewByPosition returns a pointer to a view matching the given position, or
@@ -201,7 +257,7 @@ func (g *Gui) ViewByPosition(x, y int) (*View, error) {
return v, nil
}
}
- return nil, ErrUnknownView
+ return nil, errors.Wrap(ErrUnknownView, 0)
}
// ViewPosition returns the coordinates of the view with the given name, or
@@ -212,7 +268,7 @@ func (g *Gui) ViewPosition(name string) (x0, y0, x1, y1 int, err error) {
return v.x0, v.y0, v.x1, v.y1, nil
}
}
- return 0, 0, 0, 0, ErrUnknownView
+ return 0, 0, 0, 0, errors.Wrap(ErrUnknownView, 0)
}
// DeleteView deletes a view by name.
@@ -223,7 +279,7 @@ func (g *Gui) DeleteView(name string) error {
return nil
}
}
- return ErrUnknownView
+ return errors.Wrap(ErrUnknownView, 0)
}
// SetCurrentView gives the focus to a given view.
@@ -234,7 +290,7 @@ func (g *Gui) SetCurrentView(name string) (*View, error) {
return v, nil
}
}
- return nil, ErrUnknownView
+ return nil, errors.Wrap(ErrUnknownView, 0)
}
// CurrentView returns the currently focused view, or nil if no view
@@ -253,6 +309,11 @@ func (g *Gui) SetKeybinding(viewname string, key interface{}, mod Modifier, hand
if err != nil {
return err
}
+
+ if g.isBlacklisted(k) {
+ return ErrBlacklisted
+ }
+
kb = newKeybinding(viewname, k, ch, mod, handler)
g.keybindings = append(g.keybindings, kb)
return nil
@@ -285,6 +346,28 @@ func (g *Gui) DeleteKeybindings(viewname string) {
g.keybindings = s
}
+// BlackListKeybinding adds a keybinding to the blacklist
+func (g *Gui) BlacklistKeybinding(k Key) error {
+ for _, j := range g.blacklist {
+ if j == k {
+ return ErrAlreadyBlacklisted
+ }
+ }
+ g.blacklist = append(g.blacklist, k)
+ return nil
+}
+
+// WhiteListKeybinding removes a keybinding from the blacklist
+func (g *Gui) WhitelistKeybinding(k Key) error {
+ for i, j := range g.blacklist {
+ if j == k {
+ g.blacklist = append(g.blacklist[:i], g.blacklist[i+1:]...)
+ return nil
+ }
+ }
+ return ErrNotBlacklisted
+}
+
// getKey takes an empty interface with a key and returns the corresponding
// typed Key or rune.
func getKey(key interface{}) (Key, rune, error) {
@@ -349,14 +432,24 @@ func (g *Gui) SetManagerFunc(manager func(*Gui) error) {
// MainLoop runs the main loop until an error is returned. A successful
// finish should return ErrQuit.
func (g *Gui) MainLoop() error {
+ g.loaderTick()
+ if err := g.flush(); err != nil {
+ return err
+ }
+
go func() {
for {
- g.tbEvents <- termbox.PollEvent()
+ select {
+ case <-g.stop:
+ return
+ default:
+ g.tbEvents <- termbox.PollEvent()
+ }
}
}()
inputMode := termbox.InputAlt
- if g.InputEsc {
+ if true { // previously g.InputEsc, but didn't seem to work
inputMode = termbox.InputEsc
}
if g.Mouse {
@@ -437,20 +530,25 @@ func (g *Gui) flush() error {
}
}
for _, v := range g.views {
+ if !v.Visible || v.y1 < v.y0 {
+ continue
+ }
if v.Frame {
- var fgColor, bgColor Attribute
+ var fgColor, bgColor, frameColor Attribute
if g.Highlight && v == g.currentView {
fgColor = g.SelFgColor
bgColor = g.SelBgColor
+ frameColor = g.SelFrameColor
} else {
fgColor = g.FgColor
bgColor = g.BgColor
+ frameColor = g.FrameColor
}
- if err := g.drawFrameEdges(v, fgColor, bgColor); err != nil {
+ if err := g.drawFrameEdges(v, frameColor, bgColor); err != nil {
return err
}
- if err := g.drawFrameCorners(v, fgColor, bgColor); err != nil {
+ if err := g.drawFrameCorners(v, frameColor, bgColor); err != nil {
return err
}
if v.Title != "" {
@@ -458,6 +556,11 @@ func (g *Gui) flush() error {
return err
}
}
+ if v.Subtitle != "" {
+ if err := g.drawSubtitle(v, fgColor, bgColor); err != nil {
+ return err
+ }
+ }
}
if err := g.draw(v); err != nil {
return err
@@ -507,9 +610,36 @@ func (g *Gui) drawFrameEdges(v *View, fgColor, bgColor Attribute) error {
return nil
}
+func cornerRune(index byte) rune {
+ return []rune{' ', '│', '│', '│', '─', '┘', '┐', '┤', '─', '└', '┌', '├', '├', '┴', '┬', '┼'}[index]
+}
+
+func corner(v *View, directions byte) rune {
+ index := v.Overlaps | directions
+ return cornerRune(index)
+}
+
// drawFrameCorners draws the corners of the view.
func (g *Gui) drawFrameCorners(v *View, fgColor, bgColor Attribute) error {
+ if v.y0 == v.y1 {
+ if !g.SupportOverlaps && v.x0 >= 0 && v.x1 >= 0 && v.y0 >= 0 && v.x0 < g.maxX && v.x1 < g.maxX && v.y0 < g.maxY {
+ if err := g.SetRune(v.x0, v.y0, '╶', fgColor, bgColor); err != nil {
+ return err
+ }
+ if err := g.SetRune(v.x1, v.y0, '╴', fgColor, bgColor); err != nil {
+ return err
+ }
+ }
+ return nil
+ }
+
runeTL, runeTR, runeBL, runeBR := '┌', '┐', '└', '┘'
+ if g.SupportOverlaps {
+ runeTL = corner(v, BOTTOM|RIGHT)
+ runeTR = corner(v, BOTTOM|LEFT)
+ runeBL = corner(v, TOP|RIGHT)
+ runeBR = corner(v, TOP|LEFT)
+ }
if g.ASCII {
runeTL, runeTR, runeBL, runeBR = '+', '+', '+', '+'
}
@@ -549,6 +679,28 @@ func (g *Gui) drawTitle(v *View, fgColor, bgColor Attribute) error {
return nil
}
+// drawSubtitle draws the subtitle of the view.
+func (g *Gui) drawSubtitle(v *View, fgColor, bgColor Attribute) error {
+ if v.y0 < 0 || v.y0 >= g.maxY {
+ return nil
+ }
+
+ start := v.x1 - 5 - len(v.Subtitle)
+ if start < v.x0 {
+ return nil
+ }
+ for i, ch := range v.Subtitle {
+ x := start + i
+ if x >= v.x1 {
+ break
+ }
+ if err := g.SetRune(x, v.y0, ch, fgColor, bgColor); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
// draw manages the cursor and calls the draw function of a view.
func (g *Gui) draw(v *View) error {
if g.Cursor {
@@ -620,17 +772,61 @@ func (g *Gui) onKey(ev *termbox.Event) error {
// execKeybindings executes the keybinding handlers that match the passed view
// and event. The value of matched is true if there is a match and no errors.
func (g *Gui) execKeybindings(v *View, ev *termbox.Event) (matched bool, err error) {
- matched = false
+ var globalKb *keybinding
+
for _, kb := range g.keybindings {
if kb.handler == nil {
continue
}
- if kb.matchKeypress(Key(ev.Key), ev.Ch, Modifier(ev.Mod)) && kb.matchView(v) {
- if err := kb.handler(g, v); err != nil {
- return false, err
- }
- matched = true
+
+ if !kb.matchKeypress(Key(ev.Key), ev.Ch, Modifier(ev.Mod)) {
+ continue
+ }
+
+ if kb.matchView(v) {
+ return g.execKeybinding(v, kb)
+ }
+
+ if kb.viewName == "" && ((v != nil && !v.Editable) || kb.ch == 0) {
+ globalKb = kb
+ }
+ }
+
+ if globalKb != nil {
+ return g.execKeybinding(v, globalKb)
+ }
+
+ return false, nil
+}
+
+// execKeybinding executes a given keybinding
+func (g *Gui) execKeybinding(v *View, kb *keybinding) (bool, error) {
+ if g.isBlacklisted(kb.key) {
+ return true, nil
+ }
+
+ if err := kb.handler(g, v); err != nil {
+ return false, err
+ }
+ return true, nil
+}
+
+// isBlacklisted reports whether the key is blacklisted
+func (g *Gui) isBlacklisted(k Key) bool {
+ for _, j := range g.blacklist {
+ if j == k {
+ return true
}
}
- return matched, nil
+ return false
+}
+
+// IsUnknownView reports whether the contents of an error is "unknown view".
+func IsUnknownView(err error) bool {
+ return err != nil && err.Error() == ErrUnknownView.Error()
+}
+
+// IsQuit reports whether the contents of an error is "quit".
+func IsQuit(err error) bool {
+ return err != nil && err.Error() == ErrQuit.Error()
}
diff --git a/vendor/github.com/awesome-gocui/gocui/gui_others.go b/vendor/github.com/awesome-gocui/gocui/gui_others.go
new file mode 100644
index 00000000..5d247a19
--- /dev/null
+++ b/vendor/github.com/awesome-gocui/gocui/gui_others.go
@@ -0,0 +1,60 @@
+// Copyright 2014 The gocui Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !windows
+
+package gocui
+
+import (
+ "os"
+ "os/signal"
+ "syscall"
+ "unsafe"
+
+ "github.com/go-errors/errors"
+)
+
+// getTermWindowSize is get terminal window size on linux or unix.
+// When gocui run inside the docker contaienr need to check and get the window size.
+func (g *Gui) getTermWindowSize() (int, int, error) {
+ var sz struct {
+ rows uint16
+ cols uint16
+ _ [2]uint16 // to match underlying syscall; see https://github.com/awesome-gocui/gocui/issues/33
+ }
+
+ var termw, termh int
+
+ out, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
+ if err != nil {
+ return 0, 0, err
+ }
+ defer out.Close()
+
+ signalCh := make(chan os.Signal, 1)
+ signal.Notify(signalCh, syscall.SIGWINCH, syscall.SIGINT)
+
+ for {
+ _, _, _ = syscall.Syscall(syscall.SYS_IOCTL,
+ out.Fd(), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&sz)))
+
+ // check terminal window size
+ termw, termh = int(sz.cols), int(sz.rows)
+ if termw > 0 && termh > 0 {
+ return termw, termh, nil
+ }
+
+ select {
+ case signal := <-signalCh:
+ switch signal {
+ // when the terminal window size is changed
+ case syscall.SIGWINCH:
+ continue
+ // ctrl + c to cancel
+ case syscall.SIGINT:
+ return 0, 0, errors.New("stop to get term window size")
+ }
+ }
+ }
+}
diff --git a/vendor/github.com/awesome-gocui/gocui/gui_windows.go b/vendor/github.com/awesome-gocui/gocui/gui_windows.go
new file mode 100644
index 00000000..db1faab7
--- /dev/null
+++ b/vendor/github.com/awesome-gocui/gocui/gui_windows.go
@@ -0,0 +1,53 @@
+// Copyright 2014 The gocui Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build windows
+
+package gocui
+
+import (
+ "os"
+ "syscall"
+ "unsafe"
+)
+
+type wchar uint16
+type short int16
+type dword uint32
+type word uint16
+
+type coord struct {
+ x short
+ y short
+}
+
+type smallRect struct {
+ left short
+ top short
+ right short
+ bottom short
+}
+
+type consoleScreenBufferInfo struct {
+ size coord
+ cursorPosition coord
+ attributes word
+ window smallRect
+ maximumWindowSize coord
+}
+
+var (
+ kernel32 = syscall.NewLazyDLL("kernel32.dll")
+ procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
+)
+
+// getTermWindowSize is get terminal window size on windows.
+func (g *Gui) getTermWindowSize() (int, int, error) {
+ var csbi consoleScreenBufferInfo
+ r1, _, err := procGetConsoleScreenBufferInfo.Call(os.Stdout.Fd(), uintptr(unsafe.Pointer(&csbi)))
+ if r1 == 0 {
+ return 0, 0, err
+ }
+ return int(csbi.window.right - csbi.window.left + 1), int(csbi.window.bottom - csbi.window.top + 1), nil
+}
diff --git a/vendor/github.com/MichaelMure/gocui/keybinding.go b/vendor/github.com/awesome-gocui/gocui/keybinding.go
index 03fe677c..d294e70d 100644
--- a/vendor/github.com/MichaelMure/gocui/keybinding.go
+++ b/vendor/github.com/awesome-gocui/gocui/keybinding.go
@@ -4,7 +4,18 @@
package gocui
-import "github.com/nsf/termbox-go"
+import (
+ "strings"
+
+ "github.com/awesome-gocui/termbox-go"
+)
+
+// Key represents special keys or keys combinations.
+type Key termbox.Key
+
+// Modifier allows to define special keys combinations. They can be used
+// in combination with Keys or Runes when a new keybinding is defined.
+type Modifier termbox.Modifier
// Keybidings are used to link a given key-press event with a handler.
type keybinding struct {
@@ -15,6 +26,71 @@ type keybinding struct {
handler func(*Gui, *View) error
}
+// Parse takes the input string and extracts the keybinding.
+// Returns a Key / rune, a Modifier and an error.
+func Parse(input string) (interface{}, Modifier, error) {
+ if len(input) == 1 {
+ _, r, err := getKey(rune(input[0]))
+ if err != nil {
+ return nil, ModNone, err
+ }
+ return r, ModNone, nil
+ }
+
+ var modifier Modifier
+ cleaned := make([]string, 0)
+
+ tokens := strings.Split(input, "+")
+ for _, t := range tokens {
+ normalized := strings.Title(strings.ToLower(t))
+ if t == "Alt" {
+ modifier = ModAlt
+ continue
+ }
+ cleaned = append(cleaned, normalized)
+ }
+
+ key, exist := translate[strings.Join(cleaned, "")]
+ if !exist {
+ return nil, ModNone, ErrNoSuchKeybind
+ }
+
+ return key, modifier, nil
+}
+
+// ParseAll takes an array of strings and returns a map of all keybindings.
+func ParseAll(input []string) (map[interface{}]Modifier, error) {
+ ret := make(map[interface{}]Modifier)
+ for _, i := range input {
+ k, m, err := Parse(i)
+ if err != nil {
+ return ret, err
+ }
+ ret[k] = m
+ }
+ return ret, nil
+}
+
+// MustParse takes the input string and returns a Key / rune and a Modifier.
+// It will panic if any error occured.
+func MustParse(input string) (interface{}, Modifier) {
+ k, m, err := Parse(input)
+ if err != nil {
+ panic(err)
+ }
+ return k, m
+}
+
+// MustParseAll takes an array of strings and returns a map of all keybindings.
+// It will panic if any error occured.
+func MustParseAll(input []string) map[interface{}]Modifier {
+ result, err := ParseAll(input)
+ if err != nil {
+ panic(err)
+ }
+ return result
+}
+
// newKeybinding returns a new Keybinding object.
func newKeybinding(viewname string, key Key, ch rune, mod Modifier, handler func(*Gui, *View) error) (kb *keybinding) {
kb = &keybinding{
@@ -34,14 +110,90 @@ func (kb *keybinding) matchKeypress(key Key, ch rune, mod Modifier) bool {
// matchView returns if the keybinding matches the current view.
func (kb *keybinding) matchView(v *View) bool {
- if kb.viewName == "" {
- return true
+ // if the user is typing in a field, ignore char keys
+ if v == nil || (v.Editable && kb.ch != 0) {
+ return false
}
- return v != nil && kb.viewName == v.name
+ return kb.viewName == v.name
}
-// Key represents special keys or keys combinations.
-type Key termbox.Key
+// translations for strings to keys
+var translate = map[string]Key{
+ "F1": KeyF1,
+ "F2": KeyF2,
+ "F3": KeyF3,
+ "F4": KeyF4,
+ "F5": KeyF5,
+ "F6": KeyF6,
+ "F7": KeyF7,
+ "F8": KeyF8,
+ "F9": KeyF9,
+ "F10": KeyF10,
+ "F11": KeyF11,
+ "F12": KeyF12,
+ "Insert": KeyInsert,
+ "Delete": KeyDelete,
+ "Home": KeyHome,
+ "End": KeyEnd,
+ "Pgup": KeyPgup,
+ "Pgdn": KeyPgdn,
+ "ArrowUp": KeyArrowUp,
+ "ArrowDown": KeyArrowDown,
+ "ArrowLeft": KeyArrowLeft,
+ "ArrowRight": KeyArrowRight,
+ "CtrlTilde": KeyCtrlTilde,
+ "Ctrl2": KeyCtrl2,
+ "CtrlSpace": KeyCtrlSpace,
+ "CtrlA": KeyCtrlA,
+ "CtrlB": KeyCtrlB,
+ "CtrlC": KeyCtrlC,
+ "CtrlD": KeyCtrlD,
+ "CtrlE": KeyCtrlE,
+ "CtrlF": KeyCtrlF,
+ "CtrlG": KeyCtrlG,
+ "Backspace": KeyBackspace,
+ "CtrlH": KeyCtrlH,
+ "Tab": KeyTab,
+ "CtrlI": KeyCtrlI,
+ "CtrlJ": KeyCtrlJ,
+ "CtrlK": KeyCtrlK,
+ "CtrlL": KeyCtrlL,
+ "Enter": KeyEnter,
+ "CtrlM": KeyCtrlM,
+ "CtrlN": KeyCtrlN,
+ "CtrlO": KeyCtrlO,
+ "CtrlP": KeyCtrlP,
+ "CtrlQ": KeyCtrlQ,
+ "CtrlR": KeyCtrlR,
+ "CtrlS": KeyCtrlS,
+ "CtrlT": KeyCtrlT,
+ "CtrlU": KeyCtrlU,
+ "CtrlV": KeyCtrlV,
+ "CtrlW": KeyCtrlW,
+ "CtrlX": KeyCtrlX,
+ "CtrlY": KeyCtrlY,
+ "CtrlZ": KeyCtrlZ,
+ "Esc": KeyEsc,
+ "CtrlLsqBracket": KeyCtrlLsqBracket,
+ "Ctrl3": KeyCtrl3,
+ "Ctrl4": KeyCtrl4,
+ "CtrlBackslash": KeyCtrlBackslash,
+ "Ctrl5": KeyCtrl5,
+ "CtrlRsqBracket": KeyCtrlRsqBracket,
+ "Ctrl6": KeyCtrl6,
+ "Ctrl7": KeyCtrl7,
+ "CtrlSlash": KeyCtrlSlash,
+ "CtrlUnderscore": KeyCtrlUnderscore,
+ "Space": KeySpace,
+ "Backspace2": KeyBackspace2,
+ "Ctrl8": KeyCtrl8,
+ "Mouseleft": MouseLeft,
+ "Mousemiddle": MouseMiddle,
+ "Mouseright": MouseRight,
+ "Mouserelease": MouseRelease,
+ "MousewheelUp": MouseWheelUp,
+ "MousewheelDown": MouseWheelDown,
+}
// Special keys.
const (
@@ -126,10 +278,6 @@ const (
KeyCtrl8 = Key(termbox.KeyCtrl8)
)
-// Modifier allows to define special keys combinations. They can be used
-// in combination with Keys or Runes when a new keybinding is defined.
-type Modifier termbox.Modifier
-
// Modifiers.
const (
ModNone Modifier = Modifier(0)
diff --git a/vendor/github.com/awesome-gocui/gocui/loader.go b/vendor/github.com/awesome-gocui/gocui/loader.go
new file mode 100644
index 00000000..d6715ac6
--- /dev/null
+++ b/vendor/github.com/awesome-gocui/gocui/loader.go
@@ -0,0 +1,46 @@
+package gocui
+
+import "time"
+
+func (g *Gui) loaderTick() {
+ go func() {
+ for range time.Tick(time.Millisecond * 50) {
+ for _, view := range g.Views() {
+ if view.HasLoader {
+ g.userEvents <- userEvent{func(g *Gui) error { return nil }}
+ break
+ }
+ }
+ }
+ }()
+}
+
+func (v *View) loaderLines() [][]cell {
+ duplicate := make([][]cell, len(v.lines))
+ for i := range v.lines {
+ if i < len(v.lines)-1 {
+ duplicate[i] = make([]cell, len(v.lines[i]))
+ copy(duplicate[i], v.lines[i])
+ } else {
+ duplicate[i] = make([]cell, len(v.lines[i])+2)
+ copy(duplicate[i], v.lines[i])
+ duplicate[i][len(duplicate[i])-2] = cell{chr: ' '}
+ duplicate[i][len(duplicate[i])-1] = Loader()
+ }
+ }
+
+ return duplicate
+}
+
+// Loader can show a loading animation
+func Loader() cell {
+ characters := "|/-\\"
+ now := time.Now()
+ nanos := now.UnixNano()
+ index := nanos / 50000000 % int64(len(characters))
+ str := characters[index : index+1]
+ chr := []rune(str)[0]
+ return cell{
+ chr: chr,
+ }
+}
diff --git a/vendor/github.com/MichaelMure/gocui/view.go b/vendor/github.com/awesome-gocui/gocui/view.go
index d7943497..81f90603 100644
--- a/vendor/github.com/MichaelMure/gocui/view.go
+++ b/vendor/github.com/awesome-gocui/gocui/view.go
@@ -6,29 +6,59 @@ package gocui
import (
"bytes"
- "errors"
"io"
"strings"
+ "sync"
+ "unicode/utf8"
+ "github.com/go-errors/errors"
+
+ "github.com/awesome-gocui/termbox-go"
"github.com/mattn/go-runewidth"
- "github.com/nsf/termbox-go"
+)
+
+// Constants for overlapping edges
+const (
+ TOP = 1 // view is overlapping at top edge
+ BOTTOM = 2 // view is overlapping at bottom edge
+ LEFT = 4 // view is overlapping at left edge
+ RIGHT = 8 // view is overlapping at right edge
+)
+
+var (
+ // ErrInvalidPoint is returned when client passed invalid coordinates of a cell.
+ // Most likely client has passed negative coordinates of a cell.
+ ErrInvalidPoint = errors.New("invalid point")
)
// A View is a window. It maintains its own internal buffer and cursor
// position.
type View struct {
name string
- x0, y0, x1, y1 int
- ox, oy int
- cx, cy int
- lines [][]cell
- readOffset int
- readCache string
+ x0, y0, x1, y1 int // left top right bottom
+ ox, oy int // view offsets
+ cx, cy int // cursor position
+ rx, ry int // Read() offsets
+ wx, wy int // Write() offsets
+ lines [][]cell // All the data
+
+ // readBuffer is used for storing unread bytes
+ readBuffer []byte
+
+ // tained is true if the viewLines must be updated
+ tainted bool
+
+ // internal representation of the view's buffer
+ viewLines []viewLine
+
+ // writeMutex protects locks the write process
+ writeMutex sync.Mutex
- tainted bool // marks if the viewBuffer must be updated
- viewLines []viewLine // internal representation of the view's buffer
+ // ei is used to decode ESC sequences on Write
+ ei *escapeInterpreter
- ei *escapeInterpreter // used to decode ESC sequences on Write
+ // Visible specifies whether the view is visible.
+ Visible bool
// BgColor and FgColor allow to configure the background and foreground
// colors of the View.
@@ -42,7 +72,7 @@ type View struct {
// buffer at the cursor position.
Editable bool
- // Editor allows to define the editor that manages the edition mode,
+ // Editor allows to define the editor that manages the editing mode,
// including keybindings or cursor behaviour. DefaultEditor is used by
// default.
Editor Editor
@@ -69,9 +99,18 @@ type View struct {
// If Frame is true, Title allows to configure a title for the view.
Title string
+ // If Frame is true, Subtitle allows to configure a subtitle for the view.
+ Subtitle string
+
// If Mask is true, the View will display the mask instead of the real
// content
Mask rune
+
+ // Overlaps describes which edges are overlapping with another view's edges
+ Overlaps byte
+
+ // If HasLoader is true, the message will be appended with a spinning loader animation
+ HasLoader bool
}
type viewLine struct {
@@ -103,6 +142,7 @@ func newView(name string, x0, y0, x1, y1 int, mode OutputMode) *View {
y0: y0,
x1: x1,
y1: y1,
+ Visible: true,
Frame: true,
Editor: DefaultEditor,
tainted: true,
@@ -111,6 +151,11 @@ func newView(name string, x0, y0, x1, y1 int, mode OutputMode) *View {
return v
}
+// Dimensions returns the dimensions of the View
+func (v *View) Dimensions() (int, int, int, int) {
+ return v.x0, v.y0, v.x1, v.y1
+}
+
// Size returns the number of visible columns and rows in the View.
func (v *View) Size() (x, y int) {
return v.x1 - v.x0 - 1, v.y1 - v.y0 - 1
@@ -127,9 +172,8 @@ func (v *View) Name() string {
func (v *View) setRune(x, y int, ch rune, fgColor, bgColor Attribute) error {
maxX, maxY := v.Size()
if x < 0 || x >= maxX || y < 0 || y >= maxY {
- return errors.New("invalid point")
+ return ErrInvalidPoint
}
-
var (
ry, rcy int
err error
@@ -150,8 +194,12 @@ func (v *View) setRune(x, y int, ch rune, fgColor, bgColor Attribute) error {
bgColor = v.BgColor
ch = v.Mask
} else if v.Highlight && ry == rcy {
- fgColor = v.SelFgColor
- bgColor = v.SelBgColor
+ fgColor = fgColor | AttrBold
+ }
+
+ // Don't display NUL characters
+ if ch == 0 {
+ ch = ' '
}
termbox.SetCell(v.x0+x+1, v.y0+y+1, ch,
@@ -165,7 +213,7 @@ func (v *View) setRune(x, y int, ch rune, fgColor, bgColor Attribute) error {
func (v *View) SetCursor(x, y int) error {
maxX, maxY := v.Size()
if x < 0 || x >= maxX || y < 0 || y >= maxY {
- return errors.New("invalid point")
+ return ErrInvalidPoint
}
v.cx = x
v.cy = y
@@ -184,7 +232,7 @@ func (v *View) Cursor() (x, y int) {
// or decrementing ox and oy.
func (v *View) SetOrigin(x, y int) error {
if x < 0 || y < 0 {
- return errors.New("invalid point")
+ return ErrInvalidPoint
}
v.ox = x
v.oy = y
@@ -196,39 +244,141 @@ func (v *View) Origin() (x, y int) {
return v.ox, v.oy
}
+// SetWritePos sets the write position of the view's internal buffer.
+// So the next Write call would write directly to the specified position.
+func (v *View) SetWritePos(x, y int) error {
+ if x < 0 || y < 0 {
+ return ErrInvalidPoint
+ }
+ v.wx = x
+ v.wy = y
+ return nil
+}
+
+// WritePos returns the current write position of the view's internal buffer.
+func (v *View) WritePos() (x, y int) {
+ return v.wx, v.wy
+}
+
+// SetReadPos sets the read position of the view's internal buffer.
+// So the next Read call would read from the specified position.
+func (v *View) SetReadPos(x, y int) error {
+ if x < 0 || y < 0 {
+ return ErrInvalidPoint
+ }
+ v.readBuffer = nil
+ v.rx = x
+ v.ry = y
+ return nil
+}
+
+// ReadPos returns the current read position of the view's internal buffer.
+func (v *View) ReadPos() (x, y int) {
+ return v.rx, v.ry
+}
+
+// makeWriteable creates empty cells if required to make position (x, y) writeable.
+func (v *View) makeWriteable(x, y int) {
+ // TODO: make this more efficient
+
+ // line `y` must be index-able (that's why `<=`)
+ for len(v.lines) <= y {
+ if cap(v.lines) > len(v.lines) {
+ newLen := cap(v.lines)
+ if newLen > y {
+ newLen = y + 1
+ }
+ v.lines = v.lines[:newLen]
+ } else {
+ v.lines = append(v.lines, nil)
+ }
+ }
+ // cell `x` must not be index-able (that's why `<`)
+ // append should be used by `lines[y]` user if he wants to write beyond `x`
+ for len(v.lines[y]) < x {
+ if cap(v.lines[y]) > len(v.lines[y]) {
+ newLen := cap(v.lines[y])
+ if newLen > x {
+ newLen = x
+ }
+ v.lines[y] = v.lines[y][:newLen]
+ } else {
+ v.lines[y] = append(v.lines[y], cell{})
+ }
+ }
+}
+
+// writeCells copies []cell to specified location (x, y)
+// !!! caller MUST ensure that specified location (x, y) is writeable by calling makeWriteable
+func (v *View) writeCells(x, y int, cells []cell) {
+ var newLen int
+ // use maximum len available
+ line := v.lines[y][:cap(v.lines[y])]
+ maxCopy := len(line) - x
+ if maxCopy < len(cells) {
+ copy(line[x:], cells[:maxCopy])
+ line = append(line, cells[maxCopy:]...)
+ newLen = len(line)
+ } else { // maxCopy >= len(cells)
+ copy(line[x:], cells)
+ newLen = x + len(cells)
+ if newLen < len(v.lines[y]) {
+ newLen = len(v.lines[y])
+ }
+ }
+ v.lines[y] = line[:newLen]
+}
+
// Write appends a byte slice into the view's internal buffer. Because
// View implements the io.Writer interface, it can be passed as parameter
// of functions like fmt.Fprintf, fmt.Fprintln, io.Copy, etc. Clear must
// be called to clear the view's buffer.
func (v *View) Write(p []byte) (n int, err error) {
v.tainted = true
+ v.writeMutex.Lock()
+ v.makeWriteable(v.wx, v.wy)
+ v.writeRunes(bytes.Runes(p))
+ v.writeMutex.Unlock()
+
+ return len(p), nil
+}
+
+func (v *View) WriteRunes(p []rune) {
+ v.tainted = true
+
+ // Fill with empty cells, if writing outside current view buffer
+ v.makeWriteable(v.wx, v.wy)
+ v.writeRunes(p)
+}
+
+func (v *View) WriteString(s string) {
+ v.WriteRunes([]rune(s))
+}
- for _, ch := range bytes.Runes(p) {
- switch ch {
+// writeRunes copies slice of runes into internal lines buffer.
+// caller must make sure that writing position is accessable.
+func (v *View) writeRunes(p []rune) {
+ for _, r := range p {
+ switch r {
case '\n':
- v.lines = append(v.lines, nil)
- case '\r':
- nl := len(v.lines)
- if nl > 0 {
- v.lines[nl-1] = nil
- } else {
- v.lines = make([][]cell, 1)
+ v.wy++
+ if v.wy >= len(v.lines) {
+ v.lines = append(v.lines, nil)
}
+
+ fallthrough
+ // not valid in every OS, but making runtime OS checks in cycle is bad.
+ case '\r':
+ v.wx = 0
default:
- cells := v.parseInput(ch)
+ cells := v.parseInput(r)
if cells == nil {
continue
}
-
- nl := len(v.lines)
- if nl > 0 {
- v.lines[nl-1] = append(v.lines[nl-1], cells...)
- } else {
- v.lines = append(v.lines, cells)
- }
+ v.writeCells(v.wx, v.wy, cells)
+ v.wx += len(cells)
}
}
- return len(p), nil
}
// parseInput parses char by char the input written to the View. It returns nil
@@ -252,41 +402,85 @@ func (v *View) parseInput(ch rune) []cell {
if isEscape {
return nil
}
- c := cell{
- fgColor: v.ei.curFgColor,
- bgColor: v.ei.curBgColor,
- chr: ch,
+ repeatCount := 1
+ if ch == '\t' {
+ ch = ' '
+ repeatCount = 4
+ }
+ for i := 0; i < repeatCount; i++ {
+ c := cell{
+ fgColor: v.ei.curFgColor,
+ bgColor: v.ei.curBgColor,
+ chr: ch,
+ }
+ cells = append(cells, c)
}
- cells = append(cells, c)
}
return cells
}
-// Read reads data into p. It returns the number of bytes read into p.
-// At EOF, err will be io.EOF. Calling Read() after Rewind() makes the
-// cache to be refreshed with the contents of the view.
+// Read reads data into p from the current reading position set by SetReadPos.
+// It returns the number of bytes read into p.
+// At EOF, err will be io.EOF.
func (v *View) Read(p []byte) (n int, err error) {
- if v.readOffset == 0 {
- v.readCache = v.Buffer()
+ buffer := make([]byte, utf8.UTFMax)
+ offset := 0
+ if v.readBuffer != nil {
+ copy(p, v.readBuffer)
+ if len(v.readBuffer) >= len(p) {
+ if len(v.readBuffer) > len(p) {
+ v.readBuffer = v.readBuffer[len(p):]
+ }
+ return len(p), nil
+ }
+ v.readBuffer = nil
}
- if v.readOffset < len(v.readCache) {
- n = copy(p, v.readCache[v.readOffset:])
- v.readOffset += n
- } else {
- err = io.EOF
+ for v.ry < len(v.lines) {
+ for v.rx < len(v.lines[v.ry]) {
+ count := utf8.EncodeRune(buffer, v.lines[v.ry][v.rx].chr)
+ copy(p[offset:], buffer[:count])
+ v.rx++
+ newOffset := offset + count
+ if newOffset >= len(p) {
+ if newOffset > len(p) {
+ v.readBuffer = buffer[newOffset-len(p):]
+ }
+ return len(p), nil
+ }
+ offset += count
+ }
+ v.rx = 0
+ v.ry++
}
- return
+ return offset, io.EOF
}
-// Rewind sets the offset for the next Read to 0, which also refresh the
-// read cache.
+// Rewind sets read and write pos to (0, 0).
func (v *View) Rewind() {
- v.readOffset = 0
+ if err := v.SetReadPos(0, 0); err != nil {
+ // SetReadPos returns error only if x and y are negative
+ // we are passing 0, 0, thus no error should occur.
+ panic(err)
+ }
+ if err := v.SetWritePos(0, 0); err != nil {
+ // SetWritePos returns error only if x and y are negative
+ // we are passing 0, 0, thus no error should occur.
+ panic(err)
+ }
+}
+
+// IsTainted tells us if the view is tainted
+func (v *View) IsTainted() bool {
+ return v.tainted
}
// draw re-draws the view's contents.
func (v *View) draw() error {
+ if !v.Visible {
+ return nil
+ }
+
maxX, maxY := v.Size()
if v.Wrap {
@@ -297,29 +491,25 @@ func (v *View) draw() error {
}
if v.tainted {
v.viewLines = nil
- for i, line := range v.lines {
+ lines := v.lines
+ if v.HasLoader {
+ lines = v.loaderLines()
+ }
+ for i, line := range lines {
+ wrap := 0
if v.Wrap {
- if len(line) < maxX {
- vline := viewLine{linesX: 0, linesY: i, line: line}
- v.viewLines = append(v.viewLines, vline)
- continue
- } else {
- for n := 0; n <= len(line); n += maxX {
- if len(line[n:]) <= maxX {
- vline := viewLine{linesX: n, linesY: i, line: line[n:]}
- v.viewLines = append(v.viewLines, vline)
- } else {
- vline := viewLine{linesX: n, linesY: i, line: line[n : n+maxX]}
- v.viewLines = append(v.viewLines, vline)
- }
- }
- }
- } else {
- vline := viewLine{linesX: 0, linesY: i, line: line}
+ wrap = maxX
+ }
+
+ ls := lineWrap(line, wrap)
+ for j := range ls {
+ vline := viewLine{linesX: j, linesY: i, line: ls[j]}
v.viewLines = append(v.viewLines, vline)
}
}
- v.tainted = false
+ if !v.HasLoader {
+ v.tainted = false
+ }
}
if v.Autoscroll && len(v.viewLines) > maxY {
@@ -354,7 +544,15 @@ func (v *View) draw() error {
if err := v.setRune(x, y, c.chr, fgColor, bgColor); err != nil {
return err
}
- x += runewidth.RuneWidth(c.chr)
+
+ if c.chr != 0 {
+ // If it is a rune, add rune width
+ x += runewidth.RuneWidth(c.chr)
+ } else {
+ // If it is NULL rune, add 1 to be able to use SetWritePos
+ // (runewidth.RuneWidth of space is 1)
+ x++
+ }
}
y++
}
@@ -368,7 +566,7 @@ func (v *View) realPosition(vx, vy int) (x, y int, err error) {
vy = v.oy + vy
if vx < 0 || vy < 0 {
- return 0, 0, errors.New("invalid point")
+ return 0, 0, ErrInvalidPoint
}
if len(v.viewLines) == 0 {
@@ -389,13 +587,16 @@ func (v *View) realPosition(vx, vy int) (x, y int, err error) {
}
// Clear empties the view's internal buffer.
+// And resets reading and writing offsets.
func (v *View) Clear() {
+ v.writeMutex.Lock()
+ v.Rewind()
v.tainted = true
-
+ v.ei.reset()
v.lines = nil
v.viewLines = nil
- v.readOffset = 0
v.clearRunes()
+ v.writeMutex.Unlock()
}
// clearRunes erases all the cells in the view.
@@ -424,11 +625,7 @@ func (v *View) BufferLines() []string {
// Buffer returns a string with the contents of the view's internal
// buffer.
func (v *View) Buffer() string {
- str := ""
- for _, l := range v.lines {
- str += lineType(l).String() + "\n"
- }
- return strings.Replace(str, "\x00", " ", -1)
+ return linesToString(v.lines)
}
// ViewBufferLines returns the lines in the view's internal
@@ -443,14 +640,25 @@ func (v *View) ViewBufferLines() []string {
return lines
}
+// LinesHeight is the count of view lines (i.e. lines excluding wrapping)
+func (v *View) LinesHeight() int {
+ return len(v.lines)
+}
+
+// ViewLinesHeight is the count of view lines (i.e. lines including wrapping)
+func (v *View) ViewLinesHeight() int {
+ return len(v.viewLines)
+}
+
// ViewBuffer returns a string with the contents of the view's buffer that is
// shown to the user.
func (v *View) ViewBuffer() string {
- str := ""
- for _, l := range v.viewLines {
- str += lineType(l.line).String() + "\n"
+ lines := make([][]cell, len(v.viewLines))
+ for i := range v.viewLines {
+ lines[i] = v.viewLines[i].line
}
- return strings.Replace(str, "\x00", " ", -1)
+
+ return linesToString(lines)
}
// Line returns a string with the line of the view's internal buffer
@@ -462,7 +670,7 @@ func (v *View) Line(y int) (string, error) {
}
if y < 0 || y >= len(v.lines) {
- return "", errors.New("invalid point")
+ return "", ErrInvalidPoint
}
return lineType(v.lines[y]).String(), nil
@@ -477,7 +685,7 @@ func (v *View) Word(x, y int) (string, error) {
}
if x < 0 || y < 0 || y >= len(v.lines) || x >= len(v.lines[y]) {
- return "", errors.New("invalid point")
+ return "", ErrInvalidPoint
}
str := lineType(v.lines[y]).String()
@@ -502,3 +710,91 @@ func (v *View) Word(x, y int) (string, error) {
func indexFunc(r rune) bool {
return r == ' ' || r == 0
}
+
+// SetLine changes the contents of an existing line.
+func (v *View) SetLine(y int, text string) error {
+ if y < 0 || y >= len(v.lines) {
+ err := ErrInvalidPoint
+ return err
+ }
+
+ v.tainted = true
+ line := make([]cell, 0)
+ for _, r := range text {
+ c := v.parseInput(r)
+ line = append(line, c...)
+ }
+ v.lines[y] = line
+ return nil
+}
+
+// SetHighlight toggles highlighting of separate lines, for custom lists
+// or multiple selection in views.
+func (v *View) SetHighlight(y int, on bool) error {
+ if y < 0 || y >= len(v.lines) {
+ err := ErrInvalidPoint
+ return err
+ }
+
+ line := v.lines[y]
+ cells := make([]cell, 0)
+ for _, c := range line {
+ if on {
+ c.bgColor = v.SelBgColor
+ c.fgColor = v.SelFgColor
+ } else {
+ c.bgColor = v.BgColor
+ c.fgColor = v.FgColor
+ }
+ cells = append(cells, c)
+ }
+ v.tainted = true
+ v.lines[y] = cells
+ return nil
+}
+
+func lineWidth(line []cell) (n int) {
+ for i := range line {
+ n += runewidth.RuneWidth(line[i].chr)
+ }
+
+ return
+}
+
+func lineWrap(line []cell, columns int) [][]cell {
+ if columns == 0 {
+ return [][]cell{line}
+ }
+
+ var n int
+ var offset int
+ lines := make([][]cell, 0, 1)
+ for i := range line {
+ rw := runewidth.RuneWidth(line[i].chr)
+ n += rw
+ if n > columns {
+ n = rw
+ lines = append(lines, line[offset:i])
+ offset = i
+ }
+ }
+
+ lines = append(lines, line[offset:])
+ return lines
+}
+
+func linesToString(lines [][]cell) string {
+ str := make([]string, len(lines))
+ for i := range lines {
+ rns := make([]rune, 0, len(lines[i]))
+ line := lineType(lines[i]).String()
+ for _, c := range line {
+ if c != '\x00' {
+ rns = append(rns, c)
+ }
+ }
+ str[i] = string(rns)
+ }
+
+ return strings.Join(str, "\n")
+}
diff --git a/vendor/github.com/nsf/termbox-go/AUTHORS b/vendor/github.com/awesome-gocui/termbox-go/AUTHORS
index fe26fb0f..fe26fb0f 100644
--- a/vendor/github.com/nsf/termbox-go/AUTHORS
+++ b/vendor/github.com/awesome-gocui/termbox-go/AUTHORS
diff --git a/vendor/github.com/nsf/termbox-go/LICENSE b/vendor/github.com/awesome-gocui/termbox-go/LICENSE
index d9bc068c..d9bc068c 100644
--- a/vendor/github.com/nsf/termbox-go/LICENSE
+++ b/vendor/github.com/awesome-gocui/termbox-go/LICENSE
diff --git a/vendor/github.com/nsf/termbox-go/README.md b/vendor/github.com/awesome-gocui/termbox-go/README.md
index 2ffe033e..734aab73 100644
--- a/vendor/github.com/nsf/termbox-go/README.md
+++ b/vendor/github.com/awesome-gocui/termbox-go/README.md
@@ -1,5 +1,9 @@
[![GoDoc](https://godoc.org/github.com/nsf/termbox-go?status.svg)](http://godoc.org/github.com/nsf/termbox-go)
+## IMPORTANT
+
+This library is somewhat not maintained anymore. But I'm glad that it did what I wanted the most. It moved people away from "ncurses" mindset and these days we see both re-implementations of termbox API in various languages and even possibly better libs with similar API design. If you're looking for a Go lib that provides terminal-based user interface facilities, I've heard that https://github.com/gdamore/tcell is good (never used it myself). Also for more complicated interfaces and/or computer games I recommend you to consider using HTML-based UI. Having said that, termbox still somewhat works. In fact I'm writing this line of text right now in godit (which is a text editor written using termbox-go). So, be aware. Good luck and have a nice day.
+
## Termbox
Termbox is a library that provides a minimalistic API which allows the programmer to write text-based user interfaces. The library is crossplatform and has both terminal-based implementations on *nix operating systems and a winapi console based implementation for windows operating systems. The basic idea is an abstraction of the greatest common subset of features available on all major terminals and other terminal-like APIs in a minimalistic fashion. Small API means it is easy to implement, test, maintain and learn it, that's what makes the termbox a distinct library in its area.
@@ -17,6 +21,7 @@ There are also some interesting projects using termbox-go:
- [httopd](https://github.com/verdverm/httopd) is top for httpd logs.
- [mop](https://github.com/mop-tracker/mop) is stock market tracker for hackers.
- [termui](https://github.com/gizak/termui) is a terminal dashboard.
+ - [termdash](https://github.com/mum4k/termdash) is a terminal dashboard.
- [termloop](https://github.com/JoelOtter/termloop) is a terminal game engine.
- [xterm-color-chart](https://github.com/kutuluk/xterm-color-chart) is a XTerm 256 color chart.
- [gocui](https://github.com/jroimartin/gocui) is a minimalist Go library aimed at creating console user interfaces.
@@ -38,6 +43,7 @@ There are also some interesting projects using termbox-go:
- [gotypist](https://github.com/pb-/gotypist) is a fun touch-typing tutor following Steve Yegge's method.
- [cointop](https://github.com/miguelmota/cointop) is an interactive terminal based UI application for tracking cryptocurrencies.
- [pexpo](https://github.com/nnao45/pexpo) is a terminal sending ping tool written in Go.
+ - [jid](https://github.com/simeji/jid) is an interactive JSON drill down tool using filtering queries like jq.
### API reference
[godoc.org/github.com/nsf/termbox-go](http://godoc.org/github.com/nsf/termbox-go)
diff --git a/vendor/github.com/nsf/termbox-go/api.go b/vendor/github.com/awesome-gocui/termbox-go/api.go
index d530ab5c..a461fc27 100644
--- a/vendor/github.com/nsf/termbox-go/api.go
+++ b/vendor/github.com/awesome-gocui/termbox-go/api.go
@@ -24,13 +24,21 @@ import "time"
func Init() error {
var err error
- out, err = os.OpenFile("/dev/tty", syscall.O_WRONLY, 0)
- if err != nil {
- return err
- }
- in, err = syscall.Open("/dev/tty", syscall.O_RDONLY, 0)
- if err != nil {
- return err
+ if runtime.GOOS == "openbsd" {
+ out, err = os.OpenFile("/dev/tty", os.O_RDWR, 0)
+ if err != nil {
+ return err
+ }
+ in = int(out.Fd())
+ } else {
+ out, err = os.OpenFile("/dev/tty", os.O_WRONLY, 0)
+ if err != nil {
+ return err
+ }
+ in, err = syscall.Open("/dev/tty", syscall.O_RDONLY, 0)
+ if err != nil {
+ return err
+ }
}
err = setup_term()
@@ -317,6 +325,9 @@ func PollEvent() Event {
event.Type = EventKey
status := extract_event(inbuf, &event, true)
if event.N != 0 {
+ if event.N > len(inbuf) {
+ event.N = len(inbuf)
+ }
copy(inbuf, inbuf[event.N:])
inbuf = inbuf[:len(inbuf)-event.N]
}
@@ -345,6 +356,9 @@ func PollEvent() Event {
input_comm <- ev
status := extract_event(inbuf, &event, true)
if event.N != 0 {
+ if event.N > len(inbuf) {
+ event.N = len(inbuf)
+ }
copy(inbuf, inbuf[event.N:])
inbuf = inbuf[:len(inbuf)-event.N]
}
@@ -359,6 +373,9 @@ func PollEvent() Event {
status := extract_event(inbuf, &event, false)
if event.N != 0 {
+ if event.N > len(inbuf) {
+ event.N = len(inbuf)
+ }
copy(inbuf, inbuf[event.N:])
inbuf = inbuf[:len(inbuf)-event.N]
}
diff --git a/vendor/github.com/nsf/termbox-go/api_common.go b/vendor/github.com/awesome-gocui/termbox-go/api_common.go
index 5ca1371a..5ca1371a 100644
--- a/vendor/github.com/nsf/termbox-go/api_common.go
+++ b/vendor/github.com/awesome-gocui/termbox-go/api_common.go
diff --git a/vendor/github.com/nsf/termbox-go/api_windows.go b/vendor/github.com/awesome-gocui/termbox-go/api_windows.go
index 7def30a6..5c1e388a 100644
--- a/vendor/github.com/nsf/termbox-go/api_windows.go
+++ b/vendor/github.com/awesome-gocui/termbox-go/api_windows.go
@@ -42,7 +42,7 @@ func Init() error {
return err
}
- orig_size = get_term_size(out)
+ orig_size, orig_window = get_term_size(out)
win_size := get_win_size(out)
err = set_console_screen_buffer_size(out, win_size)
@@ -50,13 +50,18 @@ func Init() error {
return err
}
+ err = fix_win_size(out, win_size)
+ if err != nil {
+ return err
+ }
+
err = get_console_cursor_info(out, &orig_cursor_info)
if err != nil {
return err
}
show_cursor(false)
- term_size = get_term_size(out)
+ term_size, _ = get_term_size(out)
back_buffer.init(int(term_size.x), int(term_size.y))
front_buffer.init(int(term_size.x), int(term_size.y))
back_buffer.clear()
@@ -86,9 +91,10 @@ func Close() {
}
<-cancel_done_comm
+ set_console_screen_buffer_size(out, orig_size)
+ set_console_window_info(out, &orig_window)
set_console_cursor_info(out, &orig_cursor_info)
set_console_cursor_position(out, coord{})
- set_console_screen_buffer_size(out, orig_size)
set_console_mode(in, orig_mode)
syscall.Close(in)
syscall.Close(out)
diff --git a/vendor/github.com/nsf/termbox-go/collect_terminfo.py b/vendor/github.com/awesome-gocui/termbox-go/collect_terminfo.py
index 5e50975e..5e50975e 100755
--- a/vendor/github.com/nsf/termbox-go/collect_terminfo.py
+++ b/vendor/github.com/awesome-gocui/termbox-go/collect_terminfo.py
diff --git a/vendor/github.com/nsf/termbox-go/escwait.go b/vendor/github.com/awesome-gocui/termbox-go/escwait.go
index b7bbb891..b7bbb891 100644
--- a/vendor/github.com/nsf/termbox-go/escwait.go
+++ b/vendor/github.com/awesome-gocui/termbox-go/escwait.go
diff --git a/vendor/github.com/nsf/termbox-go/escwait_darwin.go b/vendor/github.com/awesome-gocui/termbox-go/escwait_darwin.go
index dde69b6c..dde69b6c 100644
--- a/vendor/github.com/nsf/termbox-go/escwait_darwin.go
+++ b/vendor/github.com/awesome-gocui/termbox-go/escwait_darwin.go
diff --git a/vendor/github.com/nsf/termbox-go/syscalls.go b/vendor/github.com/awesome-gocui/termbox-go/syscalls.go
index 4f52bb9a..4f52bb9a 100644
--- a/vendor/github.com/nsf/termbox-go/syscalls.go
+++ b/vendor/github.com/awesome-gocui/termbox-go/syscalls.go
diff --git a/vendor/github.com/nsf/termbox-go/syscalls_darwin.go b/vendor/github.com/awesome-gocui/termbox-go/syscalls_darwin.go
index 25b78f7a..25b78f7a 100644
--- a/vendor/github.com/nsf/termbox-go/syscalls_darwin.go
+++ b/vendor/github.com/awesome-gocui/termbox-go/syscalls_darwin.go
diff --git a/vendor/github.com/nsf/termbox-go/syscalls_darwin_amd64.go b/vendor/github.com/awesome-gocui/termbox-go/syscalls_darwin_amd64.go
index 11f25be7..11f25be7 100644
--- a/vendor/github.com/nsf/termbox-go/syscalls_darwin_amd64.go
+++ b/vendor/github.com/awesome-gocui/termbox-go/syscalls_darwin_amd64.go
diff --git a/vendor/github.com/nsf/termbox-go/syscalls_dragonfly.go b/vendor/github.com/awesome-gocui/termbox-go/syscalls_dragonfly.go
index e03624eb..e03624eb 100644
--- a/vendor/github.com/nsf/termbox-go/syscalls_dragonfly.go
+++ b/vendor/github.com/awesome-gocui/termbox-go/syscalls_dragonfly.go
diff --git a/vendor/github.com/nsf/termbox-go/syscalls_freebsd.go b/vendor/github.com/awesome-gocui/termbox-go/syscalls_freebsd.go
index e03624eb..e03624eb 100644
--- a/vendor/github.com/nsf/termbox-go/syscalls_freebsd.go
+++ b/vendor/github.com/awesome-gocui/termbox-go/syscalls_freebsd.go
diff --git a/vendor/github.com/nsf/termbox-go/syscalls_linux.go b/vendor/github.com/awesome-gocui/termbox-go/syscalls_linux.go
index b88960de..b88960de 100644
--- a/vendor/github.com/nsf/termbox-go/syscalls_linux.go
+++ b/vendor/github.com/awesome-gocui/termbox-go/syscalls_linux.go
diff --git a/vendor/github.com/nsf/termbox-go/syscalls_netbsd.go b/vendor/github.com/awesome-gocui/termbox-go/syscalls_netbsd.go
index 49a3355b..49a3355b 100644
--- a/vendor/github.com/nsf/termbox-go/syscalls_netbsd.go
+++ b/vendor/github.com/awesome-gocui/termbox-go/syscalls_netbsd.go
diff --git a/vendor/github.com/nsf/termbox-go/syscalls_openbsd.go b/vendor/github.com/awesome-gocui/termbox-go/syscalls_openbsd.go
index 49a3355b..49a3355b 100644
--- a/vendor/github.com/nsf/termbox-go/syscalls_openbsd.go
+++ b/vendor/github.com/awesome-gocui/termbox-go/syscalls_openbsd.go
diff --git a/vendor/github.com/nsf/termbox-go/syscalls_windows.go b/vendor/github.com/awesome-gocui/termbox-go/syscalls_windows.go
index 472d002a..472d002a 100644
--- a/vendor/github.com/nsf/termbox-go/syscalls_windows.go
+++ b/vendor/github.com/awesome-gocui/termbox-go/syscalls_windows.go
diff --git a/vendor/github.com/nsf/termbox-go/termbox.go b/vendor/github.com/awesome-gocui/termbox-go/termbox.go
index fbe4c3de..fbe4c3de 100644
--- a/vendor/github.com/nsf/termbox-go/termbox.go
+++ b/vendor/github.com/awesome-gocui/termbox-go/termbox.go
diff --git a/vendor/github.com/nsf/termbox-go/termbox_common.go b/vendor/github.com/awesome-gocui/termbox-go/termbox_common.go
index c3355cc2..c3355cc2 100644
--- a/vendor/github.com/nsf/termbox-go/termbox_common.go
+++ b/vendor/github.com/awesome-gocui/termbox-go/termbox_common.go
diff --git a/vendor/github.com/nsf/termbox-go/termbox_windows.go b/vendor/github.com/awesome-gocui/termbox-go/termbox_windows.go
index 7752a175..22e0f9ea 100644
--- a/vendor/github.com/nsf/termbox-go/termbox_windows.go
+++ b/vendor/github.com/awesome-gocui/termbox-go/termbox_windows.go
@@ -76,6 +76,10 @@ func (this coord) uintptr() uintptr {
return uintptr(*(*int32)(unsafe.Pointer(&this)))
}
+func (this *small_rect) uintptr() uintptr {
+ return uintptr(unsafe.Pointer(this))
+}
+
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
var moduser32 = syscall.NewLazyDLL("user32.dll")
var is_cjk = runewidth.IsEastAsian()
@@ -83,6 +87,7 @@ var is_cjk = runewidth.IsEastAsian()
var (
proc_set_console_active_screen_buffer = kernel32.NewProc("SetConsoleActiveScreenBuffer")
proc_set_console_screen_buffer_size = kernel32.NewProc("SetConsoleScreenBufferSize")
+ proc_set_console_window_info = kernel32.NewProc("SetConsoleWindowInfo")
proc_create_console_screen_buffer = kernel32.NewProc("CreateConsoleScreenBuffer")
proc_get_console_screen_buffer_info = kernel32.NewProc("GetConsoleScreenBufferInfo")
proc_write_console_output = kernel32.NewProc("WriteConsoleOutputW")
@@ -129,6 +134,21 @@ func set_console_screen_buffer_size(h syscall.Handle, size coord) (err error) {
return
}
+func set_console_window_info(h syscall.Handle, window *small_rect) (err error) {
+ var absolute uint32
+ absolute = 1
+ r0, _, e1 := syscall.Syscall(proc_set_console_window_info.Addr(),
+ 3, uintptr(h), uintptr(absolute), window.uintptr())
+ if int(r0) == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
func create_console_screen_buffer() (h syscall.Handle, err error) {
r0, _, e1 := syscall.Syscall6(proc_create_console_screen_buffer.Addr(),
5, uintptr(generic_read|generic_write), 0, 0, console_textmode_buffer, 0, 0)
@@ -278,6 +298,7 @@ func set_console_mode(h syscall.Handle, mode dword) (err error) {
}
func fill_console_output_character(h syscall.Handle, char wchar, n int) (err error) {
+ tmp_coord = coord{0, 0}
r0, _, e1 := syscall.Syscall6(proc_fill_console_output_character.Addr(),
5, uintptr(h), uintptr(char), uintptr(n), tmp_coord.uintptr(),
uintptr(unsafe.Pointer(&tmp_arg)), 0)
@@ -292,6 +313,7 @@ func fill_console_output_character(h syscall.Handle, char wchar, n int) (err err
}
func fill_console_output_attribute(h syscall.Handle, attr word, n int) (err error) {
+ tmp_coord = coord{0, 0}
r0, _, e1 := syscall.Syscall6(proc_fill_console_output_attribute.Addr(),
5, uintptr(h), uintptr(attr), uintptr(n), tmp_coord.uintptr(),
uintptr(unsafe.Pointer(&tmp_arg)), 0)
@@ -372,6 +394,7 @@ type input_event struct {
var (
orig_cursor_info console_cursor_info
orig_size coord
+ orig_window small_rect
orig_mode dword
orig_screen syscall.Handle
back_buffer cellbuf
@@ -413,12 +436,12 @@ func get_cursor_position(out syscall.Handle) coord {
return tmp_info.cursor_position
}
-func get_term_size(out syscall.Handle) coord {
+func get_term_size(out syscall.Handle) (coord, small_rect) {
err := get_console_screen_buffer_info(out, &tmp_info)
if err != nil {
panic(err)
}
- return tmp_info.size
+ return tmp_info.size, tmp_info.window
}
func get_win_min_size(out syscall.Handle) coord {
@@ -466,10 +489,20 @@ func get_win_size(out syscall.Handle) coord {
return size
}
+func fix_win_size(out syscall.Handle, size coord) (err error) {
+ window := small_rect{}
+ window.top = 0
+ window.bottom = size.y - 1
+ window.left = 0
+ window.right = size.x - 1
+ return set_console_window_info(out, &window)
+}
+
func update_size_maybe() {
size := get_win_size(out)
if size.x != term_size.x || size.y != term_size.y {
set_console_screen_buffer_size(out, size)
+ fix_win_size(out, size)
term_size = size
back_buffer.resize(int(size.x), int(size.y))
front_buffer.resize(int(size.x), int(size.y))
@@ -490,8 +523,8 @@ var color_table_bg = []word{
background_green,
background_red | background_green, // yellow
background_blue,
- background_red | background_blue, // magenta
- background_green | background_blue, // cyan
+ background_red | background_blue, // magenta
+ background_green | background_blue, // cyan
background_red | background_blue | background_green, // white
}
@@ -502,8 +535,8 @@ var color_table_fg = []word{
foreground_green,
foreground_red | foreground_green, // yellow
foreground_blue,
- foreground_red | foreground_blue, // magenta
- foreground_green | foreground_blue, // cyan
+ foreground_red | foreground_blue, // magenta
+ foreground_green | foreground_blue, // cyan
foreground_red | foreground_blue | foreground_green, // white
}
diff --git a/vendor/github.com/nsf/termbox-go/terminfo.go b/vendor/github.com/awesome-gocui/termbox-go/terminfo.go
index ab2e7a19..ab2e7a19 100644
--- a/vendor/github.com/nsf/termbox-go/terminfo.go
+++ b/vendor/github.com/awesome-gocui/termbox-go/terminfo.go
diff --git a/vendor/github.com/nsf/termbox-go/terminfo_builtin.go b/vendor/github.com/awesome-gocui/termbox-go/terminfo_builtin.go
index a9486606..a9486606 100644
--- a/vendor/github.com/nsf/termbox-go/terminfo_builtin.go
+++ b/vendor/github.com/awesome-gocui/termbox-go/terminfo_builtin.go
diff --git a/vendor/github.com/go-errors/errors/.travis.yml b/vendor/github.com/go-errors/errors/.travis.yml
new file mode 100644
index 00000000..9d00fdd5
--- /dev/null
+++ b/vendor/github.com/go-errors/errors/.travis.yml
@@ -0,0 +1,5 @@
+language: go
+
+go:
+ - "1.8.x"
+ - "1.10.x"
diff --git a/vendor/github.com/go-errors/errors/LICENSE.MIT b/vendor/github.com/go-errors/errors/LICENSE.MIT
new file mode 100644
index 00000000..c9a5b2ee
--- /dev/null
+++ b/vendor/github.com/go-errors/errors/LICENSE.MIT
@@ -0,0 +1,7 @@
+Copyright (c) 2015 Conrad Irwin <conrad@bugsnag.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/vendor/github.com/go-errors/errors/README.md b/vendor/github.com/go-errors/errors/README.md
new file mode 100644
index 00000000..5d4f1873
--- /dev/null
+++ b/vendor/github.com/go-errors/errors/README.md
@@ -0,0 +1,66 @@
+go-errors/errors
+================
+
+[![Build Status](https://travis-ci.org/go-errors/errors.svg?branch=master)](https://travis-ci.org/go-errors/errors)
+
+Package errors adds stacktrace support to errors in go.
+
+This is particularly useful when you want to understand the state of execution
+when an error was returned unexpectedly.
+
+It provides the type \*Error which implements the standard golang error
+interface, so you can use this library interchangably with code that is
+expecting a normal error return.
+
+Usage
+-----
+
+Full documentation is available on
+[godoc](https://godoc.org/github.com/go-errors/errors), but here's a simple
+example:
+
+```go
+package crashy
+
+import "github.com/go-errors/errors"
+
+var Crashed = errors.Errorf("oh dear")
+
+func Crash() error {
+ return errors.New(Crashed)
+}
+```
+
+This can be called as follows:
+
+```go
+package main
+
+import (
+ "crashy"
+ "fmt"
+ "github.com/go-errors/errors"
+)
+
+func main() {
+ err := crashy.Crash()
+ if err != nil {
+ if errors.Is(err, crashy.Crashed) {
+ fmt.Println(err.(*errors.Error).ErrorStack())
+ } else {
+ panic(err)
+ }
+ }
+}
+```
+
+Meta-fu
+-------
+
+This package was original written to allow reporting to
+[Bugsnag](https://bugsnag.com/) from
+[bugsnag-go](https://github.com/bugsnag/bugsnag-go), but after I found similar
+packages by Facebook and Dropbox, it was moved to one canonical location so
+everyone can benefit.
+
+This package is licensed under the MIT license, see LICENSE.MIT for details.
diff --git a/vendor/github.com/go-errors/errors/cover.out b/vendor/github.com/go-errors/errors/cover.out
new file mode 100644
index 00000000..ab18b051
--- /dev/null
+++ b/vendor/github.com/go-errors/errors/cover.out
@@ -0,0 +1,89 @@
+mode: set
+github.com/go-errors/errors/stackframe.go:27.51,30.25 2 1
+github.com/go-errors/errors/stackframe.go:33.2,38.8 3 1
+github.com/go-errors/errors/stackframe.go:30.25,32.3 1 0
+github.com/go-errors/errors/stackframe.go:43.47,44.31 1 1
+github.com/go-errors/errors/stackframe.go:47.2,47.48 1 1
+github.com/go-errors/errors/stackframe.go:44.31,46.3 1 1
+github.com/go-errors/errors/stackframe.go:52.42,56.16 3 1
+github.com/go-errors/errors/stackframe.go:60.2,60.60 1 1
+github.com/go-errors/errors/stackframe.go:56.16,58.3 1 0
+github.com/go-errors/errors/stackframe.go:64.55,67.16 2 1
+github.com/go-errors/errors/stackframe.go:71.2,72.61 2 1
+github.com/go-errors/errors/stackframe.go:76.2,76.66 1 1
+github.com/go-errors/errors/stackframe.go:67.16,69.3 1 0
+github.com/go-errors/errors/stackframe.go:72.61,74.3 1 0
+github.com/go-errors/errors/stackframe.go:79.56,91.63 3 1
+github.com/go-errors/errors/stackframe.go:95.2,95.53 1 1
+github.com/go-errors/errors/stackframe.go:100.2,101.18 2 1
+github.com/go-errors/errors/stackframe.go:91.63,94.3 2 1
+github.com/go-errors/errors/stackframe.go:95.53,98.3 2 1
+github.com/go-errors/errors/error.go:70.32,73.23 2 1
+github.com/go-errors/errors/error.go:80.2,85.3 3 1
+github.com/go-errors/errors/error.go:74.2,75.10 1 1
+github.com/go-errors/errors/error.go:76.2,77.28 1 1
+github.com/go-errors/errors/error.go:92.43,95.23 2 1
+github.com/go-errors/errors/error.go:104.2,109.3 3 1
+github.com/go-errors/errors/error.go:96.2,97.11 1 1
+github.com/go-errors/errors/error.go:98.2,99.10 1 1
+github.com/go-errors/errors/error.go:100.2,101.28 1 1
+github.com/go-errors/errors/error.go:115.39,117.19 1 1
+github.com/go-errors/errors/error.go:121.2,121.29 1 1
+github.com/go-errors/errors/error.go:125.2,125.43 1 1
+github.com/go-errors/errors/error.go:129.2,129.14 1 1
+github.com/go-errors/errors/error.go:117.19,119.3 1 1
+github.com/go-errors/errors/error.go:121.29,123.3 1 1
+github.com/go-errors/errors/error.go:125.43,127.3 1 1
+github.com/go-errors/errors/error.go:135.53,137.2 1 1
+github.com/go-errors/errors/error.go:140.34,142.2 1 1
+github.com/go-errors/errors/error.go:146.34,149.42 2 1
+github.com/go-errors/errors/error.go:153.2,153.20 1 1
+github.com/go-errors/errors/error.go:149.42,151.3 1 1
+github.com/go-errors/errors/error.go:158.39,160.2 1 1
+github.com/go-errors/errors/error.go:164.46,165.23 1 1
+github.com/go-errors/errors/error.go:173.2,173.19 1 1
+github.com/go-errors/errors/error.go:165.23,168.32 2 1
+github.com/go-errors/errors/error.go:168.32,170.4 1 1
+github.com/go-errors/errors/error.go:177.37,178.42 1 1
+github.com/go-errors/errors/error.go:181.2,181.41 1 1
+github.com/go-errors/errors/error.go:178.42,180.3 1 1
+github.com/go-errors/errors/parse_panic.go:10.39,12.2 1 1
+github.com/go-errors/errors/parse_panic.go:16.46,24.34 5 1
+github.com/go-errors/errors/parse_panic.go:70.2,70.43 1 1
+github.com/go-errors/errors/parse_panic.go:73.2,73.55 1 0
+github.com/go-errors/errors/parse_panic.go:24.34,27.23 2 1
+github.com/go-errors/errors/parse_panic.go:27.23,28.42 1 1
+github.com/go-errors/errors/parse_panic.go:28.42,31.5 2 1
+github.com/go-errors/errors/parse_panic.go:31.6,33.5 1 0
+github.com/go-errors/errors/parse_panic.go:35.5,35.29 1 1
+github.com/go-errors/errors/parse_panic.go:35.29,36.86 1 1
+github.com/go-errors/errors/parse_panic.go:36.86,38.5 1 1
+github.com/go-errors/errors/parse_panic.go:40.5,40.32 1 1
+github.com/go-errors/errors/parse_panic.go:40.32,41.18 1 1
+github.com/go-errors/errors/parse_panic.go:45.4,46.46 2 1
+github.com/go-errors/errors/parse_panic.go:51.4,53.23 2 1
+github.com/go-errors/errors/parse_panic.go:57.4,58.18 2 1
+github.com/go-errors/errors/parse_panic.go:62.4,63.17 2 1
+github.com/go-errors/errors/parse_panic.go:41.18,43.10 2 1
+github.com/go-errors/errors/parse_panic.go:46.46,49.5 2 1
+github.com/go-errors/errors/parse_panic.go:53.23,55.5 1 0
+github.com/go-errors/errors/parse_panic.go:58.18,60.5 1 0
+github.com/go-errors/errors/parse_panic.go:63.17,65.10 2 1
+github.com/go-errors/errors/parse_panic.go:70.43,72.3 1 1
+github.com/go-errors/errors/parse_panic.go:80.85,82.29 2 1
+github.com/go-errors/errors/parse_panic.go:85.2,85.15 1 1
+github.com/go-errors/errors/parse_panic.go:88.2,90.63 2 1
+github.com/go-errors/errors/parse_panic.go:94.2,94.53 1 1
+github.com/go-errors/errors/parse_panic.go:99.2,101.36 2 1
+github.com/go-errors/errors/parse_panic.go:105.2,106.15 2 1
+github.com/go-errors/errors/parse_panic.go:109.2,112.49 3 1
+github.com/go-errors/errors/parse_panic.go:116.2,117.16 2 1
+github.com/go-errors/errors/parse_panic.go:121.2,126.8 1 1
+github.com/go-errors/errors/parse_panic.go:82.29,84.3 1 0
+github.com/go-errors/errors/parse_panic.go:85.15,87.3 1 1
+github.com/go-errors/errors/parse_panic.go:90.63,93.3 2 1
+github.com/go-errors/errors/parse_panic.go:94.53,97.3 2 1
+github.com/go-errors/errors/parse_panic.go:101.36,103.3 1 0
+github.com/go-errors/errors/parse_panic.go:106.15,108.3 1 0
+github.com/go-errors/errors/parse_panic.go:112.49,114.3 1 1
+github.com/go-errors/errors/parse_panic.go:117.16,119.3 1 0
diff --git a/vendor/github.com/go-errors/errors/error.go b/vendor/github.com/go-errors/errors/error.go
new file mode 100644
index 00000000..60062a43
--- /dev/null
+++ b/vendor/github.com/go-errors/errors/error.go
@@ -0,0 +1,217 @@
+// Package errors provides errors that have stack-traces.
+//
+// This is particularly useful when you want to understand the
+// state of execution when an error was returned unexpectedly.
+//
+// It provides the type *Error which implements the standard
+// golang error interface, so you can use this library interchangably
+// with code that is expecting a normal error return.
+//
+// For example:
+//
+// package crashy
+//
+// import "github.com/go-errors/errors"
+//
+// var Crashed = errors.Errorf("oh dear")
+//
+// func Crash() error {
+// return errors.New(Crashed)
+// }
+//
+// This can be called as follows:
+//
+// package main
+//
+// import (
+// "crashy"
+// "fmt"
+// "github.com/go-errors/errors"
+// )
+//
+// func main() {
+// err := crashy.Crash()
+// if err != nil {
+// if errors.Is(err, crashy.Crashed) {
+// fmt.Println(err.(*errors.Error).ErrorStack())
+// } else {
+// panic(err)
+// }
+// }
+// }
+//
+// This package was original written to allow reporting to Bugsnag,
+// but after I found similar packages by Facebook and Dropbox, it
+// was moved to one canonical location so everyone can benefit.
+package errors
+
+import (
+ "bytes"
+ "fmt"
+ "reflect"
+ "runtime"
+)
+
+// The maximum number of stackframes on any error.
+var MaxStackDepth = 50
+
+// Error is an error with an attached stacktrace. It can be used
+// wherever the builtin error interface is expected.
+type Error struct {
+ Err error
+ stack []uintptr
+ frames []StackFrame
+ prefix string
+}
+
+// New makes an Error from the given value. If that value is already an
+// error then it will be used directly, if not, it will be passed to
+// fmt.Errorf("%v"). The stacktrace will point to the line of code that
+// called New.
+func New(e interface{}) *Error {
+ var err error
+
+ switch e := e.(type) {
+ case error:
+ err = e
+ default:
+ err = fmt.Errorf("%v", e)
+ }
+
+ stack := make([]uintptr, MaxStackDepth)
+ length := runtime.Callers(2, stack[:])
+ return &Error{
+ Err: err,
+ stack: stack[:length],
+ }
+}
+
+// Wrap makes an Error from the given value. If that value is already an
+// error then it will be used directly, if not, it will be passed to
+// fmt.Errorf("%v"). The skip parameter indicates how far up the stack
+// to start the stacktrace. 0 is from the current call, 1 from its caller, etc.
+func Wrap(e interface{}, skip int) *Error {
+ var err error
+
+ switch e := e.(type) {
+ case *Error:
+ return e
+ case error:
+ err = e
+ default:
+ err = fmt.Errorf("%v", e)
+ }
+
+ stack := make([]uintptr, MaxStackDepth)
+ length := runtime.Callers(2+skip, stack[:])
+ return &Error{
+ Err: err,
+ stack: stack[:length],
+ }
+}
+
+// WrapPrefix makes an Error from the given value. If that value is already an
+// error then it will be used directly, if not, it will be passed to
+// fmt.Errorf("%v"). The prefix parameter is used to add a prefix to the
+// error message when calling Error(). The skip parameter indicates how far
+// up the stack to start the stacktrace. 0 is from the current call,
+// 1 from its caller, etc.
+func WrapPrefix(e interface{}, prefix string, skip int) *Error {
+
+ err := Wrap(e, 1+skip)
+
+ if err.prefix != "" {
+ prefix = fmt.Sprintf("%s: %s", prefix, err.prefix)
+ }
+
+ return &Error{
+ Err: err.Err,
+ stack: err.stack,
+ prefix: prefix,
+ }
+
+}
+
+// Is detects whether the error is equal to a given error. Errors
+// are considered equal by this function if they are the same object,
+// or if they both contain the same error inside an errors.Error.
+func Is(e error, original error) bool {
+
+ if e == original {
+ return true
+ }
+
+ if e, ok := e.(*Error); ok {
+ return Is(e.Err, original)
+ }
+
+ if original, ok := original.(*Error); ok {
+ return Is(e, original.Err)
+ }
+
+ return false
+}
+
+// Errorf creates a new error with the given message. You can use it
+// as a drop-in replacement for fmt.Errorf() to provide descriptive
+// errors in return values.
+func Errorf(format string, a ...interface{}) *Error {
+ return Wrap(fmt.Errorf(format, a...), 1)
+}
+
+// Error returns the underlying error's message.
+func (err *Error) Error() string {
+
+ msg := err.Err.Error()
+ if err.prefix != "" {
+ msg = fmt.Sprintf("%s: %s", err.prefix, msg)
+ }
+
+ return msg
+}
+
+// Stack returns the callstack formatted the same way that go does
+// in runtime/debug.Stack()
+func (err *Error) Stack() []byte {
+ buf := bytes.Buffer{}
+
+ for _, frame := range err.StackFrames() {
+ buf.WriteString(frame.String())
+ }
+
+ return buf.Bytes()
+}
+
+// Callers satisfies the bugsnag ErrorWithCallerS() interface
+// so that the stack can be read out.
+func (err *Error) Callers() []uintptr {
+ return err.stack
+}
+
+// ErrorStack returns a string that contains both the
+// error message and the callstack.
+func (err *Error) ErrorStack() string {
+ return err.TypeName() + " " + err.Error() + "\n" + string(err.Stack())
+}
+
+// StackFrames returns an array of frames containing information about the
+// stack.
+func (err *Error) StackFrames() []StackFrame {
+ if err.frames == nil {
+ err.frames = make([]StackFrame, len(err.stack))
+
+ for i, pc := range err.stack {
+ err.frames[i] = NewStackFrame(pc)
+ }
+ }
+
+ return err.frames
+}
+
+// TypeName returns the type this error. e.g. *errors.stringError.
+func (err *Error) TypeName() string {
+ if _, ok := err.Err.(uncaughtPanic); ok {
+ return "panic"
+ }
+ return reflect.TypeOf(err.Err).String()
+}
diff --git a/vendor/github.com/go-errors/errors/parse_panic.go b/vendor/github.com/go-errors/errors/parse_panic.go
new file mode 100644
index 00000000..cc37052d
--- /dev/null
+++ b/vendor/github.com/go-errors/errors/parse_panic.go
@@ -0,0 +1,127 @@
+package errors
+
+import (
+ "strconv"
+ "strings"
+)
+
+type uncaughtPanic struct{ message string }
+
+func (p uncaughtPanic) Error() string {
+ return p.message
+}
+
+// ParsePanic allows you to get an error object from the output of a go program
+// that panicked. This is particularly useful with https://github.com/mitchellh/panicwrap.
+func ParsePanic(text string) (*Error, error) {
+ lines := strings.Split(text, "\n")
+
+ state := "start"
+
+ var message string
+ var stack []StackFrame
+
+ for i := 0; i < len(lines); i++ {
+ line := lines[i]
+
+ if state == "start" {
+ if strings.HasPrefix(line, "panic: ") {
+ message = strings.TrimPrefix(line, "panic: ")
+ state = "seek"
+ } else {
+ return nil, Errorf("bugsnag.panicParser: Invalid line (no prefix): %s", line)
+ }
+
+ } else if state == "seek" {
+ if strings.HasPrefix(line, "goroutine ") && strings.HasSuffix(line, "[running]:") {
+ state = "parsing"
+ }
+
+ } else if state == "parsing" {
+ if line == "" {
+ state = "done"
+ break
+ }
+ createdBy := false
+ if strings.HasPrefix(line, "created by ") {
+ line = strings.TrimPrefix(line, "created by ")
+ createdBy = true
+ }
+
+ i++
+
+ if i >= len(lines) {
+ return nil, Errorf("bugsnag.panicParser: Invalid line (unpaired): %s", line)
+ }
+
+ frame, err := parsePanicFrame(line, lines[i], createdBy)
+ if err != nil {
+ return nil, err
+ }
+
+ stack = append(stack, *frame)
+ if createdBy {
+ state = "done"
+ break
+ }
+ }
+ }
+
+ if state == "done" || state == "parsing" {
+ return &Error{Err: uncaughtPanic{message}, frames: stack}, nil
+ }
+ return nil, Errorf("could not parse panic: %v", text)
+}
+
+// The lines we're passing look like this:
+//
+// main.(*foo).destruct(0xc208067e98)
+// /0/go/src/github.com/bugsnag/bugsnag-go/pan/main.go:22 +0x151
+func parsePanicFrame(name string, line string, createdBy bool) (*StackFrame, error) {
+ idx := strings.LastIndex(name, "(")
+ if idx == -1 && !createdBy {
+ return nil, Errorf("bugsnag.panicParser: Invalid line (no call): %s", name)
+ }
+ if idx != -1 {
+ name = name[:idx]
+ }
+ pkg := ""
+
+ if lastslash := strings.LastIndex(name, "/"); lastslash >= 0 {
+ pkg += name[:lastslash] + "/"
+ name = name[lastslash+1:]
+ }
+ if period := strings.Index(name, "."); period >= 0 {
+ pkg += name[:period]
+ name = name[period+1:]
+ }
+
+ name = strings.Replace(name, "·", ".", -1)
+
+ if !strings.HasPrefix(line, "\t") {
+ return nil, Errorf("bugsnag.panicParser: Invalid line (no tab): %s", line)
+ }
+
+ idx = strings.LastIndex(line, ":")
+ if idx == -1 {
+ return nil, Errorf("bugsnag.panicParser: Invalid line (no line number): %s", line)
+ }
+ file := line[1:idx]
+
+ number := line[idx+1:]
+ if idx = strings.Index(number, " +"); idx > -1 {
+ number = number[:idx]
+ }
+
+ lno, err := strconv.ParseInt(number, 10, 32)
+ if err != nil {
+ return nil, Errorf("bugsnag.panicParser: Invalid line (bad line number): %s", line)
+ }
+
+ return &StackFrame{
+ File: file,
+ LineNumber: int(lno),
+ Package: pkg,
+ Name: name,
+ }, nil
+}
diff --git a/vendor/github.com/go-errors/errors/stackframe.go b/vendor/github.com/go-errors/errors/stackframe.go
new file mode 100644
index 00000000..750ab9a5
--- /dev/null
+++ b/vendor/github.com/go-errors/errors/stackframe.go
@@ -0,0 +1,102 @@
+package errors
+
+import (
+ "bytes"
+ "fmt"
+ "io/ioutil"
+ "runtime"
+ "strings"
+)
+
+// A StackFrame contains all necessary information about to generate a line
+// in a callstack.
+type StackFrame struct {
+ // The path to the file containing this ProgramCounter
+ File string
+ // The LineNumber in that file
+ LineNumber int
+ // The Name of the function that contains this ProgramCounter
+ Name string
+ // The Package that contains this function
+ Package string
+ // The underlying ProgramCounter
+ ProgramCounter uintptr
+}
+
+// NewStackFrame popoulates a stack frame object from the program counter.
+func NewStackFrame(pc uintptr) (frame StackFrame) {
+
+ frame = StackFrame{ProgramCounter: pc}
+ if frame.Func() == nil {
+ return
+ }
+ frame.Package, frame.Name = packageAndName(frame.Func())
+
+ // pc -1 because the program counters we use are usually return addresses,
+ // and we want to show the line that corresponds to the function call
+ frame.File, frame.LineNumber = frame.Func().FileLine(pc - 1)
+ return
+
+}
+
+// Func returns the function that contained this frame.
+func (frame *StackFrame) Func() *runtime.Func {
+ if frame.ProgramCounter == 0 {
+ return nil
+ }
+ return runtime.FuncForPC(frame.ProgramCounter)
+}
+
+// String returns the stackframe formatted in the same way as go does
+// in runtime/debug.Stack()
+func (frame *StackFrame) String() string {
+ str := fmt.Sprintf("%s:%d (0x%x)\n", frame.File, frame.LineNumber, frame.ProgramCounter)
+
+ source, err := frame.SourceLine()
+ if err != nil {
+ return str
+ }
+
+ return str + fmt.Sprintf("\t%s: %s\n", frame.Name, source)
+}
+
+// SourceLine gets the line of code (from File and Line) of the original source if possible.
+func (frame *StackFrame) SourceLine() (string, error) {
+ data, err := ioutil.ReadFile(frame.File)
+
+ if err != nil {
+ return "", New(err)
+ }
+
+ lines := bytes.Split(data, []byte{'\n'})
+ if frame.LineNumber <= 0 || frame.LineNumber >= len(lines) {
+ return "???", nil
+ }
+ // -1 because line-numbers are 1 based, but our array is 0 based
+ return string(bytes.Trim(lines[frame.LineNumber-1], " \t")), nil
+}
+
+func packageAndName(fn *runtime.Func) (string, string) {
+ name := fn.Name()
+ pkg := ""
+
+ // The name includes the path name to the package, which is unnecessary
+ // since the file name is already included. Plus, it has center dots.
+ // That is, we see
+ // runtime/debug.*T·ptrmethod
+ // and want
+ // *T.ptrmethod
+ // Since the package path might contains dots (e.g. code.google.com/...),
+ // we first remove the path prefix if there is one.
+ if lastslash := strings.LastIndex(name, "/"); lastslash >= 0 {
+ pkg += name[:lastslash] + "/"
+ name = name[lastslash+1:]
+ }
+ if period := strings.Index(name, "."); period >= 0 {
+ pkg += name[:period]
+ name = name[period+1:]
+ }
+
+ name = strings.Replace(name, "·", ".", -1)
+ return pkg, name
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/README.md b/vendor/github.com/xanzy/go-gitlab/README.md
index 5f6321f5..48fb4882 100644
--- a/vendor/github.com/xanzy/go-gitlab/README.md
+++ b/vendor/github.com/xanzy/go-gitlab/README.md
@@ -97,8 +97,8 @@ users:
```go
git := gitlab.NewClient(nil, "yourtokengoeshere")
-//git.SetBaseURL("https://git.mydomain.com/api/v3")
-users, _, err := git.Users.ListUsers()
+//git.SetBaseURL("https://git.mydomain.com/api/v4")
+users, _, err := git.Users.ListUsers(&gitlab.ListUsersOptions{})
```
Some API methods have optional parameters that can be passed. For example,
diff --git a/vendor/github.com/xanzy/go-gitlab/branches.go b/vendor/github.com/xanzy/go-gitlab/branches.go
index bc5cdbe9..e61ddfe7 100644
--- a/vendor/github.com/xanzy/go-gitlab/branches.go
+++ b/vendor/github.com/xanzy/go-gitlab/branches.go
@@ -50,7 +50,10 @@ func (b Branch) String() string {
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/branches.html#list-repository-branches
-type ListBranchesOptions ListOptions
+type ListBranchesOptions struct {
+ ListOptions
+ Search *string `url:"search,omitempty" json:"search,omitempty"`
+}
// ListBranches gets a list of repository branches from a project, sorted by
// name alphabetically.
diff --git a/vendor/github.com/xanzy/go-gitlab/environments.go b/vendor/github.com/xanzy/go-gitlab/environments.go
index ee773074..397c2d28 100644
--- a/vendor/github.com/xanzy/go-gitlab/environments.go
+++ b/vendor/github.com/xanzy/go-gitlab/environments.go
@@ -35,7 +35,9 @@ type Environment struct {
ID int `json:"id"`
Name string `json:"name"`
Slug string `json:"slug"`
+ State string `json:"state"`
ExternalURL string `json:"external_url"`
+ Project *Project `json:"project"`
LastDeployment *Deployment `json:"last_deployment"`
}
diff --git a/vendor/github.com/xanzy/go-gitlab/event_types.go b/vendor/github.com/xanzy/go-gitlab/event_types.go
index b4f48631..ae04a049 100644
--- a/vendor/github.com/xanzy/go-gitlab/event_types.go
+++ b/vendor/github.com/xanzy/go-gitlab/event_types.go
@@ -561,28 +561,20 @@ type MergeEvent struct {
Email string `json:"email"`
} `json:"author"`
} `json:"last_commit"`
- WorkInProgress bool `json:"work_in_progress"`
- URL string `json:"url"`
- Action string `json:"action"`
- OldRev string `json:"oldrev"`
- Assignee struct {
- Name string `json:"name"`
- Username string `json:"username"`
- AvatarURL string `json:"avatar_url"`
- } `json:"assignee"`
+ WorkInProgress bool `json:"work_in_progress"`
+ URL string `json:"url"`
+ Action string `json:"action"`
+ OldRev string `json:"oldrev"`
+ Assignee MergeAssignee `json:"assignee"`
} `json:"object_attributes"`
- Repository *Repository `json:"repository"`
- Assignee struct {
- Name string `json:"name"`
- Username string `json:"username"`
- AvatarURL string `json:"avatar_url"`
- } `json:"assignee"`
- Labels []Label `json:"labels"`
- Changes struct {
- AssigneeID struct {
- Previous int `json:"previous"`
- Current int `json:"current"`
- } `json:"assignee_id"`
+ Repository *Repository `json:"repository"`
+ Assignee MergeAssignee `json:"assignee"`
+ Labels []Label `json:"labels"`
+ Changes struct {
+ Assignees struct {
+ Previous []MergeAssignee `json:"previous"`
+ Current []MergeAssignee `json:"current"`
+ } `json:"assignees"`
Description struct {
Previous string `json:"previous"`
Current string `json:"current"`
@@ -591,17 +583,40 @@ type MergeEvent struct {
Previous []Label `json:"previous"`
Current []Label `json:"current"`
} `json:"labels"`
- UpdatedByID struct {
+ SourceBranch struct {
+ Previous string `json:"previous"`
+ Current string `json:"current"`
+ } `json:"source_branch"`
+ SourceProjectID struct {
Previous int `json:"previous"`
Current int `json:"current"`
- } `json:"updated_by_id"`
+ } `json:"source_project_id"`
+ TargetBranch struct {
+ Previous string `json:"previous"`
+ Current string `json:"current"`
+ } `json:"target_branch"`
+ TargetProjectID struct {
+ Previous int `json:"previous"`
+ Current int `json:"current"`
+ } `json:"target_project_id"`
Title struct {
Previous string `json:"previous"`
Current string `json:"current"`
} `json:"title"`
+ UpdatedByID struct {
+ Previous int `json:"previous"`
+ Current int `json:"current"`
+ } `json:"updated_by_id"`
} `json:"changes"`
}
+// MergeAssignee represents a merge assignee.
+type MergeAssignee struct {
+ Name string `json:"name"`
+ Username string `json:"username"`
+ AvatarURL string `json:"avatar_url"`
+}
+
// MergeParams represents the merge params.
type MergeParams struct {
ForceRemoveSourceBranch bool `json:"force_remove_source_branch"`
diff --git a/vendor/github.com/xanzy/go-gitlab/gitlab.go b/vendor/github.com/xanzy/go-gitlab/gitlab.go
index 7b0dfc69..df044141 100644
--- a/vendor/github.com/xanzy/go-gitlab/gitlab.go
+++ b/vendor/github.com/xanzy/go-gitlab/gitlab.go
@@ -87,6 +87,7 @@ const (
Failed BuildStateValue = "failed"
Canceled BuildStateValue = "canceled"
Skipped BuildStateValue = "skipped"
+ Manual BuildStateValue = "manual"
)
// ISOTime represents an ISO 8601 formatted date
diff --git a/vendor/github.com/xanzy/go-gitlab/groups.go b/vendor/github.com/xanzy/go-gitlab/groups.go
index fb141638..b2e7dd32 100644
--- a/vendor/github.com/xanzy/go-gitlab/groups.go
+++ b/vendor/github.com/xanzy/go-gitlab/groups.go
@@ -32,21 +32,34 @@ type GroupsService struct {
//
// GitLab API docs: https://docs.gitlab.com/ce/api/groups.html
type Group struct {
- ID int `json:"id"`
- Name string `json:"name"`
- Path string `json:"path"`
- Description string `json:"description"`
- Visibility *VisibilityValue `json:"visibility"`
- LFSEnabled bool `json:"lfs_enabled"`
- AvatarURL string `json:"avatar_url"`
- WebURL string `json:"web_url"`
- RequestAccessEnabled bool `json:"request_access_enabled"`
- FullName string `json:"full_name"`
- FullPath string `json:"full_path"`
- ParentID int `json:"parent_id"`
- Projects []*Project `json:"projects"`
- Statistics *StorageStatistics `json:"statistics"`
- CustomAttributes []*CustomAttribute `json:"custom_attributes"`
+ ID int `json:"id"`
+ Name string `json:"name"`
+ Path string `json:"path"`
+ Description string `json:"description"`
+ Visibility *VisibilityValue `json:"visibility"`
+ LFSEnabled bool `json:"lfs_enabled"`
+ AvatarURL string `json:"avatar_url"`
+ WebURL string `json:"web_url"`
+ RequestAccessEnabled bool `json:"request_access_enabled"`
+ FullName string `json:"full_name"`
+ FullPath string `json:"full_path"`
+ ParentID int `json:"parent_id"`
+ Projects []*Project `json:"projects"`
+ Statistics *StorageStatistics `json:"statistics"`
+ CustomAttributes []*CustomAttribute `json:"custom_attributes"`
+ ShareWithGroupLock bool `json:"share_with_group_lock"`
+ RequireTwoFactorAuth bool `json:"require_two_factor_authentication"`
+ TwoFactorGracePeriod int `json:"two_factor_grace_period"`
+ ProjectCreationLevel string `json:"project_creation_level"`
+ AutoDevopsEnabled bool `json:"auto_devops_enabled"`
+ SubGroupCreationLevel string `json:"subgroup_creation_level"`
+ EmailsDisabled bool `json:"emails_disabled"`
+ RunnersToken string `json:"runners_token"`
+ SharedProjects []*Project `json:"shared_projects"`
+ LDAPCN string `json:"ldap_cn"`
+ LDAPAccess bool `json:"ldap_access"`
+ SharedRunnersMinutesLimit int `json:"shared_runners_minutes_limit"`
+ ExtraSharedRunnersMinutesLimit int `json:"extra_shared_runners_minutes_limit"`
}
// ListGroupsOptions represents the available ListGroups() options.
diff --git a/vendor/github.com/xanzy/go-gitlab/merge_request_approvals.go b/vendor/github.com/xanzy/go-gitlab/merge_request_approvals.go
index e4057c1a..c11fe357 100644
--- a/vendor/github.com/xanzy/go-gitlab/merge_request_approvals.go
+++ b/vendor/github.com/xanzy/go-gitlab/merge_request_approvals.go
@@ -161,8 +161,8 @@ func (s *MergeRequestApprovalsService) ChangeApprovalConfiguration(pid interface
// GitLab API docs:
// https://docs.gitlab.com/ee/api/merge_request_approvals.html#change-allowed-approvers-for-merge-request
type ChangeMergeRequestAllowedApproversOptions struct {
- ApproverIDs []int `url:"approver_ids,omitempty" json:"approver_ids,omitempty"`
- ApproverGroupIDs []int `url:"approver_group_ids,omitempty" json:"approver_group_ids,omitempty"`
+ ApproverIDs []int `url:"approver_ids" json:"approver_ids"`
+ ApproverGroupIDs []int `url:"approver_group_ids" json:"approver_group_ids"`
}
// ChangeAllowedApprovers updates the approvers for a merge request.
diff --git a/vendor/github.com/xanzy/go-gitlab/pipeline_schedules.go b/vendor/github.com/xanzy/go-gitlab/pipeline_schedules.go
index 3790ea37..ebcf8297 100644
--- a/vendor/github.com/xanzy/go-gitlab/pipeline_schedules.go
+++ b/vendor/github.com/xanzy/go-gitlab/pipeline_schedules.go
@@ -236,8 +236,9 @@ func (s *PipelineSchedulesService) DeletePipelineSchedule(pid interface{}, sched
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pipeline_schedules.html#create-a-new-pipeline-schedule
type CreatePipelineScheduleVariableOptions struct {
- Key *string `url:"key" json:"key"`
- Value *string `url:"value" json:"value"`
+ Key *string `url:"key" json:"key"`
+ Value *string `url:"value" json:"value"`
+ VariableType *string `url:"variable_type,omitempty" json:"variable_type,omitempty"`
}
// CreatePipelineScheduleVariable creates a pipeline schedule variable.
@@ -271,7 +272,8 @@ func (s *PipelineSchedulesService) CreatePipelineScheduleVariable(pid interface{
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pipeline_schedules.html#edit-a-pipeline-schedule-variable
type EditPipelineScheduleVariableOptions struct {
- Value *string `url:"value" json:"value"`
+ Value *string `url:"value" json:"value"`
+ VariableType *string `url:"variable_type,omitempty" json:"variable_type,omitempty"`
}
// EditPipelineScheduleVariable creates a pipeline schedule variable.
diff --git a/vendor/github.com/xanzy/go-gitlab/pipelines.go b/vendor/github.com/xanzy/go-gitlab/pipelines.go
index bf3f814b..071e5e50 100644
--- a/vendor/github.com/xanzy/go-gitlab/pipelines.go
+++ b/vendor/github.com/xanzy/go-gitlab/pipelines.go
@@ -33,8 +33,9 @@ type PipelinesService struct {
//
// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html
type PipelineVariable struct {
- Key string `json:"key"`
- Value string `json:"value"`
+ Key string `json:"key"`
+ Value string `json:"value"`
+ VariableType string `json:"variable_type"`
}
// Pipeline represents a GitLab pipeline.
@@ -82,11 +83,13 @@ func (p Pipeline) String() string {
// PipelineInfo shows the basic entities of a pipeline, mostly used as fields
// on other assets, like Commit.
type PipelineInfo struct {
- ID int `json:"id"`
- Status string `json:"status"`
- Ref string `json:"ref"`
- SHA string `json:"sha"`
- WebURL string `json:"web_url"`
+ ID int `json:"id"`
+ Status string `json:"status"`
+ Ref string `json:"ref"`
+ SHA string `json:"sha"`
+ WebURL string `json:"web_url"`
+ UpdatedAt *time.Time `json:"updated_at"`
+ CreatedAt *time.Time `json:"created_at"`
}
func (p PipelineInfo) String() string {
diff --git a/vendor/github.com/xanzy/go-gitlab/repository_files.go b/vendor/github.com/xanzy/go-gitlab/repository_files.go
index fa97bf5f..26149319 100644
--- a/vendor/github.com/xanzy/go-gitlab/repository_files.go
+++ b/vendor/github.com/xanzy/go-gitlab/repository_files.go
@@ -43,6 +43,7 @@ type File struct {
Ref string `json:"ref"`
BlobID string `json:"blob_id"`
CommitID string `json:"commit_id"`
+ SHA256 string `json:"content_sha256"`
}
func (r File) String() string {
@@ -128,6 +129,7 @@ func (s *RepositoryFilesService) GetFileMetaData(pid interface{}, fileName strin
FileName: resp.Header.Get("X-Gitlab-File-Name"),
FilePath: resp.Header.Get("X-Gitlab-File-Path"),
Ref: resp.Header.Get("X-Gitlab-Ref"),
+ SHA256: resp.Header.Get("X-Gitlab-Content-Sha256"),
}
if sizeString := resp.Header.Get("X-Gitlab-Size"); sizeString != "" {
diff --git a/vendor/github.com/xanzy/go-gitlab/services.go b/vendor/github.com/xanzy/go-gitlab/services.go
index e9df5a6b..8d16cadc 100644
--- a/vendor/github.com/xanzy/go-gitlab/services.go
+++ b/vendor/github.com/xanzy/go-gitlab/services.go
@@ -100,6 +100,98 @@ func (s *ServicesService) DeleteGitLabCIService(pid interface{}, options ...Opti
return s.client.Do(req, nil)
}
+// GithubService represents Github service settings.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#github-premium
+type GithubService struct {
+ Service
+ Properties *GithubServiceProperties `json:"properties"`
+}
+
+// GithubServiceProperties represents Github specific properties.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#github-premium
+type GithubServiceProperties struct {
+ RepositoryURL string `json:"repository_url,omitempty"`
+ StaticContext string `json:"static_context,omitempty"`
+}
+
+// GetGithubService gets Github service settings for a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#get-github-service-settings
+func (s *ServicesService) GetGithubService(pid interface{}, options ...OptionFunc) (*GithubService, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/services/github", pathEscape(project))
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ svc := new(GithubService)
+ resp, err := s.client.Do(req, svc)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return svc, resp, err
+}
+
+// SetGithubServiceOptions represents the available SetGithubService()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#createedit-github-service
+type SetGithubServiceOptions struct {
+ Token *string `url:"token,omitempty" json:"token,omitempty"`
+ RepositoryURL *string `url:"repository_url,omitempty" json:"repository_url,omitempty"`
+ StaticContext *bool `url:"static_context,omitempty" json:"static_context,omitempty"`
+}
+
+// SetGithubService sets Github service for a project
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#createedit-github-service
+func (s *ServicesService) SetGithubService(pid interface{}, opt *SetGithubServiceOptions, options ...OptionFunc) (*Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, err
+ }
+ u := fmt.Sprintf("projects/%s/services/github", pathEscape(project))
+
+ req, err := s.client.NewRequest("PUT", u, opt, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
+
+// DeleteGithubService deletes Github service for a project
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#delete-github-service
+func (s *ServicesService) DeleteGithubService(pid interface{}, options ...OptionFunc) (*Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, err
+ }
+ u := fmt.Sprintf("projects/%s/services/github", pathEscape(project))
+
+ req, err := s.client.NewRequest("DELETE", u, nil, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
+
// SetHipChatServiceOptions represents the available SetHipChatService()
// options.
//
diff --git a/vendor/github.com/xanzy/go-gitlab/settings.go b/vendor/github.com/xanzy/go-gitlab/settings.go
index 2bbc0874..c25b28aa 100644
--- a/vendor/github.com/xanzy/go-gitlab/settings.go
+++ b/vendor/github.com/xanzy/go-gitlab/settings.go
@@ -30,100 +30,171 @@ type SettingsService struct {
//
// GitLab API docs: https://docs.gitlab.com/ce/api/settings.html
type Settings struct {
- ID int `json:"id"`
- CreatedAt *time.Time `json:"created_at"`
- UpdatedAt *time.Time `json:"updated_at"`
- AdminNotificationEmail string `json:"admin_notification_email"`
- AfterSignOutPath string `json:"after_sign_out_path"`
- AfterSignUpText string `json:"after_sign_up_text"`
- AkismetAPIKey string `json:"akismet_api_key"`
- AkismetEnabled bool `json:"akismet_enabled"`
- CircuitbreakerAccessRetries int `json:"circuitbreaker_access_retries"`
- CircuitbreakerBackoffThreshold int `json:"circuitbreaker_backoff_threshold"`
- CircuitbreakerFailureCountThreshold int `json:"circuitbreaker_failure_count_threshold"`
- CircuitbreakerFailureResetTime int `json:"circuitbreaker_failure_reset_time"`
- CircuitbreakerFailureWaitTime int `json:"circuitbreaker_failure_wait_time"`
- CircuitbreakerStorageTimeout int `json:"circuitbreaker_storage_timeout"`
- ClientsideSentryDSN string `json:"clientside_sentry_dsn"`
- ClientsideSentryEnabled bool `json:"clientside_sentry_enabled"`
- ContainerRegistryTokenExpireDelay int `json:"container_registry_token_expire_delay"`
- DefaultArtifactsExpireIn string `json:"default_artifacts_expire_in"`
- DefaultBranchProtection int `json:"default_branch_protection"`
- DefaultGroupVisibility string `json:"default_group_visibility"`
- DefaultProjectVisibility string `json:"default_project_visibility"`
- DefaultProjectsLimit int `json:"default_projects_limit"`
- DefaultSnippetVisibility string `json:"default_snippet_visibility"`
- DisabledOauthSignInSources []string `json:"disabled_oauth_sign_in_sources"`
- DomainBlacklistEnabled bool `json:"domain_blacklist_enabled"`
- DomainBlacklist []string `json:"domain_blacklist"`
- DomainWhitelist []string `json:"domain_whitelist"`
- DSAKeyRestriction int `json:"dsa_key_restriction"`
- ECDSAKeyRestriction int `json:"ecdsa_key_restriction"`
- Ed25519KeyRestriction int `json:"ed25519_key_restriction"`
- EmailAuthorInBody bool `json:"email_author_in_body"`
- EnabledGitAccessProtocol string `json:"enabled_git_access_protocol"`
- GravatarEnabled bool `json:"gravatar_enabled"`
- HelpPageHideCommercialContent bool `json:"help_page_hide_commercial_content"`
- HelpPageSupportURL string `json:"help_page_support_url"`
- HomePageURL string `json:"home_page_url"`
- HousekeepingBitmapsEnabled bool `json:"housekeeping_bitmaps_enabled"`
- HousekeepingEnabled bool `json:"housekeeping_enabled"`
- HousekeepingFullRepackPeriod int `json:"housekeeping_full_repack_period"`
- HousekeepingGcPeriod int `json:"housekeeping_gc_period"`
- HousekeepingIncrementalRepackPeriod int `json:"housekeeping_incremental_repack_period"`
- HTMLEmailsEnabled bool `json:"html_emails_enabled"`
- ImportSources []string `json:"import_sources"`
- KodingEnabled bool `json:"koding_enabled"`
- KodingURL string `json:"koding_url"`
- LocalMarkdownVersion int `json:"local_markdown_version"`
- MaxArtifactsSize int `json:"max_artifacts_size"`
- MaxAttachmentSize int `json:"max_attachment_size"`
- MaxPagesSize int `json:"max_pages_size"`
- MetricsEnabled bool `json:"metrics_enabled"`
- MetricsHost string `json:"metrics_host"`
- MetricsMethodCallThreshold int `json:"metrics_method_call_threshold"`
- MetricsPacketSize int `json:"metrics_packet_size"`
- MetricsPoolSize int `json:"metrics_pool_size"`
- MetricsPort int `json:"metrics_port"`
- MetricsSampleInterval int `json:"metrics_sample_interval"`
- MetricsTimeout int `json:"metrics_timeout"`
- PasswordAuthenticationEnabledForWeb bool `json:"password_authentication_enabled_for_web"`
- PasswordAuthenticationEnabledForGit bool `json:"password_authentication_enabled_for_git"`
- PerformanceBarAllowedGroupID string `json:"performance_bar_allowed_group_id"`
- PerformanceBarEnabled bool `json:"performance_bar_enabled"`
- PlantumlEnabled bool `json:"plantuml_enabled"`
- PlantumlURL string `json:"plantuml_url"`
- PollingIntervalMultiplier float64 `json:"polling_interval_multiplier,string"`
- ProjectExportEnabled bool `json:"project_export_enabled"`
- PrometheusMetricsEnabled bool `json:"prometheus_metrics_enabled"`
- RecaptchaEnabled bool `json:"recaptcha_enabled"`
- RecaptchaPrivateKey string `json:"recaptcha_private_key"`
- RecaptchaSiteKey string `json:"recaptcha_site_key"`
- RepositoryChecksEnabled bool `json:"repository_checks_enabled"`
- RepositoryStorages []string `json:"repository_storages"`
- RequireTwoFactorAuthentication bool `json:"require_two_factor_authentication"`
- RestrictedVisibilityLevels []VisibilityValue `json:"restricted_visibility_levels"`
- RsaKeyRestriction int `json:"rsa_key_restriction"`
- SendUserConfirmationEmail bool `json:"send_user_confirmation_email"`
- SentryDSN string `json:"sentry_dsn"`
- SentryEnabled bool `json:"sentry_enabled"`
- SessionExpireDelay int `json:"session_expire_delay"`
- SharedRunnersEnabled bool `json:"shared_runners_enabled"`
- SharedRunnersText string `json:"shared_runners_text"`
- SidekiqThrottlingEnabled bool `json:"sidekiq_throttling_enabled"`
- SidekiqThrottlingFactor float64 `json:"sidekiq_throttling_factor"`
- SidekiqThrottlingQueues []string `json:"sidekiq_throttling_queues"`
- SignInText string `json:"sign_in_text"`
- SignupEnabled bool `json:"signup_enabled"`
- TerminalMaxSessionTime int `json:"terminal_max_session_time"`
- TwoFactorGracePeriod int `json:"two_factor_grace_period"`
- UniqueIPsLimitEnabled bool `json:"unique_ips_limit_enabled"`
- UniqueIPsLimitPerUser int `json:"unique_ips_limit_per_user"`
- UniqueIPsLimitTimeWindow int `json:"unique_ips_limit_time_window"`
- UsagePingEnabled bool `json:"usage_ping_enabled"`
- UserDefaultExternal bool `json:"user_default_external"`
- UserOauthApplications bool `json:"user_oauth_applications"`
- VersionCheckEnabled bool `json:"version_check_enabled"`
+ ID int `json:"id"`
+ CreatedAt *time.Time `json:"created_at"`
+ UpdatedAt *time.Time `json:"updated_at"`
+ AdminNotificationEmail string `json:"admin_notification_email"`
+ AfterSignOutPath string `json:"after_sign_out_path"`
+ AfterSignUpText string `json:"after_sign_up_text"`
+ AkismetAPIKey string `json:"akismet_api_key"`
+ AkismetEnabled bool `json:"akismet_enabled"`
+ AllowGroupOwnersToManageLDAP bool `json:"allow_group_owners_to_manage_ldap"`
+ AllowLocalRequestsFromHooksAndServices bool `json:"allow_local_requests_from_hooks_and_services"`
+ AllowLocalRequestsFromSystemHooks bool `json:"allow_local_requests_from_system_hooks"`
+ AllowLocalRequestsFromWebHooksAndServices bool `json:"allow_local_requests_from_web_hooks_and_services"`
+ ArchiveBuildsInHumanReadable string `json:"archive_builds_in_human_readable"`
+ AssetProxyEnabled bool `json:"asset_proxy_enabled"`
+ AssetProxySecretKey string `json:"asset_proxy_secret_key"`
+ AssetProxyURL string `json:"asset_proxy_url"`
+ AssetProxyWhitelist []string `json:"asset_proxy_whitelist"`
+ AuthorizedKeysEnabled bool `json:"authorized_keys_enabled_enabled"`
+ AutoDevOpsDomain string `json:"auto_devops_domain"`
+ AutoDevOpsEnabled bool `json:"auto_devops_enabled"`
+ CheckNamespacePlan bool `json:"check_namespace_plan"`
+ CommitEmailHostname string `json:"commit_email_hostname"`
+ ContainerRegistryTokenExpireDelay int `json:"container_registry_token_expire_delay"`
+ DefaultArtifactsExpireIn string `json:"default_artifacts_expire_in"`
+ DefaultBranchProtection int `json:"default_branch_protection"`
+ DefaultGroupVisibility *VisibilityValue `json:"default_group_visibility"`
+ DefaultProjectCreation int `json:"default_project_creation"`
+ DefaultProjectsLimit int `json:"default_projects_limit"`
+ DefaultProjectVisibility *VisibilityValue `json:"default_project_visibility"`
+ DefaultSnippetVisibility *VisibilityValue `json:"default_snippet_visibility"`
+ DiffMaxPatchBytes int `json:"diff_max_patch_bytes"`
+ DisabledOauthSignInSources []string `json:"disabled_oauth_sign_in_sources"`
+ DNSRebindingProtectionEnabled bool `json:"dns_rebinding_protection_enabled"`
+ DomainBlacklist []string `json:"domain_blacklist"`
+ DomainBlacklistEnabled bool `json:"domain_blacklist_enabled"`
+ DomainWhitelist []string `json:"domain_whitelist"`
+ DSAKeyRestriction int `json:"dsa_key_restriction"`
+ ECDSAKeyRestriction int `json:"ecdsa_key_restriction"`
+ Ed25519KeyRestriction int `json:"ed25519_key_restriction"`
+ ElasticsearchAWSAccessKey string `json:"elasticsearch_aws_access_key"`
+ ElasticsearchAWS bool `json:"elasticsearch_aws"`
+ ElasticsearchAWSRegion string `json:"elasticsearch_aws_region"`
+ ElasticsearchAWSSecretAccessKey string `json:"elasticsearch_aws_secret_access_key"`
+ ElasticsearchIndexing bool `json:"elasticsearch_indexing"`
+ ElasticsearchLimitIndexing bool `json:"elasticsearch_limit_indexing"`
+ ElasticsearchNamespaceIDs []int `json:"elasticsearch_namespace_ids"`
+ ElasticsearchProjectIDs []int `json:"elasticsearch_project_ids"`
+ ElasticsearchSearch bool `json:"elasticsearch_search"`
+ ElasticsearchURL []string `json:"elasticsearch_url"`
+ EmailAdditionalText string `json:"email_additional_text"`
+ EmailAuthorInBody bool `json:"email_author_in_body"`
+ EnabledGitAccessProtocol string `json:"enabled_git_access_protocol"`
+ EnforceTerms bool `json:"enforce_terms"`
+ ExternalAuthClientCert string `json:"external_auth_client_cert"`
+ ExternalAuthClientKeyPass string `json:"external_auth_client_key_pass"`
+ ExternalAuthClientKey string `json:"external_auth_client_key"`
+ ExternalAuthorizationServiceDefaultLabel string `json:"external_authorization_service_default_label"`
+ ExternalAuthorizationServiceEnabled bool `json:"external_authorization_service_enabled"`
+ ExternalAuthorizationServiceTimeout float64 `json:"external_authorization_service_timeout"`
+ ExternalAuthorizationServiceURL string `json:"external_authorization_service_url"`
+ FileTemplateProjectID int `json:"file_template_project_id"`
+ FirstDayOfWeek int `json:"first_day_of_week"`
+ GeoNodeAllowedIPs string `json:"geo_node_allowed_ips"`
+ GeoStatusTimeout int `json:"geo_status_timeout"`
+ GitalyTimeoutDefault int `json:"gitaly_timeout_default"`
+ GitalyTimeoutFast int `json:"gitaly_timeout_fast"`
+ GitalyTimeoutMedium int `json:"gitaly_timeout_medium"`
+ GrafanaEnabled bool `json:"grafana_enabled"`
+ GrafanaURL string `json:"grafana_url"`
+ GravatarEnabled bool `json:"gravatar_enabled"`
+ HashedStorageEnabled bool `json:"hashed_storage_enabled"`
+ HelpPageHideCommercialContent bool `json:"help_page_hide_commercial_content"`
+ HelpPageSupportURL string `json:"help_page_support_url"`
+ HelpPageText string `json:"help_page_text"`
+ HelpText string `json:"help_text"`
+ HideThirdPartyOffers bool `json:"hide_third_party_offers"`
+ HomePageURL string `json:"home_page_url"`
+ HousekeepingBitmapsEnabled bool `json:"housekeeping_bitmaps_enabled"`
+ HousekeepingEnabled bool `json:"housekeeping_enabled"`
+ HousekeepingFullRepackPeriod int `json:"housekeeping_full_repack_period"`
+ HousekeepingGcPeriod int `json:"housekeeping_gc_period"`
+ HousekeepingIncrementalRepackPeriod int `json:"housekeeping_incremental_repack_period"`
+ HTMLEmailsEnabled bool `json:"html_emails_enabled"`
+ ImportSources []string `json:"import_sources"`
+ InstanceStatisticsVisibilityPrivate bool `json:"instance_statistics_visibility_private"`
+ LocalMarkdownVersion int `json:"local_markdown_version"`
+ MaxArtifactsSize int `json:"max_artifacts_size"`
+ MaxAttachmentSize int `json:"max_attachment_size"`
+ MaxPagesSize int `json:"max_pages_size"`
+ MetricsEnabled bool `json:"metrics_enabled"`
+ MetricsHost string `json:"metrics_host"`
+ MetricsMethodCallThreshold int `json:"metrics_method_call_threshold"`
+ MetricsPacketSize int `json:"metrics_packet_size"`
+ MetricsPoolSize int `json:"metrics_pool_size"`
+ MetricsPort int `json:"metrics_port"`
+ MetricsSampleInterval int `json:"metrics_sample_interval"`
+ MetricsTimeout int `json:"metrics_timeout"`
+ MirrorAvailable bool `json:"mirror_available"`
+ MirrorCapacityThreshold int `json:"mirror_capacity_threshold"`
+ MirrorMaxCapacity int `json:"mirror_max_capacity"`
+ MirrorMaxDelay int `json:"mirror_max_delay"`
+ OutboundLocalRequestsWhitelist []string `json:"outbound_local_requests_whitelist"`
+ PagesDomainVerificationEnabled bool `json:"pages_domain_verification_enabled"`
+ PasswordAuthenticationEnabledForGit bool `json:"password_authentication_enabled_for_git"`
+ PasswordAuthenticationEnabledForWeb bool `json:"password_authentication_enabled_for_web"`
+ PerformanceBarAllowedGroupID string `json:"performance_bar_allowed_group_id"`
+ PerformanceBarAllowedGroupPath string `json:"performance_bar_allowed_group_path"`
+ PerformanceBarEnabled bool `json:"performance_bar_enabled"`
+ PlantumlEnabled bool `json:"plantuml_enabled"`
+ PlantumlURL string `json:"plantuml_url"`
+ PollingIntervalMultiplier float64 `json:"polling_interval_multiplier,string"`
+ ProjectExportEnabled bool `json:"project_export_enabled"`
+ PrometheusMetricsEnabled bool `json:"prometheus_metrics_enabled"`
+ ProtectedCIVariables bool `json:"protected_ci_variables"`
+ PseudonymizerEnabled bool `json:"psedonymizer_enabled"`
+ PushEventHooksLimit int `json:"push_event_hooks_limit"`
+ PushEventActivitiesLimit int `json:"push_event_activities_limit"`
+ RecaptchaEnabled bool `json:"recaptcha_enabled"`
+ RecaptchaPrivateKey string `json:"recaptcha_private_key"`
+ RecaptchaSiteKey string `json:"recaptcha_site_key"`
+ ReceiveMaxInputSize int `json:"receive_max_input_size"`
+ RepositoryChecksEnabled bool `json:"repository_checks_enabled"`
+ RepositorySizeLimit int `json:"repository_size_limit"`
+ RepositoryStorages []string `json:"repository_storages"`
+ RequireTwoFactorAuthentication bool `json:"require_two_factor_authentication"`
+ RestrictedVisibilityLevels []VisibilityValue `json:"restricted_visibility_levels"`
+ RsaKeyRestriction int `json:"rsa_key_restriction"`
+ SendUserConfirmationEmail bool `json:"send_user_confirmation_email"`
+ SessionExpireDelay int `json:"session_expire_delay"`
+ SharedRunnersEnabled bool `json:"shared_runners_enabled"`
+ SharedRunnersMinutes int `json:"shared_runners_minutes"`
+ SharedRunnersText string `json:"shared_runners_text"`
+ SignInText string `json:"sign_in_text"`
+ SignupEnabled bool `json:"signup_enabled"`
+ SlackAppEnabled bool `json:"slack_app_enabled"`
+ SlackAppID string `json:"slack_app_id"`
+ SlackAppSecret string `json:"slack_app_secret"`
+ SlackAppVerificationToken string `json:"slack_app_verification_token"`
+ SnowplowCollectorHostname string `json:"snowplow_collector_hostname"`
+ SnowplowCookieDomain string `json:"snowplow_cookie_domain"`
+ SnowplowEnabled bool `json:"snowplow_enabled"`
+ SnowplowSiteID string `json:"snowplow_site_id"`
+ TerminalMaxSessionTime int `json:"terminal_max_session_time"`
+ Terms string `json:"terms"`
+ ThrottleAuthenticatedAPIEnabled bool `json:"throttle_authenticated_api_enabled"`
+ ThrottleAuthenticatedAPIPeriodInSeconds int `json:"throttle_authenticated_api_period_in_seconds"`
+ ThrottleAuthenticatedAPIRequestsPerPeriod int `json:"throttle_authenticated_api_requests_per_period"`
+ ThrottleAuthenticatedWebEnabled bool `json:"throttle_authenticated_web_enabled"`
+ ThrottleAuthenticatedWebPeriodInSeconds int `json:"throttle_authenticated_web_period_in_seconds"`
+ ThrottleAuthenticatedWebRequestsPerPeriod int `json:"throttle_authenticated_web_requests_per_period"`
+ ThrottleUnauthenticatedEnabled bool `json:"throttle_unauthenticated_enabled"`
+ ThrottleUnauthenticatedPeriodInSeconds int `json:"throttle_unauthenticated_period_in_seconds"`
+ ThrottleUnauthenticatedRequestsPerPeriod int `json:"throttle_unauthenticated_requests_per_period"`
+ TimeTrackingLimitToHours bool `json:"time_tracking_limit_to_hours"`
+ TwoFactorGracePeriod int `json:"two_factor_grace_period"`
+ UniqueIPsLimitEnabled bool `json:"unique_ips_limit_enabled"`
+ UniqueIPsLimitPerUser int `json:"unique_ips_limit_per_user"`
+ UniqueIPsLimitTimeWindow int `json:"unique_ips_limit_time_window"`
+ UsagePingEnabled bool `json:"usage_ping_enabled"`
+ UserDefaultExternal bool `json:"user_default_external"`
+ UserDefaultInternalRegex string `json:"user_default_internal_regex"`
+ UserOauthApplications bool `json:"user_oauth_applications"`
+ UserShowAddSSHKeyMessage bool `json:"user_show_add_ssh_key_message"`
+ VersionCheckEnabled bool `json:"version_check_enabled"`
+ WebIDEClientsidePreviewEnabled bool `json:"web_ide_clientside_preview_enabled"`
}
func (s Settings) String() string {
@@ -154,97 +225,168 @@ func (s *SettingsService) GetSettings(options ...OptionFunc) (*Settings, *Respon
// GitLab API docs:
// https://docs.gitlab.com/ce/api/settings.html#change-application.settings
type UpdateSettingsOptions struct {
- AdminNotificationEmail *string `url:"admin_notification_email,omitempty" json:"admin_notification_email,omitempty"`
- AfterSignOutPath *string `url:"after_sign_out_path,omitempty" json:"after_sign_out_path,omitempty"`
- AfterSignUpText *string `url:"after_sign_up_text,omitempty" json:"after_sign_up_text,omitempty"`
- AkismetAPIKey *string `url:"akismet_api_key,omitempty" json:"akismet_api_key,omitempty"`
- AkismetEnabled *bool `url:"akismet_enabled,omitempty" json:"akismet_enabled,omitempty"`
- CircuitbreakerAccessRetries *int `url:"circuitbreaker_access_retries,omitempty" json:"circuitbreaker_access_retries,omitempty"`
- CircuitbreakerBackoffThreshold *int `url:"circuitbreaker_backoff_threshold,omitempty" json:"circuitbreaker_backoff_threshold,omitempty"`
- CircuitbreakerFailureCountThreshold *int `url:"circuitbreaker_failure_count_threshold,omitempty" json:"circuitbreaker_failure_count_threshold,omitempty"`
- CircuitbreakerFailureResetTime *int `url:"circuitbreaker_failure_reset_time,omitempty" json:"circuitbreaker_failure_reset_time,omitempty"`
- CircuitbreakerFailureWaitTime *int `url:"circuitbreaker_failure_wait_time,omitempty" json:"circuitbreaker_failure_wait_time,omitempty"`
- CircuitbreakerStorageTimeout *int `url:"circuitbreaker_storage_timeout,omitempty" json:"circuitbreaker_storage_timeout,omitempty"`
- ClientsideSentryDSN *string `url:"clientside_sentry_dsn,omitempty" json:"clientside_sentry_dsn,omitempty"`
- ClientsideSentryEnabled *bool `url:"clientside_sentry_enabled,omitempty" json:"clientside_sentry_enabled,omitempty"`
- ContainerRegistryTokenExpireDelay *int `url:"container_registry_token_expire_delay,omitempty" json:"container_registry_token_expire_delay,omitempty"`
- DefaultArtifactsExpireIn *string `url:"default_artifacts_expire_in,omitempty" json:"default_artifacts_expire_in,omitempty"`
- DefaultBranchProtection *int `url:"default_branch_protection,omitempty" json:"default_branch_protection,omitempty"`
- DefaultGroupVisibility *string `url:"default_group_visibility,omitempty" json:"default_group_visibility,omitempty"`
- DefaultProjectVisibility *string `url:"default_project_visibility,omitempty" json:"default_project_visibility,omitempty"`
- DefaultProjectsLimit *int `url:"default_projects_limit,omitempty" json:"default_projects_limit,omitempty"`
- DefaultSnippetVisibility *string `url:"default_snippet_visibility,omitempty" json:"default_snippet_visibility,omitempty"`
- DisabledOauthSignInSources []string `url:"disabled_oauth_sign_in_sources,omitempty" json:"disabled_oauth_sign_in_sources,omitempty"`
- DomainBlacklistEnabled *bool `url:"domain_blacklist_enabled,omitempty" json:"domain_blacklist_enabled,omitempty"`
- DomainBlacklist []string `url:"domain_blacklist,omitempty" json:"domain_blacklist,omitempty"`
- DomainWhitelist []string `url:"domain_whitelist,omitempty" json:"domain_whitelist,omitempty"`
- DSAKeyRestriction *int `url:"dsa_key_restriction,omitempty" json:"dsa_key_restriction,omitempty"`
- ECDSAKeyRestriction *int `url:"ecdsa_key_restriction,omitempty" json:"ecdsa_key_restriction,omitempty"`
- Ed25519KeyRestriction *int `url:"ed25519_key_restriction,omitempty" json:"ed25519_key_restriction,omitempty"`
- EmailAuthorInBody *bool `url:"email_author_in_body,omitempty" json:"email_author_in_body,omitempty"`
- EnabledGitAccessProtocol *string `url:"enabled_git_access_protocol,omitempty" json:"enabled_git_access_protocol,omitempty"`
- GravatarEnabled *bool `url:"gravatar_enabled,omitempty" json:"gravatar_enabled,omitempty"`
- HelpPageHideCommercialContent *bool `url:"help_page_hide_commercial_content,omitempty" json:"help_page_hide_commercial_content,omitempty"`
- HelpPageSupportURL *string `url:"help_page_support_url,omitempty" json:"help_page_support_url,omitempty"`
- HomePageURL *string `url:"home_page_url,omitempty" json:"home_page_url,omitempty"`
- HousekeepingBitmapsEnabled *bool `url:"housekeeping_bitmaps_enabled,omitempty" json:"housekeeping_bitmaps_enabled,omitempty"`
- HousekeepingEnabled *bool `url:"housekeeping_enabled,omitempty" json:"housekeeping_enabled,omitempty"`
- HousekeepingFullRepackPeriod *int `url:"housekeeping_full_repack_period,omitempty" json:"housekeeping_full_repack_period,omitempty"`
- HousekeepingGcPeriod *int `url:"housekeeping_gc_period,omitempty" json:"housekeeping_gc_period,omitempty"`
- HousekeepingIncrementalRepackPeriod *int `url:"housekeeping_incremental_repack_period,omitempty" json:"housekeeping_incremental_repack_period,omitempty"`
- HTMLEmailsEnabled *bool `url:"html_emails_enabled,omitempty" json:"html_emails_enabled,omitempty"`
- ImportSources []string `url:"import_sources,omitempty" json:"import_sources,omitempty"`
- KodingEnabled *bool `url:"koding_enabled,omitempty" json:"koding_enabled,omitempty"`
- KodingURL *string `url:"koding_url,omitempty" json:"koding_url,omitempty"`
- LocalMarkdownVersion *int `url:"local_markdown_version,omitempty" json:"local_markdown_version,omitempty"`
- MaxArtifactsSize *int `url:"max_artifacts_size,omitempty" json:"max_artifacts_size,omitempty"`
- MaxAttachmentSize *int `url:"max_attachment_size,omitempty" json:"max_attachment_size,omitempty"`
- MaxPagesSize *int `url:"max_pages_size,omitempty" json:"max_pages_size,omitempty"`
- MetricsEnabled *bool `url:"metrics_enabled,omitempty" json:"metrics_enabled,omitempty"`
- MetricsHost *string `url:"metrics_host,omitempty" json:"metrics_host,omitempty"`
- MetricsMethodCallThreshold *int `url:"metrics_method_call_threshold,omitempty" json:"metrics_method_call_threshold,omitempty"`
- MetricsPacketSize *int `url:"metrics_packet_size,omitempty" json:"metrics_packet_size,omitempty"`
- MetricsPoolSize *int `url:"metrics_pool_size,omitempty" json:"metrics_pool_size,omitempty"`
- MetricsPort *int `url:"metrics_port,omitempty" json:"metrics_port,omitempty"`
- MetricsSampleInterval *int `url:"metrics_sample_interval,omitempty" json:"metrics_sample_interval,omitempty"`
- MetricsTimeout *int `url:"metrics_timeout,omitempty" json:"metrics_timeout,omitempty"`
- PasswordAuthenticationEnabledForWeb *bool `url:"password_authentication_enabled_for_web,omitempty" json:"password_authentication_enabled_for_web,omitempty"`
- PasswordAuthenticationEnabledForGit *bool `url:"password_authentication_enabled_for_git,omitempty" json:"password_authentication_enabled_for_git,omitempty"`
- PerformanceBarAllowedGroupID *string `url:"performance_bar_allowed_group_id,omitempty" json:"performance_bar_allowed_group_id,omitempty"`
- PerformanceBarEnabled *bool `url:"performance_bar_enabled,omitempty" json:"performance_bar_enabled,omitempty"`
- PlantumlEnabled *bool `url:"plantuml_enabled,omitempty" json:"plantuml_enabled,omitempty"`
- PlantumlURL *string `url:"plantuml_url,omitempty" json:"plantuml_url,omitempty"`
- PollingIntervalMultiplier *float64 `url:"polling_interval_multiplier,omitempty" json:"polling_interval_multiplier,omitempty"`
- ProjectExportEnabled *bool `url:"project_export_enabled,omitempty" json:"project_export_enabled,omitempty"`
- PrometheusMetricsEnabled *bool `url:"prometheus_metrics_enabled,omitempty" json:"prometheus_metrics_enabled,omitempty"`
- RecaptchaEnabled *bool `url:"recaptcha_enabled,omitempty" json:"recaptcha_enabled,omitempty"`
- RecaptchaPrivateKey *string `url:"recaptcha_private_key,omitempty" json:"recaptcha_private_key,omitempty"`
- RecaptchaSiteKey *string `url:"recaptcha_site_key,omitempty" json:"recaptcha_site_key,omitempty"`
- RepositoryChecksEnabled *bool `url:"repository_checks_enabled,omitempty" json:"repository_checks_enabled,omitempty"`
- RepositoryStorages []string `url:"repository_storages,omitempty" json:"repository_storages,omitempty"`
- RequireTwoFactorAuthentication *bool `url:"require_two_factor_authentication,omitempty" json:"require_two_factor_authentication,omitempty"`
- RestrictedVisibilityLevels []VisibilityValue `url:"restricted_visibility_levels,omitempty" json:"restricted_visibility_levels,omitempty"`
- RsaKeyRestriction *int `url:"rsa_key_restriction,omitempty" json:"rsa_key_restriction,omitempty"`
- SendUserConfirmationEmail *bool `url:"send_user_confirmation_email,omitempty" json:"send_user_confirmation_email,omitempty"`
- SentryDSN *string `url:"sentry_dsn,omitempty" json:"sentry_dsn,omitempty"`
- SentryEnabled *bool `url:"sentry_enabled,omitempty" json:"sentry_enabled,omitempty"`
- SessionExpireDelay *int `url:"session_expire_delay,omitempty" json:"session_expire_delay,omitempty"`
- SharedRunnersEnabled *bool `url:"shared_runners_enabled,omitempty" json:"shared_runners_enabled,omitempty"`
- SharedRunnersText *string `url:"shared_runners_text,omitempty" json:"shared_runners_text,omitempty"`
- SidekiqThrottlingEnabled *bool `url:"sidekiq_throttling_enabled,omitempty" json:"sidekiq_throttling_enabled,omitempty"`
- SidekiqThrottlingFactor *float64 `url:"sidekiq_throttling_factor,omitempty" json:"sidekiq_throttling_factor,omitempty"`
- SidekiqThrottlingQueues []string `url:"sidekiq_throttling_queues,omitempty" json:"sidekiq_throttling_queues,omitempty"`
- SignInText *string `url:"sign_in_text,omitempty" json:"sign_in_text,omitempty"`
- SignupEnabled *bool `url:"signup_enabled,omitempty" json:"signup_enabled,omitempty"`
- TerminalMaxSessionTime *int `url:"terminal_max_session_time,omitempty" json:"terminal_max_session_time,omitempty"`
- TwoFactorGracePeriod *int `url:"two_factor_grace_period,omitempty" json:"two_factor_grace_period,omitempty"`
- UniqueIPsLimitEnabled *bool `url:"unique_ips_limit_enabled,omitempty" json:"unique_ips_limit_enabled,omitempty"`
- UniqueIPsLimitPerUser *int `url:"unique_ips_limit_per_user,omitempty" json:"unique_ips_limit_per_user,omitempty"`
- UniqueIPsLimitTimeWindow *int `url:"unique_ips_limit_time_window,omitempty" json:"unique_ips_limit_time_window,omitempty"`
- UsagePingEnabled *bool `url:"usage_ping_enabled,omitempty" json:"usage_ping_enabled,omitempty"`
- UserDefaultExternal *bool `url:"user_default_external,omitempty" json:"user_default_external,omitempty"`
- UserOauthApplications *bool `url:"user_oauth_applications,omitempty" json:"user_oauth_applications,omitempty"`
- VersionCheckEnabled *bool `url:"version_check_enabled,omitempty" json:"version_check_enabled,omitempty"`
+ AdminNotificationEmail *string `url:"admin_notification_email,omitempty" json:"admin_notification_email,omitempty"`
+ AfterSignOutPath *string `url:"after_sign_out_path,omitempty" json:"after_sign_out_path,omitempty"`
+ AfterSignUpText *string `url:"after_sign_up_text,omitempty" json:"after_sign_up_text,omitempty"`
+ AkismetAPIKey *string `url:"akismet_api_key,omitempty" json:"akismet_api_key,omitempty"`
+ AkismetEnabled *bool `url:"akismet_enabled,omitempty" json:"akismet_enabled,omitempty"`
+ AllowGroupOwnersToManageLDAP *bool `url:"allow_group_owners_to_manage_ldap,omitempty" json:"allow_group_owners_to_manage_ldap,omitempty"`
+ AllowLocalRequestsFromHooksAndServices *bool `url:"allow_local_requests_from_hooks_and_services,omitempty" json:"allow_local_requests_from_hooks_and_services,omitempty"`
+ AllowLocalRequestsFromSystemHooks *bool `url:"allow_local_requests_from_system_hooks,omitempty" json:"allow_local_requests_from_system_hooks,omitempty"`
+ AllowLocalRequestsFromWebHooksAndServices *bool `url:"allow_local_requests_from_web_hooks_and_services,omitempty" json:"allow_local_requests_from_web_hooks_and_services,omitempty"`
+ ArchiveBuildsInHumanReadable *string `url:"archive_builds_in_human_readable,omitempty" json:"archive_builds_in_human_readable,omitempty"`
+ AssetProxyEnabled *bool `url:"asset_proxy_enabled,omitempty" json:"asset_proxy_enabled,omitempty"`
+ AssetProxySecretKey *string `url:"asset_proxy_secret_key,omitempty" json:"asset_proxy_secret_key,omitempty"`
+ AssetProxyURL *string `url:"asset_proxy_url,omitempty" json:"asset_proxy_url,omitempty"`
+ AssetProxyWhitelist []string `url:"asset_proxy_whitelist,omitempty" json:"asset_proxy_whitelist,omitempty"`
+ AuthorizedKeysEnabled *bool `url:"authorized_keys_enabled,omitempty" json:"authorized_keys_enabled,omitempty"`
+ AutoDevOpsDomain *string `url:"auto_devops_domain,omitempty" json:"auto_devops_domain,omitempty"`
+ AutoDevOpsEnabled *bool `url:"auto_devops_enabled,omitempty" json:"auto_devops_enabled,omitempty"`
+ CheckNamespacePlan *bool `url:"check_namespace_plan,omitempty" json:"check_namespace_plan,omitempty"`
+ CommitEmailHostname *string `url:"commit_email_hostname,omitempty" json:"commit_email_hostname,omitempty"`
+ ContainerRegistryTokenExpireDelay *int `url:"container_registry_token_expire_delay,omitempty" json:"container_registry_token_expire_delay,omitempty"`
+ DefaultArtifactsExpireIn *string `url:"default_artifacts_expire_in,omitempty" json:"default_artifacts_expire_in,omitempty"`
+ DefaultBranchProtection *int `url:"default_branch_protection,omitempty" json:"default_branch_protection,omitempty"`
+ DefaultGroupVisibility *VisibilityValue `url:"default_group_visibility,omitempty" json:"default_group_visibility,omitempty"`
+ DefaultProjectCreation *int `url:"default_project_creation,omitempty" json:"default_project_creation,omitempty"`
+ DefaultProjectsLimit *int `url:"default_projects_limit,omitempty" json:"default_projects_limit,omitempty"`
+ DefaultProjectVisibility *VisibilityValue `url:"default_project_visibility,omitempty" json:"default_project_visibility,omitempty"`
+ DefaultSnippetVisibility *VisibilityValue `url:"default_snippet_visibility,omitempty" json:"default_snippet_visibility,omitempty"`
+ DiffMaxPatchBytes *int `url:"diff_max_patch_bytes,omitempty" json:"diff_max_patch_bytes,omitempty"`
+ DisabledOauthSignInSources []string `url:"disabled_oauth_sign_in_sources,omitempty" json:"disabled_oauth_sign_in_sources,omitempty"`
+ DNSRebindingProtectionEnabled *bool `url:"dns_rebinding_protection_enabled,omitempty" json:"dns_rebinding_protection_enabled,omitempty"`
+ DomainBlacklist []string `url:"domain_blacklist,omitempty" json:"domain_blacklist,omitempty"`
+ DomainBlacklistEnabled *bool `url:"domain_blacklist_enabled,omitempty" json:"domain_blacklist_enabled,omitempty"`
+ DomainWhitelist []string `url:"domain_whitelist,omitempty" json:"domain_whitelist,omitempty"`
+ DSAKeyRestriction *int `url:"dsa_key_restriction,omitempty" json:"dsa_key_restriction,omitempty"`
+ ECDSAKeyRestriction *int `url:"ecdsa_key_restriction,omitempty" json:"ecdsa_key_restriction,omitempty"`
+ Ed25519KeyRestriction *int `url:"ed25519_key_restriction,omitempty" json:"ed25519_key_restriction,omitempty"`
+ ElasticsearchAWSAccessKey *string `url:"elasticsearch_aws_access_key,omitempty" json:"elasticsearch_aws_access_key,omitempty"`
+ ElasticsearchAWS *bool `url:"elasticsearch_aws,omitempty" json:"elasticsearch_aws,omitempty"`
+ ElasticsearchAWSRegion *string `url:"elasticsearch_aws_region,omitempty" json:"elasticsearch_aws_region,omitempty"`
+ ElasticsearchAWSSecretAccessKey *string `url:"elasticsearch_aws_secret_access_key,omitempty" json:"elasticsearch_aws_secret_access_key,omitempty"`
+ ElasticsearchIndexing *bool `url:"elasticsearch_indexing,omitempty" json:"elasticsearch_indexing,omitempty"`
+ ElasticsearchLimitIndexing *bool `url:"elasticsearch_limit_indexing,omitempty" json:"elasticsearch_limit_indexing,omitempty"`
+ ElasticsearchNamespaceIDs []int `url:"elasticsearch_namespace_ids,omitempty" json:"elasticsearch_namespace_ids,omitempty"`
+ ElasticsearchProjectIDs []int `url:"elasticsearch_project_ids,omitempty" json:"elasticsearch_project_ids,omitempty"`
+ ElasticsearchSearch *bool `url:"elasticsearch_search,omitempty" json:"elasticsearch_search,omitempty"`
+ ElasticsearchURL *string `url:"elasticsearch_url,omitempty" json:"elasticsearch_url,omitempty"`
+ EmailAdditionalText *string `url:"email_additional_text,omitempty" json:"email_additional_text,omitempty"`
+ EmailAuthorInBody *bool `url:"email_author_in_body,omitempty" json:"email_author_in_body,omitempty"`
+ EnabledGitAccessProtocol *string `url:"enabled_git_access_protocol,omitempty" json:"enabled_git_access_protocol,omitempty"`
+ EnforceTerms *bool `url:"enforce_terms,omitempty" json:"enforce_terms,omitempty"`
+ ExternalAuthClientCert *string `url:"external_auth_client_cert,omitempty" json:"external_auth_client_cert,omitempty"`
+ ExternalAuthClientKeyPass *string `url:"external_auth_client_key_pass,omitempty" json:"external_auth_client_key_pass,omitempty"`
+ ExternalAuthClientKey *string `url:"external_auth_client_key,omitempty" json:"external_auth_client_key,omitempty"`
+ ExternalAuthorizationServiceDefaultLabel *string `url:"external_authorization_service_default_label,omitempty" json:"external_authorization_service_default_label,omitempty"`
+ ExternalAuthorizationServiceEnabled *bool `url:"external_authorization_service_enabled,omitempty" json:"external_authorization_service_enabled,omitempty"`
+ ExternalAuthorizationServiceTimeout *float64 `url:"external_authorization_service_timeout,omitempty" json:"external_authorization_service_timeout,omitempty"`
+ ExternalAuthorizationServiceURL *string `url:"external_authorization_service_url,omitempty" json:"external_authorization_service_url,omitempty"`
+ FileTemplateProjectID *int `url:"file_template_project_id,omitempty" json:"file_template_project_id,omitempty"`
+ FirstDayOfWeek *int `url:"first_day_of_week,omitempty" json:"first_day_of_week,omitempty"`
+ GeoNodeAllowedIPs *string `url:"geo_node_allowed_ips,omitempty" json:"geo_node_allowed_ips,omitempty"`
+ GeoStatusTimeout *int `url:"geo_status_timeout,omitempty" json:"geo_status_timeout,omitempty"`
+ GitalyTimeoutDefault *int `url:"gitaly_timeout_default,omitempty" json:"gitaly_timeout_default,omitempty"`
+ GitalyTimeoutFast *int `url:"gitaly_timeout_fast,omitempty" json:"gitaly_timeout_fast,omitempty"`
+ GitalyTimeoutMedium *int `url:"gitaly_timeout_medium,omitempty" json:"gitaly_timeout_medium,omitempty"`
+ GrafanaEnabled *bool `url:"grafana_enabled,omitempty" json:"grafana_enabled,omitempty"`
+ GrafanaURL *string `url:"grafana_url,omitempty" json:"grafana_url,omitempty"`
+ GravatarEnabled *bool `url:"gravatar_enabled,omitempty" json:"gravatar_enabled,omitempty"`
+ HashedStorageEnabled *bool `url:"hashed_storage_enabled,omitempty" json:"hashed_storage_enabled,omitempty"`
+ HelpPageHideCommercialContent *bool `url:"help_page_hide_commercial_content,omitempty" json:"help_page_hide_commercial_content,omitempty"`
+ HelpPageSupportURL *string `url:"help_page_support_url,omitempty" json:"help_page_support_url,omitempty"`
+ HelpPageText *string `url:"help_page_text,omitempty" json:"help_page_text,omitempty"`
+ HelpText *string `url:"help_text,omitempty" json:"help_text,omitempty"`
+ HideThirdPartyOffers *bool `url:"hide_third_party_offers,omitempty" json:"hide_third_party_offers,omitempty"`
+ HomePageURL *string `url:"home_page_url,omitempty" json:"home_page_url,omitempty"`
+ HousekeepingBitmapsEnabled *bool `url:"housekeeping_bitmaps_enabled,omitempty" json:"housekeeping_bitmaps_enabled,omitempty"`
+ HousekeepingEnabled *bool `url:"housekeeping_enabled,omitempty" json:"housekeeping_enabled,omitempty"`
+ HousekeepingFullRepackPeriod *int `url:"housekeeping_full_repack_period,omitempty" json:"housekeeping_full_repack_period,omitempty"`
+ HousekeepingGcPeriod *int `url:"housekeeping_gc_period,omitempty" json:"housekeeping_gc_period,omitempty"`
+ HousekeepingIncrementalRepackPeriod *int `url:"housekeeping_incremental_repack_period,omitempty" json:"housekeeping_incremental_repack_period,omitempty"`
+ HTMLEmailsEnabled *bool `url:"html_emails_enabled,omitempty" json:"html_emails_enabled,omitempty"`
+ ImportSources []string `url:"import_sources,omitempty" json:"import_sources,omitempty"`
+ InstanceStatisticsVisibilityPrivate *bool `url:"instance_statistics_visibility_private,omitempty" json:"instance_statistics_visibility_private,omitempty"`
+ LocalMarkdownVersion *int `url:"local_markdown_version,omitempty" json:"local_markdown_version,omitempty"`
+ MaxArtifactsSize *int `url:"max_artifacts_size,omitempty" json:"max_artifacts_size,omitempty"`
+ MaxAttachmentSize *int `url:"max_attachment_size,omitempty" json:"max_attachment_size,omitempty"`
+ MaxPagesSize *int `url:"max_pages_size,omitempty" json:"max_pages_size,omitempty"`
+ MetricsEnabled *bool `url:"metrics_enabled,omitempty" json:"metrics_enabled,omitempty"`
+ MetricsHost *string `url:"metrics_host,omitempty" json:"metrics_host,omitempty"`
+ MetricsMethodCallThreshold *int `url:"metrics_method_call_threshold,omitempty" json:"metrics_method_call_threshold,omitempty"`
+ MetricsPacketSize *int `url:"metrics_packet_size,omitempty" json:"metrics_packet_size,omitempty"`
+ MetricsPoolSize *int `url:"metrics_pool_size,omitempty" json:"metrics_pool_size,omitempty"`
+ MetricsPort *int `url:"metrics_port,omitempty" json:"metrics_port,omitempty"`
+ MetricsSampleInterval *int `url:"metrics_sample_interval,omitempty" json:"metrics_sample_interval,omitempty"`
+ MetricsTimeout *int `url:"metrics_timeout,omitempty" json:"metrics_timeout,omitempty"`
+ MirrorAvailable *bool `url:"mirror_available,omitempty" json:"mirror_available,omitempty"`
+ MirrorCapacityThreshold *int `url:"mirror_capacity_threshold,omitempty" json:"mirror_capacity_threshold,omitempty"`
+ MirrorMaxCapacity *int `url:"mirror_max_capacity,omitempty" json:"mirror_max_capacity,omitempty"`
+ MirrorMaxDelay *int `url:"mirror_max_delay,omitempty" json:"mirror_max_delay,omitempty"`
+ OutboundLocalRequestsWhitelist []string `url:"outbound_local_requests_whitelist,omitempty" json:"outbound_local_requests_whitelist,omitempty"`
+ PagesDomainVerificationEnabled *bool `url:"pages_domain_verification_enabled,omitempty" json:"pages_domain_verification_enabled,omitempty"`
+ PasswordAuthenticationEnabledForGit *bool `url:"password_authentication_enabled_for_git,omitempty" json:"password_authentication_enabled_for_git,omitempty"`
+ PasswordAuthenticationEnabledForWeb *bool `url:"password_authentication_enabled_for_web,omitempty" json:"password_authentication_enabled_for_web,omitempty"`
+ PerformanceBarAllowedGroupID *string `url:"performance_bar_allowed_group_id,omitempty" json:"performance_bar_allowed_group_id,omitempty"`
+ PerformanceBarAllowedGroupPath *string `url:"performance_bar_allowed_group_path,omitempty" json:"performance_bar_allowed_group_path,omitempty"`
+ PerformanceBarEnabled *bool `url:"performance_bar_enabled,omitempty" json:"performance_bar_enabled,omitempty"`
+ PlantumlEnabled *bool `url:"plantuml_enabled,omitempty" json:"plantuml_enabled,omitempty"`
+ PlantumlURL *string `url:"plantuml_url,omitempty" json:"plantuml_url,omitempty"`
+ PollingIntervalMultiplier *float64 `url:"polling_interval_multiplier,omitempty" json:"polling_interval_multiplier,omitempty"`
+ ProjectExportEnabled *bool `url:"project_export_enabled,omitempty" json:"project_export_enabled,omitempty"`
+ PrometheusMetricsEnabled *bool `url:"prometheus_metrics_enabled,omitempty" json:"prometheus_metrics_enabled,omitempty"`
+ ProtectedCIVariables *bool `url:"protected_ci_variables,omitempty" json:"protected_ci_variables,omitempty"`
+ PseudonymizerEnabled *bool `url:"psedonymizer_enabled,omitempty" json:"psedonymizer_enabled,omitempty"`
+ PushEventHooksLimit *int `url:"push_event_hooks_limit,omitempty" json:"push_event_hooks_limit,omitempty"`
+ PushEventActivitiesLimit *int `url:"push_event_activities_limit,omitempty" json:"push_event_activities_limit,omitempty"`
+ RecaptchaEnabled *bool `url:"recaptcha_enabled,omitempty" json:"recaptcha_enabled,omitempty"`
+ RecaptchaPrivateKey *string `url:"recaptcha_private_key,omitempty" json:"recaptcha_private_key,omitempty"`
+ RecaptchaSiteKey *string `url:"recaptcha_site_key,omitempty" json:"recaptcha_site_key,omitempty"`
+ ReceiveMaxInputSize *int `url:"receive_max_input_size,omitempty" json:"receive_max_input_size,omitempty"`
+ RepositoryChecksEnabled *bool `url:"repository_checks_enabled,omitempty" json:"repository_checks_enabled,omitempty"`
+ RepositorySizeLimit *int `url:"repository_size_limit,omitempty" json:"repository_size_limit,omitempty"`
+ RepositoryStorages []string `url:"repository_storages,omitempty" json:"repository_storages,omitempty"`
+ RequireTwoFactorAuthentication *bool `url:"require_two_factor_authentication,omitempty" json:"require_two_factor_authentication,omitempty"`
+ RestrictedVisibilityLevels []VisibilityValue `url:"restricted_visibility_levels,omitempty" json:"restricted_visibility_levels,omitempty"`
+ RsaKeyRestriction *int `url:"rsa_key_restriction,omitempty" json:"rsa_key_restriction,omitempty"`
+ SendUserConfirmationEmail *bool `url:"send_user_confirmation_email,omitempty" json:"send_user_confirmation_email,omitempty"`
+ SessionExpireDelay *int `url:"session_expire_delay,omitempty" json:"session_expire_delay,omitempty"`
+ SharedRunnersEnabled *bool `url:"shared_runners_enabled,omitempty" json:"shared_runners_enabled,omitempty"`
+ SharedRunnersMinutes *int `url:"shared_runners_minutes,omitempty" json:"shared_runners_minutes,omitempty"`
+ SharedRunnersText *string `url:"shared_runners_text,omitempty" json:"shared_runners_text,omitempty"`
+ SignInText *string `url:"sign_in_text,omitempty" json:"sign_in_text,omitempty"`
+ SignupEnabled *bool `url:"signup_enabled,omitempty" json:"signup_enabled,omitempty"`
+ SlackAppEnabled *bool `url:"slack_app_enabled,omitempty" json:"slack_app_enabled,omitempty"`
+ SlackAppID *string `url:"slack_app_id,omitempty" json:"slack_app_id,omitempty"`
+ SlackAppSecret *string `url:"slack_app_secret,omitempty" json:"slack_app_secret,omitempty"`
+ SlackAppVerificationToken *string `url:"slack_app_verification_token,omitempty" json:"slack_app_verification_token,omitempty"`
+ SnowplowCollectorHostname *string `url:"snowplow_collector_hostname,omitempty" json:"snowplow_collector_hostname,omitempty"`
+ SnowplowCookieDomain *string `url:"snowplow_cookie_domain,omitempty" json:"snowplow_cookie_domain,omitempty"`
+ SnowplowEnabled *bool `url:"snowplow_enabled,omitempty" json:"snowplow_enabled,omitempty"`
+ SnowplowSiteID *string `url:"snowplow_site_id,omitempty" json:"snowplow_site_id,omitempty"`
+ TerminalMaxSessionTime *int `url:"terminal_max_session_time,omitempty" json:"terminal_max_session_time,omitempty"`
+ Terms *string `url:"terms,omitempty" json:"terms,omitempty"`
+ ThrottleAuthenticatedAPIEnabled *bool `url:"throttle_authenticated_api_enabled,omitempty" json:"throttle_authenticated_api_enabled,omitempty"`
+ ThrottleAuthenticatedAPIPeriodInSeconds *int `url:"throttle_authenticated_api_period_in_seconds,omitempty" json:"throttle_authenticated_api_period_in_seconds,omitempty"`
+ ThrottleAuthenticatedAPIRequestsPerPeriod *int `url:"throttle_authenticated_api_requests_per_period,omitempty" json:"throttle_authenticated_api_requests_per_period,omitempty"`
+ ThrottleAuthenticatedWebEnabled *bool `url:"throttle_authenticated_web_enabled,omitempty" json:"throttle_authenticated_web_enabled,omitempty"`
+ ThrottleAuthenticatedWebPeriodInSeconds *int `url:"throttle_authenticated_web_period_in_seconds,omitempty" json:"throttle_authenticated_web_period_in_seconds,omitempty"`
+ ThrottleAuthenticatedWebRequestsPerPeriod *int `url:"throttle_authenticated_web_requests_per_period,omitempty" json:"throttle_authenticated_web_requests_per_period,omitempty"`
+ ThrottleUnauthenticatedEnabled *bool `url:"throttle_unauthenticated_enabled,omitempty" json:"throttle_unauthenticated_enabled,omitempty"`
+ ThrottleUnauthenticatedPeriodInSeconds *int `url:"throttle_unauthenticated_period_in_seconds,omitempty" json:"throttle_unauthenticated_period_in_seconds,omitempty"`
+ ThrottleUnauthenticatedRequestsPerPeriod *int `url:"throttle_unauthenticated_requests_per_period,omitempty" json:"throttle_unauthenticated_requests_per_period,omitempty"`
+ TimeTrackingLimitToHours *bool `url:"time_tracking_limit_to_hours,omitempty" json:"time_tracking_limit_to_hours,omitempty"`
+ TwoFactorGracePeriod *int `url:"two_factor_grace_period,omitempty" json:"two_factor_grace_period,omitempty"`
+ UniqueIPsLimitEnabled *bool `url:"unique_ips_limit_enabled,omitempty" json:"unique_ips_limit_enabled,omitempty"`
+ UniqueIPsLimitPerUser *int `url:"unique_ips_limit_per_user,omitempty" json:"unique_ips_limit_per_user,omitempty"`
+ UniqueIPsLimitTimeWindow *int `url:"unique_ips_limit_time_window,omitempty" json:"unique_ips_limit_time_window,omitempty"`
+ UsagePingEnabled *bool `url:"usage_ping_enabled,omitempty" json:"usage_ping_enabled,omitempty"`
+ UserDefaultExternal *bool `url:"user_default_external,omitempty" json:"user_default_external,omitempty"`
+ UserDefaultInternalRegex *string `url:"user_default_internal_regex,omitempty" json:"user_default_internal_regex,omitempty"`
+ UserOauthApplications *bool `url:"user_oauth_applications,omitempty" json:"user_oauth_applications,omitempty"`
+ UserShowAddSSHKeyMessage *bool `url:"user_show_add_ssh_key_message,omitempty" json:"user_show_add_ssh_key_message,omitempty"`
+ VersionCheckEnabled *bool `url:"version_check_enabled,omitempty" json:"version_check_enabled,omitempty"`
+ WebIDEClientsidePreviewEnabled *bool `url:"web_ide_clientside_preview_enabled,omitempty" json:"web_ide_clientside_preview_enabled,omitempty"`
}
// UpdateSettings updates the application settings.