aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.codespellrc6
-rw-r--r--.github/workflows/codespell.yml19
-rw-r--r--README.md51
-rw-r--r--bridge/core/bridge.go2
-rw-r--r--bridge/github/client.go2
-rw-r--r--bridge/github/import_test.go4
-rw-r--r--bridge/gitlab/export.go2
-rw-r--r--bridge/jira/client.go2
-rw-r--r--bridge/launchpad/launchpad_api.go4
-rw-r--r--cache/subcache.go51
-rw-r--r--commands/bug/bug_comment.go2
-rw-r--r--commands/bug/bug_comment_add.go2
-rw-r--r--commands/bug/bug_comment_test.go1
-rw-r--r--commands/bug/bug_label.go2
-rw-r--r--commands/bug/bug_label_new.go4
-rw-r--r--commands/bug/bug_label_rm.go4
-rw-r--r--commands/bug/bug_select.go2
-rw-r--r--commands/bug/bug_show.go4
-rw-r--r--commands/bug/bug_status.go2
-rw-r--r--commands/bug/bug_status_close.go2
-rw-r--r--commands/bug/bug_status_open.go2
-rw-r--r--commands/bug/bug_test.go82
-rw-r--r--commands/bug/bug_title.go2
-rw-r--r--commands/bug/bug_title_edit.go2
-rw-r--r--commands/bug/completion.go4
-rw-r--r--commands/cmdtest/regex.go62
-rw-r--r--commands/cmdtest/regex_test.go25
-rw-r--r--doc/architecture.md4
-rw-r--r--doc/feature_matrix.md137
-rw-r--r--doc/howto-github.md2
-rw-r--r--entities/identity/key.go2
-rw-r--r--entity/dag/op_set_metadata_test.go8
-rw-r--r--entity/dag/operation_pack.go2
-rw-r--r--go.mod6
-rw-r--r--go.sum12
-rw-r--r--misc/git_integration/git-bug.go1
-rw-r--r--webui/src/components/Header/Header.tsx2
37 files changed, 420 insertions, 103 deletions
diff --git a/.codespellrc b/.codespellrc
new file mode 100644
index 00000000..750692b5
--- /dev/null
+++ b/.codespellrc
@@ -0,0 +1,6 @@
+[codespell]
+skip = .git,.venv,*.svg,package-lock.json
+# ot,fo,te - used as short variable names
+# optionall - OptionAll but codespell is case insensitive
+# testing - TestIn
+ignore-words-list = ot,fo,te,optionall,testin
diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml
new file mode 100644
index 00000000..5768d7c6
--- /dev/null
+++ b/.github/workflows/codespell.yml
@@ -0,0 +1,19 @@
+---
+name: Codespell
+
+on:
+ push:
+ branches: [master]
+ pull_request:
+ branches: [master]
+
+jobs:
+ codespell:
+ name: Check for spelling errors
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ - name: Codespell
+ uses: codespell-project/actions-codespell@v1
diff --git a/README.md b/README.md
index 889346c6..e9790bc5 100644
--- a/README.md
+++ b/README.md
@@ -25,8 +25,6 @@
- **integrates with your tooling**: use the UI you like (CLI, terminal, web) or integrate with your existing tools through the CLI or the GraphQL API
- **bridges to other bug trackers**: use [bridges](#bridges) to import and export to other trackers.
-:construction: This is now more than a proof of concept, but still not fully stable. Expect dragons and unfinished business. :construction:
-
## Help needed!
This project has grown bigger than I can handle by myself, especially with a day job. I'm looking for people to help on or maintain part of it:
@@ -182,7 +180,7 @@ An interactive terminal UI is available using the command `git bug termui` to br
![Termui recording](misc/termui_recording.gif)
-## Web UI (status: WIP)
+## Web UI
You can launch a rich Web UI with `git bug webui`.
@@ -200,34 +198,28 @@ The web UI interact with the backend through a GraphQL API. The schema is availa
## Bridges
+βœ…: working 🟠: partial implementation ❌: not working
+
### Importer implementations
-| | Github | Gitlab | Jira | Launchpad |
-|-------------------------------------------------|:------------------:|:------------------:|:------------------:|:------------------:|
-| **incremental**<br/>(can import more than once) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :x: |
-| **with resume**<br/>(download only new data) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :x: |
-| **identities** | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
-| identities update | :x: | :x: | :x: | :x: |
-| **bug** | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
-| comments | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
-| comment editions | :heavy_check_mark: | :x: | :heavy_check_mark: | :x: |
-| labels | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :x: |
-| status | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :x: |
-| title edition | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :x: |
-| **media/files** | :x: | :x: | :x: | :x: |
-| **automated test suite** | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: |
+| | Github | Gitlab | Jira | Launchpad |
+|-------------------------------------------------|:------:|:------:|:----:|:---------:|
+| **incremental**<br/>(can import more than once) | βœ… | βœ… | βœ… | ❌ |
+| **with resume**<br/>(download only new data) | βœ… | βœ… | βœ… | ❌ |
+| **identities** | 🟠 | 🟠 | 🟠 | 🟠 |
+| **bugs** | βœ… | βœ… | βœ… | 🟠 |
+| **board** | ❌ | ❌ | ❌ | ❌ |
+| **media/files** | ❌ | ❌ | ❌ | ❌ |
+| **automated test suite** | βœ… | βœ… | ❌ | ❌ |
### Exporter implementations
-| | Github | Gitlab | Jira | Launchpad |
-|--------------------------|:------------------:|:------------------:|:------------------:|:---------:|
-| **bug** | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :x: |
-| comments | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :x: |
-| comment editions | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :x: |
-| labels | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :x: |
-| status | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :x: |
-| title edition | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :x: |
-| **automated test suite** | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: |
+| | Github | Gitlab | Jira | Launchpad |
+|--------------------------|:------:|:------:|:----:|:---------:|
+| **identities** | 🟠 | 🟠 | 🟠 | 🟠 |
+| **bug** | βœ… | βœ… | βœ… | ❌ |
+| **board** | ❌ | ❌ | ❌ | ❌ |
+| **automated test suite** | βœ… | βœ… | ❌ | ❌ |
#### Bridge usage
@@ -281,14 +273,15 @@ See also all the [docs](doc).
## Planned features
-- media embedding
-- more bridges
+The [feature matrix](doc/feature_matrix.md) gives a good overview of what is planned, without being exhaustive.
+
+Additional planned feature:
- webUI that can be used as a public portal to accept user's input
- inflatable raptor
## Contribute
-PRs accepted. Drop by the [Gitter lobby](https://gitter.im/the-git-bug/Lobby) for a chat or browse the issues to see what is worked on or discussed.
+PRs accepted. Drop by the [Gitter lobby](https://gitter.im/the-git-bug/Lobby) or the [Matrix room](https://matrix.to/#/#the-git-bug_Lobby:gitter.im) for a chat, look at the [feature matrix](doc/feature_matrix.md) or browse the issues to see what is worked on or discussed.
```shell
git clone git@github.com:MichaelMure/git-bug.git
diff --git a/bridge/core/bridge.go b/bridge/core/bridge.go
index b410b470..1fc631f0 100644
--- a/bridge/core/bridge.go
+++ b/bridge/core/bridge.go
@@ -141,7 +141,7 @@ func DefaultBridge(repo *cache.RepoCache) (*Bridge, error) {
}
if len(bridges) > 1 {
- return nil, fmt.Errorf("multiple bridge are configured, you need to select one explicitely")
+ return nil, fmt.Errorf("multiple bridge are configured, you need to select one explicitly")
}
return LoadBridge(repo, bridges[0])
diff --git a/bridge/github/client.go b/bridge/github/client.go
index 361d0ee5..974c3067 100644
--- a/bridge/github/client.go
+++ b/bridge/github/client.go
@@ -20,7 +20,7 @@ type Client interface {
Query(context.Context, interface{}, map[string]interface{}) error
}
-// rateLimitHandlerClient wrapps the Github client and adds improved error handling and handling of
+// rateLimitHandlerClient wraps the Github client and adds improved error handling and handling of
// Github's GraphQL rate limit.
type rateLimitHandlerClient struct {
sc Client
diff --git a/bridge/github/import_test.go b/bridge/github/import_test.go
index 5fafcce1..52a3f852 100644
--- a/bridge/github/import_test.go
+++ b/bridge/github/import_test.go
@@ -123,11 +123,11 @@ func TestGithubImporter(t *testing.T) {
},
},
{
- name: "transfered issue",
+ name: "transferred issue",
url: "https://github.com/MichaelMure/git-bug-test-github-bridge/issues/8",
bug: &bug.Snapshot{
Operations: []dag.Operation{
- bug.NewCreateOp(author, 0, "transfered issue", "", nil),
+ bug.NewCreateOp(author, 0, "transferred issue", "", nil),
},
},
},
diff --git a/bridge/gitlab/export.go b/bridge/gitlab/export.go
index b3a02447..3beaec38 100644
--- a/bridge/gitlab/export.go
+++ b/bridge/gitlab/export.go
@@ -356,7 +356,7 @@ func (ge *gitlabExporter) exportBug(ctx context.Context, b *cache.BugCache, out
case *bug.LabelChangeOperation:
// we need to set the actual list of labels at each label change operation
- // because gitlab update issue requests need directly the latest list of the verison
+ // because gitlab update issue requests need directly the latest list of the version
for _, label := range op.Added {
labelSet[label.String()] = struct{}{}
diff --git a/bridge/jira/client.go b/bridge/jira/client.go
index c5fd1776..0e4e561f 100644
--- a/bridge/jira/client.go
+++ b/bridge/jira/client.go
@@ -1361,7 +1361,7 @@ func (client *Client) DoTransition(issueKeyOrID string, transitionID string) (ti
// TODO(josh)[767ee72]: Figure out a good way to "configure" the
// open/close state mapping. It would be *great* if we could actually
- // *compute* the necessary transitions and prompt for missing metatdata...
+ // *compute* the necessary transitions and prompt for missing metadata...
// but that is complex
var buffer bytes.Buffer
_, _ = fmt.Fprintf(&buffer,
diff --git a/bridge/launchpad/launchpad_api.go b/bridge/launchpad/launchpad_api.go
index 763e774e..d8b31e44 100644
--- a/bridge/launchpad/launchpad_api.go
+++ b/bridge/launchpad/launchpad_api.go
@@ -10,7 +10,7 @@ package launchpad
* - SearchTasks should yield bugs one by one
*
* TODO (maybe):
- * - Authentication (this might help retrieving email adresses)
+ * - Authentication (this might help retrieving email addresses)
*/
import (
@@ -83,7 +83,7 @@ func (lapi *launchpadAPI) SearchTasks(ctx context.Context, project string) ([]LP
var bugs []LPBug
// First, let us build the URL. Not all statuses are included by
- // default, so we have to explicitely enumerate them.
+ // default, so we have to explicitly enumerate them.
validStatuses := [13]string{
"New", "Incomplete", "Opinion", "Invalid",
"Won't Fix", "Expired", "Confirmed", "Triaged",
diff --git a/cache/subcache.go b/cache/subcache.go
index 09e53c23..b0ba6e52 100644
--- a/cache/subcache.go
+++ b/cache/subcache.go
@@ -187,6 +187,27 @@ func (sc *SubCache[EntityT, ExcerptT, CacheT]) write() error {
}
func (sc *SubCache[EntityT, ExcerptT, CacheT]) Build() <-chan BuildEvent {
+ // value chosen experimentally as giving the fasted indexing, while
+ // not driving the cache size on disk too high.
+ //
+ // | batchCount | bugIndex (MB) | idIndex (kB) | time (s) |
+ // |:----------:|:-------------:|:------------:|:--------:|
+ // | 10 | 24 | 84 | 1,59 |
+ // | 30 | 26 | 84 | 1,388 |
+ // | 50 | 26 | 84 | 1,44 |
+ // | 60 | 26 | 80 | 1,377 |
+ // | 68 | 27 | 80 | 1,385 |
+ // | 75 | 26 | 84 | 1,32 |
+ // | 80 | 26 | 80 | 1,37 |
+ // | 85 | 27 | 80 | 1,317 |
+ // | 100 | 26 | 80 | 1,455 |
+ // | 150 | 26 | 80 | 2,066 |
+ // | 200 | 28 | 80 | 2,885 |
+ // | 250 | 30 | 72 | 3,555 |
+ // | 300 | 31 | 72 | 4,787 |
+ // | 500 | 23 | 72 | 5,4 |
+ const maxBatchCount = 75
+
out := make(chan BuildEvent)
go func() {
@@ -221,6 +242,7 @@ func (sc *SubCache[EntityT, ExcerptT, CacheT]) Build() <-chan BuildEvent {
}
indexer, indexEnd := index.IndexBatch()
+ var batchCount int
for e := range allEntities {
if e.Err != nil {
@@ -245,6 +267,21 @@ func (sc *SubCache[EntityT, ExcerptT, CacheT]) Build() <-chan BuildEvent {
return
}
+ batchCount++
+ if batchCount >= maxBatchCount {
+ err = indexEnd()
+ if err != nil {
+ out <- BuildEvent{
+ Typename: sc.typename,
+ Err: err,
+ }
+ return
+ }
+
+ indexer, indexEnd = index.IndexBatch()
+ batchCount = 0
+ }
+
out <- BuildEvent{
Typename: sc.typename,
Event: BuildEventProgress,
@@ -253,13 +290,15 @@ func (sc *SubCache[EntityT, ExcerptT, CacheT]) Build() <-chan BuildEvent {
}
}
- err = indexEnd()
- if err != nil {
- out <- BuildEvent{
- Typename: sc.typename,
- Err: err,
+ if batchCount > 0 {
+ err = indexEnd()
+ if err != nil {
+ out <- BuildEvent{
+ Typename: sc.typename,
+ Err: err,
+ }
+ return
}
- return
}
err = sc.write()
diff --git a/commands/bug/bug_comment.go b/commands/bug/bug_comment.go
index 5cb8ff17..b037f688 100644
--- a/commands/bug/bug_comment.go
+++ b/commands/bug/bug_comment.go
@@ -26,7 +26,7 @@ func newBugCommentCommand(env *execenv.Env) *cobra.Command {
}
func runBugComment(env *execenv.Env, args []string) error {
- b, args, err := ResolveSelected(env.Backend, args)
+ b, _, err := ResolveSelected(env.Backend, args)
if err != nil {
return err
}
diff --git a/commands/bug/bug_comment_add.go b/commands/bug/bug_comment_add.go
index 152a1893..132de233 100644
--- a/commands/bug/bug_comment_add.go
+++ b/commands/bug/bug_comment_add.go
@@ -41,7 +41,7 @@ func newBugCommentNewCommand(env *execenv.Env) *cobra.Command {
}
func runBugCommentNew(env *execenv.Env, opts bugCommentNewOptions, args []string) error {
- b, args, err := ResolveSelected(env.Backend, args)
+ b, _, err := ResolveSelected(env.Backend, args)
if err != nil {
return err
}
diff --git a/commands/bug/bug_comment_test.go b/commands/bug/bug_comment_test.go
index ecc1c5f6..add48b21 100644
--- a/commands/bug/bug_comment_test.go
+++ b/commands/bug/bug_comment_test.go
@@ -140,7 +140,6 @@ func requireCommentsEqual(t *testing.T, golden string, env *execenv.Env) {
comments = normalizeParsedComments(t, comments)
if *cmdtest.Update {
- t.Log("Got here")
for i, comment := range comments {
fileName := fmt.Sprintf(goldenFilePattern, golden, i)
require.NoError(t, os.WriteFile(fileName, []byte(comment.message), 0644))
diff --git a/commands/bug/bug_label.go b/commands/bug/bug_label.go
index 554496e3..ec076922 100644
--- a/commands/bug/bug_label.go
+++ b/commands/bug/bug_label.go
@@ -24,7 +24,7 @@ func newBugLabelCommand(env *execenv.Env) *cobra.Command {
}
func runBugLabel(env *execenv.Env, args []string) error {
- b, args, err := ResolveSelected(env.Backend, args)
+ b, _, err := ResolveSelected(env.Backend, args)
if err != nil {
return err
}
diff --git a/commands/bug/bug_label_new.go b/commands/bug/bug_label_new.go
index 1e1f2d4f..9fff851c 100644
--- a/commands/bug/bug_label_new.go
+++ b/commands/bug/bug_label_new.go
@@ -22,12 +22,12 @@ func newBugLabelNewCommand(env *execenv.Env) *cobra.Command {
}
func runBugLabelNew(env *execenv.Env, args []string) error {
- b, args, err := ResolveSelected(env.Backend, args)
+ b, cleanArgs, err := ResolveSelected(env.Backend, args)
if err != nil {
return err
}
- added := args
+ added := cleanArgs
changes, _, err := b.ChangeLabels(text.CleanupOneLineArray(added), nil)
diff --git a/commands/bug/bug_label_rm.go b/commands/bug/bug_label_rm.go
index 6dda007c..d389830f 100644
--- a/commands/bug/bug_label_rm.go
+++ b/commands/bug/bug_label_rm.go
@@ -22,12 +22,12 @@ func newBugLabelRmCommand(env *execenv.Env) *cobra.Command {
}
func runBugLabelRm(env *execenv.Env, args []string) error {
- b, args, err := ResolveSelected(env.Backend, args)
+ b, cleanArgs, err := ResolveSelected(env.Backend, args)
if err != nil {
return err
}
- removed := args
+ removed := cleanArgs
changes, _, err := b.ChangeLabels(nil, text.CleanupOneLineArray(removed))
diff --git a/commands/bug/bug_select.go b/commands/bug/bug_select.go
index 652c61ea..c93cd7b1 100644
--- a/commands/bug/bug_select.go
+++ b/commands/bug/bug_select.go
@@ -44,7 +44,7 @@ The complementary command is "git bug deselect" performing the opposite operatio
func runBugSelect(env *execenv.Env, args []string) error {
if len(args) == 0 {
- return errors.New("You must provide a bug id")
+ return errors.New("a bug id must be provided")
}
prefix := args[0]
diff --git a/commands/bug/bug_show.go b/commands/bug/bug_show.go
index 9a03c9a3..ef20df2a 100644
--- a/commands/bug/bug_show.go
+++ b/commands/bug/bug_show.go
@@ -47,7 +47,7 @@ func newBugShowCommand(env *execenv.Env) *cobra.Command {
}
func runBugShow(env *execenv.Env, opts bugShowOptions, args []string) error {
- b, args, err := ResolveSelected(env.Backend, args)
+ b, _, err := ResolveSelected(env.Backend, args)
if err != nil {
return err
}
@@ -91,7 +91,7 @@ func runBugShow(env *execenv.Env, opts bugShowOptions, args []string) error {
case "title":
env.Out.Printf("%s\n", snap.Title)
default:
- return fmt.Errorf("\nUnsupported field: %s\n", opts.fields)
+ return fmt.Errorf("unsupported field: %s", opts.fields)
}
return nil
diff --git a/commands/bug/bug_status.go b/commands/bug/bug_status.go
index 59bef3fd..c8542240 100644
--- a/commands/bug/bug_status.go
+++ b/commands/bug/bug_status.go
@@ -24,7 +24,7 @@ func newBugStatusCommand(env *execenv.Env) *cobra.Command {
}
func runBugStatus(env *execenv.Env, args []string) error {
- b, args, err := ResolveSelected(env.Backend, args)
+ b, _, err := ResolveSelected(env.Backend, args)
if err != nil {
return err
}
diff --git a/commands/bug/bug_status_close.go b/commands/bug/bug_status_close.go
index 1d06007b..7a8ae4de 100644
--- a/commands/bug/bug_status_close.go
+++ b/commands/bug/bug_status_close.go
@@ -21,7 +21,7 @@ func newBugStatusCloseCommand(env *execenv.Env) *cobra.Command {
}
func runBugStatusClose(env *execenv.Env, args []string) error {
- b, args, err := ResolveSelected(env.Backend, args)
+ b, _, err := ResolveSelected(env.Backend, args)
if err != nil {
return err
}
diff --git a/commands/bug/bug_status_open.go b/commands/bug/bug_status_open.go
index e99d2db0..0c0c02f0 100644
--- a/commands/bug/bug_status_open.go
+++ b/commands/bug/bug_status_open.go
@@ -21,7 +21,7 @@ func newBugStatusOpenCommand(env *execenv.Env) *cobra.Command {
}
func runBugStatusOpen(env *execenv.Env, args []string) error {
- b, args, err := ResolveSelected(env.Backend, args)
+ b, _, err := ResolveSelected(env.Backend, args)
if err != nil {
return err
}
diff --git a/commands/bug/bug_test.go b/commands/bug/bug_test.go
index 6b0aa4e0..c13e8db7 100644
--- a/commands/bug/bug_test.go
+++ b/commands/bug/bug_test.go
@@ -1,13 +1,12 @@
package bugcmd
import (
- "encoding/json"
"testing"
"github.com/stretchr/testify/require"
"github.com/MichaelMure/git-bug/commands/bug/testenv"
- "github.com/MichaelMure/git-bug/commands/cmdjson"
+ . "github.com/MichaelMure/git-bug/commands/cmdtest"
)
func Test_repairQuery(t *testing.T) {
@@ -47,24 +46,69 @@ func Test_repairQuery(t *testing.T) {
}
func TestBug_Format(t *testing.T) {
- const expOrgMode = `^#+TODO: OPEN | CLOSED
-[*] OPEN [0-9a-f]{7} \[\d\d\d\d-\d\d-\d\d [[:alpha:]]{3} \d\d:\d\d\] John Doe: this is a bug title ::
-[*]{2} Last Edited: \[\d\d\d\d-\d\d-\d\d [[:alpha:]]{3} \d\d:\d\d\]
-[*]{2} Actors:
-: [0-9a-f]{7} John Doe
-[*]{2} Participants:
-: [0-9a-f]{7} John Doe
-$`
+ const expOrgMode = `#+TODO: OPEN | CLOSED
+* OPEN ` + ExpHumanId + ` [` + ExpOrgModeDate + `] John Doe: this is a bug title ::
+** Last Edited: [` + ExpOrgModeDate + `]
+** Actors:
+: ` + ExpHumanId + ` John Doe
+** Participants:
+: ` + ExpHumanId + ` John Doe
+`
+
+ const expJson = `[
+ {
+ "id": "` + ExpId + `",
+ "human_id": "` + ExpHumanId + `",
+ "create_time": {
+ "timestamp": ` + ExpTimestamp + `,
+ "time": "` + ExpISO8601 + `",
+ "lamport": 2
+ },
+ "edit_time": {
+ "timestamp": ` + ExpTimestamp + `,
+ "time": "` + ExpISO8601 + `",
+ "lamport": 2
+ },
+ "status": "open",
+ "labels": null,
+ "title": "this is a bug title",
+ "actors": [
+ {
+ "id": "` + ExpId + `",
+ "human_id": "` + ExpHumanId + `",
+ "name": "John Doe",
+ "login": ""
+ }
+ ],
+ "participants": [
+ {
+ "id": "` + ExpId + `",
+ "human_id": "` + ExpHumanId + `",
+ "name": "John Doe",
+ "login": ""
+ }
+ ],
+ "author": {
+ "id": "` + ExpId + `",
+ "human_id": "` + ExpHumanId + `",
+ "name": "John Doe",
+ "login": ""
+ },
+ "comments": 1,
+ "metadata": {}
+ }
+]
+`
cases := []struct {
format string
exp string
}{
- {"default", "^[0-9a-f]{7}\topen\tthis is a bug title John Doe \n$"},
- {"plain", "^[0-9a-f]{7}\topen\tthis is a bug title\n$"},
- {"id", "^[0-9a-f]{64}\n$"},
+ {"default", ExpHumanId + "\topen\tthis is a bug title John Doe \n"},
+ {"plain", ExpHumanId + "\topen\tthis is a bug title\n"},
+ {"id", ExpId + "\n"},
{"org-mode", expOrgMode},
- {"json", ".*"},
+ {"json", expJson},
}
for _, testcase := range cases {
@@ -79,15 +123,7 @@ $`
}
require.NoError(t, runBug(env, opts, []string{}))
-
- switch testcase.format {
- case "json":
- var bugs []cmdjson.BugExcerpt
- require.NoError(t, json.Unmarshal(env.Out.Bytes(), &bugs))
- require.Len(t, bugs, 1)
- default:
- require.Regexp(t, testcase.exp, env.Out.String())
- }
+ require.Regexp(t, MakeExpectedRegex(testcase.exp), env.Out.String())
})
}
}
diff --git a/commands/bug/bug_title.go b/commands/bug/bug_title.go
index 47603410..af959fb7 100644
--- a/commands/bug/bug_title.go
+++ b/commands/bug/bug_title.go
@@ -23,7 +23,7 @@ func newBugTitleCommand(env *execenv.Env) *cobra.Command {
}
func runBugTitle(env *execenv.Env, args []string) error {
- b, args, err := ResolveSelected(env.Backend, args)
+ b, _, err := ResolveSelected(env.Backend, args)
if err != nil {
return err
}
diff --git a/commands/bug/bug_title_edit.go b/commands/bug/bug_title_edit.go
index fc60824f..2d6bafca 100644
--- a/commands/bug/bug_title_edit.go
+++ b/commands/bug/bug_title_edit.go
@@ -38,7 +38,7 @@ func newBugTitleEditCommand(env *execenv.Env) *cobra.Command {
}
func runBugTitleEdit(env *execenv.Env, opts bugTitleEditOptions, args []string) error {
- b, args, err := ResolveSelected(env.Backend, args)
+ b, _, err := ResolveSelected(env.Backend, args)
if err != nil {
return err
}
diff --git a/commands/bug/completion.go b/commands/bug/completion.go
index 4754f97d..62bf658a 100644
--- a/commands/bug/completion.go
+++ b/commands/bug/completion.go
@@ -50,7 +50,7 @@ func BugAndLabelsCompletion(env *execenv.Env, addOrRemove bool) completion.Valid
_ = env.Backend.Close()
}()
- b, args, err := ResolveSelected(env.Backend, args)
+ b, cleanArgs, err := ResolveSelected(env.Backend, args)
if _select.IsErrNoValidId(err) {
// we need a bug first to complete labels
return bugWithBackend(env.Backend, toComplete)
@@ -62,7 +62,7 @@ func BugAndLabelsCompletion(env *execenv.Env, addOrRemove bool) completion.Valid
snap := b.Snapshot()
seenLabels := map[bug.Label]bool{}
- for _, label := range args {
+ for _, label := range cleanArgs {
seenLabels[bug.Label(label)] = addOrRemove
}
diff --git a/commands/cmdtest/regex.go b/commands/cmdtest/regex.go
new file mode 100644
index 00000000..0b9cb672
--- /dev/null
+++ b/commands/cmdtest/regex.go
@@ -0,0 +1,62 @@
+package cmdtest
+
+import (
+ "regexp"
+ "strings"
+)
+
+const ExpId = "\x07id\x07"
+const ExpHumanId = "\x07human-id\x07"
+const ExpTimestamp = "\x07timestamp\x07"
+const ExpISO8601 = "\x07iso8601\x07"
+
+const ExpOrgModeDate = "\x07org-mode-date\x07"
+
+// MakeExpectedRegex transform a raw string of an expected output into a regex suitable for testing.
+// Some markers like ExpId are available to substitute the appropriate regex for element that can vary randomly.
+func MakeExpectedRegex(input string) string {
+ var substitutes = map[string]string{
+ ExpId: `[0-9a-f]{64}`,
+ ExpHumanId: `[0-9a-f]{7}`,
+ ExpTimestamp: `[0-9]{7,10}`,
+ ExpISO8601: `\d{4}(-\d\d(-\d\d(T\d\d:\d\d(:\d\d)?(\.\d+)?(([+-]\d\d:\d\d)|Z)?)?)?)?`,
+ ExpOrgModeDate: `\d\d\d\d-\d\d-\d\d [[:alpha:]]{3} \d\d:\d\d`,
+ }
+
+ escaped := []rune(regexp.QuoteMeta(input))
+
+ var result strings.Builder
+ var inSubstitute bool
+ var substitute strings.Builder
+
+ result.WriteString("^")
+
+ for i := 0; i < len(escaped); i++ {
+ r := escaped[i]
+ if !inSubstitute && r == '\x07' {
+ substitute.Reset()
+ substitute.WriteRune(r)
+ inSubstitute = true
+ continue
+ }
+ if inSubstitute && r == '\x07' {
+ substitute.WriteRune(r)
+ sub, ok := substitutes[substitute.String()]
+ if !ok {
+ panic("unknown substitute: " + substitute.String())
+ }
+ result.WriteString(sub)
+ inSubstitute = false
+ continue
+ }
+ if inSubstitute {
+ substitute.WriteRune(r)
+ } else {
+ result.WriteRune(r)
+ }
+ }
+
+ result.WriteString("$")
+
+ return result.String()
+}
diff --git a/commands/cmdtest/regex_test.go b/commands/cmdtest/regex_test.go
new file mode 100644
index 00000000..7f2e75fa
--- /dev/null
+++ b/commands/cmdtest/regex_test.go
@@ -0,0 +1,25 @@
+package cmdtest
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func TestMakeExpectedRegex(t *testing.T) {
+ cases := []struct {
+ sub string
+ text string
+ }{
+ {ExpId, "d96dc877077a571414168c946eb013035888715b561e75682cfae9ef785e3227"},
+ {ExpHumanId, "d96dc87"},
+ {ExpTimestamp, "1674368486"},
+ {ExpISO8601, "2023-01-22T07:21:26+01:00"},
+ }
+
+ for _, tc := range cases {
+ t.Run(tc.sub, func(t *testing.T) {
+ require.Regexp(t, MakeExpectedRegex(tc.text), tc.text)
+ })
+ }
+}
diff --git a/doc/architecture.md b/doc/architecture.md
index 5f81462b..395886ea 100644
--- a/doc/architecture.md
+++ b/doc/architecture.md
@@ -88,7 +88,7 @@ The package `termui` contains the interactive terminal user interface, implement
## graphql
-The package `graphql` implement the GraphQL API, mapping the data model and providing read/write access from outside of the process. This API is in particular used by the webUI but could be used to implement other user interfaces or bridges with other systems.
+The package `graphql` implement the GraphQL API, mapping the data model and providing read/write access from outside the process. This API is in particular used by the webUI but could be used to implement other user interfaces or bridges with other systems.
## webui
@@ -96,7 +96,7 @@ The package `webui` hold the web based user interface, implemented in both go an
The javascript code is compiled and packaged inside the go binary, allowing for a single file distribution of git-bug.
-When the webUI is started from the CLI command, a localhost HTTP server is started to serve the webUI resources (html, js, css), as well as the GraphQL API. When the webUI is loaded in the browser, it interact with the git-bug process through the GraphQL API to load and edit bugs.
+When the webUI is started from the CLI command, a localhost HTTP server is started to serve the webUI resources (html, js, css), as well as the GraphQL API. When the webUI is loaded in the browser, it interacts with the git-bug process through the GraphQL API to load and edit bugs.
## bridge
diff --git a/doc/feature_matrix.md b/doc/feature_matrix.md
new file mode 100644
index 00000000..ee551bd4
--- /dev/null
+++ b/doc/feature_matrix.md
@@ -0,0 +1,137 @@
+# User facing capabilities
+
+This document tries to give an overview of what is currently supported, and by extension where effort can be focused to bring feature completion and parity.
+
+As git-bug is a free software project, accept and rely on contributor, those feature matrices kinda define a roadmap, in the sense than anything mentioned below is a planned feature and can be worked on. This does not mean that a feature not mentioned here should not be considered, just maybe check the issue tracker and come talk about it.
+
+This document however does not show all the untold work required to support those user-facing capabilities. There has been a ton of work behind the scene and more will be required over time.
+
+βœ…: working 🟠: partial implementation ❌: not working
+
+## Other goals
+
+Some goals don't really fit below, so I'll mention them here:
+- have the webUI accept external OAuth (Github, ...) and act as a public portal where user outside the project can browse and interact with the project
+- project configuration (valid labels, ...)
+- commit signature to fully authenticate user's interaction
+- interface with the system keyring, to distribute and expose known public keys and allow checking signed commit in normal git workflow
+- privileged roles (admin, ...) and enforcing the corresponding rules
+- package the webui as a desktop app
+
+Additionally, some other are captured as [Github issues](https://github.com/MichaelMure/git-bug/issues) or [Discussions](https://github.com/MichaelMure/git-bug/discussions).
+
+## Entities
+
+The most high level overview of what kind of entities are supported and where.
+
+| | Core | CLI | TermUI | WebUI |
+|----------------|:----:|:---:|:------:|:-----:|
+| Identities | βœ… | βœ… | βœ… | βœ… |
+| Bug | βœ… | βœ… | βœ… | βœ… |
+| Board | 🟠 | 🟠 | ❌ | ❌ |
+| Pull-request | ❌ | ❌ | ❌ | ❌ |
+| Project Config | ❌ | ❌ | ❌ | ❌ |
+
+More specific features across the board.
+
+| | Core | CLI | TermUI | WebUI |
+|--------------------|:----:|:---:|:------:|:-----:|
+| Media embedding | 🟠 | ❌ | ❌ | ❌ |
+| Fast indexing | βœ… | βœ… | βœ… | βœ… |
+| Markdown rendering | N/A | ❌ | ❌ | βœ… |
+
+#### Identities
+
+| | Core | CLI | TermUI | WebUI |
+|-------------------------|:----:|:---:|:------:|:-----:|
+| Public keys | 🟠 | ❌ | ❌ | ❌ |
+| Private keys management | 🟠 | ❌ | ❌ | ❌ |
+| Identity edition | βœ… | βœ… | ❌ | ❌ |
+| Identity adoption | βœ… | βœ… | ❌ | ❌ |
+| Identity protection | 🟠 | ❌ | ❌ | ❌ |
+
+#### Bugs
+
+| | Core | CLI | TermUI | WebUI |
+|-------------------|:----:|:---:|:------:|:-----:|
+| Comments | βœ… | βœ… | βœ… | βœ… |
+| Comments edition | βœ… | βœ… | βœ… | βœ… |
+| Comments deletion | βœ… | ❌ | ❌ | ❌ |
+| Labels | βœ… | βœ… | βœ… | βœ… |
+| Status | βœ… | βœ… | βœ… | βœ… |
+| Title edition | βœ… | βœ… | βœ… | βœ… |
+| Assignee | ❌ | ❌ | ❌ | ❌ |
+| Milestone | ❌ | ❌ | ❌ | ❌ |
+
+
+## Bridges
+
+### Importers
+
+General capabilities of importers:
+
+| | Github | Gitlab | Jira | Launchpad |
+|-------------------------------------------------|:------:|:------:|:----:|:---------:|
+| **incremental**<br/>(can import more than once) | βœ… | βœ… | βœ… | ❌ |
+| **with resume**<br/>(download only new data) | βœ… | βœ… | βœ… | ❌ |
+| **media/files** | ❌ | ❌ | ❌ | ❌ |
+| **automated test suite** | βœ… | βœ… | ❌ | ❌ |
+
+Identity support:
+
+| | Github | Gitlab | Jira | Launchpad |
+|-------------------|:------:|:------:|:----:|:---------:|
+| **identities** | βœ… | βœ… | βœ… | βœ… |
+| identities update | ❌ | ❌ | ❌ | ❌ |
+| public keys | ❌ | ❌ | ❌ | ❌ |
+
+Bug support:
+
+| | Github | Gitlab | Jira | Launchpad |
+|------------------|:------:|:------:|:----:|:---------:|
+| **bug** | βœ… | βœ… | βœ… | βœ… |
+| comments | βœ… | βœ… | βœ… | βœ… |
+| comment editions | βœ… | ❌ | βœ… | ❌ |
+| labels | βœ… | βœ… | βœ… | ❌ |
+| status | βœ… | βœ… | βœ… | ❌ |
+| title edition | βœ… | βœ… | βœ… | ❌ |
+| Assignee | ❌ | ❌ | ❌ | ❌ |
+| Milestone | ❌ | ❌ | ❌ | ❌ |
+
+Board support:
+
+| | Github | Gitlab | Jira | Launchpad |
+|-----------|:------:|:------:|:----:|:---------:|
+| **board** | ❌ | ❌ | ❌ | ❌ |
+
+### Exporters
+
+**General capabilities of exporters**:
+
+| | Github | Gitlab | Jira |
+|-------------------------------------------------|:------:|:------:|:----:|
+| **incremental**<br/>(can export more than once) | βœ… | βœ… | βœ… |
+| **with resume**<br/>(upload only new data) | βœ… | βœ… | βœ… |
+| **automated test suite** | βœ… | βœ… | ❌ |
+
+**Identity support**:
+
+| | Github | Gitlab | Jira |
+|-------------------|:------:|:------:|:----:|
+| **identities** | βœ… | βœ… | βœ… |
+| identities update | ❌ | ❌ | ❌ |
+
+Note: as the target bug tracker require accounts and credentials, there is only so much that an exporter can do about identities. A bridge should be able to load and use credentials for multiple remote account, but when they are not available, the corresponding changes can't be replicated.
+
+**Bug support**:
+
+| | Github | Gitlab | Jira |
+|------------------|:------:|:------:|:----:|
+| **bugs** | βœ… | βœ… | βœ… |
+| comments | βœ… | βœ… | βœ… |
+| comment editions | βœ… | βœ… | βœ… |
+| labels | βœ… | βœ… | βœ… |
+| status | βœ… | βœ… | βœ… |
+| title edition | βœ… | βœ… | βœ… |
+| Assignee | ❌ | ❌ | ❌ |
+| Milestone | ❌ | ❌ | ❌ |
diff --git a/doc/howto-github.md b/doc/howto-github.md
index ba3e380c..47d93434 100644
--- a/doc/howto-github.md
+++ b/doc/howto-github.md
@@ -72,7 +72,7 @@ For a richer and more user friendly UI, `git-bug` proposes a web UI (read-only a
## Want more?
-If you interested to read more about `git-bug`, have a look at the followings:
+If you interested to read more about `git-bug`, have a look at the following:
- [the project itself, with a more complete readme](https://github.com/MichaelMure/git-bug)
- [a bird view of the internals](https://github.com/MichaelMure/git-bug/blob/master/doc/architecture.md)
- [a description of the data model](https://github.com/MichaelMure/git-bug/blob/master/doc/model.md)
diff --git a/entities/identity/key.go b/entities/identity/key.go
index 82b9b95c..87271dd5 100644
--- a/entities/identity/key.go
+++ b/entities/identity/key.go
@@ -23,7 +23,7 @@ type Key struct {
private *packet.PrivateKey
}
-// GenerateKey generate a keypair (public+private)
+// GenerateKey generate a key pair (public+private)
// The type and configuration of the key is determined by the default value in go's OpenPGP.
func GenerateKey() *Key {
entity, err := openpgp.NewEntity("", "", "", &packet.Config{
diff --git a/entity/dag/op_set_metadata_test.go b/entity/dag/op_set_metadata_test.go
index 07ece013..a06f89da 100644
--- a/entity/dag/op_set_metadata_test.go
+++ b/entity/dag/op_set_metadata_test.go
@@ -54,7 +54,7 @@ func TestSetMetadata(t *testing.T) {
target1Metadata := snap.AllOperations()[0].AllMetadata()
require.Len(t, target1Metadata, 2)
- // original key is not overrided
+ // original key is not overridden
require.Equal(t, target1Metadata["key"], "value")
// new key is set
require.Equal(t, target1Metadata["key2"], "value")
@@ -78,7 +78,7 @@ func TestSetMetadata(t *testing.T) {
target2Metadata = snap.AllOperations()[1].AllMetadata()
require.Len(t, target2Metadata, 2)
- // original key is not overrided
+ // original key is not overridden
require.Equal(t, target2Metadata["key2"], "value2")
// new key is set
require.Equal(t, target2Metadata["key3"], "value3")
@@ -93,9 +93,9 @@ func TestSetMetadata(t *testing.T) {
target1Metadata = snap.AllOperations()[0].AllMetadata()
require.Len(t, target1Metadata, 2)
- // original key is not overrided
+ // original key is not overridden
require.Equal(t, target1Metadata["key"], "value")
- // previously set key is not overrided
+ // previously set key is not overridden
require.Equal(t, target1Metadata["key2"], "value")
target2Metadata = snap.AllOperations()[1].AllMetadata()
diff --git a/entity/dag/operation_pack.go b/entity/dag/operation_pack.go
index cc6c81f4..c999ff23 100644
--- a/entity/dag/operation_pack.go
+++ b/entity/dag/operation_pack.go
@@ -82,7 +82,7 @@ func (opp *operationPack) Validate() error {
}
// Write writes the OperationPack in git, with zero, one or more parent commits.
-// If the repository has a keypair able to sign (that is, with a private key), the resulting commit is signed with that key.
+// If the repository has a key pair able to sign (that is, with a private key), the resulting commit is signed with that key.
// Return the hash of the created commit.
func (opp *operationPack) Write(def Definition, repo repository.Repo, parentCommit ...repository.Hash) (repository.Hash, error) {
if err := opp.Validate(); err != nil {
diff --git a/go.mod b/go.mod
index 0008ef92..b73e80d2 100644
--- a/go.mod
+++ b/go.mod
@@ -28,9 +28,9 @@ require (
github.com/stretchr/testify v1.8.1
github.com/vbauerster/mpb/v8 v8.1.4
github.com/vektah/gqlparser/v2 v2.5.1
- github.com/xanzy/go-gitlab v0.77.0
+ github.com/xanzy/go-gitlab v0.79.1
golang.org/x/crypto v0.5.0
- golang.org/x/oauth2 v0.0.0-20220722155238-128564f6959c
+ golang.org/x/oauth2 v0.4.0
golang.org/x/sync v0.1.0
golang.org/x/sys v0.4.0
golang.org/x/text v0.6.0
@@ -115,7 +115,7 @@ require (
golang.org/x/mod v0.7.0 // indirect
golang.org/x/net v0.5.0 // indirect
golang.org/x/term v0.4.0
- golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect
+ golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.4.0 // indirect
golang.org/x/vuln v0.0.0-20220908155419-5537ad2271a7
google.golang.org/appengine v1.6.7 // indirect
diff --git a/go.sum b/go.sum
index 6c735e9e..86849bf2 100644
--- a/go.sum
+++ b/go.sum
@@ -312,8 +312,8 @@ github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+
github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
github.com/willf/bitset v1.1.10 h1:NotGKqX0KwQ72NUzqrjZq5ipPNDQex9lo3WpaS8L2sc=
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
-github.com/xanzy/go-gitlab v0.77.0 h1:UrbGlxkWVCbkpa6Fk6cM8ARh+rLACWemkJnsawT7t98=
-github.com/xanzy/go-gitlab v0.77.0/go.mod h1:d/a0vswScO7Agg1CZNz15Ic6SSvBG9vfw8egL99t4kA=
+github.com/xanzy/go-gitlab v0.79.1 h1:ZmEei8RZYlqk4D7nYrWWZqywmKBOd7vmPMlJbueZXUU=
+github.com/xanzy/go-gitlab v0.79.1/go.mod h1:DlByVTSXhPsJMYL6+cm8e8fTJjeBmhrXdC/yvkKKt6M=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
@@ -352,8 +352,8 @@ golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfS
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
-golang.org/x/oauth2 v0.0.0-20220722155238-128564f6959c h1:q3gFqPqH7NVofKo3c3yETAP//pPI+G5mvB7qqj1Y5kY=
-golang.org/x/oauth2 v0.0.0-20220722155238-128564f6959c/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
+golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M=
+golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -405,8 +405,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ=
-golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
+golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
diff --git a/misc/git_integration/git-bug.go b/misc/git_integration/git-bug.go
new file mode 100644
index 00000000..accb2674
--- /dev/null
+++ b/misc/git_integration/git-bug.go
@@ -0,0 +1 @@
+package git_integration
diff --git a/webui/src/components/Header/Header.tsx b/webui/src/components/Header/Header.tsx
index 961696d7..0bbbe3bb 100644
--- a/webui/src/components/Header/Header.tsx
+++ b/webui/src/components/Header/Header.tsx
@@ -69,7 +69,7 @@ function Header() {
const location = useLocation();
// Prevents error of invalid tab selection in <Tabs>
- // Will return a valid tab path or false if path is unkown.
+ // Will return a valid tab path or false if path is unknown.
function highlightTab() {
const validTabs = ['/', '/code', '/pulls', '/settings'];
const tab = validTabs.find((tabPath) => tabPath === location.pathname);