mirror of
https://github.com/MichaelMure/git-bug.git
synced 2024-12-14 08:45:30 +03:00
Merge pull request #835 from MichaelMure/op-base
entity/dag: proper base operation for simplified implementation
This commit is contained in:
commit
cd52872475
4
.github/workflows/go.yml
vendored
4
.github/workflows/go.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.16.x]
|
go-version: [1.18.x]
|
||||||
platform: [ubuntu-latest, macos-latest, windows-latest]
|
platform: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
|
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
@ -46,7 +46,7 @@ jobs:
|
|||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: 1.16.x
|
go-version: 1.18.x
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
//go:generate genny -in=connection_template.go -out=gen_lazy_bug.go gen "Name=LazyBug NodeType=entity.Id EdgeType=LazyBugEdge ConnectionType=models.BugConnection"
|
//go:generate genny -in=connection_template.go -out=gen_lazy_bug.go gen "Name=LazyBug NodeType=entity.Id EdgeType=LazyBugEdge ConnectionType=models.BugConnection"
|
||||||
//go:generate genny -in=connection_template.go -out=gen_lazy_identity.go gen "Name=LazyIdentity NodeType=entity.Id EdgeType=LazyIdentityEdge ConnectionType=models.IdentityConnection"
|
//go:generate genny -in=connection_template.go -out=gen_lazy_identity.go gen "Name=LazyIdentity NodeType=entity.Id EdgeType=LazyIdentityEdge ConnectionType=models.IdentityConnection"
|
||||||
//go:generate genny -in=connection_template.go -out=gen_identity.go gen "Name=Identity NodeType=models.IdentityWrapper EdgeType=models.IdentityEdge ConnectionType=models.IdentityConnection"
|
//go:generate genny -in=connection_template.go -out=gen_identity.go gen "Name=Identity NodeType=models.IdentityWrapper EdgeType=models.IdentityEdge ConnectionType=models.IdentityConnection"
|
||||||
//go:generate genny -in=connection_template.go -out=gen_operation.go gen "Name=Operation NodeType=bug.Operation EdgeType=models.OperationEdge ConnectionType=models.OperationConnection"
|
//go:generate genny -in=connection_template.go -out=gen_operation.go gen "Name=Operation NodeType=dag.Operation EdgeType=models.OperationEdge ConnectionType=models.OperationConnection"
|
||||||
//go:generate genny -in=connection_template.go -out=gen_comment.go gen "Name=Comment NodeType=bug.Comment EdgeType=models.CommentEdge ConnectionType=models.CommentConnection"
|
//go:generate genny -in=connection_template.go -out=gen_comment.go gen "Name=Comment NodeType=bug.Comment EdgeType=models.CommentEdge ConnectionType=models.CommentConnection"
|
||||||
//go:generate genny -in=connection_template.go -out=gen_timeline.go gen "Name=TimelineItem NodeType=bug.TimelineItem EdgeType=models.TimelineItemEdge ConnectionType=models.TimelineItemConnection"
|
//go:generate genny -in=connection_template.go -out=gen_timeline.go gen "Name=TimelineItem NodeType=bug.TimelineItem EdgeType=models.TimelineItemEdge ConnectionType=models.TimelineItemConnection"
|
||||||
//go:generate genny -in=connection_template.go -out=gen_label.go gen "Name=Label NodeType=bug.Label EdgeType=models.LabelEdge ConnectionType=models.LabelConnection"
|
//go:generate genny -in=connection_template.go -out=gen_label.go gen "Name=Label NodeType=bug.Label EdgeType=models.LabelEdge ConnectionType=models.LabelConnection"
|
||||||
|
@ -8,23 +8,23 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/MichaelMure/git-bug/api/graphql/models"
|
"github.com/MichaelMure/git-bug/api/graphql/models"
|
||||||
"github.com/MichaelMure/git-bug/bug"
|
"github.com/MichaelMure/git-bug/entity/dag"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BugOperationEdgeMaker define a function that take a bug.Operation and an offset and
|
// DagOperationEdgeMaker define a function that take a dag.Operation and an offset and
|
||||||
// create an Edge.
|
// create an Edge.
|
||||||
type OperationEdgeMaker func(value bug.Operation, offset int) Edge
|
type OperationEdgeMaker func(value dag.Operation, offset int) Edge
|
||||||
|
|
||||||
// OperationConMaker define a function that create a models.OperationConnection
|
// OperationConMaker define a function that create a models.OperationConnection
|
||||||
type OperationConMaker func(
|
type OperationConMaker func(
|
||||||
edges []*models.OperationEdge,
|
edges []*models.OperationEdge,
|
||||||
nodes []bug.Operation,
|
nodes []dag.Operation,
|
||||||
info *models.PageInfo,
|
info *models.PageInfo,
|
||||||
totalCount int) (*models.OperationConnection, error)
|
totalCount int) (*models.OperationConnection, error)
|
||||||
|
|
||||||
// OperationCon will paginate a source according to the input of a relay connection
|
// OperationCon will paginate a source according to the input of a relay connection
|
||||||
func OperationCon(source []bug.Operation, edgeMaker OperationEdgeMaker, conMaker OperationConMaker, input models.ConnectionInput) (*models.OperationConnection, error) {
|
func OperationCon(source []dag.Operation, edgeMaker OperationEdgeMaker, conMaker OperationConMaker, input models.ConnectionInput) (*models.OperationConnection, error) {
|
||||||
var nodes []bug.Operation
|
var nodes []dag.Operation
|
||||||
var edges []*models.OperationEdge
|
var edges []*models.OperationEdge
|
||||||
var cursors []string
|
var cursors []string
|
||||||
var pageInfo = &models.PageInfo{}
|
var pageInfo = &models.PageInfo{}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// +build ignore
|
//go:build ignore
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ models:
|
|||||||
Hash:
|
Hash:
|
||||||
model: github.com/MichaelMure/git-bug/repository.Hash
|
model: github.com/MichaelMure/git-bug/repository.Hash
|
||||||
Operation:
|
Operation:
|
||||||
model: github.com/MichaelMure/git-bug/bug.Operation
|
model: github.com/MichaelMure/git-bug/entity/dag.Operation
|
||||||
CreateOperation:
|
CreateOperation:
|
||||||
model: github.com/MichaelMure/git-bug/bug.CreateOperation
|
model: github.com/MichaelMure/git-bug/bug.CreateOperation
|
||||||
SetTitleOperation:
|
SetTitleOperation:
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -8,6 +8,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/MichaelMure/git-bug/bug"
|
"github.com/MichaelMure/git-bug/bug"
|
||||||
|
"github.com/MichaelMure/git-bug/entity/dag"
|
||||||
"github.com/MichaelMure/git-bug/repository"
|
"github.com/MichaelMure/git-bug/repository"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -250,7 +251,7 @@ type OpenBugPayload struct {
|
|||||||
// The connection type for an Operation
|
// The connection type for an Operation
|
||||||
type OperationConnection struct {
|
type OperationConnection struct {
|
||||||
Edges []*OperationEdge `json:"edges"`
|
Edges []*OperationEdge `json:"edges"`
|
||||||
Nodes []bug.Operation `json:"nodes"`
|
Nodes []dag.Operation `json:"nodes"`
|
||||||
PageInfo *PageInfo `json:"pageInfo"`
|
PageInfo *PageInfo `json:"pageInfo"`
|
||||||
TotalCount int `json:"totalCount"`
|
TotalCount int `json:"totalCount"`
|
||||||
}
|
}
|
||||||
@ -258,7 +259,7 @@ type OperationConnection struct {
|
|||||||
// Represent an Operation
|
// Represent an Operation
|
||||||
type OperationEdge struct {
|
type OperationEdge struct {
|
||||||
Cursor string `json:"cursor"`
|
Cursor string `json:"cursor"`
|
||||||
Node bug.Operation `json:"node"`
|
Node dag.Operation `json:"node"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Information about pagination in a connection.
|
// Information about pagination in a connection.
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/MichaelMure/git-bug/bug"
|
"github.com/MichaelMure/git-bug/bug"
|
||||||
"github.com/MichaelMure/git-bug/cache"
|
"github.com/MichaelMure/git-bug/cache"
|
||||||
"github.com/MichaelMure/git-bug/entity"
|
"github.com/MichaelMure/git-bug/entity"
|
||||||
|
"github.com/MichaelMure/git-bug/entity/dag"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BugWrapper is an interface used by the GraphQL resolvers to handle a bug.
|
// BugWrapper is an interface used by the GraphQL resolvers to handle a bug.
|
||||||
@ -24,7 +25,7 @@ type BugWrapper interface {
|
|||||||
Participants() ([]IdentityWrapper, error)
|
Participants() ([]IdentityWrapper, error)
|
||||||
CreatedAt() time.Time
|
CreatedAt() time.Time
|
||||||
Timeline() ([]bug.TimelineItem, error)
|
Timeline() ([]bug.TimelineItem, error)
|
||||||
Operations() ([]bug.Operation, error)
|
Operations() ([]dag.Operation, error)
|
||||||
|
|
||||||
IsAuthored()
|
IsAuthored()
|
||||||
}
|
}
|
||||||
@ -144,7 +145,7 @@ func (lb *lazyBug) Timeline() ([]bug.TimelineItem, error) {
|
|||||||
return lb.snap.Timeline, nil
|
return lb.snap.Timeline, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lb *lazyBug) Operations() ([]bug.Operation, error) {
|
func (lb *lazyBug) Operations() ([]dag.Operation, error) {
|
||||||
err := lb.load()
|
err := lb.load()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -210,6 +211,6 @@ func (l *loadedBug) Timeline() ([]bug.TimelineItem, error) {
|
|||||||
return l.Snapshot.Timeline, nil
|
return l.Snapshot.Timeline, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *loadedBug) Operations() ([]bug.Operation, error) {
|
func (l *loadedBug) Operations() ([]dag.Operation, error) {
|
||||||
return l.Snapshot.Operations, nil
|
return l.Snapshot.Operations, nil
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/MichaelMure/git-bug/api/graphql/graph"
|
"github.com/MichaelMure/git-bug/api/graphql/graph"
|
||||||
"github.com/MichaelMure/git-bug/api/graphql/models"
|
"github.com/MichaelMure/git-bug/api/graphql/models"
|
||||||
"github.com/MichaelMure/git-bug/bug"
|
"github.com/MichaelMure/git-bug/bug"
|
||||||
|
"github.com/MichaelMure/git-bug/entity/dag"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ graph.BugResolver = &bugResolver{}
|
var _ graph.BugResolver = &bugResolver{}
|
||||||
@ -69,14 +70,14 @@ func (bugResolver) Operations(_ context.Context, obj models.BugWrapper, after *s
|
|||||||
Last: last,
|
Last: last,
|
||||||
}
|
}
|
||||||
|
|
||||||
edger := func(op bug.Operation, offset int) connections.Edge {
|
edger := func(op dag.Operation, offset int) connections.Edge {
|
||||||
return models.OperationEdge{
|
return models.OperationEdge{
|
||||||
Node: op,
|
Node: op,
|
||||||
Cursor: connections.OffsetToCursor(offset),
|
Cursor: connections.OffsetToCursor(offset),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
conMaker := func(edges []*models.OperationEdge, nodes []bug.Operation, info *models.PageInfo, totalCount int) (*models.OperationConnection, error) {
|
conMaker := func(edges []*models.OperationEdge, nodes []dag.Operation, info *models.PageInfo, totalCount int) (*models.OperationConnection, error) {
|
||||||
return &models.OperationConnection{
|
return &models.OperationConnection{
|
||||||
Edges: edges,
|
Edges: edges,
|
||||||
Nodes: nodes,
|
Nodes: nodes,
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"github.com/MichaelMure/git-bug/bug"
|
"github.com/MichaelMure/git-bug/bug"
|
||||||
"github.com/MichaelMure/git-bug/cache"
|
"github.com/MichaelMure/git-bug/cache"
|
||||||
"github.com/MichaelMure/git-bug/entity"
|
"github.com/MichaelMure/git-bug/entity"
|
||||||
|
"github.com/MichaelMure/git-bug/entity/dag"
|
||||||
"github.com/MichaelMure/git-bug/identity"
|
"github.com/MichaelMure/git-bug/identity"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -288,7 +289,7 @@ func (ge *githubExporter) exportBug(ctx context.Context, b *cache.BugCache, out
|
|||||||
|
|
||||||
for _, op := range snapshot.Operations[1:] {
|
for _, op := range snapshot.Operations[1:] {
|
||||||
// ignore SetMetadata operations
|
// ignore SetMetadata operations
|
||||||
if _, ok := op.(*bug.SetMetadataOperation); ok {
|
if _, ok := op.(dag.OperationDoesntChangeSnapshot); ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,8 +15,8 @@ import (
|
|||||||
|
|
||||||
"github.com/MichaelMure/git-bug/bridge/core"
|
"github.com/MichaelMure/git-bug/bridge/core"
|
||||||
"github.com/MichaelMure/git-bug/bridge/core/auth"
|
"github.com/MichaelMure/git-bug/bridge/core/auth"
|
||||||
"github.com/MichaelMure/git-bug/bug"
|
|
||||||
"github.com/MichaelMure/git-bug/cache"
|
"github.com/MichaelMure/git-bug/cache"
|
||||||
|
"github.com/MichaelMure/git-bug/entity/dag"
|
||||||
"github.com/MichaelMure/git-bug/repository"
|
"github.com/MichaelMure/git-bug/repository"
|
||||||
"github.com/MichaelMure/git-bug/util/interrupt"
|
"github.com/MichaelMure/git-bug/util/interrupt"
|
||||||
)
|
)
|
||||||
@ -245,7 +245,7 @@ func TestGithubPushPull(t *testing.T) {
|
|||||||
// verify operation have correct metadata
|
// verify operation have correct metadata
|
||||||
for _, op := range tt.bug.Snapshot().Operations {
|
for _, op := range tt.bug.Snapshot().Operations {
|
||||||
// Check if the originals operations (*not* SetMetadata) are tagged properly
|
// Check if the originals operations (*not* SetMetadata) are tagged properly
|
||||||
if _, ok := op.(*bug.SetMetadataOperation); !ok {
|
if _, ok := op.(dag.OperationDoesntChangeSnapshot); !ok {
|
||||||
_, haveIDMetadata := op.GetMetadata(metaKeyGithubId)
|
_, haveIDMetadata := op.GetMetadata(metaKeyGithubId)
|
||||||
require.True(t, haveIDMetadata)
|
require.True(t, haveIDMetadata)
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/MichaelMure/git-bug/bridge/core/auth"
|
"github.com/MichaelMure/git-bug/bridge/core/auth"
|
||||||
"github.com/MichaelMure/git-bug/bug"
|
"github.com/MichaelMure/git-bug/bug"
|
||||||
"github.com/MichaelMure/git-bug/cache"
|
"github.com/MichaelMure/git-bug/cache"
|
||||||
|
"github.com/MichaelMure/git-bug/entity/dag"
|
||||||
"github.com/MichaelMure/git-bug/identity"
|
"github.com/MichaelMure/git-bug/identity"
|
||||||
"github.com/MichaelMure/git-bug/repository"
|
"github.com/MichaelMure/git-bug/repository"
|
||||||
"github.com/MichaelMure/git-bug/util/interrupt"
|
"github.com/MichaelMure/git-bug/util/interrupt"
|
||||||
@ -44,7 +45,7 @@ func TestGithubImporter(t *testing.T) {
|
|||||||
name: "simple issue",
|
name: "simple issue",
|
||||||
url: "https://github.com/MichaelMure/git-bug-test-github-bridge/issues/1",
|
url: "https://github.com/MichaelMure/git-bug-test-github-bridge/issues/1",
|
||||||
bug: &bug.Snapshot{
|
bug: &bug.Snapshot{
|
||||||
Operations: []bug.Operation{
|
Operations: []dag.Operation{
|
||||||
bug.NewCreateOp(author, 0, "simple issue", "initial comment", nil),
|
bug.NewCreateOp(author, 0, "simple issue", "initial comment", nil),
|
||||||
bug.NewAddCommentOp(author, 0, "first comment", nil),
|
bug.NewAddCommentOp(author, 0, "first comment", nil),
|
||||||
bug.NewAddCommentOp(author, 0, "second comment", nil),
|
bug.NewAddCommentOp(author, 0, "second comment", nil),
|
||||||
@ -55,7 +56,7 @@ func TestGithubImporter(t *testing.T) {
|
|||||||
name: "empty issue",
|
name: "empty issue",
|
||||||
url: "https://github.com/MichaelMure/git-bug-test-github-bridge/issues/2",
|
url: "https://github.com/MichaelMure/git-bug-test-github-bridge/issues/2",
|
||||||
bug: &bug.Snapshot{
|
bug: &bug.Snapshot{
|
||||||
Operations: []bug.Operation{
|
Operations: []dag.Operation{
|
||||||
bug.NewCreateOp(author, 0, "empty issue", "", nil),
|
bug.NewCreateOp(author, 0, "empty issue", "", nil),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -64,7 +65,7 @@ func TestGithubImporter(t *testing.T) {
|
|||||||
name: "complex issue",
|
name: "complex issue",
|
||||||
url: "https://github.com/MichaelMure/git-bug-test-github-bridge/issues/3",
|
url: "https://github.com/MichaelMure/git-bug-test-github-bridge/issues/3",
|
||||||
bug: &bug.Snapshot{
|
bug: &bug.Snapshot{
|
||||||
Operations: []bug.Operation{
|
Operations: []dag.Operation{
|
||||||
bug.NewCreateOp(author, 0, "complex issue", "initial comment", nil),
|
bug.NewCreateOp(author, 0, "complex issue", "initial comment", nil),
|
||||||
bug.NewLabelChangeOperation(author, 0, []bug.Label{"bug"}, []bug.Label{}),
|
bug.NewLabelChangeOperation(author, 0, []bug.Label{"bug"}, []bug.Label{}),
|
||||||
bug.NewLabelChangeOperation(author, 0, []bug.Label{"duplicate"}, []bug.Label{}),
|
bug.NewLabelChangeOperation(author, 0, []bug.Label{"duplicate"}, []bug.Label{}),
|
||||||
@ -81,7 +82,7 @@ func TestGithubImporter(t *testing.T) {
|
|||||||
name: "editions",
|
name: "editions",
|
||||||
url: "https://github.com/MichaelMure/git-bug-test-github-bridge/issues/4",
|
url: "https://github.com/MichaelMure/git-bug-test-github-bridge/issues/4",
|
||||||
bug: &bug.Snapshot{
|
bug: &bug.Snapshot{
|
||||||
Operations: []bug.Operation{
|
Operations: []dag.Operation{
|
||||||
bug.NewCreateOp(author, 0, "editions", "initial comment edited", nil),
|
bug.NewCreateOp(author, 0, "editions", "initial comment edited", nil),
|
||||||
bug.NewEditCommentOp(author, 0, "", "erased then edited again", nil),
|
bug.NewEditCommentOp(author, 0, "", "erased then edited again", nil),
|
||||||
bug.NewAddCommentOp(author, 0, "first comment", nil),
|
bug.NewAddCommentOp(author, 0, "first comment", nil),
|
||||||
@ -93,7 +94,7 @@ func TestGithubImporter(t *testing.T) {
|
|||||||
name: "comment deletion",
|
name: "comment deletion",
|
||||||
url: "https://github.com/MichaelMure/git-bug-test-github-bridge/issues/5",
|
url: "https://github.com/MichaelMure/git-bug-test-github-bridge/issues/5",
|
||||||
bug: &bug.Snapshot{
|
bug: &bug.Snapshot{
|
||||||
Operations: []bug.Operation{
|
Operations: []dag.Operation{
|
||||||
bug.NewCreateOp(author, 0, "comment deletion", "", nil),
|
bug.NewCreateOp(author, 0, "comment deletion", "", nil),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -102,7 +103,7 @@ func TestGithubImporter(t *testing.T) {
|
|||||||
name: "edition deletion",
|
name: "edition deletion",
|
||||||
url: "https://github.com/MichaelMure/git-bug-test-github-bridge/issues/6",
|
url: "https://github.com/MichaelMure/git-bug-test-github-bridge/issues/6",
|
||||||
bug: &bug.Snapshot{
|
bug: &bug.Snapshot{
|
||||||
Operations: []bug.Operation{
|
Operations: []dag.Operation{
|
||||||
bug.NewCreateOp(author, 0, "edition deletion", "initial comment", nil),
|
bug.NewCreateOp(author, 0, "edition deletion", "initial comment", nil),
|
||||||
bug.NewEditCommentOp(author, 0, "", "initial comment edited again", nil),
|
bug.NewEditCommentOp(author, 0, "", "initial comment edited again", nil),
|
||||||
bug.NewAddCommentOp(author, 0, "first comment", nil),
|
bug.NewAddCommentOp(author, 0, "first comment", nil),
|
||||||
@ -114,7 +115,7 @@ func TestGithubImporter(t *testing.T) {
|
|||||||
name: "hidden comment",
|
name: "hidden comment",
|
||||||
url: "https://github.com/MichaelMure/git-bug-test-github-bridge/issues/7",
|
url: "https://github.com/MichaelMure/git-bug-test-github-bridge/issues/7",
|
||||||
bug: &bug.Snapshot{
|
bug: &bug.Snapshot{
|
||||||
Operations: []bug.Operation{
|
Operations: []dag.Operation{
|
||||||
bug.NewCreateOp(author, 0, "hidden comment", "initial comment", nil),
|
bug.NewCreateOp(author, 0, "hidden comment", "initial comment", nil),
|
||||||
bug.NewAddCommentOp(author, 0, "first comment", nil),
|
bug.NewAddCommentOp(author, 0, "first comment", nil),
|
||||||
},
|
},
|
||||||
@ -124,7 +125,7 @@ func TestGithubImporter(t *testing.T) {
|
|||||||
name: "transfered issue",
|
name: "transfered issue",
|
||||||
url: "https://github.com/MichaelMure/git-bug-test-github-bridge/issues/8",
|
url: "https://github.com/MichaelMure/git-bug-test-github-bridge/issues/8",
|
||||||
bug: &bug.Snapshot{
|
bug: &bug.Snapshot{
|
||||||
Operations: []bug.Operation{
|
Operations: []dag.Operation{
|
||||||
bug.NewCreateOp(author, 0, "transfered issue", "", nil),
|
bug.NewCreateOp(author, 0, "transfered issue", "", nil),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -133,7 +134,7 @@ func TestGithubImporter(t *testing.T) {
|
|||||||
name: "unicode control characters",
|
name: "unicode control characters",
|
||||||
url: "https://github.com/MichaelMure/git-bug-test-github-bridge/issues/10",
|
url: "https://github.com/MichaelMure/git-bug-test-github-bridge/issues/10",
|
||||||
bug: &bug.Snapshot{
|
bug: &bug.Snapshot{
|
||||||
Operations: []bug.Operation{
|
Operations: []dag.Operation{
|
||||||
bug.NewCreateOp(author, 0, "unicode control characters", "u0000: \nu0001: \nu0002: \nu0003: \nu0004: \nu0005: \nu0006: \nu0007: \nu0008: \nu0009: \t\nu0010: \nu0011: \nu0012: \nu0013: \nu0014: \nu0015: \nu0016: \nu0017: \nu0018: \nu0019:", nil),
|
bug.NewCreateOp(author, 0, "unicode control characters", "u0000: \nu0001: \nu0002: \nu0003: \nu0004: \nu0005: \nu0006: \nu0007: \nu0008: \nu0009: \t\nu0010: \nu0011: \nu0012: \nu0013: \nu0014: \nu0015: \nu0016: \nu0017: \nu0018: \nu0019:", nil),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/MichaelMure/git-bug/bug"
|
"github.com/MichaelMure/git-bug/bug"
|
||||||
"github.com/MichaelMure/git-bug/cache"
|
"github.com/MichaelMure/git-bug/cache"
|
||||||
"github.com/MichaelMure/git-bug/entity"
|
"github.com/MichaelMure/git-bug/entity"
|
||||||
|
"github.com/MichaelMure/git-bug/entity/dag"
|
||||||
"github.com/MichaelMure/git-bug/identity"
|
"github.com/MichaelMure/git-bug/identity"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -256,7 +257,7 @@ func (ge *gitlabExporter) exportBug(ctx context.Context, b *cache.BugCache, out
|
|||||||
labelSet := make(map[string]struct{})
|
labelSet := make(map[string]struct{})
|
||||||
for _, op := range snapshot.Operations[1:] {
|
for _, op := range snapshot.Operations[1:] {
|
||||||
// ignore SetMetadata operations
|
// ignore SetMetadata operations
|
||||||
if _, ok := op.(*bug.SetMetadataOperation); ok {
|
if _, ok := op.(dag.OperationDoesntChangeSnapshot); ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,11 +11,12 @@ import (
|
|||||||
|
|
||||||
"github.com/xanzy/go-gitlab"
|
"github.com/xanzy/go-gitlab"
|
||||||
|
|
||||||
|
"github.com/MichaelMure/git-bug/entity/dag"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/MichaelMure/git-bug/bridge/core"
|
"github.com/MichaelMure/git-bug/bridge/core"
|
||||||
"github.com/MichaelMure/git-bug/bridge/core/auth"
|
"github.com/MichaelMure/git-bug/bridge/core/auth"
|
||||||
"github.com/MichaelMure/git-bug/bug"
|
|
||||||
"github.com/MichaelMure/git-bug/cache"
|
"github.com/MichaelMure/git-bug/cache"
|
||||||
"github.com/MichaelMure/git-bug/repository"
|
"github.com/MichaelMure/git-bug/repository"
|
||||||
"github.com/MichaelMure/git-bug/util/interrupt"
|
"github.com/MichaelMure/git-bug/util/interrupt"
|
||||||
@ -247,7 +248,7 @@ func TestGitlabPushPull(t *testing.T) {
|
|||||||
// verify operation have correct metadata
|
// verify operation have correct metadata
|
||||||
for _, op := range tt.bug.Snapshot().Operations {
|
for _, op := range tt.bug.Snapshot().Operations {
|
||||||
// Check if the originals operations (*not* SetMetadata) are tagged properly
|
// Check if the originals operations (*not* SetMetadata) are tagged properly
|
||||||
if _, ok := op.(*bug.SetMetadataOperation); !ok {
|
if _, ok := op.(dag.OperationDoesntChangeSnapshot); !ok {
|
||||||
_, haveIDMetadata := op.GetMetadata(metaKeyGitlabId)
|
_, haveIDMetadata := op.GetMetadata(metaKeyGitlabId)
|
||||||
require.True(t, haveIDMetadata)
|
require.True(t, haveIDMetadata)
|
||||||
|
|
||||||
@ -272,7 +273,7 @@ func TestGitlabPushPull(t *testing.T) {
|
|||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
require.Equal(t, issueOrigin, target)
|
require.Equal(t, issueOrigin, target)
|
||||||
|
|
||||||
//TODO: maybe more tests to ensure bug final state
|
// TODO: maybe more tests to ensure bug final state
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/MichaelMure/git-bug/bridge/core/auth"
|
"github.com/MichaelMure/git-bug/bridge/core/auth"
|
||||||
"github.com/MichaelMure/git-bug/bug"
|
"github.com/MichaelMure/git-bug/bug"
|
||||||
"github.com/MichaelMure/git-bug/cache"
|
"github.com/MichaelMure/git-bug/cache"
|
||||||
|
"github.com/MichaelMure/git-bug/entity/dag"
|
||||||
"github.com/MichaelMure/git-bug/identity"
|
"github.com/MichaelMure/git-bug/identity"
|
||||||
"github.com/MichaelMure/git-bug/repository"
|
"github.com/MichaelMure/git-bug/repository"
|
||||||
"github.com/MichaelMure/git-bug/util/interrupt"
|
"github.com/MichaelMure/git-bug/util/interrupt"
|
||||||
@ -49,7 +50,7 @@ func TestGitlabImport(t *testing.T) {
|
|||||||
name: "simple issue",
|
name: "simple issue",
|
||||||
url: "https://gitlab.com/git-bug/test/-/issues/1",
|
url: "https://gitlab.com/git-bug/test/-/issues/1",
|
||||||
bug: &bug.Snapshot{
|
bug: &bug.Snapshot{
|
||||||
Operations: []bug.Operation{
|
Operations: []dag.Operation{
|
||||||
bug.NewCreateOp(author, 0, "simple issue", "initial comment", nil),
|
bug.NewCreateOp(author, 0, "simple issue", "initial comment", nil),
|
||||||
bug.NewAddCommentOp(author, 0, "first comment", nil),
|
bug.NewAddCommentOp(author, 0, "first comment", nil),
|
||||||
bug.NewAddCommentOp(author, 0, "second comment", nil),
|
bug.NewAddCommentOp(author, 0, "second comment", nil),
|
||||||
@ -60,7 +61,7 @@ func TestGitlabImport(t *testing.T) {
|
|||||||
name: "empty issue",
|
name: "empty issue",
|
||||||
url: "https://gitlab.com/git-bug/test/-/issues/2",
|
url: "https://gitlab.com/git-bug/test/-/issues/2",
|
||||||
bug: &bug.Snapshot{
|
bug: &bug.Snapshot{
|
||||||
Operations: []bug.Operation{
|
Operations: []dag.Operation{
|
||||||
bug.NewCreateOp(author, 0, "empty issue", "", nil),
|
bug.NewCreateOp(author, 0, "empty issue", "", nil),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -69,7 +70,7 @@ func TestGitlabImport(t *testing.T) {
|
|||||||
name: "complex issue",
|
name: "complex issue",
|
||||||
url: "https://gitlab.com/git-bug/test/-/issues/3",
|
url: "https://gitlab.com/git-bug/test/-/issues/3",
|
||||||
bug: &bug.Snapshot{
|
bug: &bug.Snapshot{
|
||||||
Operations: []bug.Operation{
|
Operations: []dag.Operation{
|
||||||
bug.NewCreateOp(author, 0, "complex issue", "initial comment", nil),
|
bug.NewCreateOp(author, 0, "complex issue", "initial comment", nil),
|
||||||
bug.NewAddCommentOp(author, 0, "### header\n\n**bold**\n\n_italic_\n\n> with quote\n\n`inline code`\n\n```\nmultiline code\n```\n\n- bulleted\n- list\n\n1. numbered\n1. list\n\n- [ ] task\n- [x] list\n\n@MichaelMure mention\n\n#2 reference issue\n#3 auto-reference issue", nil),
|
bug.NewAddCommentOp(author, 0, "### header\n\n**bold**\n\n_italic_\n\n> with quote\n\n`inline code`\n\n```\nmultiline code\n```\n\n- bulleted\n- list\n\n1. numbered\n1. list\n\n- [ ] task\n- [x] list\n\n@MichaelMure mention\n\n#2 reference issue\n#3 auto-reference issue", nil),
|
||||||
bug.NewSetTitleOp(author, 0, "complex issue edited", "complex issue"),
|
bug.NewSetTitleOp(author, 0, "complex issue edited", "complex issue"),
|
||||||
@ -86,7 +87,7 @@ func TestGitlabImport(t *testing.T) {
|
|||||||
name: "editions",
|
name: "editions",
|
||||||
url: "https://gitlab.com/git-bug/test/-/issues/4",
|
url: "https://gitlab.com/git-bug/test/-/issues/4",
|
||||||
bug: &bug.Snapshot{
|
bug: &bug.Snapshot{
|
||||||
Operations: []bug.Operation{
|
Operations: []dag.Operation{
|
||||||
bug.NewCreateOp(author, 0, "editions", "initial comment edited", nil),
|
bug.NewCreateOp(author, 0, "editions", "initial comment edited", nil),
|
||||||
bug.NewAddCommentOp(author, 0, "first comment edited", nil),
|
bug.NewAddCommentOp(author, 0, "first comment edited", nil),
|
||||||
},
|
},
|
||||||
|
@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/MichaelMure/git-bug/bug"
|
"github.com/MichaelMure/git-bug/bug"
|
||||||
"github.com/MichaelMure/git-bug/cache"
|
"github.com/MichaelMure/git-bug/cache"
|
||||||
"github.com/MichaelMure/git-bug/entity"
|
"github.com/MichaelMure/git-bug/entity"
|
||||||
|
"github.com/MichaelMure/git-bug/entity/dag"
|
||||||
"github.com/MichaelMure/git-bug/identity"
|
"github.com/MichaelMure/git-bug/identity"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -297,7 +298,7 @@ func (je *jiraExporter) exportBug(ctx context.Context, b *cache.BugCache, out ch
|
|||||||
|
|
||||||
for _, op := range snapshot.Operations[1:] {
|
for _, op := range snapshot.Operations[1:] {
|
||||||
// ignore SetMetadata operations
|
// ignore SetMetadata operations
|
||||||
if _, ok := op.(*bug.SetMetadataOperation); ok {
|
if _, ok := op.(dag.OperationDoesntChangeSnapshot); ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/MichaelMure/git-bug/bug"
|
"github.com/MichaelMure/git-bug/bug"
|
||||||
"github.com/MichaelMure/git-bug/cache"
|
"github.com/MichaelMure/git-bug/cache"
|
||||||
"github.com/MichaelMure/git-bug/entity"
|
"github.com/MichaelMure/git-bug/entity"
|
||||||
|
"github.com/MichaelMure/git-bug/entity/dag"
|
||||||
"github.com/MichaelMure/git-bug/util/text"
|
"github.com/MichaelMure/git-bug/util/text"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -377,7 +378,7 @@ func labelSetsMatch(jiraSet []string, gitbugSet []bug.Label) bool {
|
|||||||
|
|
||||||
// Create a bug.Operation (or a series of operations) from a JIRA changelog
|
// Create a bug.Operation (or a series of operations) from a JIRA changelog
|
||||||
// entry
|
// entry
|
||||||
func (ji *jiraImporter) ensureChange(repo *cache.RepoCache, b *cache.BugCache, entry ChangeLogEntry, potentialOp bug.Operation) error {
|
func (ji *jiraImporter) ensureChange(repo *cache.RepoCache, b *cache.BugCache, entry ChangeLogEntry, potentialOp dag.Operation) error {
|
||||||
|
|
||||||
// If we have an operation which is already mapped to the entire changelog
|
// If we have an operation which is already mapped to the entire changelog
|
||||||
// entry then that means this changelog entry was induced by an export
|
// entry then that means this changelog entry was induced by an export
|
||||||
|
16
bug/bug.go
16
bug/bug.go
@ -44,11 +44,7 @@ func NewBug() *Bug {
|
|||||||
|
|
||||||
// Read will read a bug from a repository
|
// Read will read a bug from a repository
|
||||||
func Read(repo repository.ClockedRepo, id entity.Id) (*Bug, error) {
|
func Read(repo repository.ClockedRepo, id entity.Id) (*Bug, error) {
|
||||||
e, err := dag.Read(def, repo, identity.NewSimpleResolver(repo), id)
|
return ReadWithResolver(repo, identity.NewSimpleResolver(repo), id)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &Bug{Entity: e}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadWithResolver will read a bug from its Id, with a custom identity.Resolver
|
// ReadWithResolver will read a bug from its Id, with a custom identity.Resolver
|
||||||
@ -144,21 +140,21 @@ func (bug *Bug) Operations() []Operation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Compile a bug in a easily usable snapshot
|
// Compile a bug in a easily usable snapshot
|
||||||
func (bug *Bug) Compile() Snapshot {
|
func (bug *Bug) Compile() *Snapshot {
|
||||||
snap := Snapshot{
|
snap := &Snapshot{
|
||||||
id: bug.Id(),
|
id: bug.Id(),
|
||||||
Status: OpenStatus,
|
Status: OpenStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, op := range bug.Operations() {
|
for _, op := range bug.Operations() {
|
||||||
op.Apply(&snap)
|
op.Apply(snap)
|
||||||
snap.Operations = append(snap.Operations, op)
|
snap.Operations = append(snap.Operations, op)
|
||||||
}
|
}
|
||||||
|
|
||||||
return snap
|
return snap
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lookup for the very first operation of the bug.
|
// FirstOp lookup for the very first operation of the bug.
|
||||||
// For a valid Bug, this operation should be a CreateOp
|
// For a valid Bug, this operation should be a CreateOp
|
||||||
func (bug *Bug) FirstOp() Operation {
|
func (bug *Bug) FirstOp() Operation {
|
||||||
if fo := bug.Entity.FirstOp(); fo != nil {
|
if fo := bug.Entity.FirstOp(); fo != nil {
|
||||||
@ -167,7 +163,7 @@ func (bug *Bug) FirstOp() Operation {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lookup for the very last operation of the bug.
|
// LastOp lookup for the very last operation of the bug.
|
||||||
// For a valid Bug, should never be nil
|
// For a valid Bug, should never be nil
|
||||||
func (bug *Bug) LastOp() Operation {
|
func (bug *Bug) LastOp() Operation {
|
||||||
if lo := bug.Entity.LastOp(); lo != nil {
|
if lo := bug.Entity.LastOp(); lo != nil {
|
||||||
|
@ -41,5 +41,5 @@ func (c Comment) FormatTime() string {
|
|||||||
return c.UnixTime.Time().Format("Mon Jan 2 15:04:05 2006 +0200")
|
return c.UnixTime.Time().Format("Mon Jan 2 15:04:05 2006 +0200")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sign post method for gqlgen
|
// IsAuthored is a sign post method for gqlgen
|
||||||
func (c Comment) IsAuthored() {}
|
func (c Comment) IsAuthored() {}
|
||||||
|
@ -7,34 +7,34 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Interface interface {
|
type Interface interface {
|
||||||
// Id return the Bug identifier
|
// Id returns the Bug identifier
|
||||||
Id() entity.Id
|
Id() entity.Id
|
||||||
|
|
||||||
// Validate check if the Bug data is valid
|
// Validate checks if the Bug data is valid
|
||||||
Validate() error
|
Validate() error
|
||||||
|
|
||||||
// Append an operation into the staging area, to be committed later
|
// Append an operation into the staging area, to be committed later
|
||||||
Append(op Operation)
|
Append(op Operation)
|
||||||
|
|
||||||
// Operations return the ordered operations
|
// Operations returns the ordered operations
|
||||||
Operations() []Operation
|
Operations() []Operation
|
||||||
|
|
||||||
// Indicate that the in-memory state changed and need to be commit in the repository
|
// NeedCommit indicates that the in-memory state changed and need to be commit in the repository
|
||||||
NeedCommit() bool
|
NeedCommit() bool
|
||||||
|
|
||||||
// Commit write the staging area in Git and move the operations to the packs
|
// Commit writes the staging area in Git and move the operations to the packs
|
||||||
Commit(repo repository.ClockedRepo) error
|
Commit(repo repository.ClockedRepo) error
|
||||||
|
|
||||||
// Lookup for the very first operation of the bug.
|
// FirstOp lookup for the very first operation of the bug.
|
||||||
// For a valid Bug, this operation should be a CreateOp
|
// For a valid Bug, this operation should be a CreateOp
|
||||||
FirstOp() Operation
|
FirstOp() Operation
|
||||||
|
|
||||||
// Lookup for the very last operation of the bug.
|
// LastOp lookup for the very last operation of the bug.
|
||||||
// For a valid Bug, should never be nil
|
// For a valid Bug, should never be nil
|
||||||
LastOp() Operation
|
LastOp() Operation
|
||||||
|
|
||||||
// Compile a bug in a easily usable snapshot
|
// Compile a bug in an easily usable snapshot
|
||||||
Compile() Snapshot
|
Compile() *Snapshot
|
||||||
|
|
||||||
// CreateLamportTime return the Lamport time of creation
|
// CreateLamportTime return the Lamport time of creation
|
||||||
CreateLamportTime() lamport.Time
|
CreateLamportTime() lamport.Time
|
||||||
@ -42,14 +42,3 @@ type Interface interface {
|
|||||||
// EditLamportTime return the Lamport time of the last edit
|
// EditLamportTime return the Lamport time of the last edit
|
||||||
EditLamportTime() lamport.Time
|
EditLamportTime() lamport.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func bugFromInterface(bug Interface) *Bug {
|
|
||||||
switch bug := bug.(type) {
|
|
||||||
case *Bug:
|
|
||||||
return bug
|
|
||||||
case *WithSnapshot:
|
|
||||||
return bug.Bug
|
|
||||||
default:
|
|
||||||
panic("missing type case")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package bug
|
package bug
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/MichaelMure/git-bug/entity"
|
"github.com/MichaelMure/git-bug/entity"
|
||||||
@ -17,24 +16,24 @@ var _ dag.OperationWithFiles = &AddCommentOperation{}
|
|||||||
|
|
||||||
// AddCommentOperation will add a new comment in the bug
|
// AddCommentOperation will add a new comment in the bug
|
||||||
type AddCommentOperation struct {
|
type AddCommentOperation struct {
|
||||||
OpBase
|
dag.OpBase
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
// TODO: change for a map[string]util.hash to store the filename ?
|
// TODO: change for a map[string]util.hash to store the filename ?
|
||||||
Files []repository.Hash `json:"files"`
|
Files []repository.Hash `json:"files"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (op *AddCommentOperation) Id() entity.Id {
|
func (op *AddCommentOperation) Id() entity.Id {
|
||||||
return idOperation(op, &op.OpBase)
|
return dag.IdOperation(op, &op.OpBase)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (op *AddCommentOperation) Apply(snapshot *Snapshot) {
|
func (op *AddCommentOperation) Apply(snapshot *Snapshot) {
|
||||||
snapshot.addActor(op.Author_)
|
snapshot.addActor(op.Author())
|
||||||
snapshot.addParticipant(op.Author_)
|
snapshot.addParticipant(op.Author())
|
||||||
|
|
||||||
comment := Comment{
|
comment := Comment{
|
||||||
id: entity.CombineIds(snapshot.Id(), op.Id()),
|
id: entity.CombineIds(snapshot.Id(), op.Id()),
|
||||||
Message: op.Message,
|
Message: op.Message,
|
||||||
Author: op.Author_,
|
Author: op.Author(),
|
||||||
Files: op.Files,
|
Files: op.Files,
|
||||||
UnixTime: timestamp.Timestamp(op.UnixTime),
|
UnixTime: timestamp.Timestamp(op.UnixTime),
|
||||||
}
|
}
|
||||||
@ -64,64 +63,31 @@ func (op *AddCommentOperation) Validate() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON is a two-steps JSON unmarshalling
|
|
||||||
// This workaround is necessary to avoid the inner OpBase.MarshalJSON
|
|
||||||
// overriding the outer op's MarshalJSON
|
|
||||||
func (op *AddCommentOperation) UnmarshalJSON(data []byte) error {
|
|
||||||
// Unmarshal OpBase and the op separately
|
|
||||||
|
|
||||||
base := OpBase{}
|
|
||||||
err := json.Unmarshal(data, &base)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
aux := struct {
|
|
||||||
Message string `json:"message"`
|
|
||||||
Files []repository.Hash `json:"files"`
|
|
||||||
}{}
|
|
||||||
|
|
||||||
err = json.Unmarshal(data, &aux)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
op.OpBase = base
|
|
||||||
op.Message = aux.Message
|
|
||||||
op.Files = aux.Files
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign post method for gqlgen
|
|
||||||
func (op *AddCommentOperation) IsAuthored() {}
|
|
||||||
|
|
||||||
func NewAddCommentOp(author identity.Interface, unixTime int64, message string, files []repository.Hash) *AddCommentOperation {
|
func NewAddCommentOp(author identity.Interface, unixTime int64, message string, files []repository.Hash) *AddCommentOperation {
|
||||||
return &AddCommentOperation{
|
return &AddCommentOperation{
|
||||||
OpBase: newOpBase(AddCommentOp, author, unixTime),
|
OpBase: dag.NewOpBase(AddCommentOp, author, unixTime),
|
||||||
Message: message,
|
Message: message,
|
||||||
Files: files,
|
Files: files,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateTimelineItem replace a AddComment operation in the Timeline and hold its edition history
|
// AddCommentTimelineItem hold a comment in the timeline
|
||||||
type AddCommentTimelineItem struct {
|
type AddCommentTimelineItem struct {
|
||||||
CommentTimelineItem
|
CommentTimelineItem
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sign post method for gqlgen
|
// IsAuthored is a sign post method for gqlgen
|
||||||
func (a *AddCommentTimelineItem) IsAuthored() {}
|
func (a *AddCommentTimelineItem) IsAuthored() {}
|
||||||
|
|
||||||
// Convenience function to apply the operation
|
// AddComment is a convenience function to add a comment to a bug
|
||||||
func AddComment(b Interface, author identity.Interface, unixTime int64, message string) (*AddCommentOperation, error) {
|
func AddComment(b Interface, author identity.Interface, unixTime int64, message string, files []repository.Hash, metadata map[string]string) (*AddCommentOperation, error) {
|
||||||
return AddCommentWithFiles(b, author, unixTime, message, nil)
|
op := NewAddCommentOp(author, unixTime, message, files)
|
||||||
}
|
for key, val := range metadata {
|
||||||
|
op.SetMetadata(key, val)
|
||||||
func AddCommentWithFiles(b Interface, author identity.Interface, unixTime int64, message string, files []repository.Hash) (*AddCommentOperation, error) {
|
}
|
||||||
addCommentOp := NewAddCommentOp(author, unixTime, message, files)
|
if err := op.Validate(); err != nil {
|
||||||
if err := addCommentOp.Validate(); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
b.Append(addCommentOp)
|
b.Append(op)
|
||||||
return addCommentOp, nil
|
return op, nil
|
||||||
}
|
}
|
||||||
|
@ -1,37 +1,18 @@
|
|||||||
package bug
|
package bug
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
|
"github.com/MichaelMure/git-bug/entity/dag"
|
||||||
"github.com/MichaelMure/git-bug/identity"
|
"github.com/MichaelMure/git-bug/identity"
|
||||||
"github.com/MichaelMure/git-bug/repository"
|
"github.com/MichaelMure/git-bug/repository"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAddCommentSerialize(t *testing.T) {
|
func TestAddCommentSerialize(t *testing.T) {
|
||||||
repo := repository.NewMockRepo()
|
dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *AddCommentOperation {
|
||||||
|
return NewAddCommentOp(author, unixTime, "message", nil)
|
||||||
rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr")
|
})
|
||||||
require.NoError(t, err)
|
dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *AddCommentOperation {
|
||||||
|
return NewAddCommentOp(author, unixTime, "message", []repository.Hash{"hash1", "hash2"})
|
||||||
unix := time.Now().Unix()
|
})
|
||||||
before := NewAddCommentOp(rene, unix, "message", nil)
|
|
||||||
|
|
||||||
data, err := json.Marshal(before)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var after AddCommentOperation
|
|
||||||
err = json.Unmarshal(data, &after)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// enforce creating the ID
|
|
||||||
before.Id()
|
|
||||||
|
|
||||||
// Replace the identity as it's not serialized
|
|
||||||
after.Author_ = rene
|
|
||||||
|
|
||||||
require.Equal(t, before, &after)
|
|
||||||
}
|
}
|
||||||
|
103
bug/op_create.go
103
bug/op_create.go
@ -1,7 +1,6 @@
|
|||||||
package bug
|
package bug
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/MichaelMure/git-bug/entity"
|
"github.com/MichaelMure/git-bug/entity"
|
||||||
@ -17,53 +16,38 @@ var _ dag.OperationWithFiles = &CreateOperation{}
|
|||||||
|
|
||||||
// CreateOperation define the initial creation of a bug
|
// CreateOperation define the initial creation of a bug
|
||||||
type CreateOperation struct {
|
type CreateOperation struct {
|
||||||
OpBase
|
dag.OpBase
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
Files []repository.Hash `json:"files"`
|
Files []repository.Hash `json:"files"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (op *CreateOperation) Id() entity.Id {
|
func (op *CreateOperation) Id() entity.Id {
|
||||||
return idOperation(op, &op.OpBase)
|
return dag.IdOperation(op, &op.OpBase)
|
||||||
}
|
|
||||||
|
|
||||||
// OVERRIDE
|
|
||||||
func (op *CreateOperation) SetMetadata(key string, value string) {
|
|
||||||
// sanity check: we make sure we are not in the following scenario:
|
|
||||||
// - the bug is created with a first operation
|
|
||||||
// - Id() is used
|
|
||||||
// - metadata are added, which will change the Id
|
|
||||||
// - Id() is used again
|
|
||||||
|
|
||||||
if op.id != entity.UnsetId {
|
|
||||||
panic("usage of Id() after changing the first operation")
|
|
||||||
}
|
|
||||||
|
|
||||||
op.OpBase.SetMetadata(key, value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (op *CreateOperation) Apply(snapshot *Snapshot) {
|
func (op *CreateOperation) Apply(snapshot *Snapshot) {
|
||||||
// sanity check: will fail when adding a second Create
|
// sanity check: will fail when adding a second Create
|
||||||
if snapshot.id != "" && snapshot.id != entity.UnsetId && snapshot.id != op.Id() {
|
if snapshot.id != "" && snapshot.id != entity.UnsetId && snapshot.id != op.Id() {
|
||||||
panic("adding a second Create operation")
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
snapshot.id = op.Id()
|
snapshot.id = op.Id()
|
||||||
|
|
||||||
snapshot.addActor(op.Author_)
|
snapshot.addActor(op.Author())
|
||||||
snapshot.addParticipant(op.Author_)
|
snapshot.addParticipant(op.Author())
|
||||||
|
|
||||||
snapshot.Title = op.Title
|
snapshot.Title = op.Title
|
||||||
|
|
||||||
comment := Comment{
|
comment := Comment{
|
||||||
id: entity.CombineIds(snapshot.Id(), op.Id()),
|
id: entity.CombineIds(snapshot.Id(), op.Id()),
|
||||||
Message: op.Message,
|
Message: op.Message,
|
||||||
Author: op.Author_,
|
Author: op.Author(),
|
||||||
UnixTime: timestamp.Timestamp(op.UnixTime),
|
UnixTime: timestamp.Timestamp(op.UnixTime),
|
||||||
}
|
}
|
||||||
|
|
||||||
snapshot.Comments = []Comment{comment}
|
snapshot.Comments = []Comment{comment}
|
||||||
snapshot.Author = op.Author_
|
snapshot.Author = op.Author()
|
||||||
snapshot.CreateTime = op.Time()
|
snapshot.CreateTime = op.Time()
|
||||||
|
|
||||||
snapshot.Timeline = []TimelineItem{
|
snapshot.Timeline = []TimelineItem{
|
||||||
@ -82,13 +66,6 @@ func (op *CreateOperation) Validate() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(op.Nonce) > 64 {
|
|
||||||
return fmt.Errorf("create nonce is too big")
|
|
||||||
}
|
|
||||||
if len(op.Nonce) < 20 {
|
|
||||||
return fmt.Errorf("create nonce is too small")
|
|
||||||
}
|
|
||||||
|
|
||||||
if text.Empty(op.Title) {
|
if text.Empty(op.Title) {
|
||||||
return fmt.Errorf("title is empty")
|
return fmt.Errorf("title is empty")
|
||||||
}
|
}
|
||||||
@ -103,45 +80,9 @@ func (op *CreateOperation) Validate() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON is a two step JSON unmarshalling
|
|
||||||
// This workaround is necessary to avoid the inner OpBase.MarshalJSON
|
|
||||||
// overriding the outer op's MarshalJSON
|
|
||||||
func (op *CreateOperation) UnmarshalJSON(data []byte) error {
|
|
||||||
// Unmarshal OpBase and the op separately
|
|
||||||
|
|
||||||
base := OpBase{}
|
|
||||||
err := json.Unmarshal(data, &base)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
aux := struct {
|
|
||||||
Nonce []byte `json:"nonce"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
Files []repository.Hash `json:"files"`
|
|
||||||
}{}
|
|
||||||
|
|
||||||
err = json.Unmarshal(data, &aux)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
op.OpBase = base
|
|
||||||
op.Nonce = aux.Nonce
|
|
||||||
op.Title = aux.Title
|
|
||||||
op.Message = aux.Message
|
|
||||||
op.Files = aux.Files
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign post method for gqlgen
|
|
||||||
func (op *CreateOperation) IsAuthored() {}
|
|
||||||
|
|
||||||
func NewCreateOp(author identity.Interface, unixTime int64, title, message string, files []repository.Hash) *CreateOperation {
|
func NewCreateOp(author identity.Interface, unixTime int64, title, message string, files []repository.Hash) *CreateOperation {
|
||||||
return &CreateOperation{
|
return &CreateOperation{
|
||||||
OpBase: newOpBase(CreateOp, author, unixTime),
|
OpBase: dag.NewOpBase(CreateOp, author, unixTime),
|
||||||
Title: title,
|
Title: title,
|
||||||
Message: message,
|
Message: message,
|
||||||
Files: files,
|
Files: files,
|
||||||
@ -153,23 +94,19 @@ type CreateTimelineItem struct {
|
|||||||
CommentTimelineItem
|
CommentTimelineItem
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sign post method for gqlgen
|
// IsAuthored is a sign post method for gqlgen
|
||||||
func (c *CreateTimelineItem) IsAuthored() {}
|
func (c *CreateTimelineItem) IsAuthored() {}
|
||||||
|
|
||||||
// Convenience function to apply the operation
|
// Create is a convenience function to create a bug
|
||||||
func Create(author identity.Interface, unixTime int64, title, message string) (*Bug, *CreateOperation, error) {
|
func Create(author identity.Interface, unixTime int64, title, message string, files []repository.Hash, metadata map[string]string) (*Bug, *CreateOperation, error) {
|
||||||
return CreateWithFiles(author, unixTime, title, message, nil)
|
b := NewBug()
|
||||||
}
|
op := NewCreateOp(author, unixTime, title, message, files)
|
||||||
|
for key, val := range metadata {
|
||||||
func CreateWithFiles(author identity.Interface, unixTime int64, title, message string, files []repository.Hash) (*Bug, *CreateOperation, error) {
|
op.SetMetadata(key, val)
|
||||||
newBug := NewBug()
|
|
||||||
createOp := NewCreateOp(author, unixTime, title, message, files)
|
|
||||||
|
|
||||||
if err := createOp.Validate(); err != nil {
|
|
||||||
return nil, createOp, err
|
|
||||||
}
|
}
|
||||||
|
if err := op.Validate(); err != nil {
|
||||||
newBug.Append(createOp)
|
return nil, op, err
|
||||||
|
}
|
||||||
return newBug, createOp, nil
|
b.Append(op)
|
||||||
|
return b, op, nil
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
package bug
|
package bug
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/MichaelMure/git-bug/entity"
|
"github.com/MichaelMure/git-bug/entity"
|
||||||
|
"github.com/MichaelMure/git-bug/entity/dag"
|
||||||
"github.com/MichaelMure/git-bug/identity"
|
"github.com/MichaelMure/git-bug/identity"
|
||||||
"github.com/MichaelMure/git-bug/repository"
|
"github.com/MichaelMure/git-bug/repository"
|
||||||
"github.com/MichaelMure/git-bug/util/timestamp"
|
"github.com/MichaelMure/git-bug/util/timestamp"
|
||||||
@ -58,26 +58,10 @@ func TestCreate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateSerialize(t *testing.T) {
|
func TestCreateSerialize(t *testing.T) {
|
||||||
repo := repository.NewMockRepo()
|
dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *CreateOperation {
|
||||||
|
return NewCreateOp(author, unixTime, "title", "message", nil)
|
||||||
rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr")
|
})
|
||||||
require.NoError(t, err)
|
dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *CreateOperation {
|
||||||
|
return NewCreateOp(author, unixTime, "title", "message", []repository.Hash{"hash1", "hash2"})
|
||||||
unix := time.Now().Unix()
|
})
|
||||||
before := NewCreateOp(rene, unix, "title", "message", nil)
|
|
||||||
|
|
||||||
data, err := json.Marshal(before)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var after CreateOperation
|
|
||||||
err = json.Unmarshal(data, &after)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// enforce creating the ID
|
|
||||||
before.Id()
|
|
||||||
|
|
||||||
// Replace the identity as it's not serialized
|
|
||||||
after.Author_ = rene
|
|
||||||
|
|
||||||
require.Equal(t, before, &after)
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package bug
|
package bug
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -20,14 +19,14 @@ var _ dag.OperationWithFiles = &EditCommentOperation{}
|
|||||||
|
|
||||||
// EditCommentOperation will change a comment in the bug
|
// EditCommentOperation will change a comment in the bug
|
||||||
type EditCommentOperation struct {
|
type EditCommentOperation struct {
|
||||||
OpBase
|
dag.OpBase
|
||||||
Target entity.Id `json:"target"`
|
Target entity.Id `json:"target"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
Files []repository.Hash `json:"files"`
|
Files []repository.Hash `json:"files"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (op *EditCommentOperation) Id() entity.Id {
|
func (op *EditCommentOperation) Id() entity.Id {
|
||||||
return idOperation(op, &op.OpBase)
|
return dag.IdOperation(op, &op.OpBase)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (op *EditCommentOperation) Apply(snapshot *Snapshot) {
|
func (op *EditCommentOperation) Apply(snapshot *Snapshot) {
|
||||||
@ -68,7 +67,7 @@ func (op *EditCommentOperation) Apply(snapshot *Snapshot) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
snapshot.addActor(op.Author_)
|
snapshot.addActor(op.Author())
|
||||||
|
|
||||||
// Updating the corresponding comment
|
// Updating the corresponding comment
|
||||||
|
|
||||||
@ -101,43 +100,9 @@ func (op *EditCommentOperation) Validate() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON is two steps JSON unmarshalling
|
|
||||||
// This workaround is necessary to avoid the inner OpBase.MarshalJSON
|
|
||||||
// overriding the outer op's MarshalJSON
|
|
||||||
func (op *EditCommentOperation) UnmarshalJSON(data []byte) error {
|
|
||||||
// Unmarshal OpBase and the op separately
|
|
||||||
|
|
||||||
base := OpBase{}
|
|
||||||
err := json.Unmarshal(data, &base)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
aux := struct {
|
|
||||||
Target entity.Id `json:"target"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
Files []repository.Hash `json:"files"`
|
|
||||||
}{}
|
|
||||||
|
|
||||||
err = json.Unmarshal(data, &aux)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
op.OpBase = base
|
|
||||||
op.Target = aux.Target
|
|
||||||
op.Message = aux.Message
|
|
||||||
op.Files = aux.Files
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign post method for gqlgen
|
|
||||||
func (op *EditCommentOperation) IsAuthored() {}
|
|
||||||
|
|
||||||
func NewEditCommentOp(author identity.Interface, unixTime int64, target entity.Id, message string, files []repository.Hash) *EditCommentOperation {
|
func NewEditCommentOp(author identity.Interface, unixTime int64, target entity.Id, message string, files []repository.Hash) *EditCommentOperation {
|
||||||
return &EditCommentOperation{
|
return &EditCommentOperation{
|
||||||
OpBase: newOpBase(EditCommentOp, author, unixTime),
|
OpBase: dag.NewOpBase(EditCommentOp, author, unixTime),
|
||||||
Target: target,
|
Target: target,
|
||||||
Message: message,
|
Message: message,
|
||||||
Files: files,
|
Files: files,
|
||||||
@ -145,27 +110,20 @@ func NewEditCommentOp(author identity.Interface, unixTime int64, target entity.I
|
|||||||
}
|
}
|
||||||
|
|
||||||
// EditComment is a convenience function to apply the operation
|
// EditComment is a convenience function to apply the operation
|
||||||
func EditComment(b Interface, author identity.Interface, unixTime int64, target entity.Id, message string) (*EditCommentOperation, error) {
|
func EditComment(b Interface, author identity.Interface, unixTime int64, target entity.Id, message string, files []repository.Hash, metadata map[string]string) (*EditCommentOperation, error) {
|
||||||
return EditCommentWithFiles(b, author, unixTime, target, message, nil)
|
op := NewEditCommentOp(author, unixTime, target, message, files)
|
||||||
}
|
for key, val := range metadata {
|
||||||
|
op.SetMetadata(key, val)
|
||||||
func EditCommentWithFiles(b Interface, author identity.Interface, unixTime int64, target entity.Id, message string, files []repository.Hash) (*EditCommentOperation, error) {
|
}
|
||||||
editCommentOp := NewEditCommentOp(author, unixTime, target, message, files)
|
if err := op.Validate(); err != nil {
|
||||||
if err := editCommentOp.Validate(); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
b.Append(editCommentOp)
|
b.Append(op)
|
||||||
return editCommentOp, nil
|
return op, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditCreateComment is a convenience function to edit the body of a bug (the first comment)
|
// EditCreateComment is a convenience function to edit the body of a bug (the first comment)
|
||||||
func EditCreateComment(b Interface, author identity.Interface, unixTime int64, message string) (*EditCommentOperation, error) {
|
func EditCreateComment(b Interface, author identity.Interface, unixTime int64, message string, files []repository.Hash, metadata map[string]string) (*EditCommentOperation, error) {
|
||||||
createOp := b.FirstOp().(*CreateOperation)
|
createOp := b.FirstOp().(*CreateOperation)
|
||||||
return EditComment(b, author, unixTime, createOp.Id(), message)
|
return EditComment(b, author, unixTime, createOp.Id(), message, files, metadata)
|
||||||
}
|
|
||||||
|
|
||||||
// EditCreateCommentWithFiles is a convenience function to edit the body of a bug (the first comment)
|
|
||||||
func EditCreateCommentWithFiles(b Interface, author identity.Interface, unixTime int64, message string, files []repository.Hash) (*EditCommentOperation, error) {
|
|
||||||
createOp := b.FirstOp().(*CreateOperation)
|
|
||||||
return EditCommentWithFiles(b, author, unixTime, createOp.Id(), message, files)
|
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
package bug
|
package bug
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/MichaelMure/git-bug/entity/dag"
|
||||||
"github.com/MichaelMure/git-bug/identity"
|
"github.com/MichaelMure/git-bug/identity"
|
||||||
"github.com/MichaelMure/git-bug/repository"
|
"github.com/MichaelMure/git-bug/repository"
|
||||||
)
|
)
|
||||||
@ -75,26 +75,10 @@ func TestEdit(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEditCommentSerialize(t *testing.T) {
|
func TestEditCommentSerialize(t *testing.T) {
|
||||||
repo := repository.NewMockRepo()
|
dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *EditCommentOperation {
|
||||||
|
return NewEditCommentOp(author, unixTime, "target", "message", nil)
|
||||||
rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr")
|
})
|
||||||
require.NoError(t, err)
|
dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *EditCommentOperation {
|
||||||
|
return NewEditCommentOp(author, unixTime, "target", "message", []repository.Hash{"hash1", "hash2"})
|
||||||
unix := time.Now().Unix()
|
})
|
||||||
before := NewEditCommentOp(rene, unix, "target", "message", nil)
|
|
||||||
|
|
||||||
data, err := json.Marshal(before)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var after EditCommentOperation
|
|
||||||
err = json.Unmarshal(data, &after)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// enforce creating the ID
|
|
||||||
before.Id()
|
|
||||||
|
|
||||||
// Replace the identity as it's not serialized
|
|
||||||
after.Author_ = rene
|
|
||||||
|
|
||||||
require.Equal(t, before, &after)
|
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
package bug
|
package bug
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/MichaelMure/git-bug/entity"
|
"github.com/MichaelMure/git-bug/entity"
|
||||||
|
"github.com/MichaelMure/git-bug/entity/dag"
|
||||||
"github.com/MichaelMure/git-bug/identity"
|
"github.com/MichaelMure/git-bug/identity"
|
||||||
"github.com/MichaelMure/git-bug/util/timestamp"
|
"github.com/MichaelMure/git-bug/util/timestamp"
|
||||||
)
|
)
|
||||||
@ -16,18 +16,18 @@ var _ Operation = &LabelChangeOperation{}
|
|||||||
|
|
||||||
// LabelChangeOperation define a Bug operation to add or remove labels
|
// LabelChangeOperation define a Bug operation to add or remove labels
|
||||||
type LabelChangeOperation struct {
|
type LabelChangeOperation struct {
|
||||||
OpBase
|
dag.OpBase
|
||||||
Added []Label `json:"added"`
|
Added []Label `json:"added"`
|
||||||
Removed []Label `json:"removed"`
|
Removed []Label `json:"removed"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (op *LabelChangeOperation) Id() entity.Id {
|
func (op *LabelChangeOperation) Id() entity.Id {
|
||||||
return idOperation(op, &op.OpBase)
|
return dag.IdOperation(op, &op.OpBase)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply apply the operation
|
// Apply applies the operation
|
||||||
func (op *LabelChangeOperation) Apply(snapshot *Snapshot) {
|
func (op *LabelChangeOperation) Apply(snapshot *Snapshot) {
|
||||||
snapshot.addActor(op.Author_)
|
snapshot.addActor(op.Author())
|
||||||
|
|
||||||
// Add in the set
|
// Add in the set
|
||||||
AddLoop:
|
AddLoop:
|
||||||
@ -59,7 +59,7 @@ AddLoop:
|
|||||||
|
|
||||||
item := &LabelChangeTimelineItem{
|
item := &LabelChangeTimelineItem{
|
||||||
id: op.Id(),
|
id: op.Id(),
|
||||||
Author: op.Author_,
|
Author: op.Author(),
|
||||||
UnixTime: timestamp.Timestamp(op.UnixTime),
|
UnixTime: timestamp.Timestamp(op.UnixTime),
|
||||||
Added: op.Added,
|
Added: op.Added,
|
||||||
Removed: op.Removed,
|
Removed: op.Removed,
|
||||||
@ -92,41 +92,9 @@ func (op *LabelChangeOperation) Validate() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON is a two step JSON unmarshalling
|
|
||||||
// This workaround is necessary to avoid the inner OpBase.MarshalJSON
|
|
||||||
// overriding the outer op's MarshalJSON
|
|
||||||
func (op *LabelChangeOperation) UnmarshalJSON(data []byte) error {
|
|
||||||
// Unmarshal OpBase and the op separately
|
|
||||||
|
|
||||||
base := OpBase{}
|
|
||||||
err := json.Unmarshal(data, &base)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
aux := struct {
|
|
||||||
Added []Label `json:"added"`
|
|
||||||
Removed []Label `json:"removed"`
|
|
||||||
}{}
|
|
||||||
|
|
||||||
err = json.Unmarshal(data, &aux)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
op.OpBase = base
|
|
||||||
op.Added = aux.Added
|
|
||||||
op.Removed = aux.Removed
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign post method for gqlgen
|
|
||||||
func (op *LabelChangeOperation) IsAuthored() {}
|
|
||||||
|
|
||||||
func NewLabelChangeOperation(author identity.Interface, unixTime int64, added, removed []Label) *LabelChangeOperation {
|
func NewLabelChangeOperation(author identity.Interface, unixTime int64, added, removed []Label) *LabelChangeOperation {
|
||||||
return &LabelChangeOperation{
|
return &LabelChangeOperation{
|
||||||
OpBase: newOpBase(LabelChangeOp, author, unixTime),
|
OpBase: dag.NewOpBase(LabelChangeOp, author, unixTime),
|
||||||
Added: added,
|
Added: added,
|
||||||
Removed: removed,
|
Removed: removed,
|
||||||
}
|
}
|
||||||
@ -144,11 +112,11 @@ func (l LabelChangeTimelineItem) Id() entity.Id {
|
|||||||
return l.id
|
return l.id
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sign post method for gqlgen
|
// IsAuthored is a sign post method for gqlgen
|
||||||
func (l *LabelChangeTimelineItem) IsAuthored() {}
|
func (l LabelChangeTimelineItem) IsAuthored() {}
|
||||||
|
|
||||||
// ChangeLabels is a convenience function to apply the operation
|
// ChangeLabels is a convenience function to change labels on a bug
|
||||||
func ChangeLabels(b Interface, author identity.Interface, unixTime int64, add, remove []string) ([]LabelChangeResult, *LabelChangeOperation, error) {
|
func ChangeLabels(b Interface, author identity.Interface, unixTime int64, add, remove []string, metadata map[string]string) ([]LabelChangeResult, *LabelChangeOperation, error) {
|
||||||
var added, removed []Label
|
var added, removed []Label
|
||||||
var results []LabelChangeResult
|
var results []LabelChangeResult
|
||||||
|
|
||||||
@ -196,23 +164,25 @@ func ChangeLabels(b Interface, author identity.Interface, unixTime int64, add, r
|
|||||||
return results, nil, fmt.Errorf("no label added or removed")
|
return results, nil, fmt.Errorf("no label added or removed")
|
||||||
}
|
}
|
||||||
|
|
||||||
labelOp := NewLabelChangeOperation(author, unixTime, added, removed)
|
op := NewLabelChangeOperation(author, unixTime, added, removed)
|
||||||
|
for key, val := range metadata {
|
||||||
if err := labelOp.Validate(); err != nil {
|
op.SetMetadata(key, val)
|
||||||
|
}
|
||||||
|
if err := op.Validate(); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
b.Append(labelOp)
|
b.Append(op)
|
||||||
|
|
||||||
return results, labelOp, nil
|
return results, op, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForceChangeLabels is a convenience function to apply the operation
|
// ForceChangeLabels is a convenience function to apply the operation
|
||||||
// The difference with ChangeLabels is that no checks of deduplications are done. You are entirely
|
// The difference with ChangeLabels is that no checks of deduplications are done. You are entirely
|
||||||
// responsible of what you are doing. In the general case, you want to use ChangeLabels instead.
|
// responsible for what you are doing. In the general case, you want to use ChangeLabels instead.
|
||||||
// The intended use of this function is to allow importers to create legal but unexpected label changes,
|
// The intended use of this function is to allow importers to create legal but unexpected label changes,
|
||||||
// like removing a label with no information of when it was added before.
|
// like removing a label with no information of when it was added before.
|
||||||
func ForceChangeLabels(b Interface, author identity.Interface, unixTime int64, add, remove []string) (*LabelChangeOperation, error) {
|
func ForceChangeLabels(b Interface, author identity.Interface, unixTime int64, add, remove []string, metadata map[string]string) (*LabelChangeOperation, error) {
|
||||||
added := make([]Label, len(add))
|
added := make([]Label, len(add))
|
||||||
for i, str := range add {
|
for i, str := range add {
|
||||||
added[i] = Label(str)
|
added[i] = Label(str)
|
||||||
@ -223,15 +193,18 @@ func ForceChangeLabels(b Interface, author identity.Interface, unixTime int64, a
|
|||||||
removed[i] = Label(str)
|
removed[i] = Label(str)
|
||||||
}
|
}
|
||||||
|
|
||||||
labelOp := NewLabelChangeOperation(author, unixTime, added, removed)
|
op := NewLabelChangeOperation(author, unixTime, added, removed)
|
||||||
|
|
||||||
if err := labelOp.Validate(); err != nil {
|
for key, val := range metadata {
|
||||||
|
op.SetMetadata(key, val)
|
||||||
|
}
|
||||||
|
if err := op.Validate(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
b.Append(labelOp)
|
b.Append(op)
|
||||||
|
|
||||||
return labelOp, nil
|
return op, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func labelExist(labels []Label, label Label) bool {
|
func labelExist(labels []Label, label Label) bool {
|
||||||
|
@ -1,37 +1,20 @@
|
|||||||
package bug
|
package bug
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
|
"github.com/MichaelMure/git-bug/entity/dag"
|
||||||
"github.com/MichaelMure/git-bug/identity"
|
"github.com/MichaelMure/git-bug/identity"
|
||||||
"github.com/MichaelMure/git-bug/repository"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLabelChangeSerialize(t *testing.T) {
|
func TestLabelChangeSerialize(t *testing.T) {
|
||||||
repo := repository.NewMockRepo()
|
dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *LabelChangeOperation {
|
||||||
|
return NewLabelChangeOperation(author, unixTime, []Label{"added"}, []Label{"removed"})
|
||||||
rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr")
|
})
|
||||||
require.NoError(t, err)
|
dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *LabelChangeOperation {
|
||||||
|
return NewLabelChangeOperation(author, unixTime, []Label{"added"}, nil)
|
||||||
unix := time.Now().Unix()
|
})
|
||||||
before := NewLabelChangeOperation(rene, unix, []Label{"added"}, []Label{"removed"})
|
dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *LabelChangeOperation {
|
||||||
|
return NewLabelChangeOperation(author, unixTime, nil, []Label{"removed"})
|
||||||
data, err := json.Marshal(before)
|
})
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var after LabelChangeOperation
|
|
||||||
err = json.Unmarshal(data, &after)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// enforce creating the ID
|
|
||||||
before.Id()
|
|
||||||
|
|
||||||
// Replace the identity as it's not serialized
|
|
||||||
after.Author_ = rene
|
|
||||||
|
|
||||||
require.Equal(t, before, &after)
|
|
||||||
}
|
}
|
||||||
|
@ -1,77 +0,0 @@
|
|||||||
package bug
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/MichaelMure/git-bug/entity"
|
|
||||||
"github.com/MichaelMure/git-bug/identity"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ Operation = &NoOpOperation{}
|
|
||||||
|
|
||||||
// NoOpOperation is an operation that does not change the bug state. It can
|
|
||||||
// however be used to store arbitrary metadata in the bug history, for example
|
|
||||||
// to support a bridge feature.
|
|
||||||
type NoOpOperation struct {
|
|
||||||
OpBase
|
|
||||||
}
|
|
||||||
|
|
||||||
func (op *NoOpOperation) Id() entity.Id {
|
|
||||||
return idOperation(op, &op.OpBase)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (op *NoOpOperation) Apply(snapshot *Snapshot) {
|
|
||||||
// Nothing to do
|
|
||||||
}
|
|
||||||
|
|
||||||
func (op *NoOpOperation) Validate() error {
|
|
||||||
return op.OpBase.Validate(op, NoOpOp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON is a two step JSON unmarshalling
|
|
||||||
// This workaround is necessary to avoid the inner OpBase.MarshalJSON
|
|
||||||
// overriding the outer op's MarshalJSON
|
|
||||||
func (op *NoOpOperation) UnmarshalJSON(data []byte) error {
|
|
||||||
// Unmarshal OpBase and the op separately
|
|
||||||
|
|
||||||
base := OpBase{}
|
|
||||||
err := json.Unmarshal(data, &base)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
aux := struct{}{}
|
|
||||||
|
|
||||||
err = json.Unmarshal(data, &aux)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
op.OpBase = base
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign post method for gqlgen
|
|
||||||
func (op *NoOpOperation) IsAuthored() {}
|
|
||||||
|
|
||||||
func NewNoOpOp(author identity.Interface, unixTime int64) *NoOpOperation {
|
|
||||||
return &NoOpOperation{
|
|
||||||
OpBase: newOpBase(NoOpOp, author, unixTime),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convenience function to apply the operation
|
|
||||||
func NoOp(b Interface, author identity.Interface, unixTime int64, metadata map[string]string) (*NoOpOperation, error) {
|
|
||||||
op := NewNoOpOp(author, unixTime)
|
|
||||||
|
|
||||||
for key, value := range metadata {
|
|
||||||
op.SetMetadata(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := op.Validate(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b.Append(op)
|
|
||||||
return op, nil
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
package bug
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/MichaelMure/git-bug/identity"
|
|
||||||
"github.com/MichaelMure/git-bug/repository"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNoopSerialize(t *testing.T) {
|
|
||||||
repo := repository.NewMockRepo()
|
|
||||||
|
|
||||||
rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
unix := time.Now().Unix()
|
|
||||||
before := NewNoOpOp(rene, unix)
|
|
||||||
|
|
||||||
data, err := json.Marshal(before)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
var after NoOpOperation
|
|
||||||
err = json.Unmarshal(data, &after)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
// enforce creating the ID
|
|
||||||
before.Id()
|
|
||||||
|
|
||||||
// Replace the identity as it's not serialized
|
|
||||||
after.Author_ = rene
|
|
||||||
|
|
||||||
assert.Equal(t, before, &after)
|
|
||||||
}
|
|
@ -1,108 +1,21 @@
|
|||||||
package bug
|
package bug
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
|
|
||||||
"github.com/MichaelMure/git-bug/entity"
|
"github.com/MichaelMure/git-bug/entity"
|
||||||
|
"github.com/MichaelMure/git-bug/entity/dag"
|
||||||
"github.com/MichaelMure/git-bug/identity"
|
"github.com/MichaelMure/git-bug/identity"
|
||||||
"github.com/MichaelMure/git-bug/util/text"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ Operation = &SetMetadataOperation{}
|
func NewSetMetadataOp(author identity.Interface, unixTime int64, target entity.Id, newMetadata map[string]string) *dag.SetMetadataOperation[*Snapshot] {
|
||||||
|
return dag.NewSetMetadataOp[*Snapshot](SetMetadataOp, author, unixTime, target, newMetadata)
|
||||||
type SetMetadataOperation struct {
|
|
||||||
OpBase
|
|
||||||
Target entity.Id `json:"target"`
|
|
||||||
NewMetadata map[string]string `json:"new_metadata"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (op *SetMetadataOperation) Id() entity.Id {
|
// SetMetadata is a convenience function to add metadata on another operation
|
||||||
return idOperation(op, &op.OpBase)
|
func SetMetadata(b Interface, author identity.Interface, unixTime int64, target entity.Id, newMetadata map[string]string) (*dag.SetMetadataOperation[*Snapshot], error) {
|
||||||
}
|
op := NewSetMetadataOp(author, unixTime, target, newMetadata)
|
||||||
|
if err := op.Validate(); err != nil {
|
||||||
func (op *SetMetadataOperation) Apply(snapshot *Snapshot) {
|
|
||||||
for _, target := range snapshot.Operations {
|
|
||||||
if target.Id() == op.Target {
|
|
||||||
// Apply the metadata in an immutable way: if a metadata already
|
|
||||||
// exist, it's not possible to override it.
|
|
||||||
for key, value := range op.NewMetadata {
|
|
||||||
target.setExtraMetadataImmutable(key, value)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (op *SetMetadataOperation) Validate() error {
|
|
||||||
if err := op.OpBase.Validate(op, SetMetadataOp); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := op.Target.Validate(); err != nil {
|
|
||||||
return errors.Wrap(err, "target invalid")
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, val := range op.NewMetadata {
|
|
||||||
if !text.SafeOneLine(key) {
|
|
||||||
return fmt.Errorf("metadata key is unsafe")
|
|
||||||
}
|
|
||||||
if !text.Safe(val) {
|
|
||||||
return fmt.Errorf("metadata value is not fully printable")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON is a two step JSON unmarshalling
|
|
||||||
// This workaround is necessary to avoid the inner OpBase.MarshalJSON
|
|
||||||
// overriding the outer op's MarshalJSON
|
|
||||||
func (op *SetMetadataOperation) UnmarshalJSON(data []byte) error {
|
|
||||||
// Unmarshal OpBase and the op separately
|
|
||||||
|
|
||||||
base := OpBase{}
|
|
||||||
err := json.Unmarshal(data, &base)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
aux := struct {
|
|
||||||
Target entity.Id `json:"target"`
|
|
||||||
NewMetadata map[string]string `json:"new_metadata"`
|
|
||||||
}{}
|
|
||||||
|
|
||||||
err = json.Unmarshal(data, &aux)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
op.OpBase = base
|
|
||||||
op.Target = aux.Target
|
|
||||||
op.NewMetadata = aux.NewMetadata
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign post method for gqlgen
|
|
||||||
func (op *SetMetadataOperation) IsAuthored() {}
|
|
||||||
|
|
||||||
func NewSetMetadataOp(author identity.Interface, unixTime int64, target entity.Id, newMetadata map[string]string) *SetMetadataOperation {
|
|
||||||
return &SetMetadataOperation{
|
|
||||||
OpBase: newOpBase(SetMetadataOp, author, unixTime),
|
|
||||||
Target: target,
|
|
||||||
NewMetadata: newMetadata,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convenience function to apply the operation
|
|
||||||
func SetMetadata(b Interface, author identity.Interface, unixTime int64, target entity.Id, newMetadata map[string]string) (*SetMetadataOperation, error) {
|
|
||||||
SetMetadataOp := NewSetMetadataOp(author, unixTime, target, newMetadata)
|
|
||||||
if err := SetMetadataOp.Validate(); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
b.Append(SetMetadataOp)
|
b.Append(op)
|
||||||
return SetMetadataOp, nil
|
return op, nil
|
||||||
}
|
}
|
||||||
|
@ -1,126 +0,0 @@
|
|||||||
package bug
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/MichaelMure/git-bug/identity"
|
|
||||||
"github.com/MichaelMure/git-bug/repository"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSetMetadata(t *testing.T) {
|
|
||||||
snapshot := Snapshot{}
|
|
||||||
|
|
||||||
repo := repository.NewMockRepo()
|
|
||||||
|
|
||||||
rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
unix := time.Now().Unix()
|
|
||||||
|
|
||||||
create := NewCreateOp(rene, unix, "title", "create", nil)
|
|
||||||
create.SetMetadata("key", "value")
|
|
||||||
create.Apply(&snapshot)
|
|
||||||
snapshot.Operations = append(snapshot.Operations, create)
|
|
||||||
|
|
||||||
id1 := create.Id()
|
|
||||||
require.NoError(t, id1.Validate())
|
|
||||||
|
|
||||||
comment := NewAddCommentOp(rene, unix, "comment", nil)
|
|
||||||
comment.SetMetadata("key2", "value2")
|
|
||||||
comment.Apply(&snapshot)
|
|
||||||
snapshot.Operations = append(snapshot.Operations, comment)
|
|
||||||
|
|
||||||
id2 := comment.Id()
|
|
||||||
require.NoError(t, id2.Validate())
|
|
||||||
|
|
||||||
op1 := NewSetMetadataOp(rene, unix, id1, map[string]string{
|
|
||||||
"key": "override",
|
|
||||||
"key2": "value",
|
|
||||||
})
|
|
||||||
|
|
||||||
op1.Apply(&snapshot)
|
|
||||||
snapshot.Operations = append(snapshot.Operations, op1)
|
|
||||||
|
|
||||||
createMetadata := snapshot.Operations[0].AllMetadata()
|
|
||||||
require.Len(t, createMetadata, 2)
|
|
||||||
// original key is not overrided
|
|
||||||
require.Equal(t, createMetadata["key"], "value")
|
|
||||||
// new key is set
|
|
||||||
require.Equal(t, createMetadata["key2"], "value")
|
|
||||||
|
|
||||||
commentMetadata := snapshot.Operations[1].AllMetadata()
|
|
||||||
require.Len(t, commentMetadata, 1)
|
|
||||||
require.Equal(t, commentMetadata["key2"], "value2")
|
|
||||||
|
|
||||||
op2 := NewSetMetadataOp(rene, unix, id2, map[string]string{
|
|
||||||
"key2": "value",
|
|
||||||
"key3": "value3",
|
|
||||||
})
|
|
||||||
|
|
||||||
op2.Apply(&snapshot)
|
|
||||||
snapshot.Operations = append(snapshot.Operations, op2)
|
|
||||||
|
|
||||||
createMetadata = snapshot.Operations[0].AllMetadata()
|
|
||||||
require.Len(t, createMetadata, 2)
|
|
||||||
require.Equal(t, createMetadata["key"], "value")
|
|
||||||
require.Equal(t, createMetadata["key2"], "value")
|
|
||||||
|
|
||||||
commentMetadata = snapshot.Operations[1].AllMetadata()
|
|
||||||
require.Len(t, commentMetadata, 2)
|
|
||||||
// original key is not overrided
|
|
||||||
require.Equal(t, commentMetadata["key2"], "value2")
|
|
||||||
// new key is set
|
|
||||||
require.Equal(t, commentMetadata["key3"], "value3")
|
|
||||||
|
|
||||||
op3 := NewSetMetadataOp(rene, unix, id1, map[string]string{
|
|
||||||
"key": "override",
|
|
||||||
"key2": "override",
|
|
||||||
})
|
|
||||||
|
|
||||||
op3.Apply(&snapshot)
|
|
||||||
snapshot.Operations = append(snapshot.Operations, op3)
|
|
||||||
|
|
||||||
createMetadata = snapshot.Operations[0].AllMetadata()
|
|
||||||
require.Len(t, createMetadata, 2)
|
|
||||||
// original key is not overrided
|
|
||||||
require.Equal(t, createMetadata["key"], "value")
|
|
||||||
// previously set key is not overrided
|
|
||||||
require.Equal(t, createMetadata["key2"], "value")
|
|
||||||
|
|
||||||
commentMetadata = snapshot.Operations[1].AllMetadata()
|
|
||||||
require.Len(t, commentMetadata, 2)
|
|
||||||
require.Equal(t, commentMetadata["key2"], "value2")
|
|
||||||
require.Equal(t, commentMetadata["key3"], "value3")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSetMetadataSerialize(t *testing.T) {
|
|
||||||
repo := repository.NewMockRepo()
|
|
||||||
|
|
||||||
rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
unix := time.Now().Unix()
|
|
||||||
before := NewSetMetadataOp(rene, unix, "message", map[string]string{
|
|
||||||
"key1": "value1",
|
|
||||||
"key2": "value2",
|
|
||||||
})
|
|
||||||
|
|
||||||
data, err := json.Marshal(before)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var after SetMetadataOperation
|
|
||||||
err = json.Unmarshal(data, &after)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// enforce creating the ID
|
|
||||||
before.Id()
|
|
||||||
|
|
||||||
// Replace the identity as it's not serialized
|
|
||||||
after.Author_ = rene
|
|
||||||
|
|
||||||
require.Equal(t, before, &after)
|
|
||||||
}
|
|
@ -1,11 +1,10 @@
|
|||||||
package bug
|
package bug
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/MichaelMure/git-bug/entity"
|
"github.com/MichaelMure/git-bug/entity"
|
||||||
|
"github.com/MichaelMure/git-bug/entity/dag"
|
||||||
"github.com/MichaelMure/git-bug/identity"
|
"github.com/MichaelMure/git-bug/identity"
|
||||||
"github.com/MichaelMure/git-bug/util/timestamp"
|
"github.com/MichaelMure/git-bug/util/timestamp"
|
||||||
)
|
)
|
||||||
@ -14,21 +13,21 @@ var _ Operation = &SetStatusOperation{}
|
|||||||
|
|
||||||
// SetStatusOperation will change the status of a bug
|
// SetStatusOperation will change the status of a bug
|
||||||
type SetStatusOperation struct {
|
type SetStatusOperation struct {
|
||||||
OpBase
|
dag.OpBase
|
||||||
Status Status `json:"status"`
|
Status Status `json:"status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (op *SetStatusOperation) Id() entity.Id {
|
func (op *SetStatusOperation) Id() entity.Id {
|
||||||
return idOperation(op, &op.OpBase)
|
return dag.IdOperation(op, &op.OpBase)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (op *SetStatusOperation) Apply(snapshot *Snapshot) {
|
func (op *SetStatusOperation) Apply(snapshot *Snapshot) {
|
||||||
snapshot.Status = op.Status
|
snapshot.Status = op.Status
|
||||||
snapshot.addActor(op.Author_)
|
snapshot.addActor(op.Author())
|
||||||
|
|
||||||
item := &SetStatusTimelineItem{
|
item := &SetStatusTimelineItem{
|
||||||
id: op.Id(),
|
id: op.Id(),
|
||||||
Author: op.Author_,
|
Author: op.Author(),
|
||||||
UnixTime: timestamp.Timestamp(op.UnixTime),
|
UnixTime: timestamp.Timestamp(op.UnixTime),
|
||||||
Status: op.Status,
|
Status: op.Status,
|
||||||
}
|
}
|
||||||
@ -48,39 +47,9 @@ func (op *SetStatusOperation) Validate() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON is a two step JSON unmarshalling
|
|
||||||
// This workaround is necessary to avoid the inner OpBase.MarshalJSON
|
|
||||||
// overriding the outer op's MarshalJSON
|
|
||||||
func (op *SetStatusOperation) UnmarshalJSON(data []byte) error {
|
|
||||||
// Unmarshal OpBase and the op separately
|
|
||||||
|
|
||||||
base := OpBase{}
|
|
||||||
err := json.Unmarshal(data, &base)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
aux := struct {
|
|
||||||
Status Status `json:"status"`
|
|
||||||
}{}
|
|
||||||
|
|
||||||
err = json.Unmarshal(data, &aux)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
op.OpBase = base
|
|
||||||
op.Status = aux.Status
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign post method for gqlgen
|
|
||||||
func (op *SetStatusOperation) IsAuthored() {}
|
|
||||||
|
|
||||||
func NewSetStatusOp(author identity.Interface, unixTime int64, status Status) *SetStatusOperation {
|
func NewSetStatusOp(author identity.Interface, unixTime int64, status Status) *SetStatusOperation {
|
||||||
return &SetStatusOperation{
|
return &SetStatusOperation{
|
||||||
OpBase: newOpBase(SetStatusOp, author, unixTime),
|
OpBase: dag.NewOpBase(SetStatusOp, author, unixTime),
|
||||||
Status: status,
|
Status: status,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -96,12 +65,15 @@ func (s SetStatusTimelineItem) Id() entity.Id {
|
|||||||
return s.id
|
return s.id
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sign post method for gqlgen
|
// IsAuthored is a sign post method for gqlgen
|
||||||
func (s *SetStatusTimelineItem) IsAuthored() {}
|
func (s SetStatusTimelineItem) IsAuthored() {}
|
||||||
|
|
||||||
// Convenience function to apply the operation
|
// Open is a convenience function to change a bugs state to Open
|
||||||
func Open(b Interface, author identity.Interface, unixTime int64) (*SetStatusOperation, error) {
|
func Open(b Interface, author identity.Interface, unixTime int64, metadata map[string]string) (*SetStatusOperation, error) {
|
||||||
op := NewSetStatusOp(author, unixTime, OpenStatus)
|
op := NewSetStatusOp(author, unixTime, OpenStatus)
|
||||||
|
for key, value := range metadata {
|
||||||
|
op.SetMetadata(key, value)
|
||||||
|
}
|
||||||
if err := op.Validate(); err != nil {
|
if err := op.Validate(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -109,9 +81,12 @@ func Open(b Interface, author identity.Interface, unixTime int64) (*SetStatusOpe
|
|||||||
return op, nil
|
return op, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convenience function to apply the operation
|
// Close is a convenience function to change a bugs state to Close
|
||||||
func Close(b Interface, author identity.Interface, unixTime int64) (*SetStatusOperation, error) {
|
func Close(b Interface, author identity.Interface, unixTime int64, metadata map[string]string) (*SetStatusOperation, error) {
|
||||||
op := NewSetStatusOp(author, unixTime, ClosedStatus)
|
op := NewSetStatusOp(author, unixTime, ClosedStatus)
|
||||||
|
for key, value := range metadata {
|
||||||
|
op.SetMetadata(key, value)
|
||||||
|
}
|
||||||
if err := op.Validate(); err != nil {
|
if err := op.Validate(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -1,37 +1,14 @@
|
|||||||
package bug
|
package bug
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
|
"github.com/MichaelMure/git-bug/entity/dag"
|
||||||
"github.com/MichaelMure/git-bug/identity"
|
"github.com/MichaelMure/git-bug/identity"
|
||||||
"github.com/MichaelMure/git-bug/repository"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSetStatusSerialize(t *testing.T) {
|
func TestSetStatusSerialize(t *testing.T) {
|
||||||
repo := repository.NewMockRepo()
|
dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *SetStatusOperation {
|
||||||
|
return NewSetStatusOp(author, unixTime, ClosedStatus)
|
||||||
rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr")
|
})
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
unix := time.Now().Unix()
|
|
||||||
before := NewSetStatusOp(rene, unix, ClosedStatus)
|
|
||||||
|
|
||||||
data, err := json.Marshal(before)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var after SetStatusOperation
|
|
||||||
err = json.Unmarshal(data, &after)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// enforce creating the ID
|
|
||||||
before.Id()
|
|
||||||
|
|
||||||
// Replace the identity as it's not serialized
|
|
||||||
after.Author_ = rene
|
|
||||||
|
|
||||||
require.Equal(t, before, &after)
|
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
package bug
|
package bug
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/MichaelMure/git-bug/entity"
|
"github.com/MichaelMure/git-bug/entity"
|
||||||
|
"github.com/MichaelMure/git-bug/entity/dag"
|
||||||
"github.com/MichaelMure/git-bug/identity"
|
"github.com/MichaelMure/git-bug/identity"
|
||||||
"github.com/MichaelMure/git-bug/util/timestamp"
|
"github.com/MichaelMure/git-bug/util/timestamp"
|
||||||
|
|
||||||
@ -15,22 +15,22 @@ var _ Operation = &SetTitleOperation{}
|
|||||||
|
|
||||||
// SetTitleOperation will change the title of a bug
|
// SetTitleOperation will change the title of a bug
|
||||||
type SetTitleOperation struct {
|
type SetTitleOperation struct {
|
||||||
OpBase
|
dag.OpBase
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Was string `json:"was"`
|
Was string `json:"was"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (op *SetTitleOperation) Id() entity.Id {
|
func (op *SetTitleOperation) Id() entity.Id {
|
||||||
return idOperation(op, &op.OpBase)
|
return dag.IdOperation(op, &op.OpBase)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (op *SetTitleOperation) Apply(snapshot *Snapshot) {
|
func (op *SetTitleOperation) Apply(snapshot *Snapshot) {
|
||||||
snapshot.Title = op.Title
|
snapshot.Title = op.Title
|
||||||
snapshot.addActor(op.Author_)
|
snapshot.addActor(op.Author())
|
||||||
|
|
||||||
item := &SetTitleTimelineItem{
|
item := &SetTitleTimelineItem{
|
||||||
id: op.Id(),
|
id: op.Id(),
|
||||||
Author: op.Author_,
|
Author: op.Author(),
|
||||||
UnixTime: timestamp.Timestamp(op.UnixTime),
|
UnixTime: timestamp.Timestamp(op.UnixTime),
|
||||||
Title: op.Title,
|
Title: op.Title,
|
||||||
Was: op.Was,
|
Was: op.Was,
|
||||||
@ -59,41 +59,9 @@ func (op *SetTitleOperation) Validate() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON is a two step JSON unmarshalling
|
|
||||||
// This workaround is necessary to avoid the inner OpBase.MarshalJSON
|
|
||||||
// overriding the outer op's MarshalJSON
|
|
||||||
func (op *SetTitleOperation) UnmarshalJSON(data []byte) error {
|
|
||||||
// Unmarshal OpBase and the op separately
|
|
||||||
|
|
||||||
base := OpBase{}
|
|
||||||
err := json.Unmarshal(data, &base)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
aux := struct {
|
|
||||||
Title string `json:"title"`
|
|
||||||
Was string `json:"was"`
|
|
||||||
}{}
|
|
||||||
|
|
||||||
err = json.Unmarshal(data, &aux)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
op.OpBase = base
|
|
||||||
op.Title = aux.Title
|
|
||||||
op.Was = aux.Was
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign post method for gqlgen
|
|
||||||
func (op *SetTitleOperation) IsAuthored() {}
|
|
||||||
|
|
||||||
func NewSetTitleOp(author identity.Interface, unixTime int64, title string, was string) *SetTitleOperation {
|
func NewSetTitleOp(author identity.Interface, unixTime int64, title string, was string) *SetTitleOperation {
|
||||||
return &SetTitleOperation{
|
return &SetTitleOperation{
|
||||||
OpBase: newOpBase(SetTitleOp, author, unixTime),
|
OpBase: dag.NewOpBase(SetTitleOp, author, unixTime),
|
||||||
Title: title,
|
Title: title,
|
||||||
Was: was,
|
Was: was,
|
||||||
}
|
}
|
||||||
@ -111,11 +79,11 @@ func (s SetTitleTimelineItem) Id() entity.Id {
|
|||||||
return s.id
|
return s.id
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sign post method for gqlgen
|
// IsAuthored is a sign post method for gqlgen
|
||||||
func (s *SetTitleTimelineItem) IsAuthored() {}
|
func (s SetTitleTimelineItem) IsAuthored() {}
|
||||||
|
|
||||||
// Convenience function to apply the operation
|
// SetTitle is a convenience function to change a bugs title
|
||||||
func SetTitle(b Interface, author identity.Interface, unixTime int64, title string) (*SetTitleOperation, error) {
|
func SetTitle(b Interface, author identity.Interface, unixTime int64, title string, metadata map[string]string) (*SetTitleOperation, error) {
|
||||||
var lastTitleOp *SetTitleOperation
|
var lastTitleOp *SetTitleOperation
|
||||||
for _, op := range b.Operations() {
|
for _, op := range b.Operations() {
|
||||||
switch op := op.(type) {
|
switch op := op.(type) {
|
||||||
@ -131,12 +99,14 @@ func SetTitle(b Interface, author identity.Interface, unixTime int64, title stri
|
|||||||
was = b.FirstOp().(*CreateOperation).Title
|
was = b.FirstOp().(*CreateOperation).Title
|
||||||
}
|
}
|
||||||
|
|
||||||
setTitleOp := NewSetTitleOp(author, unixTime, title, was)
|
op := NewSetTitleOp(author, unixTime, title, was)
|
||||||
|
for key, value := range metadata {
|
||||||
if err := setTitleOp.Validate(); err != nil {
|
op.SetMetadata(key, value)
|
||||||
|
}
|
||||||
|
if err := op.Validate(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
b.Append(setTitleOp)
|
b.Append(op)
|
||||||
return setTitleOp, nil
|
return op, nil
|
||||||
}
|
}
|
||||||
|
@ -1,37 +1,14 @@
|
|||||||
package bug
|
package bug
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
|
"github.com/MichaelMure/git-bug/entity/dag"
|
||||||
"github.com/MichaelMure/git-bug/identity"
|
"github.com/MichaelMure/git-bug/identity"
|
||||||
"github.com/MichaelMure/git-bug/repository"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSetTitleSerialize(t *testing.T) {
|
func TestSetTitleSerialize(t *testing.T) {
|
||||||
repo := repository.NewMockRepo()
|
dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *SetTitleOperation {
|
||||||
|
return NewSetTitleOp(author, unixTime, "title", "was")
|
||||||
rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr")
|
})
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
unix := time.Now().Unix()
|
|
||||||
before := NewSetTitleOp(rene, unix, "title", "was")
|
|
||||||
|
|
||||||
data, err := json.Marshal(before)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var after SetTitleOperation
|
|
||||||
err = json.Unmarshal(data, &after)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// enforce creating the ID
|
|
||||||
before.Id()
|
|
||||||
|
|
||||||
// Replace the identity as it's not serialized
|
|
||||||
after.Author_ = rene
|
|
||||||
|
|
||||||
require.Equal(t, before, &after)
|
|
||||||
}
|
}
|
||||||
|
240
bug/operation.go
240
bug/operation.go
@ -1,23 +1,15 @@
|
|||||||
package bug
|
package bug
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
|
|
||||||
"github.com/MichaelMure/git-bug/entity"
|
|
||||||
"github.com/MichaelMure/git-bug/entity/dag"
|
"github.com/MichaelMure/git-bug/entity/dag"
|
||||||
"github.com/MichaelMure/git-bug/identity"
|
"github.com/MichaelMure/git-bug/identity"
|
||||||
)
|
)
|
||||||
|
|
||||||
// OperationType is an operation type identifier
|
|
||||||
type OperationType int
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
_ OperationType = iota
|
_ dag.OperationType = iota
|
||||||
CreateOp
|
CreateOp
|
||||||
SetTitleOp
|
SetTitleOp
|
||||||
AddCommentOp
|
AddCommentOp
|
||||||
@ -32,55 +24,24 @@ const (
|
|||||||
type Operation interface {
|
type Operation interface {
|
||||||
dag.Operation
|
dag.Operation
|
||||||
|
|
||||||
// Type return the type of the operation
|
|
||||||
Type() OperationType
|
|
||||||
|
|
||||||
// Time return the time when the operation was added
|
|
||||||
Time() time.Time
|
|
||||||
// Apply the operation to a Snapshot to create the final state
|
// Apply the operation to a Snapshot to create the final state
|
||||||
Apply(snapshot *Snapshot)
|
Apply(snapshot *Snapshot)
|
||||||
|
|
||||||
// SetMetadata store arbitrary metadata about the operation
|
|
||||||
SetMetadata(key string, value string)
|
|
||||||
// GetMetadata retrieve arbitrary metadata about the operation
|
|
||||||
GetMetadata(key string) (string, bool)
|
|
||||||
// AllMetadata return all metadata for this operation
|
|
||||||
AllMetadata() map[string]string
|
|
||||||
|
|
||||||
setExtraMetadataImmutable(key string, value string)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func idOperation(op Operation, base *OpBase) entity.Id {
|
// make sure that package external operations do conform to our interface
|
||||||
if base.id == "" {
|
var _ Operation = &dag.NoOpOperation[*Snapshot]{}
|
||||||
// something went really wrong
|
var _ Operation = &dag.SetMetadataOperation[*Snapshot]{}
|
||||||
panic("op's id not set")
|
|
||||||
}
|
|
||||||
if base.id == entity.UnsetId {
|
|
||||||
// This means we are trying to get the op's Id *before* it has been stored, for instance when
|
|
||||||
// adding multiple ops in one go in an OperationPack.
|
|
||||||
// As the Id is computed based on the actual bytes written on the disk, we are going to predict
|
|
||||||
// those and then get the Id. This is safe as it will be the exact same code writing on disk later.
|
|
||||||
|
|
||||||
data, err := json.Marshal(op)
|
func operationUnmarshaller(raw json.RawMessage, resolver identity.Resolver) (dag.Operation, error) {
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
base.id = entity.DeriveId(data)
|
|
||||||
}
|
|
||||||
return base.id
|
|
||||||
}
|
|
||||||
|
|
||||||
func operationUnmarshaller(author identity.Interface, raw json.RawMessage, resolver identity.Resolver) (dag.Operation, error) {
|
|
||||||
var t struct {
|
var t struct {
|
||||||
OperationType OperationType `json:"type"`
|
OperationType dag.OperationType `json:"type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(raw, &t); err != nil {
|
if err := json.Unmarshal(raw, &t); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var op Operation
|
var op dag.Operation
|
||||||
|
|
||||||
switch t.OperationType {
|
switch t.OperationType {
|
||||||
case AddCommentOp:
|
case AddCommentOp:
|
||||||
@ -92,9 +53,9 @@ func operationUnmarshaller(author identity.Interface, raw json.RawMessage, resol
|
|||||||
case LabelChangeOp:
|
case LabelChangeOp:
|
||||||
op = &LabelChangeOperation{}
|
op = &LabelChangeOperation{}
|
||||||
case NoOpOp:
|
case NoOpOp:
|
||||||
op = &NoOpOperation{}
|
op = &dag.NoOpOperation[*Snapshot]{}
|
||||||
case SetMetadataOp:
|
case SetMetadataOp:
|
||||||
op = &SetMetadataOperation{}
|
op = &dag.SetMetadataOperation[*Snapshot]{}
|
||||||
case SetStatusOp:
|
case SetStatusOp:
|
||||||
op = &SetStatusOperation{}
|
op = &SetStatusOperation{}
|
||||||
case SetTitleOp:
|
case SetTitleOp:
|
||||||
@ -108,188 +69,5 @@ func operationUnmarshaller(author identity.Interface, raw json.RawMessage, resol
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch op := op.(type) {
|
|
||||||
case *AddCommentOperation:
|
|
||||||
op.Author_ = author
|
|
||||||
case *CreateOperation:
|
|
||||||
op.Author_ = author
|
|
||||||
case *EditCommentOperation:
|
|
||||||
op.Author_ = author
|
|
||||||
case *LabelChangeOperation:
|
|
||||||
op.Author_ = author
|
|
||||||
case *NoOpOperation:
|
|
||||||
op.Author_ = author
|
|
||||||
case *SetMetadataOperation:
|
|
||||||
op.Author_ = author
|
|
||||||
case *SetStatusOperation:
|
|
||||||
op.Author_ = author
|
|
||||||
case *SetTitleOperation:
|
|
||||||
op.Author_ = author
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("unknown operation type %T", op))
|
|
||||||
}
|
|
||||||
|
|
||||||
return op, nil
|
return op, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpBase implement the common code for all operations
|
|
||||||
type OpBase struct {
|
|
||||||
OperationType OperationType `json:"type"`
|
|
||||||
Author_ identity.Interface `json:"-"` // not serialized
|
|
||||||
// TODO: part of the data model upgrade, this should eventually be a timestamp + lamport
|
|
||||||
UnixTime int64 `json:"timestamp"`
|
|
||||||
Metadata map[string]string `json:"metadata,omitempty"`
|
|
||||||
|
|
||||||
// mandatory random bytes to ensure a better randomness of the data used to later generate the ID
|
|
||||||
// len(Nonce) should be > 20 and < 64 bytes
|
|
||||||
// It has no functional purpose and should be ignored.
|
|
||||||
Nonce []byte `json:"nonce"`
|
|
||||||
|
|
||||||
// Not serialized. Store the op's id in memory.
|
|
||||||
id entity.Id
|
|
||||||
// Not serialized. Store the extra metadata in memory,
|
|
||||||
// compiled from SetMetadataOperation.
|
|
||||||
extraMetadata map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
// newOpBase is the constructor for an OpBase
|
|
||||||
func newOpBase(opType OperationType, author identity.Interface, unixTime int64) OpBase {
|
|
||||||
return OpBase{
|
|
||||||
OperationType: opType,
|
|
||||||
Author_: author,
|
|
||||||
UnixTime: unixTime,
|
|
||||||
Nonce: makeNonce(20),
|
|
||||||
id: entity.UnsetId,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeNonce(len int) []byte {
|
|
||||||
result := make([]byte, len)
|
|
||||||
_, err := rand.Read(result)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func (base *OpBase) UnmarshalJSON(data []byte) error {
|
|
||||||
// Compute the Id when loading the op from disk.
|
|
||||||
base.id = entity.DeriveId(data)
|
|
||||||
|
|
||||||
aux := struct {
|
|
||||||
OperationType OperationType `json:"type"`
|
|
||||||
UnixTime int64 `json:"timestamp"`
|
|
||||||
Metadata map[string]string `json:"metadata,omitempty"`
|
|
||||||
Nonce []byte `json:"nonce"`
|
|
||||||
}{}
|
|
||||||
|
|
||||||
if err := json.Unmarshal(data, &aux); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
base.OperationType = aux.OperationType
|
|
||||||
base.UnixTime = aux.UnixTime
|
|
||||||
base.Metadata = aux.Metadata
|
|
||||||
base.Nonce = aux.Nonce
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (base *OpBase) Type() OperationType {
|
|
||||||
return base.OperationType
|
|
||||||
}
|
|
||||||
|
|
||||||
// Time return the time when the operation was added
|
|
||||||
func (base *OpBase) Time() time.Time {
|
|
||||||
return time.Unix(base.UnixTime, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate check the OpBase for errors
|
|
||||||
func (base *OpBase) Validate(op Operation, opType OperationType) error {
|
|
||||||
if base.OperationType != opType {
|
|
||||||
return fmt.Errorf("incorrect operation type (expected: %v, actual: %v)", opType, base.OperationType)
|
|
||||||
}
|
|
||||||
|
|
||||||
if op.Time().Unix() == 0 {
|
|
||||||
return fmt.Errorf("time not set")
|
|
||||||
}
|
|
||||||
|
|
||||||
if base.Author_ == nil {
|
|
||||||
return fmt.Errorf("author not set")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := op.Author().Validate(); err != nil {
|
|
||||||
return errors.Wrap(err, "author")
|
|
||||||
}
|
|
||||||
|
|
||||||
if op, ok := op.(dag.OperationWithFiles); ok {
|
|
||||||
for _, hash := range op.GetFiles() {
|
|
||||||
if !hash.IsValid() {
|
|
||||||
return fmt.Errorf("file with invalid hash %v", hash)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(base.Nonce) > 64 {
|
|
||||||
return fmt.Errorf("nonce is too big")
|
|
||||||
}
|
|
||||||
if len(base.Nonce) < 20 {
|
|
||||||
return fmt.Errorf("nonce is too small")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetMetadata store arbitrary metadata about the operation
|
|
||||||
func (base *OpBase) SetMetadata(key string, value string) {
|
|
||||||
if base.Metadata == nil {
|
|
||||||
base.Metadata = make(map[string]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
base.Metadata[key] = value
|
|
||||||
base.id = entity.UnsetId
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMetadata retrieve arbitrary metadata about the operation
|
|
||||||
func (base *OpBase) GetMetadata(key string) (string, bool) {
|
|
||||||
val, ok := base.Metadata[key]
|
|
||||||
|
|
||||||
if ok {
|
|
||||||
return val, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// extraMetadata can't replace the original operations value if any
|
|
||||||
val, ok = base.extraMetadata[key]
|
|
||||||
|
|
||||||
return val, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// AllMetadata return all metadata for this operation
|
|
||||||
func (base *OpBase) AllMetadata() map[string]string {
|
|
||||||
result := make(map[string]string)
|
|
||||||
|
|
||||||
for key, val := range base.extraMetadata {
|
|
||||||
result[key] = val
|
|
||||||
}
|
|
||||||
|
|
||||||
// Original metadata take precedence
|
|
||||||
for key, val := range base.Metadata {
|
|
||||||
result[key] = val
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func (base *OpBase) setExtraMetadataImmutable(key string, value string) {
|
|
||||||
if base.extraMetadata == nil {
|
|
||||||
base.extraMetadata = make(map[string]string)
|
|
||||||
}
|
|
||||||
if _, exist := base.extraMetadata[key]; !exist {
|
|
||||||
base.extraMetadata[key] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Author return author identity
|
|
||||||
func (base *OpBase) Author() identity.Interface {
|
|
||||||
return base.Author_
|
|
||||||
}
|
|
||||||
|
@ -6,10 +6,13 @@ import (
|
|||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/MichaelMure/git-bug/entity/dag"
|
||||||
"github.com/MichaelMure/git-bug/identity"
|
"github.com/MichaelMure/git-bug/identity"
|
||||||
"github.com/MichaelMure/git-bug/repository"
|
"github.com/MichaelMure/git-bug/repository"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO: move to entity/dag?
|
||||||
|
|
||||||
func TestValidate(t *testing.T) {
|
func TestValidate(t *testing.T) {
|
||||||
repo := repository.NewMockRepoClock()
|
repo := repository.NewMockRepoClock()
|
||||||
|
|
||||||
@ -44,11 +47,7 @@ func TestValidate(t *testing.T) {
|
|||||||
NewSetStatusOp(makeIdentity(t, "René Descartes", "rene@descartes.fr\u001b"), unix, ClosedStatus),
|
NewSetStatusOp(makeIdentity(t, "René Descartes", "rene@descartes.fr\u001b"), unix, ClosedStatus),
|
||||||
NewSetStatusOp(makeIdentity(t, "René \nDescartes", "rene@descartes.fr"), unix, ClosedStatus),
|
NewSetStatusOp(makeIdentity(t, "René \nDescartes", "rene@descartes.fr"), unix, ClosedStatus),
|
||||||
NewSetStatusOp(makeIdentity(t, "René Descartes", "rene@\ndescartes.fr"), unix, ClosedStatus),
|
NewSetStatusOp(makeIdentity(t, "René Descartes", "rene@\ndescartes.fr"), unix, ClosedStatus),
|
||||||
&CreateOperation{OpBase: OpBase{
|
&CreateOperation{OpBase: dag.NewOpBase(CreateOp, rene, 0),
|
||||||
Author_: rene,
|
|
||||||
UnixTime: 0,
|
|
||||||
OperationType: CreateOp,
|
|
||||||
},
|
|
||||||
Title: "title",
|
Title: "title",
|
||||||
Message: "message",
|
Message: "message",
|
||||||
},
|
},
|
||||||
@ -105,7 +104,7 @@ func TestID(t *testing.T) {
|
|||||||
err = rene.Commit(repo)
|
err = rene.Commit(repo)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
b, op, err := Create(rene, time.Now().Unix(), "title", "message")
|
b, op, err := Create(rene, time.Now().Unix(), "title", "message", nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
id1 := op.Id()
|
id1 := op.Id()
|
||||||
|
@ -5,9 +5,12 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/MichaelMure/git-bug/entity"
|
"github.com/MichaelMure/git-bug/entity"
|
||||||
|
"github.com/MichaelMure/git-bug/entity/dag"
|
||||||
"github.com/MichaelMure/git-bug/identity"
|
"github.com/MichaelMure/git-bug/identity"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var _ dag.Snapshot = &Snapshot{}
|
||||||
|
|
||||||
// Snapshot is a compiled form of the Bug data structure used for storage and merge
|
// Snapshot is a compiled form of the Bug data structure used for storage and merge
|
||||||
type Snapshot struct {
|
type Snapshot struct {
|
||||||
id entity.Id
|
id entity.Id
|
||||||
@ -23,7 +26,7 @@ type Snapshot struct {
|
|||||||
|
|
||||||
Timeline []TimelineItem
|
Timeline []TimelineItem
|
||||||
|
|
||||||
Operations []Operation
|
Operations []dag.Operation
|
||||||
}
|
}
|
||||||
|
|
||||||
// Id returns the Bug identifier
|
// Id returns the Bug identifier
|
||||||
@ -35,6 +38,10 @@ func (snap *Snapshot) Id() entity.Id {
|
|||||||
return snap.id
|
return snap.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (snap *Snapshot) AllOperations() []dag.Operation {
|
||||||
|
return snap.Operations
|
||||||
|
}
|
||||||
|
|
||||||
// EditTime returns the last time a bug was modified
|
// EditTime returns the last time a bug was modified
|
||||||
func (snap *Snapshot) EditTime() time.Time {
|
func (snap *Snapshot) EditTime() time.Time {
|
||||||
if len(snap.Operations) == 0 {
|
if len(snap.Operations) == 0 {
|
||||||
@ -133,5 +140,5 @@ func (snap *Snapshot) HasAnyActor(ids ...entity.Id) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sign post method for gqlgen
|
// IsAuthored is a sign post method for gqlgen
|
||||||
func (snap *Snapshot) IsAuthored() {}
|
func (snap *Snapshot) IsAuthored() {}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package bug
|
package bug
|
||||||
|
|
||||||
import "github.com/MichaelMure/git-bug/repository"
|
import (
|
||||||
|
"github.com/MichaelMure/git-bug/repository"
|
||||||
|
)
|
||||||
|
|
||||||
var _ Interface = &WithSnapshot{}
|
var _ Interface = &WithSnapshot{}
|
||||||
|
|
||||||
@ -10,11 +12,10 @@ type WithSnapshot struct {
|
|||||||
snap *Snapshot
|
snap *Snapshot
|
||||||
}
|
}
|
||||||
|
|
||||||
// Snapshot return the current snapshot
|
func (b *WithSnapshot) Compile() *Snapshot {
|
||||||
func (b *WithSnapshot) Snapshot() *Snapshot {
|
|
||||||
if b.snap == nil {
|
if b.snap == nil {
|
||||||
snap := b.Bug.Compile()
|
snap := b.Bug.Compile()
|
||||||
b.snap = &snap
|
b.snap = snap
|
||||||
}
|
}
|
||||||
return b.snap
|
return b.snap
|
||||||
}
|
}
|
||||||
|
105
cache/bug_cache.go
vendored
105
cache/bug_cache.go
vendored
@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/MichaelMure/git-bug/bug"
|
"github.com/MichaelMure/git-bug/bug"
|
||||||
"github.com/MichaelMure/git-bug/entity"
|
"github.com/MichaelMure/git-bug/entity"
|
||||||
|
"github.com/MichaelMure/git-bug/entity/dag"
|
||||||
"github.com/MichaelMure/git-bug/repository"
|
"github.com/MichaelMure/git-bug/repository"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -33,7 +34,7 @@ func NewBugCache(repoCache *RepoCache, b *bug.Bug) *BugCache {
|
|||||||
func (c *BugCache) Snapshot() *bug.Snapshot {
|
func (c *BugCache) Snapshot() *bug.Snapshot {
|
||||||
c.mu.RLock()
|
c.mu.RLock()
|
||||||
defer c.mu.RUnlock()
|
defer c.mu.RUnlock()
|
||||||
return c.bug.Snapshot()
|
return c.bug.Compile()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *BugCache) Id() entity.Id {
|
func (c *BugCache) Id() entity.Id {
|
||||||
@ -84,18 +85,11 @@ func (c *BugCache) AddCommentWithFiles(message string, files []repository.Hash)
|
|||||||
|
|
||||||
func (c *BugCache) AddCommentRaw(author *IdentityCache, unixTime int64, message string, files []repository.Hash, metadata map[string]string) (*bug.AddCommentOperation, error) {
|
func (c *BugCache) AddCommentRaw(author *IdentityCache, unixTime int64, message string, files []repository.Hash, metadata map[string]string) (*bug.AddCommentOperation, error) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
op, err := bug.AddCommentWithFiles(c.bug, author.Identity, unixTime, message, files)
|
op, err := bug.AddComment(c.bug, author, unixTime, message, files, metadata)
|
||||||
|
c.mu.Unlock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.mu.Unlock()
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for key, value := range metadata {
|
|
||||||
op.SetMetadata(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.mu.Unlock()
|
|
||||||
|
|
||||||
return op, c.notifyUpdated()
|
return op, c.notifyUpdated()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,24 +104,12 @@ func (c *BugCache) ChangeLabels(added []string, removed []string) ([]bug.LabelCh
|
|||||||
|
|
||||||
func (c *BugCache) ChangeLabelsRaw(author *IdentityCache, unixTime int64, added []string, removed []string, metadata map[string]string) ([]bug.LabelChangeResult, *bug.LabelChangeOperation, error) {
|
func (c *BugCache) ChangeLabelsRaw(author *IdentityCache, unixTime int64, added []string, removed []string, metadata map[string]string) ([]bug.LabelChangeResult, *bug.LabelChangeOperation, error) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
changes, op, err := bug.ChangeLabels(c.bug, author.Identity, unixTime, added, removed)
|
changes, op, err := bug.ChangeLabels(c.bug, author.Identity, unixTime, added, removed, metadata)
|
||||||
|
c.mu.Unlock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.mu.Unlock()
|
|
||||||
return changes, nil, err
|
return changes, nil, err
|
||||||
}
|
}
|
||||||
|
return changes, op, c.notifyUpdated()
|
||||||
for key, value := range metadata {
|
|
||||||
op.SetMetadata(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.mu.Unlock()
|
|
||||||
|
|
||||||
err = c.notifyUpdated()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return changes, op, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *BugCache) ForceChangeLabels(added []string, removed []string) (*bug.LabelChangeOperation, error) {
|
func (c *BugCache) ForceChangeLabels(added []string, removed []string) (*bug.LabelChangeOperation, error) {
|
||||||
@ -141,23 +123,12 @@ func (c *BugCache) ForceChangeLabels(added []string, removed []string) (*bug.Lab
|
|||||||
|
|
||||||
func (c *BugCache) ForceChangeLabelsRaw(author *IdentityCache, unixTime int64, added []string, removed []string, metadata map[string]string) (*bug.LabelChangeOperation, error) {
|
func (c *BugCache) ForceChangeLabelsRaw(author *IdentityCache, unixTime int64, added []string, removed []string, metadata map[string]string) (*bug.LabelChangeOperation, error) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
op, err := bug.ForceChangeLabels(c.bug, author.Identity, unixTime, added, removed)
|
op, err := bug.ForceChangeLabels(c.bug, author.Identity, unixTime, added, removed, metadata)
|
||||||
if err != nil {
|
|
||||||
c.mu.Unlock()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, value := range metadata {
|
|
||||||
op.SetMetadata(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
err = c.notifyUpdated()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return op, c.notifyUpdated()
|
||||||
return op, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *BugCache) Open() (*bug.SetStatusOperation, error) {
|
func (c *BugCache) Open() (*bug.SetStatusOperation, error) {
|
||||||
@ -171,17 +142,11 @@ func (c *BugCache) Open() (*bug.SetStatusOperation, error) {
|
|||||||
|
|
||||||
func (c *BugCache) OpenRaw(author *IdentityCache, unixTime int64, metadata map[string]string) (*bug.SetStatusOperation, error) {
|
func (c *BugCache) OpenRaw(author *IdentityCache, unixTime int64, metadata map[string]string) (*bug.SetStatusOperation, error) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
op, err := bug.Open(c.bug, author.Identity, unixTime)
|
op, err := bug.Open(c.bug, author.Identity, unixTime, metadata)
|
||||||
|
c.mu.Unlock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.mu.Unlock()
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for key, value := range metadata {
|
|
||||||
op.SetMetadata(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.mu.Unlock()
|
|
||||||
return op, c.notifyUpdated()
|
return op, c.notifyUpdated()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,17 +161,11 @@ func (c *BugCache) Close() (*bug.SetStatusOperation, error) {
|
|||||||
|
|
||||||
func (c *BugCache) CloseRaw(author *IdentityCache, unixTime int64, metadata map[string]string) (*bug.SetStatusOperation, error) {
|
func (c *BugCache) CloseRaw(author *IdentityCache, unixTime int64, metadata map[string]string) (*bug.SetStatusOperation, error) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
op, err := bug.Close(c.bug, author.Identity, unixTime)
|
op, err := bug.Close(c.bug, author.Identity, unixTime, metadata)
|
||||||
|
c.mu.Unlock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.mu.Unlock()
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for key, value := range metadata {
|
|
||||||
op.SetMetadata(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.mu.Unlock()
|
|
||||||
return op, c.notifyUpdated()
|
return op, c.notifyUpdated()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -221,17 +180,11 @@ func (c *BugCache) SetTitle(title string) (*bug.SetTitleOperation, error) {
|
|||||||
|
|
||||||
func (c *BugCache) SetTitleRaw(author *IdentityCache, unixTime int64, title string, metadata map[string]string) (*bug.SetTitleOperation, error) {
|
func (c *BugCache) SetTitleRaw(author *IdentityCache, unixTime int64, title string, metadata map[string]string) (*bug.SetTitleOperation, error) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
op, err := bug.SetTitle(c.bug, author.Identity, unixTime, title)
|
op, err := bug.SetTitle(c.bug, author.Identity, unixTime, title, metadata)
|
||||||
|
c.mu.Unlock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.mu.Unlock()
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for key, value := range metadata {
|
|
||||||
op.SetMetadata(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.mu.Unlock()
|
|
||||||
return op, c.notifyUpdated()
|
return op, c.notifyUpdated()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -248,17 +201,11 @@ func (c *BugCache) EditCreateComment(body string) (*bug.EditCommentOperation, er
|
|||||||
// EditCreateCommentRaw is a convenience function to edit the body of a bug (the first comment)
|
// EditCreateCommentRaw is a convenience function to edit the body of a bug (the first comment)
|
||||||
func (c *BugCache) EditCreateCommentRaw(author *IdentityCache, unixTime int64, body string, metadata map[string]string) (*bug.EditCommentOperation, error) {
|
func (c *BugCache) EditCreateCommentRaw(author *IdentityCache, unixTime int64, body string, metadata map[string]string) (*bug.EditCommentOperation, error) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
op, err := bug.EditCreateComment(c.bug, author.Identity, unixTime, body)
|
op, err := bug.EditCreateComment(c.bug, author.Identity, unixTime, body, nil, metadata)
|
||||||
|
c.mu.Unlock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.mu.Unlock()
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for key, value := range metadata {
|
|
||||||
op.SetMetadata(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.mu.Unlock()
|
|
||||||
return op, c.notifyUpdated()
|
return op, c.notifyUpdated()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,21 +220,15 @@ func (c *BugCache) EditComment(target entity.Id, message string) (*bug.EditComme
|
|||||||
|
|
||||||
func (c *BugCache) EditCommentRaw(author *IdentityCache, unixTime int64, target entity.Id, message string, metadata map[string]string) (*bug.EditCommentOperation, error) {
|
func (c *BugCache) EditCommentRaw(author *IdentityCache, unixTime int64, target entity.Id, message string, metadata map[string]string) (*bug.EditCommentOperation, error) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
op, err := bug.EditComment(c.bug, author.Identity, unixTime, target, message)
|
op, err := bug.EditComment(c.bug, author.Identity, unixTime, target, message, nil, metadata)
|
||||||
|
c.mu.Unlock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.mu.Unlock()
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for key, value := range metadata {
|
|
||||||
op.SetMetadata(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.mu.Unlock()
|
|
||||||
return op, c.notifyUpdated()
|
return op, c.notifyUpdated()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *BugCache) SetMetadata(target entity.Id, newMetadata map[string]string) (*bug.SetMetadataOperation, error) {
|
func (c *BugCache) SetMetadata(target entity.Id, newMetadata map[string]string) (*dag.SetMetadataOperation[*bug.Snapshot], error) {
|
||||||
author, err := c.repoCache.GetUserIdentity()
|
author, err := c.repoCache.GetUserIdentity()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -296,15 +237,13 @@ func (c *BugCache) SetMetadata(target entity.Id, newMetadata map[string]string)
|
|||||||
return c.SetMetadataRaw(author, time.Now().Unix(), target, newMetadata)
|
return c.SetMetadataRaw(author, time.Now().Unix(), target, newMetadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *BugCache) SetMetadataRaw(author *IdentityCache, unixTime int64, target entity.Id, newMetadata map[string]string) (*bug.SetMetadataOperation, error) {
|
func (c *BugCache) SetMetadataRaw(author *IdentityCache, unixTime int64, target entity.Id, newMetadata map[string]string) (*dag.SetMetadataOperation[*bug.Snapshot], error) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
op, err := bug.SetMetadata(c.bug, author.Identity, unixTime, target, newMetadata)
|
op, err := bug.SetMetadata(c.bug, author.Identity, unixTime, target, newMetadata)
|
||||||
|
c.mu.Unlock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.mu.Unlock()
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
c.mu.Unlock()
|
|
||||||
return op, c.notifyUpdated()
|
return op, c.notifyUpdated()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
6
cache/repo_cache.go
vendored
6
cache/repo_cache.go
vendored
@ -209,9 +209,9 @@ func (c *RepoCache) buildCache() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
snap := b.Bug.Compile()
|
snap := b.Bug.Compile()
|
||||||
c.bugExcerpts[b.Bug.Id()] = NewBugExcerpt(b.Bug, &snap)
|
c.bugExcerpts[b.Bug.Id()] = NewBugExcerpt(b.Bug, snap)
|
||||||
|
|
||||||
if err := c.addBugToSearchIndex(&snap); err != nil {
|
if err := c.addBugToSearchIndex(snap); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -222,7 +222,7 @@ func (c *RepoCache) buildCache() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// repoIsAvailable check is the given repository is locked by a Cache.
|
// repoIsAvailable check is the given repository is locked by a Cache.
|
||||||
// Note: this is a smart function that will cleanup the lock file if the
|
// Note: this is a smart function that will clean the lock file if the
|
||||||
// corresponding process is not there anymore.
|
// corresponding process is not there anymore.
|
||||||
// If no error is returned, the repo is free to edit.
|
// If no error is returned, the repo is free to edit.
|
||||||
func repoIsAvailable(repo repository.RepoStorage) error {
|
func repoIsAvailable(repo repository.RepoStorage) error {
|
||||||
|
6
cache/repo_cache_bug.go
vendored
6
cache/repo_cache_bug.go
vendored
@ -461,15 +461,11 @@ func (c *RepoCache) NewBugWithFiles(title string, message string, files []reposi
|
|||||||
// well as metadata for the Create operation.
|
// well as metadata for the Create operation.
|
||||||
// The new bug is written in the repository (commit)
|
// The new bug is written in the repository (commit)
|
||||||
func (c *RepoCache) NewBugRaw(author *IdentityCache, unixTime int64, title string, message string, files []repository.Hash, metadata map[string]string) (*BugCache, *bug.CreateOperation, error) {
|
func (c *RepoCache) NewBugRaw(author *IdentityCache, unixTime int64, title string, message string, files []repository.Hash, metadata map[string]string) (*BugCache, *bug.CreateOperation, error) {
|
||||||
b, op, err := bug.CreateWithFiles(author.Identity, unixTime, title, message, files)
|
b, op, err := bug.Create(author.Identity, unixTime, title, message, files, metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for key, value := range metadata {
|
|
||||||
op.SetMetadata(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = b.Commit(c.repo)
|
err = b.Commit(c.repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
|
4
cache/repo_cache_common.go
vendored
4
cache/repo_cache_common.go
vendored
@ -36,7 +36,7 @@ func (c *RepoCache) Keyring() repository.Keyring {
|
|||||||
return c.repo.Keyring()
|
return c.repo.Keyring()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserName returns the name the the user has used to configure git
|
// GetUserName returns the name the user has used to configure git
|
||||||
func (c *RepoCache) GetUserName() (string, error) {
|
func (c *RepoCache) GetUserName() (string, error) {
|
||||||
return c.repo.GetUserName()
|
return c.repo.GetUserName()
|
||||||
}
|
}
|
||||||
@ -131,7 +131,7 @@ func (c *RepoCache) MergeAll(remote string) <-chan entity.MergeResult {
|
|||||||
b := result.Entity.(*bug.Bug)
|
b := result.Entity.(*bug.Bug)
|
||||||
snap := b.Compile()
|
snap := b.Compile()
|
||||||
c.muBug.Lock()
|
c.muBug.Lock()
|
||||||
c.bugExcerpts[result.Id] = NewBugExcerpt(b, &snap)
|
c.bugExcerpts[result.Id] = NewBugExcerpt(b, snap)
|
||||||
c.muBug.Unlock()
|
c.muBug.Unlock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,78 +18,73 @@ import (
|
|||||||
Operations
|
Operations
|
||||||
*/
|
*/
|
||||||
|
|
||||||
type op1 struct {
|
const (
|
||||||
author identity.Interface
|
_ OperationType = iota
|
||||||
|
Op1
|
||||||
|
Op2
|
||||||
|
)
|
||||||
|
|
||||||
OperationType int `json:"type"`
|
type op1 struct {
|
||||||
Field1 string `json:"field_1"`
|
OpBase
|
||||||
Files []repository.Hash `json:"files"`
|
Field1 string `json:"field_1"`
|
||||||
|
Files []repository.Hash `json:"files"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func newOp1(author identity.Interface, field1 string, files ...repository.Hash) *op1 {
|
func newOp1(author identity.Interface, field1 string, files ...repository.Hash) *op1 {
|
||||||
return &op1{author: author, OperationType: 1, Field1: field1, Files: files}
|
return &op1{OpBase: NewOpBase(Op1, author, 0), Field1: field1, Files: files}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *op1) Id() entity.Id {
|
func (op *op1) Id() entity.Id {
|
||||||
data, _ := json.Marshal(o)
|
return IdOperation(op, &op.OpBase)
|
||||||
return entity.DeriveId(data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *op1) Validate() error { return nil }
|
func (op *op1) Validate() error { return nil }
|
||||||
|
|
||||||
func (o *op1) Author() identity.Interface {
|
func (op *op1) GetFiles() []repository.Hash {
|
||||||
return o.author
|
return op.Files
|
||||||
}
|
|
||||||
|
|
||||||
func (o *op1) GetFiles() []repository.Hash {
|
|
||||||
return o.Files
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type op2 struct {
|
type op2 struct {
|
||||||
author identity.Interface
|
OpBase
|
||||||
|
Field2 string `json:"field_2"`
|
||||||
OperationType int `json:"type"`
|
|
||||||
Field2 string `json:"field_2"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newOp2(author identity.Interface, field2 string) *op2 {
|
func newOp2(author identity.Interface, field2 string) *op2 {
|
||||||
return &op2{author: author, OperationType: 2, Field2: field2}
|
return &op2{OpBase: NewOpBase(Op2, author, 0), Field2: field2}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *op2) Id() entity.Id {
|
func (op *op2) Id() entity.Id {
|
||||||
data, _ := json.Marshal(o)
|
return IdOperation(op, &op.OpBase)
|
||||||
return entity.DeriveId(data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *op2) Validate() error { return nil }
|
func (op *op2) Validate() error { return nil }
|
||||||
|
|
||||||
func (o *op2) Author() identity.Interface {
|
func unmarshaler(raw json.RawMessage, resolver identity.Resolver) (Operation, error) {
|
||||||
return o.author
|
|
||||||
}
|
|
||||||
|
|
||||||
func unmarshaler(author identity.Interface, raw json.RawMessage, resolver identity.Resolver) (Operation, error) {
|
|
||||||
var t struct {
|
var t struct {
|
||||||
OperationType int `json:"type"`
|
OperationType OperationType `json:"type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(raw, &t); err != nil {
|
if err := json.Unmarshal(raw, &t); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var op Operation
|
||||||
|
|
||||||
switch t.OperationType {
|
switch t.OperationType {
|
||||||
case 1:
|
case Op1:
|
||||||
op := &op1{}
|
op = &op1{}
|
||||||
err := json.Unmarshal(raw, &op)
|
case Op2:
|
||||||
op.author = author
|
op = &op2{}
|
||||||
return op, err
|
|
||||||
case 2:
|
|
||||||
op := &op2{}
|
|
||||||
err := json.Unmarshal(raw, &op)
|
|
||||||
op.author = author
|
|
||||||
return op, err
|
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unknown operation type %v", t.OperationType)
|
return nil, fmt.Errorf("unknown operation type %v", t.OperationType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err := json.Unmarshal(raw, &op)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return op, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -26,7 +26,7 @@ type Definition struct {
|
|||||||
// the Namespace in git references (bugs, prs, ...)
|
// the Namespace in git references (bugs, prs, ...)
|
||||||
Namespace string
|
Namespace string
|
||||||
// a function decoding a JSON message into an Operation
|
// a function decoding a JSON message into an Operation
|
||||||
OperationUnmarshaler func(author identity.Interface, raw json.RawMessage, resolver identity.Resolver) (Operation, error)
|
OperationUnmarshaler func(raw json.RawMessage, resolver identity.Resolver) (Operation, error)
|
||||||
// the expected format version number, that can be used for data migration/upgrade
|
// the expected format version number, that can be used for data migration/upgrade
|
||||||
FormatVersion uint
|
FormatVersion uint
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,8 @@ func TestWriteReadMultipleAuthor(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func assertEqualEntities(t *testing.T, a, b *Entity) {
|
func assertEqualEntities(t *testing.T, a, b *Entity) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
// testify doesn't support comparing functions and systematically fail if they are not nil
|
// testify doesn't support comparing functions and systematically fail if they are not nil
|
||||||
// so we have to set them to nil temporarily
|
// so we have to set them to nil temporarily
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/MichaelMure/git-bug/entity"
|
"github.com/MichaelMure/git-bug/entity"
|
||||||
"github.com/MichaelMure/git-bug/entity/dag"
|
"github.com/MichaelMure/git-bug/entity/dag"
|
||||||
@ -64,10 +65,8 @@ type Operation interface {
|
|||||||
Apply(snapshot *Snapshot)
|
Apply(snapshot *Snapshot)
|
||||||
}
|
}
|
||||||
|
|
||||||
type OperationType int
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
_ OperationType = iota
|
_ dag.OperationType = iota
|
||||||
SetSignatureRequiredOp
|
SetSignatureRequiredOp
|
||||||
AddAdministratorOp
|
AddAdministratorOp
|
||||||
RemoveAdministratorOp
|
RemoveAdministratorOp
|
||||||
@ -75,37 +74,30 @@ const (
|
|||||||
|
|
||||||
// SetSignatureRequired is an operation to set/unset if git signature are required.
|
// SetSignatureRequired is an operation to set/unset if git signature are required.
|
||||||
type SetSignatureRequired struct {
|
type SetSignatureRequired struct {
|
||||||
author identity.Interface
|
dag.OpBase
|
||||||
OperationType OperationType `json:"type"`
|
Value bool `json:"value"`
|
||||||
Value bool `json:"value"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSetSignatureRequired(author identity.Interface, value bool) *SetSignatureRequired {
|
func NewSetSignatureRequired(author identity.Interface, value bool) *SetSignatureRequired {
|
||||||
return &SetSignatureRequired{author: author, OperationType: SetSignatureRequiredOp, Value: value}
|
return &SetSignatureRequired{
|
||||||
|
OpBase: dag.NewOpBase(SetSignatureRequiredOp, author, time.Now().Unix()),
|
||||||
|
Value: value,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ssr *SetSignatureRequired) Id() entity.Id {
|
func (ssr *SetSignatureRequired) Id() entity.Id {
|
||||||
// the Id of the operation is the hash of the serialized data.
|
// the Id of the operation is the hash of the serialized data.
|
||||||
// we could memorize the Id when deserializing, but that will do
|
return dag.IdOperation(ssr, &ssr.OpBase)
|
||||||
data, _ := json.Marshal(ssr)
|
|
||||||
return entity.DeriveId(data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ssr *SetSignatureRequired) Validate() error {
|
func (ssr *SetSignatureRequired) Validate() error {
|
||||||
if ssr.author == nil {
|
return ssr.OpBase.Validate(ssr, SetSignatureRequiredOp)
|
||||||
return fmt.Errorf("author not set")
|
|
||||||
}
|
|
||||||
return ssr.author.Validate()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ssr *SetSignatureRequired) Author() identity.Interface {
|
|
||||||
return ssr.author
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply is the function that makes changes on the snapshot
|
// Apply is the function that makes changes on the snapshot
|
||||||
func (ssr *SetSignatureRequired) Apply(snapshot *Snapshot) {
|
func (ssr *SetSignatureRequired) Apply(snapshot *Snapshot) {
|
||||||
// check that we are allowed to change the config
|
// check that we are allowed to change the config
|
||||||
if _, ok := snapshot.Administrator[ssr.author]; !ok {
|
if _, ok := snapshot.Administrator[ssr.Author()]; !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
snapshot.SignatureRequired = ssr.Value
|
snapshot.SignatureRequired = ssr.Value
|
||||||
@ -113,24 +105,20 @@ func (ssr *SetSignatureRequired) Apply(snapshot *Snapshot) {
|
|||||||
|
|
||||||
// AddAdministrator is an operation to add a new administrator in the set
|
// AddAdministrator is an operation to add a new administrator in the set
|
||||||
type AddAdministrator struct {
|
type AddAdministrator struct {
|
||||||
author identity.Interface
|
dag.OpBase
|
||||||
OperationType OperationType `json:"type"`
|
ToAdd []identity.Interface `json:"to_add"`
|
||||||
ToAdd []identity.Interface `json:"to_add"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// addAdministratorJson is a helper struct to deserialize identities with a concrete type.
|
|
||||||
type addAdministratorJson struct {
|
|
||||||
ToAdd []identity.IdentityStub `json:"to_add"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAddAdministratorOp(author identity.Interface, toAdd ...identity.Interface) *AddAdministrator {
|
func NewAddAdministratorOp(author identity.Interface, toAdd ...identity.Interface) *AddAdministrator {
|
||||||
return &AddAdministrator{author: author, OperationType: AddAdministratorOp, ToAdd: toAdd}
|
return &AddAdministrator{
|
||||||
|
OpBase: dag.NewOpBase(AddAdministratorOp, author, time.Now().Unix()),
|
||||||
|
ToAdd: toAdd,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (aa *AddAdministrator) Id() entity.Id {
|
func (aa *AddAdministrator) Id() entity.Id {
|
||||||
// we could memorize the Id when deserializing, but that will do
|
// the Id of the operation is the hash of the serialized data.
|
||||||
data, _ := json.Marshal(aa)
|
return dag.IdOperation(aa, &aa.OpBase)
|
||||||
return entity.DeriveId(data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (aa *AddAdministrator) Validate() error {
|
func (aa *AddAdministrator) Validate() error {
|
||||||
@ -138,20 +126,13 @@ func (aa *AddAdministrator) Validate() error {
|
|||||||
if len(aa.ToAdd) == 0 {
|
if len(aa.ToAdd) == 0 {
|
||||||
return fmt.Errorf("nothing to add")
|
return fmt.Errorf("nothing to add")
|
||||||
}
|
}
|
||||||
if aa.author == nil {
|
return aa.OpBase.Validate(aa, AddAdministratorOp)
|
||||||
return fmt.Errorf("author not set")
|
|
||||||
}
|
|
||||||
return aa.author.Validate()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (aa *AddAdministrator) Author() identity.Interface {
|
|
||||||
return aa.author
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply is the function that makes changes on the snapshot
|
// Apply is the function that makes changes on the snapshot
|
||||||
func (aa *AddAdministrator) Apply(snapshot *Snapshot) {
|
func (aa *AddAdministrator) Apply(snapshot *Snapshot) {
|
||||||
// check that we are allowed to change the config ... or if there is no admin yet
|
// check that we are allowed to change the config ... or if there is no admin yet
|
||||||
if !snapshot.HasAdministrator(aa.author) && len(snapshot.Administrator) != 0 {
|
if !snapshot.HasAdministrator(aa.Author()) && len(snapshot.Administrator) != 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, toAdd := range aa.ToAdd {
|
for _, toAdd := range aa.ToAdd {
|
||||||
@ -161,25 +142,20 @@ func (aa *AddAdministrator) Apply(snapshot *Snapshot) {
|
|||||||
|
|
||||||
// RemoveAdministrator is an operation to remove an administrator from the set
|
// RemoveAdministrator is an operation to remove an administrator from the set
|
||||||
type RemoveAdministrator struct {
|
type RemoveAdministrator struct {
|
||||||
author identity.Interface
|
dag.OpBase
|
||||||
OperationType OperationType `json:"type"`
|
|
||||||
ToRemove []identity.Interface `json:"to_remove"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// removeAdministratorJson is a helper struct to deserialize identities with a concrete type.
|
|
||||||
type removeAdministratorJson struct {
|
|
||||||
ToRemove []identity.Interface `json:"to_remove"`
|
ToRemove []identity.Interface `json:"to_remove"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRemoveAdministratorOp(author identity.Interface, toRemove ...identity.Interface) *RemoveAdministrator {
|
func NewRemoveAdministratorOp(author identity.Interface, toRemove ...identity.Interface) *RemoveAdministrator {
|
||||||
return &RemoveAdministrator{author: author, OperationType: RemoveAdministratorOp, ToRemove: toRemove}
|
return &RemoveAdministrator{
|
||||||
|
OpBase: dag.NewOpBase(RemoveAdministratorOp, author, time.Now().Unix()),
|
||||||
|
ToRemove: toRemove,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ra *RemoveAdministrator) Id() entity.Id {
|
func (ra *RemoveAdministrator) Id() entity.Id {
|
||||||
// the Id of the operation is the hash of the serialized data.
|
// the Id of the operation is the hash of the serialized data.
|
||||||
// we could memorize the Id when deserializing, but that will do
|
return dag.IdOperation(ra, &ra.OpBase)
|
||||||
data, _ := json.Marshal(ra)
|
|
||||||
return entity.DeriveId(data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ra *RemoveAdministrator) Validate() error {
|
func (ra *RemoveAdministrator) Validate() error {
|
||||||
@ -188,26 +164,19 @@ func (ra *RemoveAdministrator) Validate() error {
|
|||||||
if len(ra.ToRemove) == 0 {
|
if len(ra.ToRemove) == 0 {
|
||||||
return fmt.Errorf("nothing to remove")
|
return fmt.Errorf("nothing to remove")
|
||||||
}
|
}
|
||||||
if ra.author == nil {
|
return ra.OpBase.Validate(ra, RemoveAdministratorOp)
|
||||||
return fmt.Errorf("author not set")
|
|
||||||
}
|
|
||||||
return ra.author.Validate()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ra *RemoveAdministrator) Author() identity.Interface {
|
|
||||||
return ra.author
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply is the function that makes changes on the snapshot
|
// Apply is the function that makes changes on the snapshot
|
||||||
func (ra *RemoveAdministrator) Apply(snapshot *Snapshot) {
|
func (ra *RemoveAdministrator) Apply(snapshot *Snapshot) {
|
||||||
// check if we are allowed to make changes
|
// check if we are allowed to make changes
|
||||||
if !snapshot.HasAdministrator(ra.author) {
|
if !snapshot.HasAdministrator(ra.Author()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// special rule: we can't end up with no administrator
|
// special rule: we can't end up with no administrator
|
||||||
stillSome := false
|
stillSome := false
|
||||||
for admin, _ := range snapshot.Administrator {
|
for admin, _ := range snapshot.Administrator {
|
||||||
if admin != ra.author {
|
if admin != ra.Author() {
|
||||||
stillSome = true
|
stillSome = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -245,71 +214,52 @@ var def = dag.Definition{
|
|||||||
|
|
||||||
// operationUnmarshaller is a function doing the de-serialization of the JSON data into our own
|
// operationUnmarshaller is a function doing the de-serialization of the JSON data into our own
|
||||||
// concrete Operations. If needed, we can use the resolver to connect to other entities.
|
// concrete Operations. If needed, we can use the resolver to connect to other entities.
|
||||||
func operationUnmarshaller(author identity.Interface, raw json.RawMessage, resolver identity.Resolver) (dag.Operation, error) {
|
func operationUnmarshaller(raw json.RawMessage, resolver identity.Resolver) (dag.Operation, error) {
|
||||||
var t struct {
|
var t struct {
|
||||||
OperationType OperationType `json:"type"`
|
OperationType dag.OperationType `json:"type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(raw, &t); err != nil {
|
if err := json.Unmarshal(raw, &t); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var value interface{}
|
var op dag.Operation
|
||||||
|
|
||||||
switch t.OperationType {
|
switch t.OperationType {
|
||||||
case AddAdministratorOp:
|
case AddAdministratorOp:
|
||||||
value = &addAdministratorJson{}
|
op = &AddAdministrator{}
|
||||||
case RemoveAdministratorOp:
|
case RemoveAdministratorOp:
|
||||||
value = &removeAdministratorJson{}
|
op = &RemoveAdministrator{}
|
||||||
case SetSignatureRequiredOp:
|
case SetSignatureRequiredOp:
|
||||||
value = &SetSignatureRequired{}
|
op = &SetSignatureRequired{}
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("unknown operation type %v", t.OperationType))
|
panic(fmt.Sprintf("unknown operation type %v", t.OperationType))
|
||||||
}
|
}
|
||||||
|
|
||||||
err := json.Unmarshal(raw, &value)
|
err := json.Unmarshal(raw, &op)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var op Operation
|
switch op := op.(type) {
|
||||||
|
case *AddAdministrator:
|
||||||
switch value := value.(type) {
|
// We need to resolve identities
|
||||||
case *SetSignatureRequired:
|
for i, stub := range op.ToAdd {
|
||||||
value.author = author
|
|
||||||
op = value
|
|
||||||
case *addAdministratorJson:
|
|
||||||
// We need something less straightforward to deserialize and resolve identities
|
|
||||||
aa := &AddAdministrator{
|
|
||||||
author: author,
|
|
||||||
OperationType: AddAdministratorOp,
|
|
||||||
ToAdd: make([]identity.Interface, len(value.ToAdd)),
|
|
||||||
}
|
|
||||||
for i, stub := range value.ToAdd {
|
|
||||||
iden, err := resolver.ResolveIdentity(stub.Id())
|
iden, err := resolver.ResolveIdentity(stub.Id())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
aa.ToAdd[i] = iden
|
op.ToAdd[i] = iden
|
||||||
}
|
}
|
||||||
op = aa
|
case *RemoveAdministrator:
|
||||||
case *removeAdministratorJson:
|
// We need to resolve identities
|
||||||
// We need something less straightforward to deserialize and resolve identities
|
for i, stub := range op.ToRemove {
|
||||||
ra := &RemoveAdministrator{
|
|
||||||
author: author,
|
|
||||||
OperationType: RemoveAdministratorOp,
|
|
||||||
ToRemove: make([]identity.Interface, len(value.ToRemove)),
|
|
||||||
}
|
|
||||||
for i, stub := range value.ToRemove {
|
|
||||||
iden, err := resolver.ResolveIdentity(stub.Id())
|
iden, err := resolver.ResolveIdentity(stub.Id())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ra.ToRemove[i] = iden
|
op.ToRemove[i] = iden
|
||||||
}
|
}
|
||||||
op = ra
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("unknown operation type %T", value))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return op, nil
|
return op, nil
|
||||||
|
39
entity/dag/op_noop.go
Normal file
39
entity/dag/op_noop.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package dag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/MichaelMure/git-bug/entity"
|
||||||
|
"github.com/MichaelMure/git-bug/identity"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ Operation = &NoOpOperation[Snapshot]{}
|
||||||
|
var _ OperationDoesntChangeSnapshot = &NoOpOperation[Snapshot]{}
|
||||||
|
|
||||||
|
// NoOpOperation is an operation that does not change the entity state. It can
|
||||||
|
// however be used to store arbitrary metadata in the entity history, for example
|
||||||
|
// to support a bridge feature.
|
||||||
|
type NoOpOperation[SnapT Snapshot] struct {
|
||||||
|
OpBase
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNoOpOp[SnapT Snapshot](opType OperationType, author identity.Interface, unixTime int64) *NoOpOperation[SnapT] {
|
||||||
|
return &NoOpOperation[SnapT]{
|
||||||
|
OpBase: NewOpBase(opType, author, unixTime),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (op *NoOpOperation[SnapT]) Id() entity.Id {
|
||||||
|
return IdOperation(op, &op.OpBase)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (op *NoOpOperation[SnapT]) Apply(snapshot SnapT) {
|
||||||
|
// Nothing to do
|
||||||
|
}
|
||||||
|
|
||||||
|
func (op *NoOpOperation[SnapT]) Validate() error {
|
||||||
|
if err := op.OpBase.Validate(op, op.OperationType); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (op *NoOpOperation[SnapT]) DoesntChangeSnapshot() {}
|
13
entity/dag/op_noop_test.go
Normal file
13
entity/dag/op_noop_test.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package dag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/MichaelMure/git-bug/identity"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNoopSerialize(t *testing.T) {
|
||||||
|
SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *NoOpOperation[*snapshotMock] {
|
||||||
|
return NewNoOpOp[*snapshotMock](1, author, unixTime)
|
||||||
|
})
|
||||||
|
}
|
68
entity/dag/op_set_metadata.go
Normal file
68
entity/dag/op_set_metadata.go
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
package dag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/MichaelMure/git-bug/entity"
|
||||||
|
"github.com/MichaelMure/git-bug/identity"
|
||||||
|
"github.com/MichaelMure/git-bug/util/text"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ Operation = &SetMetadataOperation[Snapshot]{}
|
||||||
|
var _ OperationDoesntChangeSnapshot = &SetMetadataOperation[Snapshot]{}
|
||||||
|
|
||||||
|
type SetMetadataOperation[SnapT Snapshot] struct {
|
||||||
|
OpBase
|
||||||
|
Target entity.Id `json:"target"`
|
||||||
|
NewMetadata map[string]string `json:"new_metadata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSetMetadataOp[SnapT Snapshot](opType OperationType, author identity.Interface, unixTime int64, target entity.Id, newMetadata map[string]string) *SetMetadataOperation[SnapT] {
|
||||||
|
return &SetMetadataOperation[SnapT]{
|
||||||
|
OpBase: NewOpBase(opType, author, unixTime),
|
||||||
|
Target: target,
|
||||||
|
NewMetadata: newMetadata,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (op *SetMetadataOperation[SnapT]) Id() entity.Id {
|
||||||
|
return IdOperation(op, &op.OpBase)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (op *SetMetadataOperation[SnapT]) Apply(snapshot SnapT) {
|
||||||
|
for _, target := range snapshot.AllOperations() {
|
||||||
|
if target.Id() == op.Target {
|
||||||
|
// Apply the metadata in an immutable way: if a metadata already
|
||||||
|
// exist, it's not possible to override it.
|
||||||
|
for key, value := range op.NewMetadata {
|
||||||
|
target.setExtraMetadataImmutable(key, value)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (op *SetMetadataOperation[SnapT]) Validate() error {
|
||||||
|
if err := op.OpBase.Validate(op, op.OperationType); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := op.Target.Validate(); err != nil {
|
||||||
|
return errors.Wrap(err, "target invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, val := range op.NewMetadata {
|
||||||
|
if !text.SafeOneLine(key) {
|
||||||
|
return fmt.Errorf("metadata key is unsafe")
|
||||||
|
}
|
||||||
|
if !text.Safe(val) {
|
||||||
|
return fmt.Errorf("metadata value is not fully printable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (op *SetMetadataOperation[SnapT]) DoesntChangeSnapshot() {}
|
106
entity/dag/op_set_metadata_test.go
Normal file
106
entity/dag/op_set_metadata_test.go
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
package dag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/MichaelMure/git-bug/identity"
|
||||||
|
"github.com/MichaelMure/git-bug/repository"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
type snapshotMock struct {
|
||||||
|
ops []Operation
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *snapshotMock) AllOperations() []Operation {
|
||||||
|
return s.ops
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetMetadata(t *testing.T) {
|
||||||
|
snap := &snapshotMock{}
|
||||||
|
|
||||||
|
repo := repository.NewMockRepo()
|
||||||
|
|
||||||
|
rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
unix := time.Now().Unix()
|
||||||
|
|
||||||
|
target1 := NewNoOpOp[*snapshotMock](1, rene, unix)
|
||||||
|
target1.SetMetadata("key", "value")
|
||||||
|
snap.ops = append(snap.ops, target1)
|
||||||
|
|
||||||
|
target2 := NewNoOpOp[*snapshotMock](1, rene, unix)
|
||||||
|
target2.SetMetadata("key2", "value2")
|
||||||
|
snap.ops = append(snap.ops, target2)
|
||||||
|
|
||||||
|
op1 := NewSetMetadataOp[*snapshotMock](2, rene, unix, target1.Id(), map[string]string{
|
||||||
|
"key": "override",
|
||||||
|
"key2": "value",
|
||||||
|
})
|
||||||
|
|
||||||
|
op1.Apply(snap)
|
||||||
|
snap.ops = append(snap.ops, op1)
|
||||||
|
|
||||||
|
target1Metadata := snap.AllOperations()[0].AllMetadata()
|
||||||
|
require.Len(t, target1Metadata, 2)
|
||||||
|
// original key is not overrided
|
||||||
|
require.Equal(t, target1Metadata["key"], "value")
|
||||||
|
// new key is set
|
||||||
|
require.Equal(t, target1Metadata["key2"], "value")
|
||||||
|
|
||||||
|
target2Metadata := snap.AllOperations()[1].AllMetadata()
|
||||||
|
require.Len(t, target2Metadata, 1)
|
||||||
|
require.Equal(t, target2Metadata["key2"], "value2")
|
||||||
|
|
||||||
|
op2 := NewSetMetadataOp[*snapshotMock](2, rene, unix, target2.Id(), map[string]string{
|
||||||
|
"key2": "value",
|
||||||
|
"key3": "value3",
|
||||||
|
})
|
||||||
|
|
||||||
|
op2.Apply(snap)
|
||||||
|
snap.ops = append(snap.ops, op2)
|
||||||
|
|
||||||
|
target1Metadata = snap.AllOperations()[0].AllMetadata()
|
||||||
|
require.Len(t, target1Metadata, 2)
|
||||||
|
require.Equal(t, target1Metadata["key"], "value")
|
||||||
|
require.Equal(t, target1Metadata["key2"], "value")
|
||||||
|
|
||||||
|
target2Metadata = snap.AllOperations()[1].AllMetadata()
|
||||||
|
require.Len(t, target2Metadata, 2)
|
||||||
|
// original key is not overrided
|
||||||
|
require.Equal(t, target2Metadata["key2"], "value2")
|
||||||
|
// new key is set
|
||||||
|
require.Equal(t, target2Metadata["key3"], "value3")
|
||||||
|
|
||||||
|
op3 := NewSetMetadataOp[*snapshotMock](2, rene, unix, target1.Id(), map[string]string{
|
||||||
|
"key": "override",
|
||||||
|
"key2": "override",
|
||||||
|
})
|
||||||
|
|
||||||
|
op3.Apply(snap)
|
||||||
|
snap.ops = append(snap.ops, op3)
|
||||||
|
|
||||||
|
target1Metadata = snap.AllOperations()[0].AllMetadata()
|
||||||
|
require.Len(t, target1Metadata, 2)
|
||||||
|
// original key is not overrided
|
||||||
|
require.Equal(t, target1Metadata["key"], "value")
|
||||||
|
// previously set key is not overrided
|
||||||
|
require.Equal(t, target1Metadata["key2"], "value")
|
||||||
|
|
||||||
|
target2Metadata = snap.AllOperations()[1].AllMetadata()
|
||||||
|
require.Len(t, target2Metadata, 2)
|
||||||
|
require.Equal(t, target2Metadata["key2"], "value2")
|
||||||
|
require.Equal(t, target2Metadata["key3"], "value3")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetMetadataSerialize(t *testing.T) {
|
||||||
|
SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *SetMetadataOperation[*snapshotMock] {
|
||||||
|
return NewSetMetadataOp[*snapshotMock](1, author, unixTime, "message", map[string]string{
|
||||||
|
"key1": "value1",
|
||||||
|
"key2": "value2",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
@ -1,11 +1,21 @@
|
|||||||
package dag
|
package dag
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/MichaelMure/git-bug/entity"
|
"github.com/MichaelMure/git-bug/entity"
|
||||||
"github.com/MichaelMure/git-bug/identity"
|
"github.com/MichaelMure/git-bug/identity"
|
||||||
"github.com/MichaelMure/git-bug/repository"
|
"github.com/MichaelMure/git-bug/repository"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// OperationType is an operation type identifier
|
||||||
|
type OperationType int
|
||||||
|
|
||||||
// Operation is a piece of data defining a change to reflect on the state of an Entity.
|
// Operation is a piece of data defining a change to reflect on the state of an Entity.
|
||||||
// What this Operation or Entity's state looks like is not of the resort of this package as it only deals with the
|
// What this Operation or Entity's state looks like is not of the resort of this package as it only deals with the
|
||||||
// data structure and storage.
|
// data structure and storage.
|
||||||
@ -22,23 +32,39 @@ type Operation interface {
|
|||||||
// a minimal amount of entropy and avoid collision.
|
// a minimal amount of entropy and avoid collision.
|
||||||
//
|
//
|
||||||
// Author's note: I tried to find a clever way around that inelegance (stuffing random useless data into the stored
|
// Author's note: I tried to find a clever way around that inelegance (stuffing random useless data into the stored
|
||||||
// structure is not exactly elegant) but I failed to find a proper way. Essentially, anything that would reuse some
|
// structure is not exactly elegant), but I failed to find a proper way. Essentially, anything that would reuse some
|
||||||
// other data (parent operation's Id, lamport clock) or the graph structure (depth) impose that the Id would only
|
// other data (parent operation's Id, lamport clock) or the graph structure (depth) impose that the Id would only
|
||||||
// make sense in the context of the graph and yield some deep coupling between Entity and Operation. This in turn
|
// make sense in the context of the graph and yield some deep coupling between Entity and Operation. This in turn
|
||||||
// make the whole thing even less elegant.
|
// make the whole thing even less elegant.
|
||||||
//
|
//
|
||||||
// A common way to derive an Id will be to use the entity.DeriveId() function on the serialized operation data.
|
// A common way to derive an Id will be to use the entity.DeriveId() function on the serialized operation data.
|
||||||
Id() entity.Id
|
Id() entity.Id
|
||||||
|
// Type return the type of the operation
|
||||||
|
Type() OperationType
|
||||||
// Validate check if the Operation data is valid
|
// Validate check if the Operation data is valid
|
||||||
Validate() error
|
Validate() error
|
||||||
// Author returns the author of this operation
|
// Author returns the author of this operation
|
||||||
Author() identity.Interface
|
Author() identity.Interface
|
||||||
|
// Time return the time when the operation was added
|
||||||
|
Time() time.Time
|
||||||
|
|
||||||
|
// SetMetadata store arbitrary metadata about the operation
|
||||||
|
SetMetadata(key string, value string)
|
||||||
|
// GetMetadata retrieve arbitrary metadata about the operation
|
||||||
|
GetMetadata(key string) (string, bool)
|
||||||
|
// AllMetadata return all metadata for this operation
|
||||||
|
AllMetadata() map[string]string
|
||||||
|
|
||||||
|
// setId allow to set the Id, used when unmarshalling only
|
||||||
|
setId(id entity.Id)
|
||||||
|
// setAuthor allow to set the author, used when unmarshalling only
|
||||||
|
setAuthor(author identity.Interface)
|
||||||
|
// setExtraMetadataImmutable add a metadata not carried by the operation itself on the operation
|
||||||
|
setExtraMetadataImmutable(key string, value string)
|
||||||
}
|
}
|
||||||
|
|
||||||
// OperationWithFiles is an extended Operation that has files dependency, stored in git.
|
// OperationWithFiles is an optional extension for an Operation that has files dependency, stored in git.
|
||||||
type OperationWithFiles interface {
|
type OperationWithFiles interface {
|
||||||
Operation
|
|
||||||
|
|
||||||
// GetFiles return the files needed by this operation
|
// GetFiles return the files needed by this operation
|
||||||
// This implies that the Operation maintain and store internally the references to those files. This is how
|
// This implies that the Operation maintain and store internally the references to those files. This is how
|
||||||
// this information is read later, when loading from storage.
|
// this information is read later, when loading from storage.
|
||||||
@ -46,3 +72,201 @@ type OperationWithFiles interface {
|
|||||||
// hash).
|
// hash).
|
||||||
GetFiles() []repository.Hash
|
GetFiles() []repository.Hash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OperationDoesntChangeSnapshot is an interface signaling that the Operation implementing it doesn't change the
|
||||||
|
// snapshot, for example a metadata operation that act on other operations.
|
||||||
|
type OperationDoesntChangeSnapshot interface {
|
||||||
|
DoesntChangeSnapshot()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Snapshot is the minimal interface that a snapshot need to implement
|
||||||
|
type Snapshot interface {
|
||||||
|
// AllOperations returns all the operations that have been applied to that snapshot, in order
|
||||||
|
AllOperations() []Operation
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpBase implement the common feature that every Operation should support.
|
||||||
|
type OpBase struct {
|
||||||
|
// Not serialized. Store the op's id in memory.
|
||||||
|
id entity.Id
|
||||||
|
// Not serialized
|
||||||
|
author identity.Interface
|
||||||
|
|
||||||
|
OperationType OperationType `json:"type"`
|
||||||
|
UnixTime int64 `json:"timestamp"`
|
||||||
|
|
||||||
|
// mandatory random bytes to ensure a better randomness of the data used to later generate the ID
|
||||||
|
// len(Nonce) should be > 20 and < 64 bytes
|
||||||
|
// It has no functional purpose and should be ignored.
|
||||||
|
Nonce []byte `json:"nonce"`
|
||||||
|
|
||||||
|
Metadata map[string]string `json:"metadata,omitempty"`
|
||||||
|
// Not serialized. Store the extra metadata in memory,
|
||||||
|
// compiled from SetMetadataOperation.
|
||||||
|
extraMetadata map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOpBase(opType OperationType, author identity.Interface, unixTime int64) OpBase {
|
||||||
|
return OpBase{
|
||||||
|
OperationType: opType,
|
||||||
|
author: author,
|
||||||
|
UnixTime: unixTime,
|
||||||
|
Nonce: makeNonce(20),
|
||||||
|
id: entity.UnsetId,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeNonce(len int) []byte {
|
||||||
|
result := make([]byte, len)
|
||||||
|
_, err := rand.Read(result)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func IdOperation(op Operation, base *OpBase) entity.Id {
|
||||||
|
if base.id == "" {
|
||||||
|
// something went really wrong
|
||||||
|
panic("op's id not set")
|
||||||
|
}
|
||||||
|
if base.id == entity.UnsetId {
|
||||||
|
// This means we are trying to get the op's Id *before* it has been stored, for instance when
|
||||||
|
// adding multiple ops in one go in an OperationPack.
|
||||||
|
// As the Id is computed based on the actual bytes written on the disk, we are going to predict
|
||||||
|
// those and then get the Id. This is safe as it will be the exact same code writing on disk later.
|
||||||
|
|
||||||
|
data, err := json.Marshal(op)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
base.id = entity.DeriveId(data)
|
||||||
|
}
|
||||||
|
return base.id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (base *OpBase) Type() OperationType {
|
||||||
|
return base.OperationType
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time return the time when the operation was added
|
||||||
|
func (base *OpBase) Time() time.Time {
|
||||||
|
return time.Unix(base.UnixTime, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate check the OpBase for errors
|
||||||
|
func (base *OpBase) Validate(op Operation, opType OperationType) error {
|
||||||
|
if base.OperationType == 0 {
|
||||||
|
return fmt.Errorf("operation type unset")
|
||||||
|
}
|
||||||
|
if base.OperationType != opType {
|
||||||
|
return fmt.Errorf("incorrect operation type (expected: %v, actual: %v)", opType, base.OperationType)
|
||||||
|
}
|
||||||
|
|
||||||
|
if op.Time().Unix() == 0 {
|
||||||
|
return fmt.Errorf("time not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
if base.author == nil {
|
||||||
|
return fmt.Errorf("author not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := op.Author().Validate(); err != nil {
|
||||||
|
return errors.Wrap(err, "author")
|
||||||
|
}
|
||||||
|
|
||||||
|
if op, ok := op.(OperationWithFiles); ok {
|
||||||
|
for _, hash := range op.GetFiles() {
|
||||||
|
if !hash.IsValid() {
|
||||||
|
return fmt.Errorf("file with invalid hash %v", hash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(base.Nonce) > 64 {
|
||||||
|
return fmt.Errorf("nonce is too big")
|
||||||
|
}
|
||||||
|
if len(base.Nonce) < 20 {
|
||||||
|
return fmt.Errorf("nonce is too small")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAuthored is a sign post method for gqlgen
|
||||||
|
func (base *OpBase) IsAuthored() {}
|
||||||
|
|
||||||
|
// Author return author identity
|
||||||
|
func (base *OpBase) Author() identity.Interface {
|
||||||
|
return base.author
|
||||||
|
}
|
||||||
|
|
||||||
|
// IdIsSet returns true if the id has been set already
|
||||||
|
func (base *OpBase) IdIsSet() bool {
|
||||||
|
return base.id != "" && base.id != entity.UnsetId
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMetadata store arbitrary metadata about the operation
|
||||||
|
func (base *OpBase) SetMetadata(key string, value string) {
|
||||||
|
if base.IdIsSet() {
|
||||||
|
panic("set metadata on an operation with already an Id")
|
||||||
|
}
|
||||||
|
|
||||||
|
if base.Metadata == nil {
|
||||||
|
base.Metadata = make(map[string]string)
|
||||||
|
}
|
||||||
|
base.Metadata[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMetadata retrieve arbitrary metadata about the operation
|
||||||
|
func (base *OpBase) GetMetadata(key string) (string, bool) {
|
||||||
|
val, ok := base.Metadata[key]
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
return val, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// extraMetadata can't replace the original operations value if any
|
||||||
|
val, ok = base.extraMetadata[key]
|
||||||
|
|
||||||
|
return val, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllMetadata return all metadata for this operation
|
||||||
|
func (base *OpBase) AllMetadata() map[string]string {
|
||||||
|
result := make(map[string]string)
|
||||||
|
|
||||||
|
for key, val := range base.extraMetadata {
|
||||||
|
result[key] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
// Original metadata take precedence
|
||||||
|
for key, val := range base.Metadata {
|
||||||
|
result[key] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// setId allow to set the Id, used when unmarshalling only
|
||||||
|
func (base *OpBase) setId(id entity.Id) {
|
||||||
|
if base.id != "" && base.id != entity.UnsetId {
|
||||||
|
panic("trying to set id again")
|
||||||
|
}
|
||||||
|
base.id = id
|
||||||
|
}
|
||||||
|
|
||||||
|
// setAuthor allow to set the author, used when unmarshalling only
|
||||||
|
func (base *OpBase) setAuthor(author identity.Interface) {
|
||||||
|
base.author = author
|
||||||
|
}
|
||||||
|
|
||||||
|
func (base *OpBase) setExtraMetadataImmutable(key string, value string) {
|
||||||
|
if base.extraMetadata == nil {
|
||||||
|
base.extraMetadata = make(map[string]string)
|
||||||
|
}
|
||||||
|
if _, exist := base.extraMetadata[key]; !exist {
|
||||||
|
base.extraMetadata[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -314,10 +314,15 @@ func unmarshallPack(def Definition, resolver identity.Resolver, data []byte) ([]
|
|||||||
|
|
||||||
for _, raw := range aux.Operations {
|
for _, raw := range aux.Operations {
|
||||||
// delegate to specialized unmarshal function
|
// delegate to specialized unmarshal function
|
||||||
op, err := def.OperationUnmarshaler(author, raw, resolver)
|
op, err := def.OperationUnmarshaler(raw, resolver)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
// Set the id from the serialized data
|
||||||
|
op.setId(entity.DeriveId(raw))
|
||||||
|
// Set the author, taken from the OperationPack
|
||||||
|
op.setAuthor(author)
|
||||||
|
|
||||||
ops = append(ops, op)
|
ops = append(ops, op)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,13 +11,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestOperationPackReadWrite(t *testing.T) {
|
func TestOperationPackReadWrite(t *testing.T) {
|
||||||
repo, id1, _, resolver, def := makeTestContext()
|
repo, author, _, resolver, def := makeTestContext()
|
||||||
|
|
||||||
opp := &operationPack{
|
opp := &operationPack{
|
||||||
Author: id1,
|
Author: author,
|
||||||
Operations: []Operation{
|
Operations: []Operation{
|
||||||
newOp1(id1, "foo"),
|
newOp1(author, "foo"),
|
||||||
newOp2(id1, "bar"),
|
newOp2(author, "bar"),
|
||||||
},
|
},
|
||||||
CreateTime: 123,
|
CreateTime: 123,
|
||||||
EditTime: 456,
|
EditTime: 456,
|
||||||
@ -32,34 +32,26 @@ func TestOperationPackReadWrite(t *testing.T) {
|
|||||||
opp2, err := readOperationPack(def, repo, resolver, commit)
|
opp2, err := readOperationPack(def, repo, resolver, commit)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Equal(t, opp, opp2)
|
for _, op := range opp.Operations {
|
||||||
|
// force the creation of the id
|
||||||
// make sure we get the same Id with the same data
|
op.Id()
|
||||||
opp3 := &operationPack{
|
|
||||||
Author: id1,
|
|
||||||
Operations: []Operation{
|
|
||||||
newOp1(id1, "foo"),
|
|
||||||
newOp2(id1, "bar"),
|
|
||||||
},
|
|
||||||
CreateTime: 123,
|
|
||||||
EditTime: 456,
|
|
||||||
}
|
}
|
||||||
require.Equal(t, opp.Id(), opp3.Id())
|
require.Equal(t, opp, opp2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOperationPackSignedReadWrite(t *testing.T) {
|
func TestOperationPackSignedReadWrite(t *testing.T) {
|
||||||
repo, id1, _, resolver, def := makeTestContext()
|
repo, author, _, resolver, def := makeTestContext()
|
||||||
|
|
||||||
err := id1.(*identity.Identity).Mutate(repo, func(orig *identity.Mutator) {
|
err := author.(*identity.Identity).Mutate(repo, func(orig *identity.Mutator) {
|
||||||
orig.Keys = append(orig.Keys, identity.GenerateKey())
|
orig.Keys = append(orig.Keys, identity.GenerateKey())
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
opp := &operationPack{
|
opp := &operationPack{
|
||||||
Author: id1,
|
Author: author,
|
||||||
Operations: []Operation{
|
Operations: []Operation{
|
||||||
newOp1(id1, "foo"),
|
newOp1(author, "foo"),
|
||||||
newOp2(id1, "bar"),
|
newOp2(author, "bar"),
|
||||||
},
|
},
|
||||||
CreateTime: 123,
|
CreateTime: 123,
|
||||||
EditTime: 456,
|
EditTime: 456,
|
||||||
@ -74,23 +66,15 @@ func TestOperationPackSignedReadWrite(t *testing.T) {
|
|||||||
opp2, err := readOperationPack(def, repo, resolver, commit)
|
opp2, err := readOperationPack(def, repo, resolver, commit)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Equal(t, opp, opp2)
|
for _, op := range opp.Operations {
|
||||||
|
// force the creation of the id
|
||||||
// make sure we get the same Id with the same data
|
op.Id()
|
||||||
opp3 := &operationPack{
|
|
||||||
Author: id1,
|
|
||||||
Operations: []Operation{
|
|
||||||
newOp1(id1, "foo"),
|
|
||||||
newOp2(id1, "bar"),
|
|
||||||
},
|
|
||||||
CreateTime: 123,
|
|
||||||
EditTime: 456,
|
|
||||||
}
|
}
|
||||||
require.Equal(t, opp.Id(), opp3.Id())
|
require.Equal(t, opp, opp2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOperationPackFiles(t *testing.T) {
|
func TestOperationPackFiles(t *testing.T) {
|
||||||
repo, id1, _, resolver, def := makeTestContext()
|
repo, author, _, resolver, def := makeTestContext()
|
||||||
|
|
||||||
blobHash1, err := repo.StoreData(randomData())
|
blobHash1, err := repo.StoreData(randomData())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -99,10 +83,10 @@ func TestOperationPackFiles(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
opp := &operationPack{
|
opp := &operationPack{
|
||||||
Author: id1,
|
Author: author,
|
||||||
Operations: []Operation{
|
Operations: []Operation{
|
||||||
newOp1(id1, "foo", blobHash1, blobHash2),
|
newOp1(author, "foo", blobHash1, blobHash2),
|
||||||
newOp1(id1, "foo", blobHash2),
|
newOp1(author, "foo", blobHash2),
|
||||||
},
|
},
|
||||||
CreateTime: 123,
|
CreateTime: 123,
|
||||||
EditTime: 456,
|
EditTime: 456,
|
||||||
@ -117,6 +101,10 @@ func TestOperationPackFiles(t *testing.T) {
|
|||||||
opp2, err := readOperationPack(def, repo, resolver, commit)
|
opp2, err := readOperationPack(def, repo, resolver, commit)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
for _, op := range opp.Operations {
|
||||||
|
// force the creation of the id
|
||||||
|
op.Id()
|
||||||
|
}
|
||||||
require.Equal(t, opp, opp2)
|
require.Equal(t, opp, opp2)
|
||||||
|
|
||||||
require.ElementsMatch(t, opp2.Operations[0].(OperationWithFiles).GetFiles(), []repository.Hash{
|
require.ElementsMatch(t, opp2.Operations[0].(OperationWithFiles).GetFiles(), []repository.Hash{
|
||||||
|
57
entity/dag/operation_testing.go
Normal file
57
entity/dag/operation_testing.go
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package dag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/MichaelMure/git-bug/entity"
|
||||||
|
"github.com/MichaelMure/git-bug/identity"
|
||||||
|
"github.com/MichaelMure/git-bug/repository"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SerializeRoundTripTest realize a marshall/unmarshall round-trip in the same condition as with OperationPack,
|
||||||
|
// and check if the recovered operation is identical.
|
||||||
|
func SerializeRoundTripTest[OpT Operation](t *testing.T, maker func(author identity.Interface, unixTime int64) OpT) {
|
||||||
|
repo := repository.NewMockRepo()
|
||||||
|
|
||||||
|
rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
op := maker(rene, time.Now().Unix())
|
||||||
|
// enforce having an id
|
||||||
|
op.Id()
|
||||||
|
|
||||||
|
rdt := &roundTripper[OpT]{Before: op, author: rene}
|
||||||
|
|
||||||
|
data, err := json.Marshal(rdt)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = json.Unmarshal(data, &rdt)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, op, rdt.after)
|
||||||
|
}
|
||||||
|
|
||||||
|
type roundTripper[OpT Operation] struct {
|
||||||
|
Before OpT
|
||||||
|
author identity.Interface
|
||||||
|
after OpT
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *roundTripper[OpT]) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(r.Before)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *roundTripper[OpT]) UnmarshalJSON(data []byte) error {
|
||||||
|
if err := json.Unmarshal(data, &r.after); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Set the id from the serialized data
|
||||||
|
r.after.setId(entity.DeriveId(data))
|
||||||
|
// Set the author, as OperationPack would do
|
||||||
|
r.after.setAuthor(r.author)
|
||||||
|
return nil
|
||||||
|
}
|
78
go.mod
78
go.mod
@ -1,17 +1,16 @@
|
|||||||
module github.com/MichaelMure/git-bug
|
module github.com/MichaelMure/git-bug
|
||||||
|
|
||||||
go 1.16
|
go 1.18
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/99designs/gqlgen v0.17.1
|
github.com/99designs/gqlgen v0.17.13
|
||||||
github.com/99designs/keyring v1.2.1
|
github.com/99designs/keyring v1.2.1
|
||||||
github.com/MichaelMure/go-term-text v0.3.1
|
github.com/MichaelMure/go-term-text v0.3.1
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7
|
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7
|
||||||
github.com/araddon/dateparse v0.0.0-20190622164848-0fb0a474d195
|
github.com/araddon/dateparse v0.0.0-20190622164848-0fb0a474d195
|
||||||
github.com/awesome-gocui/gocui v1.1.0
|
github.com/awesome-gocui/gocui v1.1.0
|
||||||
github.com/blevesearch/bleve v1.0.14
|
github.com/blevesearch/bleve v1.0.14
|
||||||
github.com/cheekybits/genny v0.0.0-20170328200008-9127e812e1e9
|
github.com/cheekybits/genny v1.0.0
|
||||||
github.com/corpix/uarand v0.1.1 // indirect
|
|
||||||
github.com/dustin/go-humanize v1.0.0
|
github.com/dustin/go-humanize v1.0.0
|
||||||
github.com/fatih/color v1.13.0
|
github.com/fatih/color v1.13.0
|
||||||
github.com/go-git/go-billy/v5 v5.3.1
|
github.com/go-git/go-billy/v5 v5.3.1
|
||||||
@ -23,11 +22,10 @@ require (
|
|||||||
github.com/phayes/freeport v0.0.0-20171002181615-b8543db493a5
|
github.com/phayes/freeport v0.0.0-20171002181615-b8543db493a5
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/shurcooL/githubv4 v0.0.0-20190601194912-068505affed7
|
github.com/shurcooL/githubv4 v0.0.0-20190601194912-068505affed7
|
||||||
github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f // indirect
|
|
||||||
github.com/skratchdot/open-golang v0.0.0-20190402232053-79abb63cd66e
|
github.com/skratchdot/open-golang v0.0.0-20190402232053-79abb63cd66e
|
||||||
github.com/spf13/cobra v1.4.0
|
github.com/spf13/cobra v1.4.0
|
||||||
github.com/stretchr/testify v1.7.2
|
github.com/stretchr/testify v1.7.2
|
||||||
github.com/vektah/gqlparser/v2 v2.4.1
|
github.com/vektah/gqlparser/v2 v2.4.6
|
||||||
github.com/xanzy/go-gitlab v0.68.0
|
github.com/xanzy/go-gitlab v0.68.0
|
||||||
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838
|
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838
|
||||||
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5
|
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5
|
||||||
@ -35,3 +33,71 @@ require (
|
|||||||
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a
|
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a
|
||||||
golang.org/x/text v0.3.7
|
golang.org/x/text v0.3.7
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
|
||||||
|
github.com/Microsoft/go-winio v0.4.16 // indirect
|
||||||
|
github.com/RoaringBitmap/roaring v0.4.23 // indirect
|
||||||
|
github.com/acomagu/bufpipe v1.0.3 // indirect
|
||||||
|
github.com/agnivade/levenshtein v1.1.1 // indirect
|
||||||
|
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
|
||||||
|
github.com/blevesearch/mmap-go v1.0.2 // indirect
|
||||||
|
github.com/blevesearch/segment v0.9.0 // indirect
|
||||||
|
github.com/blevesearch/snowballstem v0.9.0 // indirect
|
||||||
|
github.com/blevesearch/zap/v11 v11.0.14 // indirect
|
||||||
|
github.com/blevesearch/zap/v12 v12.0.14 // indirect
|
||||||
|
github.com/blevesearch/zap/v13 v13.0.6 // indirect
|
||||||
|
github.com/blevesearch/zap/v14 v14.0.5 // indirect
|
||||||
|
github.com/blevesearch/zap/v15 v15.0.3 // indirect
|
||||||
|
github.com/corpix/uarand v0.1.1 // indirect
|
||||||
|
github.com/couchbase/vellum v1.0.2 // indirect
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
|
||||||
|
github.com/danieljoos/wincred v1.1.2 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/dvsekhvalnov/jose2go v1.5.0 // indirect
|
||||||
|
github.com/emirpasic/gods v1.12.0 // indirect
|
||||||
|
github.com/gdamore/encoding v1.0.0 // indirect
|
||||||
|
github.com/gdamore/tcell/v2 v2.4.0 // indirect
|
||||||
|
github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2 // indirect
|
||||||
|
github.com/go-git/gcfg v1.5.0 // indirect
|
||||||
|
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
|
||||||
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
|
github.com/golang/snappy v0.0.1 // indirect
|
||||||
|
github.com/google/go-querystring v1.1.0 // indirect
|
||||||
|
github.com/gorilla/websocket v1.5.0 // indirect
|
||||||
|
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
|
||||||
|
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||||
|
github.com/hashicorp/go-retryablehttp v0.7.1 // indirect
|
||||||
|
github.com/imdario/mergo v0.3.12 // indirect
|
||||||
|
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||||
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||||
|
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.0.3 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.12 // indirect
|
||||||
|
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||||
|
github.com/mitchellh/mapstructure v1.3.1 // indirect
|
||||||
|
github.com/mschoch/smat v0.2.0 // indirect
|
||||||
|
github.com/mtibben/percent v0.2.1 // indirect
|
||||||
|
github.com/philhofer/fwd v1.0.0 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/rivo/uniseg v0.1.0 // indirect
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
|
github.com/sergi/go-diff v1.1.0 // indirect
|
||||||
|
github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f // indirect
|
||||||
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
github.com/steveyen/gtreap v0.1.0 // indirect
|
||||||
|
github.com/stretchr/objx v0.3.0 // indirect
|
||||||
|
github.com/tinylib/msgp v1.1.0 // indirect
|
||||||
|
github.com/willf/bitset v1.1.10 // indirect
|
||||||
|
github.com/xanzy/ssh-agent v0.3.0 // indirect
|
||||||
|
go.etcd.io/bbolt v1.3.5 // indirect
|
||||||
|
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 // indirect
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||||
|
golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect
|
||||||
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
|
google.golang.org/protobuf v1.28.0 // indirect
|
||||||
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
||||||
|
51
go.sum
51
go.sum
@ -33,11 +33,12 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
|
|||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs=
|
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs=
|
||||||
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4=
|
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4=
|
||||||
github.com/99designs/gqlgen v0.17.1 h1:i2qQMPKHQjHgBWYIpO4TsaQpPqMHCPK1+h95ipvH8VU=
|
github.com/99designs/gqlgen v0.17.13 h1:ETUEqvRg5Zvr1lXtpoRdj026fzVay0ZlJPwI33qXLIw=
|
||||||
github.com/99designs/gqlgen v0.17.1/go.mod h1:K5fzLKwtph+FFgh9j7nFbRUdBKvTcGnsta51fsMTn3o=
|
github.com/99designs/gqlgen v0.17.13/go.mod h1:w1brbeOdqVyNJI553BGwtwdVcYu1LKeYE1opLWN9RgQ=
|
||||||
github.com/99designs/keyring v1.2.1 h1:tYLp1ULvO7i3fI5vE21ReQuj99QFSs7lGm0xWyJo87o=
|
github.com/99designs/keyring v1.2.1 h1:tYLp1ULvO7i3fI5vE21ReQuj99QFSs7lGm0xWyJo87o=
|
||||||
github.com/99designs/keyring v1.2.1/go.mod h1:fc+wB5KTk9wQ9sDx0kFXB3A0MaeGHM9AwRStKOQ5vOA=
|
github.com/99designs/keyring v1.2.1/go.mod h1:fc+wB5KTk9wQ9sDx0kFXB3A0MaeGHM9AwRStKOQ5vOA=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/Masterminds/glide v0.13.2/go.mod h1:STyF5vcenH/rUqTEv+/hBXlSTo7KYwg2oc2f4tzPWic=
|
github.com/Masterminds/glide v0.13.2/go.mod h1:STyF5vcenH/rUqTEv+/hBXlSTo7KYwg2oc2f4tzPWic=
|
||||||
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||||
@ -54,8 +55,8 @@ github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06
|
|||||||
github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk=
|
github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk=
|
||||||
github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
|
github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
|
||||||
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
|
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
|
||||||
github.com/agnivade/levenshtein v1.1.0 h1:n6qGwyHG61v3ABce1rPVZklEYRT8NFpCMrpZdBUbYGM=
|
github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8=
|
||||||
github.com/agnivade/levenshtein v1.1.0/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
|
github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
|
||||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
|
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
|
||||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
|
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
|
||||||
@ -101,8 +102,8 @@ github.com/blevesearch/zap/v14 v14.0.5/go.mod h1:bWe8S7tRrSBTIaZ6cLRbgNH4TUDaC9L
|
|||||||
github.com/blevesearch/zap/v15 v15.0.3 h1:Ylj8Oe+mo0P25tr9iLPp33lN6d4qcztGjaIsP51UxaY=
|
github.com/blevesearch/zap/v15 v15.0.3 h1:Ylj8Oe+mo0P25tr9iLPp33lN6d4qcztGjaIsP51UxaY=
|
||||||
github.com/blevesearch/zap/v15 v15.0.3/go.mod h1:iuwQrImsh1WjWJ0Ue2kBqY83a0rFtJTqfa9fp1rbVVU=
|
github.com/blevesearch/zap/v15 v15.0.3/go.mod h1:iuwQrImsh1WjWJ0Ue2kBqY83a0rFtJTqfa9fp1rbVVU=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/cheekybits/genny v0.0.0-20170328200008-9127e812e1e9 h1:a1zrFsLFac2xoM6zG1u72DWJwZG3ayttYLfmLbxVETk=
|
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
|
||||||
github.com/cheekybits/genny v0.0.0-20170328200008-9127e812e1e9/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
@ -118,9 +119,7 @@ github.com/couchbase/ghistogram v0.1.0/go.mod h1:s1Jhy76zqfEecpNWJfWUiKZookAFaiG
|
|||||||
github.com/couchbase/moss v0.1.0/go.mod h1:9MaHIaRuy9pvLPUJxB8sh8OrLfyDczECVL37grCIubs=
|
github.com/couchbase/moss v0.1.0/go.mod h1:9MaHIaRuy9pvLPUJxB8sh8OrLfyDczECVL37grCIubs=
|
||||||
github.com/couchbase/vellum v1.0.2 h1:BrbP0NKiyDdndMPec8Jjhy0U47CZ0Lgx3xUC2r9rZqw=
|
github.com/couchbase/vellum v1.0.2 h1:BrbP0NKiyDdndMPec8Jjhy0U47CZ0Lgx3xUC2r9rZqw=
|
||||||
github.com/couchbase/vellum v1.0.2/go.mod h1:FcwrEivFpNi24R3jLOs3n+fs5RnuQnQqCLBJ1uAg1W4=
|
github.com/couchbase/vellum v1.0.2/go.mod h1:FcwrEivFpNi24R3jLOs3n+fs5RnuQnQqCLBJ1uAg1W4=
|
||||||
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
|
|
||||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
|
github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
@ -236,8 +235,8 @@ github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99 h1:twflg0XRTjwKp
|
|||||||
github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU=
|
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU=
|
||||||
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0=
|
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0=
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||||
@ -289,11 +288,10 @@ github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i
|
|||||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
|
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
|
||||||
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
|
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
|
||||||
github.com/matryer/moq v0.2.3/go.mod h1:9RtPYjTnH1bSBIkpvtHkFN7nbWAnO7oRpdJkEIn6UtE=
|
github.com/matryer/moq v0.2.7/go.mod h1:kITsx543GOENm48TUAQyJ9+SAvFSr7iGQXPoth/VUBk=
|
||||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
|
||||||
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
|
|
||||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
|
||||||
|
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
@ -303,8 +301,8 @@ github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRC
|
|||||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/mitchellh/mapstructure v1.2.3 h1:f/MjBEBDLttYCGfRaKBbKSRVF5aV2O6fnBpzknuE3jU=
|
github.com/mitchellh/mapstructure v1.3.1 h1:cCBH2gTD2K0OtLlv/Y5H01VQCqmlDxz30kS5Y5bqfLA=
|
||||||
github.com/mitchellh/mapstructure v1.2.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
|
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
|
||||||
github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
|
github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
|
||||||
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
|
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
|
||||||
@ -331,9 +329,7 @@ github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qq
|
|||||||
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
|
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
|
||||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
|
|
||||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||||
@ -342,7 +338,6 @@ github.com/shurcooL/githubv4 v0.0.0-20190601194912-068505affed7 h1:Vk3RiBQpF0Ja+
|
|||||||
github.com/shurcooL/githubv4 v0.0.0-20190601194912-068505affed7/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo=
|
github.com/shurcooL/githubv4 v0.0.0-20190601194912-068505affed7/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo=
|
||||||
github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f h1:tygelZueB1EtXkPI6mQ4o9DQ0+FKW41hTbunoXZCTqk=
|
github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f h1:tygelZueB1EtXkPI6mQ4o9DQ0+FKW41hTbunoXZCTqk=
|
||||||
github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f/go.mod h1:AuYgA5Kyo4c7HfUmvRGs/6rGlMMV/6B1bVnB9JxJEEg=
|
github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f/go.mod h1:AuYgA5Kyo4c7HfUmvRGs/6rGlMMV/6B1bVnB9JxJEEg=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
|
||||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||||
github.com/skratchdot/open-golang v0.0.0-20190402232053-79abb63cd66e h1:VAzdS5Nw68fbf5RZ8RDVlUvPXNU6Z3jtPCK/qvm4FoQ=
|
github.com/skratchdot/open-golang v0.0.0-20190402232053-79abb63cd66e h1:VAzdS5Nw68fbf5RZ8RDVlUvPXNU6Z3jtPCK/qvm4FoQ=
|
||||||
github.com/skratchdot/open-golang v0.0.0-20190402232053-79abb63cd66e/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
github.com/skratchdot/open-golang v0.0.0-20190402232053-79abb63cd66e/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||||
@ -366,6 +361,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
|
|||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
|
github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
|
||||||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||||
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
||||||
@ -376,10 +372,9 @@ github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c/go.mod h1:ahpPrc7
|
|||||||
github.com/tinylib/msgp v1.1.0 h1:9fQd+ICuRIu/ue4vxJZu6/LzxN0HwMds2nq/0cFvxHU=
|
github.com/tinylib/msgp v1.1.0 h1:9fQd+ICuRIu/ue4vxJZu6/LzxN0HwMds2nq/0cFvxHU=
|
||||||
github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
github.com/urfave/cli/v2 v2.8.1/go.mod h1:Z41J9TPoffeoqP0Iza0YbAhGvymRdZAd2uPmZ5JxRdY=
|
||||||
github.com/vektah/gqlparser/v2 v2.4.0/go.mod h1:flJWIR04IMQPGz+BXLrORkrARBxv/rtyIAFvd/MceW0=
|
github.com/vektah/gqlparser/v2 v2.4.6 h1:Yjzp66g6oVq93Jihbi0qhGnf/6zIWjcm8H6gA27zstE=
|
||||||
github.com/vektah/gqlparser/v2 v2.4.1 h1:QOyEn8DAPMUMARGMeshKDkDgNmVoEaEGiDB0uWxcSlQ=
|
github.com/vektah/gqlparser/v2 v2.4.6/go.mod h1:flJWIR04IMQPGz+BXLrORkrARBxv/rtyIAFvd/MceW0=
|
||||||
github.com/vektah/gqlparser/v2 v2.4.1/go.mod h1:flJWIR04IMQPGz+BXLrORkrARBxv/rtyIAFvd/MceW0=
|
|
||||||
github.com/willf/bitset v1.1.10 h1:NotGKqX0KwQ72NUzqrjZq5ipPNDQex9lo3WpaS8L2sc=
|
github.com/willf/bitset v1.1.10 h1:NotGKqX0KwQ72NUzqrjZq5ipPNDQex9lo3WpaS8L2sc=
|
||||||
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||||
github.com/xanzy/go-gitlab v0.68.0 h1:b2iMQHgZ1V+NyRqLRJVv6RFfr4xnd/AASeS/PETYL0Y=
|
github.com/xanzy/go-gitlab v0.68.0 h1:b2iMQHgZ1V+NyRqLRJVv6RFfr4xnd/AASeS/PETYL0Y=
|
||||||
@ -387,6 +382,7 @@ github.com/xanzy/go-gitlab v0.68.0/go.mod h1:o4yExCtdaqlM8YGdDJWuZoBmfxBsmA9TPEj
|
|||||||
github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI=
|
github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI=
|
||||||
github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
|
github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
|
||||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||||
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
@ -407,6 +403,7 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
|||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838 h1:71vQrMauZZhcTVK6KdYM+rklehEEwb3E+ZhaE5jrPrE=
|
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838 h1:71vQrMauZZhcTVK6KdYM+rklehEEwb3E+ZhaE5jrPrE=
|
||||||
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
@ -440,6 +437,7 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB
|
|||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
@ -470,7 +468,6 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R
|
|||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
|
golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
|
||||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
|
||||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 h1:NWy5+hlRbC7HK+PmcXVUmW1IMyFce7to56IUvhUFm7Y=
|
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 h1:NWy5+hlRbC7HK+PmcXVUmW1IMyFce7to56IUvhUFm7Y=
|
||||||
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
@ -497,7 +494,6 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h
|
|||||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@ -532,6 +528,7 @@ golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a h1:ppl5mZgokTT8uPkmYOyEUmPTr3ypaKkg5eFOGrAmxxE=
|
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a h1:ppl5mZgokTT8uPkmYOyEUmPTr3ypaKkg5eFOGrAmxxE=
|
||||||
@ -592,9 +589,9 @@ golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roY
|
|||||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
golang.org/x/tools v0.0.0-20200815165600-90abf76919f3/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
|
||||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||||
|
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
@ -693,14 +690,12 @@ gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
|||||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
@ -87,6 +87,7 @@ func generateRandomBugsWithSeed(opts Options, seed int64) []*bug.Bug {
|
|||||||
time.Now().Unix(),
|
time.Now().Unix(),
|
||||||
fake.Sentence(),
|
fake.Sentence(),
|
||||||
paragraphs(),
|
paragraphs(),
|
||||||
|
nil, nil,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -143,19 +144,19 @@ func paragraphs() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func comment(b bug.Interface, p identity.Interface, timestamp int64) {
|
func comment(b bug.Interface, p identity.Interface, timestamp int64) {
|
||||||
_, _ = bug.AddComment(b, p, timestamp, paragraphs())
|
_, _ = bug.AddComment(b, p, timestamp, paragraphs(), nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func title(b bug.Interface, p identity.Interface, timestamp int64) {
|
func title(b bug.Interface, p identity.Interface, timestamp int64) {
|
||||||
_, _ = bug.SetTitle(b, p, timestamp, fake.Sentence())
|
_, _ = bug.SetTitle(b, p, timestamp, fake.Sentence(), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func open(b bug.Interface, p identity.Interface, timestamp int64) {
|
func open(b bug.Interface, p identity.Interface, timestamp int64) {
|
||||||
_, _ = bug.Open(b, p, timestamp)
|
_, _ = bug.Open(b, p, timestamp, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func close(b bug.Interface, p identity.Interface, timestamp int64) {
|
func close(b bug.Interface, p identity.Interface, timestamp int64) {
|
||||||
_, _ = bug.Close(b, p, timestamp)
|
_, _ = bug.Close(b, p, timestamp, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
var addedLabels []string
|
var addedLabels []string
|
||||||
@ -182,5 +183,5 @@ func labels(b bug.Interface, p identity.Interface, timestamp int64) {
|
|||||||
// ignore error
|
// ignore error
|
||||||
// if the randomisation produce no changes, no op
|
// if the randomisation produce no changes, no op
|
||||||
// is added to the bug
|
// is added to the bug
|
||||||
_, _, _ = bug.ChangeLabels(b, p, timestamp, added, removed)
|
_, _, _ = bug.ChangeLabels(b, p, timestamp, added, removed, nil)
|
||||||
}
|
}
|
||||||
|
@ -235,7 +235,7 @@ func (repo *GoGitRepo) Keyring() Keyring {
|
|||||||
return repo.keyring
|
return repo.keyring
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserName returns the name the the user has used to configure git
|
// GetUserName returns the name the user has used to configure git
|
||||||
func (repo *GoGitRepo) GetUserName() (string, error) {
|
func (repo *GoGitRepo) GetUserName() (string, error) {
|
||||||
return repo.AnyConfig().ReadString("user.name")
|
return repo.AnyConfig().ReadString("user.name")
|
||||||
}
|
}
|
||||||
|
@ -60,9 +60,9 @@ type RepoKeyring interface {
|
|||||||
Keyring() Keyring
|
Keyring() Keyring
|
||||||
}
|
}
|
||||||
|
|
||||||
// RepoCommon represent the common function the we want all the repo to implement
|
// RepoCommon represent the common function we want all repos to implement
|
||||||
type RepoCommon interface {
|
type RepoCommon interface {
|
||||||
// GetUserName returns the name the the user has used to configure git
|
// GetUserName returns the name the user has used to configure git
|
||||||
GetUserName() (string, error)
|
GetUserName() (string, error)
|
||||||
|
|
||||||
// GetUserEmail returns the email address that the user has used to configure git.
|
// GetUserEmail returns the email address that the user has used to configure git.
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// +build debugwebui
|
//go:build debugwebui
|
||||||
|
|
||||||
package webui
|
package webui
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// +build ignore
|
//go:build ignore
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// Code generated by vfsgen; DO NOT EDIT.
|
// Code generated by vfsgen; DO NOT EDIT.
|
||||||
|
|
||||||
// +build !debugwebui
|
//go:build !debugwebui
|
||||||
|
|
||||||
package webui
|
package webui
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user