Refactor commit type and logic to account for future commit types

---
type: change
message: |-
  Refactor commit type and logic to account for future commit types

  This commit introduces a CommitInterface which CommitChange (previously
  ChangeCommit) now implements. Additionally, now all commit messages will include
  a type field and a "---" separator. The code is written to still accept all the
  old commit messages used in this repo. Other than those changes, most of this is
  just rearranging existing code.
change_hash: AHjWAUxCjXgOL0Sb+oQZc6TmuVgVHJn08zpGreYyChwx
credentials:
- type: pgp_signature
  pub_key_id: 95C46FA6A41148AC
  body: iQIzBAABAgAdFiEEJ6tQKp6olvZKJ0lwlcRvpqQRSKwFAl5gOuoACgkQlcRvpqQRSKxapA//bODd2IwX2D7nFBkEEd00ol1l4vaw7pgwCjqyQtyskeCZ5IH6H6PkYOSmU9DIBde+cGo35Oi5ynChmfnSatvUZ1dLRJqm8FfOGDw/IsccyYDd1iptj16Ckr6Bsht1XgFJNN10hufuAg77fRIwbGi003WCQdnrJZ1Xbtgex6a4rUqVFW+sjXAB1msmo5B5nabfm/ta0epptxhlINIY2qP5Vb+ftdb2lRUNQwkIEr5GErfAgN3sxYEfqQvLzr/007tEeVznyhdgww47awWsBaTEd6njycbWpphnNA8PbcgUGKDYsj3qLz/bCR1VQOdWgod8PLEru2O+uC2+72ssfT9NDn8HB/WrBKG4W4oykJIhXJ5um18/B9ZSxPMlTQv2Yr4nF7w+Sx3UynEdUTducbIOUq2K+JnI+Ln3gHe0yRw5UEfrzymR4f2Nfe1I13rJk2W7SVRDmOYidr0MwBlLs8tmnJFMWmWZtd1hpPOpyOyUF1jKgvMuR9ferb5niuc3Lk4trqDiaF+tjy/NmAv/7c+qiVmKKpVJVVqvT60TqBR9DTHjRGktcPFD50sc811Th+Xd9RdhzpIYM+0DT790FTf8E0hY6wm/NKTGplfqwBSNZk87SeIiFTu7sZWVpAaPz1vTmVGduC1oj3/Zlv6TzNrUAp3VwBepROBhZlHCHUr9tKg=
  account: mediocregopher
This commit is contained in:
mediocregopher 2020-03-04 16:34:02 -07:00
parent 9bfd012221
commit cf05b3a072
9 changed files with 316 additions and 212 deletions

View File

@ -20,6 +20,9 @@ set, only a sequence of milestones and the requirements to hit them.
## Milestone: Enough polish to show off the project ## Milestone: Enough polish to show off the project
* Maybe coalesce the `accessctl`, `fs`, and `sigcred` packages back into the
root "dehub" package.
* Polish commands * Polish commands
- New flag system, some kind of interactivity support (e.g. user doesn't - New flag system, some kind of interactivity support (e.g. user doesn't
specify required argument, give them a prompt on the CLI to input it specify required argument, give them a prompt on the CLI to input it

View File

@ -155,12 +155,17 @@ var subCmds = []subCmd{
return fmt.Errorf("could not cast %+v to SignifierInterface: %w", sig, err) return fmt.Errorf("could not cast %+v to SignifierInterface: %w", sig, err)
} }
tc, err := sctx.repo().NewChangeCommit(*msg, *accountID, sigInt) commit, err := sctx.repo().NewCommitChange(*msg)
if err != nil { if err != nil {
return fmt.Errorf("could not construct change commit: %w", err) return fmt.Errorf("could not construct change commit: %w", err)
} }
hash, err := sctx.repo().Commit(tc, *accountID) commit, err = sctx.repo().AccreditCommit(commit, *accountID, sigInt)
if err != nil {
return fmt.Errorf("could not accredit commit: %w", err)
}
hash, err := sctx.repo().Commit(commit, *accountID)
if err != nil { if err != nil {
return fmt.Errorf("could not commit change commit: %w", err) return fmt.Errorf("could not commit change commit: %w", err)
} }
@ -191,7 +196,7 @@ var subCmds = []subCmd{
branchName = plumbing.NewBranchReferenceName(*branch) branchName = plumbing.NewBranchReferenceName(*branch)
} }
if err := sctx.repo().VerifyChangeCommit(branchName, *h); err != nil { if err := sctx.repo().VerifyCommit(branchName, *h); err != nil {
return fmt.Errorf("could not verify commit at %q (%s): %w", *rev, *h, err) return fmt.Errorf("could not verify commit at %q (%s): %w", *rev, *h, err)
} }
@ -278,7 +283,7 @@ var subCmds = []subCmd{
for i := len(hashesToCheck) - 1; i >= 0; i-- { for i := len(hashesToCheck) - 1; i >= 0; i-- {
hash := hashesToCheck[i] hash := hashesToCheck[i]
fmt.Printf("Verifying change commit %q\n", hash) fmt.Printf("Verifying change commit %q\n", hash)
if err := sctx.repo().VerifyChangeCommit(branchName, hash); err != nil { if err := sctx.repo().VerifyCommit(branchName, hash); err != nil {
return fmt.Errorf("could not verify change commit %q: %w", hash, err) return fmt.Errorf("could not verify change commit %q: %w", hash, err)
} }
} }

210
commit.go
View File

@ -5,10 +5,9 @@ import (
"dehub/accessctl" "dehub/accessctl"
"dehub/fs" "dehub/fs"
"dehub/sigcred" "dehub/sigcred"
"dehub/yamlutil" "dehub/typeobj"
"encoding" "encoding"
"encoding/base64" "encoding/base64"
"errors"
"fmt" "fmt"
"strings" "strings"
"time" "time"
@ -19,59 +18,116 @@ import (
yaml "gopkg.in/yaml.v2" yaml "gopkg.in/yaml.v2"
) )
// ChangeCommit describes the structure of a change commit message. // CommitInterface describes the methods which must be implemented by the
type ChangeCommit struct { // different commit types.
Message string `yaml:"message"` type CommitInterface interface {
ChangeHash yamlutil.Blob `yaml:"change_hash"` // MessageHead returns the head of the commit message (i.e. the first line).
MessageHead() (string, error)
// Hash returns the raw hash which Signifiers can sign to accredit this
// commit. The tree objects given describe the filesystem state of the
// parent commit, and the filesystem state of this commit.
//
// This method should _not_ change any fields on the commit.
Hash(parent, this *object.Tree) ([]byte, error)
// GetHash returns the signable Hash embedded in the commit, which should
// hopefully correspond to the Commit's Credentials.
GetHash() []byte
}
// Commit represents a single Commit which is being added to a branch. Only one
// field should be set on a Commit, unless otherwise noted.
type Commit struct {
Change *CommitChange `type:"change,default"`
// Credentials represent all created Credentials for this commit, and can be
// set on all Commit objects regardless of other fields being set.
Credentials []sigcred.Credential `yaml:"credentials"` Credentials []sigcred.Credential `yaml:"credentials"`
} }
type ccYAML struct { // MarshalYAML implements the yaml.Marshaler interface.
Val ChangeCommit `yaml:",inline"` func (c Commit) MarshalYAML() (interface{}, error) {
return typeobj.MarshalYAML(c)
} }
func msgHead(msg string) string { // UnmarshalYAML implements the yaml.Unmarshaler interface.
i := strings.Index(msg, "\n") func (c *Commit) UnmarshalYAML(unmarshal func(interface{}) error) error {
if i > 0 { return typeobj.UnmarshalYAML(c, unmarshal)
return msg[:i]
} }
return msg
// Interface returns the CommitInterface instance encapsulated by this Commit
// object.
func (c Commit) Interface() (CommitInterface, error) {
el, _, err := typeobj.Element(c)
if err != nil {
return nil, err
}
return el.(CommitInterface), nil
} }
// MarshalText implements the encoding.TextMarshaler interface by returning the // MarshalText implements the encoding.TextMarshaler interface by returning the
// form the ChangeCommit object takes in the git commit message. // form the Commit object takes in the git commit message.
func (cc ChangeCommit) MarshalText() ([]byte, error) { func (c Commit) MarshalText() ([]byte, error) {
changeCommitEncoded, err := yaml.Marshal(ccYAML{cc}) commitInt, err := c.Interface()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to encode ChangeCommit message: %w", err) return nil, fmt.Errorf("could not cast Commit %+v to interface : %w", c, err)
} }
fullMsg := msgHead(cc.Message) + "\n\n" + string(changeCommitEncoded) msgHead, err := commitInt.MessageHead()
return []byte(fullMsg), nil if err != nil {
return nil, fmt.Errorf("error constructing message head: %w", err)
}
msgBodyB, err := yaml.Marshal(c)
if err != nil {
return nil, fmt.Errorf("error marshaling commit %+v as yaml: %w", c, err)
}
w := new(bytes.Buffer)
w.WriteString(msgHead)
w.WriteString("\n\n---\n")
w.Write(msgBodyB)
return w.Bytes(), nil
} }
// UnmarshalText implements the encoding.TextUnmarshaler interface by decoding a // UnmarshalText implements the encoding.TextUnmarshaler interface by decoding a
// ChangeCommit object which has been encoded into a git commit message. // Commit object which has been encoded into a git commit message.
func (cc *ChangeCommit) UnmarshalText(msg []byte) error { func (c *Commit) UnmarshalText(msg []byte) error {
i := bytes.Index(msg, []byte("\n")) i := bytes.Index(msg, []byte("\n"))
if i < 0 { if i < 0 {
return fmt.Errorf("commit message %q is malformed", msg) return fmt.Errorf("commit message %q is malformed, it has no body", msg)
} }
msgHead, msg := msg[:i], msg[i:] msgBody := msg[i:]
var ccy ccYAML if err := yaml.Unmarshal(msgBody, c); err != nil {
if err := yaml.Unmarshal(msg, &ccy); err != nil { return fmt.Errorf("could not unmarshal Commit message from yaml: %w", err)
return fmt.Errorf("could not unmarshal ChangeCommit message: %w", err)
} }
*cc = ccy.Val
if !strings.HasPrefix(cc.Message, string(msgHead)) {
return errors.New("encoded ChangeCommit is malformed, it might not be an encoded ChangeCommit")
}
return nil return nil
} }
// AccreditCommit returns the given Commit with an appended Credential provided
// by the given account and its Signifier.
func (r *Repo) AccreditCommit(commit Commit, accountID string, sigInt sigcred.SignifierInterface) (Commit, error) {
commitInt, err := commit.Interface()
if err != nil {
return commit, fmt.Errorf("could not cast commit %+v to interface: %w", commit, err)
}
headFS, err := r.HeadFS()
if err != nil {
return commit, fmt.Errorf("could not grab snapshot of HEAD fs: %w", err)
}
cred, err := sigInt.Sign(headFS, commitInt.GetHash())
if err != nil {
return commit, fmt.Errorf("could not accreddit change commit: %w", err)
}
cred.AccountID = accountID
commit.Credentials = append(commit.Credentials, cred)
return commit, nil
}
// Commit uses the given TextMarshaler to create a git commit object (with the // Commit uses the given TextMarshaler to create a git commit object (with the
// specified accountID as the author) and commits it to the current HEAD, // specified accountID as the author) and commits it to the current HEAD,
// returning the hash of the commit. // returning the hash of the commit.
@ -116,50 +172,6 @@ func (r *Repo) HasStagedChanges() (bool, error) {
return any, nil return any, nil
} }
// NewChangeCommit constructs a ChangeCommit. If sig is given then it is used to
// create a Credential for the ChangeCommit.
func (r *Repo) NewChangeCommit(msg, accountID string, sig sigcred.SignifierInterface) (ChangeCommit, error) {
_, headTree, err := r.head()
if errors.Is(err, plumbing.ErrReferenceNotFound) {
headTree = &object.Tree{}
} else if err != nil {
return ChangeCommit{}, err
}
_, stagedTree, err := fs.FromStagedChangesTree(r.GitRepo)
if err != nil {
return ChangeCommit{}, err
}
changeHash := genChangeHash(nil, msg, headTree, stagedTree)
var creds []sigcred.Credential
if sig != nil {
// this is necessarily different than headTree for the case of there
// being no HEAD (ie it's the first commit). In that case we want
// headTree to be empty (because it's being used to generate the change
// hash), but we want the signifier to use the raw fs (because that's
// where the signifier's data might be).
sigFS, err := r.headOrRawFS()
if err != nil {
return ChangeCommit{}, err
}
cred, err := sig.Sign(sigFS, changeHash)
if err != nil {
return ChangeCommit{}, fmt.Errorf("failed to sign commit hash: %w", err)
}
cred.AccountID = accountID
creds = append(creds, cred)
}
return ChangeCommit{
Message: msg,
ChangeHash: changeHash,
Credentials: creds,
}, nil
}
func (r *Repo) assertAccessControls( func (r *Repo) assertAccessControls(
accessCtls []accessctl.BranchAccessControl, creds []sigcred.Credential, accessCtls []accessctl.BranchAccessControl, creds []sigcred.Credential,
branch plumbing.ReferenceName, from, to *object.Tree, branch plumbing.ReferenceName, from, to *object.Tree,
@ -197,28 +209,23 @@ func (r *Repo) assertAccessControls(
return nil return nil
} }
// VerifyChangeCommit verifies that the change commit at the given hash, which // VerifyCommit verifies that the commit at the given hash, which is presumably
// is presumably on the given branch, is gucci. // on the given branch, is gucci.
func (r *Repo) VerifyChangeCommit(branch plumbing.ReferenceName, h plumbing.Hash) error { func (r *Repo) VerifyCommit(branch plumbing.ReferenceName, h plumbing.Hash) error {
commit, err := r.GitRepo.CommitObject(h) commitObj, err := r.GitRepo.CommitObject(h)
if err != nil { if err != nil {
return fmt.Errorf("could not retrieve commit object: %w", err) return fmt.Errorf("could not retrieve commit object: %w", err)
} }
commitTree, err := r.GitRepo.TreeObject(commit.TreeHash) commitTree, err := r.GitRepo.TreeObject(commitObj.TreeHash)
if err != nil { if err != nil {
return fmt.Errorf("could not retrieve tree object: %w", err) return fmt.Errorf("could not retrieve tree object: %w", err)
} }
var changeCommit ChangeCommit
if err := changeCommit.UnmarshalText([]byte(commit.Message)); err != nil {
return err
}
sigTree := commitTree // only for root commit sigTree := commitTree // only for root commit
parentTree := &object.Tree{} parentTree := &object.Tree{}
if commit.NumParents() > 0 { if commitObj.NumParents() > 0 {
parent, err := commit.Parent(0) parent, err := commitObj.Parent(0)
if err != nil { if err != nil {
return fmt.Errorf("could not retrieve parent of commit: %w", err) return fmt.Errorf("could not retrieve parent of commit: %w", err)
} else if parentTree, err = r.GitRepo.TreeObject(parent.TreeHash); err != nil { } else if parentTree, err = r.GitRepo.TreeObject(parent.TreeHash); err != nil {
@ -228,27 +235,40 @@ func (r *Repo) VerifyChangeCommit(branch plumbing.ReferenceName, h plumbing.Hash
} }
sigFS := fs.FromTree(sigTree) sigFS := fs.FromTree(sigTree)
var commit Commit
if err := commit.UnmarshalText([]byte(commitObj.Message)); err != nil {
return err
}
cfg, err := r.loadConfig(sigFS) cfg, err := r.loadConfig(sigFS)
if err != nil { if err != nil {
return fmt.Errorf("error loading config: %w", err) return fmt.Errorf("error loading config: %w", err)
} }
err = r.assertAccessControls( err = r.assertAccessControls(
cfg.AccessControls, changeCommit.Credentials, cfg.AccessControls, commit.Credentials,
branch, parentTree, commitTree, branch, parentTree, commitTree,
) )
if err != nil { if err != nil {
return fmt.Errorf("failed to satisfy all access controls: %w", err) return fmt.Errorf("failed to satisfy all access controls: %w", err)
} }
expectedChangeHash := genChangeHash(nil, changeCommit.Message, parentTree, commitTree) commitInt, err := commit.Interface()
if !bytes.Equal(changeCommit.ChangeHash, expectedChangeHash) { if err != nil {
return fmt.Errorf("malformed change_hash in commit body, is %s but should be %s", return fmt.Errorf("could not cast commit %+v to interface: %w", commit, err)
base64.StdEncoding.EncodeToString(expectedChangeHash),
base64.StdEncoding.EncodeToString(changeCommit.ChangeHash))
} }
for _, cred := range changeCommit.Credentials { changeHash := commitInt.GetHash()
expectedChangeHash, err := commitInt.Hash(parentTree, commitTree)
if err != nil {
return fmt.Errorf("error calculating expected change hash: %w", err)
} else if !bytes.Equal(changeHash, expectedChangeHash) {
return fmt.Errorf("malformed change_hash in commit body, is %s but should be %s",
base64.StdEncoding.EncodeToString(expectedChangeHash),
base64.StdEncoding.EncodeToString(changeHash))
}
for _, cred := range commit.Credentials {
sig, err := r.signifierForCredential(sigFS, cred) sig, err := r.signifierForCredential(sigFS, cred)
if err != nil { if err != nil {
return fmt.Errorf("error finding signifier for credential %+v: %w", cred, err) return fmt.Errorf("error finding signifier for credential %+v: %w", cred, err)

64
commit_change.go Normal file
View File

@ -0,0 +1,64 @@
package dehub
import (
"dehub/fs"
"dehub/yamlutil"
"errors"
"strings"
"gopkg.in/src-d/go-git.v4/plumbing"
"gopkg.in/src-d/go-git.v4/plumbing/object"
)
// CommitChange describes the structure of a change commit message.
type CommitChange struct {
Message string `yaml:"message"`
ChangeHash yamlutil.Blob `yaml:"change_hash"`
}
var _ CommitInterface = CommitChange{}
// NewCommitChange constructs a Commit populated with a CommitChange
// encompassing the currently staged file changes. The Credentials of the
// returned Commit will _not_ be filled in.
func (r *Repo) NewCommitChange(msg string) (Commit, error) {
_, headTree, err := r.head()
if errors.Is(err, plumbing.ErrReferenceNotFound) {
headTree = &object.Tree{}
} else if err != nil {
return Commit{}, err
}
_, stagedTree, err := fs.FromStagedChangesTree(r.GitRepo)
if err != nil {
return Commit{}, err
}
cc := CommitChange{Message: msg}
if cc.ChangeHash, err = cc.Hash(headTree, stagedTree); err != nil {
return Commit{}, err
}
return Commit{
Change: &cc,
}, nil
}
// MessageHead implements the method for the CommitInterface interface.
func (cc CommitChange) MessageHead() (string, error) {
i := strings.Index(cc.Message, "\n")
if i > 0 {
return cc.Message[:i], nil
}
return cc.Message, nil
}
// Hash implements the method for the CommitInterface interface.
func (cc CommitChange) Hash(parent, this *object.Tree) ([]byte, error) {
return genChangeHash(nil, cc.Message, parent, this), nil
}
// GetHash implements the method for the CommitInterface interface.
func (cc CommitChange) GetHash() []byte {
return cc.ChangeHash
}

105
commit_change_test.go Normal file
View File

@ -0,0 +1,105 @@
package dehub
import (
"reflect"
"strings"
"testing"
"github.com/davecgh/go-spew/spew"
)
func TestChangeCommitVerify(t *testing.T) {
type step struct {
msg string
msgHead string // defaults to msg
tree map[string]string
}
testCases := []struct {
descr string
steps []step
}{
{
descr: "single commit",
steps: []step{
{
msg: "first commit",
tree: map[string]string{"a": "0", "b": "1"},
},
},
},
{
descr: "multiple commits",
steps: []step{
{
msg: "first commit",
tree: map[string]string{"a": "0", "b": "1"},
},
{
msg: "second commit, changing a",
tree: map[string]string{"a": "1"},
},
{
msg: "third commit, empty",
},
{
msg: "fourth commit, adding c, removing b",
tree: map[string]string{"b": "", "c": "2"},
},
},
},
{
descr: "big body commits",
steps: []step{
{
msg: "first commit, single line but with newline\n",
},
{
msg: "second commit, single line but with two newlines\n\n",
msgHead: "second commit, single line but with two newlines\n\n",
},
{
msg: "third commit, multi-line with one newline\nanother line!",
msgHead: "third commit, multi-line with one newline\n\n",
},
{
msg: "fourth commit, multi-line with two newlines\n\nanother line!",
msgHead: "fourth commit, multi-line with two newlines\n\n",
},
},
},
}
for _, test := range testCases {
t.Run(test.descr, func(t *testing.T) {
h := newHarness(t)
for _, step := range test.steps {
h.stage(step.tree)
account := h.cfg.Accounts[0]
commit, hash := h.changeCommit(step.msg, account.ID, h.sig)
if err := h.repo.VerifyCommit(MainRefName, hash); err != nil {
t.Fatalf("could not verify hash %v: %v", hash, err)
}
commitObj, err := h.repo.GitRepo.CommitObject(hash)
if err != nil {
t.Fatalf("failed to retrieve commit %v: %v", hash, err)
} else if step.msgHead == "" {
step.msgHead = strings.TrimSpace(step.msg) + "\n\n"
}
if !strings.HasPrefix(commitObj.Message, step.msgHead) {
t.Fatalf("commit message %q does not start with expected head %q", commitObj.Message, step.msgHead)
}
var actualCommit Commit
if err := actualCommit.UnmarshalText([]byte(commitObj.Message)); err != nil {
t.Fatalf("error unmarshaling commit body: %v", err)
} else if !reflect.DeepEqual(actualCommit, commit) {
t.Fatalf("returned change commit:\n%s\ndoes not match actual one:\n%s",
spew.Sdump(commit), spew.Sdump(actualCommit))
}
}
})
}
}

View File

@ -2,112 +2,13 @@ package dehub
import ( import (
"dehub/sigcred" "dehub/sigcred"
"reflect"
"strings"
"testing" "testing"
"github.com/davecgh/go-spew/spew"
"gopkg.in/src-d/go-git.v4" "gopkg.in/src-d/go-git.v4"
"gopkg.in/src-d/go-git.v4/plumbing" "gopkg.in/src-d/go-git.v4/plumbing"
yaml "gopkg.in/yaml.v2" yaml "gopkg.in/yaml.v2"
) )
func TestChangeCommitVerify(t *testing.T) {
type step struct {
msg string
msgHead string // defaults to msg
tree map[string]string
}
testCases := []struct {
descr string
steps []step
}{
{
descr: "single commit",
steps: []step{
{
msg: "first commit",
tree: map[string]string{"a": "0", "b": "1"},
},
},
},
{
descr: "multiple commits",
steps: []step{
{
msg: "first commit",
tree: map[string]string{"a": "0", "b": "1"},
},
{
msg: "second commit, changing a",
tree: map[string]string{"a": "1"},
},
{
msg: "third commit, empty",
},
{
msg: "fourth commit, adding c, removing b",
tree: map[string]string{"b": "", "c": "2"},
},
},
},
{
descr: "big body commits",
steps: []step{
{
msg: "first commit, single line but with newline\n",
},
{
msg: "second commit, single line but with two newlines\n\n",
msgHead: "second commit, single line but with two newlines\n\n",
},
{
msg: "third commit, multi-line with one newline\nanother line!",
msgHead: "third commit, multi-line with one newline\n\n",
},
{
msg: "fourth commit, multi-line with two newlines\n\nanother line!",
msgHead: "fourth commit, multi-line with two newlines\n\n",
},
},
},
}
for _, test := range testCases {
t.Run(test.descr, func(t *testing.T) {
h := newHarness(t)
for _, step := range test.steps {
h.stage(step.tree)
account := h.cfg.Accounts[0]
changeCommit, hash := h.changeCommit(step.msg, account.ID, h.sig)
if err := h.repo.VerifyChangeCommit(MainRefName, hash); err != nil {
t.Fatalf("could not verify hash %v: %v", hash, err)
}
commit, err := h.repo.GitRepo.CommitObject(hash)
if err != nil {
t.Fatalf("failed to retrieve commit %v: %v", hash, err)
} else if step.msgHead == "" {
step.msgHead = strings.TrimSpace(step.msg) + "\n\n"
}
if !strings.HasPrefix(commit.Message, step.msgHead) {
t.Fatalf("commit message %q does not start with expected head %q", commit.Message, step.msgHead)
}
var actualChangeCommit ChangeCommit
if err := actualChangeCommit.UnmarshalText([]byte(commit.Message)); err != nil {
t.Fatalf("error unmarshaling commit body: %v", err)
} else if !reflect.DeepEqual(actualChangeCommit, changeCommit) {
t.Fatalf("returned change commit:\n%s\ndoes not match actual one:\n%s",
spew.Sdump(changeCommit), spew.Sdump(actualChangeCommit))
}
}
})
}
}
func TestConfigChange(t *testing.T) { func TestConfigChange(t *testing.T) {
h := newHarness(t) h := newHarness(t)
@ -137,7 +38,7 @@ func TestConfigChange(t *testing.T) {
h.stage(map[string]string{ConfigPath: string(cfgBody)}) h.stage(map[string]string{ConfigPath: string(cfgBody)})
_, badHash := h.changeCommit("add toot user", h.cfg.Accounts[1].ID, newSig) _, badHash := h.changeCommit("add toot user", h.cfg.Accounts[1].ID, newSig)
if err := h.repo.VerifyChangeCommit(MainRefName, badHash); err == nil { if err := h.repo.VerifyCommit(MainRefName, badHash); err == nil {
t.Fatal("toot user shouldn't be able to add itself to config") t.Fatal("toot user shouldn't be able to add itself to config")
} }
h.reset(hash, git.HardReset) h.reset(hash, git.HardReset)
@ -153,7 +54,7 @@ func TestConfigChange(t *testing.T) {
hashes = append(hashes, hash) hashes = append(hashes, hash)
for i, hash := range hashes { for i, hash := range hashes {
if err := h.repo.VerifyChangeCommit(MainRefName, hash); err != nil { if err := h.repo.VerifyCommit(MainRefName, hash); err != nil {
t.Fatalf("commit %d (%v) should have been verified but wasn't: %v", i, hash, err) t.Fatalf("commit %d (%v) should have been verified but wasn't: %v", i, hash, err)
} }
} }

View File

@ -44,7 +44,7 @@ func (r *Repo) loadConfig(fs fs.FS) (Config, error) {
// LoadConfig loads the Config object from the HEAD of the repo, or directly // LoadConfig loads the Config object from the HEAD of the repo, or directly
// from the filesystem if there is no HEAD yet. // from the filesystem if there is no HEAD yet.
func (r *Repo) LoadConfig() (Config, error) { func (r *Repo) LoadConfig() (Config, error) {
headFS, err := r.headOrRawFS() headFS, err := r.HeadFS()
if err != nil { if err != nil {
return Config{}, fmt.Errorf("error retrieving repo HEAD: %w", err) return Config{}, fmt.Errorf("error retrieving repo HEAD: %w", err)
} }

View File

@ -158,9 +158,9 @@ func (r *Repo) head() (*object.Commit, *object.Tree, error) {
return headCommit, headTree, nil return headCommit, headTree, nil
} }
// headOrRawFS returns an FS based on the HEAD commit, or if there is no HEAD // HeadFS returns an FS based on the HEAD commit, or if there is no HEAD commit
// commit (it's an empty repo) an FS based on the raw filesystem. // (it's an empty repo) an FS based on the raw filesystem.
func (r *Repo) headOrRawFS() (fs.FS, error) { func (r *Repo) HeadFS() (fs.FS, error) {
_, headTree, err := r.head() _, headTree, err := r.head()
if errors.Is(err, plumbing.ErrReferenceNotFound) { if errors.Is(err, plumbing.ErrReferenceNotFound) {
bfs, err := r.billyFilesystem() bfs, err := r.billyFilesystem()

View File

@ -107,18 +107,24 @@ func (h *harness) stage(tree map[string]string) {
} }
} }
func (h *harness) changeCommit(msg, accountID string, sig sigcred.SignifierInterface) (ChangeCommit, plumbing.Hash) { func (h *harness) changeCommit(msg, accountID string, sig sigcred.SignifierInterface) (Commit, plumbing.Hash) {
tc, err := h.repo.NewChangeCommit(msg, accountID, sig) commit, err := h.repo.NewCommitChange(msg)
if err != nil { if err != nil {
h.t.Fatalf("failed to make ChangeCommit: %v", err) h.t.Fatalf("failed to create CommitChange: %v", err)
} }
hash, err := h.repo.Commit(tc, accountID) if sig != nil {
if commit, err = h.repo.AccreditCommit(commit, accountID, sig); err != nil {
h.t.Fatalf("failed to accredit commit: %v", err)
}
}
hash, err := h.repo.Commit(commit, accountID)
if err != nil { if err != nil {
h.t.Fatalf("failed to commit ChangeCommit: %v", err) h.t.Fatalf("failed to commit ChangeCommit: %v", err)
} }
return tc, hash return commit, hash
} }
func (h *harness) reset(to plumbing.Hash, mode git.ResetMode) { func (h *harness) reset(to plumbing.Hash, mode git.ResetMode) {
@ -193,9 +199,9 @@ access_controls:
_, hash1 := harness.changeCommit("ain't no laws", "toot", nil) _, hash1 := harness.changeCommit("ain't no laws", "toot", nil)
// verifying the first should work, but not the second. // verifying the first should work, but not the second.
if err := harness.repo.VerifyChangeCommit(MainRefName, hash0); err != nil { if err := harness.repo.VerifyCommit(MainRefName, hash0); err != nil {
t.Fatalf("first commit %q should be verifiable, but got: %v", hash0, err) t.Fatalf("first commit %q should be verifiable, but got: %v", hash0, err)
} else if err := harness.repo.VerifyChangeCommit(MainRefName, hash1); err == nil { } else if err := harness.repo.VerifyCommit(MainRefName, hash1); err == nil {
t.Fatalf("second commit %q should not have been verified", hash1) t.Fatalf("second commit %q should not have been verified", hash1)
} }
@ -212,7 +218,7 @@ accounts:
path: ".dehub/root.asc" path: ".dehub/root.asc"
`}) `})
_, hash2 := harness.changeCommit("Fix the config!", "root", harness.sig) _, hash2 := harness.changeCommit("Fix the config!", "root", harness.sig)
if err := harness.repo.VerifyChangeCommit(MainRefName, hash2); err != nil { if err := harness.repo.VerifyCommit(MainRefName, hash2); err != nil {
t.Fatalf("config fix commit %q should be verifiable, but got: %v", hash2, err) t.Fatalf("config fix commit %q should be verifiable, but got: %v", hash2, err)
} }
} }