Implement comment commits

---
type: change
message: |-
  Implement comment commits

  This ended up requiring a little refactoring here and there, as well as a fix
  for typeobj after running into a bug. Also made the commit message unmarshaling
  a bit more durable, after running into a case where the error message that gets
  produced for an invalid commit is not very helpful.

  Currently comment commits are not very well tested. The tests for commit objects
  in general need to be rethought completely, so they better test the hashes as
  well as message head parsing.
change_hash: AO7Cnfcgbuusch969HHVuo8OJCKh+uuiof+qhnmiD5xo
credentials:
- type: pgp_signature
  pub_key_id: 95C46FA6A41148AC
  body: iQIzBAABAgAdFiEEJ6tQKp6olvZKJ0lwlcRvpqQRSKwFAl52W+EACgkQlcRvpqQRSKzFVhAAj3cscGnlSHc9oP20ddKsro1lnJ53Ha22JKKJ3aO8Z33mIX4aXdVTheLFy8gvmtzeVThBzfshCGt3REHD9rYaSfUEUpd3iDwDBYu907h7fDfx9OhxsZhCcbn3k2si9xe2ESDWE4nFuzKq0W9C8HYBhtTDYV6pW3AmJTlLfLeSoH86nemDAsZ/JlwoIXqYbT63h2Y7FZbnPXHzPTo6ZCbf3u6gdOUDG8vifWTXCbxuueSutTYTJ5vHKejhz/WB21GJhvuZnCVrim0T0mVyyE2evCci7SP249tGj2bmSDF/vVD4aurKsyyd8l6Q38MSGYnsNmAMgZPmSzFTrqmY9bjJ8sir8mv0Pn85/ixWNGHVkZ/A9i515YYGNXQfRaHbHU72Yb5mlPWJQhywffhWLxP7HoGn27P2UJ6hh4f6KMiV26nH/4meV6Y2o0623fZzoETHVVp6oks6dMCcYRb5FF54Ogg/uBPeBOvtiIfy8hTwBqGVrzg7Dm2Sl0ystBuN8ZKrxqa24Wl+MukA5bLH/J9himDwl9XQtL+BYyb4vJkJcNqaGkfKyGU4q3Ggt38X71wGMCmYtWMZ9CIx5VC+OJC5B7C1fUOxG67wKG2X9ShY9MVfCtjrdAtnnECjalyPKXwSkbqB1xsdujxmnXhazYJ3Bf//QXc7tJuSuiG6jMjS24I=
  account: mediocregopher
This commit is contained in:
mediocregopher 2020-03-21 12:24:38 -06:00
parent b74186446e
commit eeb74ea22b
8 changed files with 110 additions and 11 deletions

View File

@ -6,7 +6,6 @@ set, only a sequence of milestones and the requirements to hit them.
## Milestone: Be able to add other developers to the project
* Thread commits (comments)
* Coalesce command
* Fast-forward perms on branches
* Authorship in commit messages

View File

@ -126,5 +126,29 @@ func cmdCommit(ctx context.Context, cmd *dcmd.Cmd) {
},
)
cmd.SubCmd("comment", "Commit a comment to a branch",
func(ctx context.Context, cmd *dcmd.Cmd) {
flag := cmd.FlagSet()
msg := flag.String("msg", "", "Comment message")
cmd.Run(func() (context.Context, error) {
if *msg == "" {
var err error
if *msg, err = tmpFileMsg(); err != nil {
return nil, fmt.Errorf("collecting comment message from user: %w", err)
} else if *msg == "" {
return nil, errors.New("empty comment message, not doing anything")
}
}
commit, err := repo.NewCommitComment(*msg)
if err != nil {
return nil, fmt.Errorf("constructing comment commit: %w", err)
}
return nil, accreditAndCommit(commit)
})
},
)
cmd.Run(body)
}

View File

@ -29,7 +29,7 @@ func tmpFileMsg() (string, error) {
tmpBody := bytes.NewBufferString(`
# Please enter the commit message for your changes. Lines starting
# Please enter the commit message for your commit. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.`)
_, err = io.Copy(tmpf, tmpBody)

View File

@ -10,7 +10,9 @@ import (
"encoding/base64"
"errors"
"fmt"
"reflect"
"sort"
"strings"
"time"
"gopkg.in/src-d/go-git.v4"
@ -59,11 +61,23 @@ func (cc CommitCommon) credAccountIDs() []string {
return s
}
func abbrevCommitMessage(msg string) string {
i := strings.Index(msg, "\n")
if i > 0 {
msg = msg[:i]
}
if len(msg) > 80 {
msg = msg[:80] + "..."
}
return msg
}
// 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"`
Credential *CommitCredential `type:"credential"`
Comment *CommitComment `type:"comment"`
Common CommitCommon `yaml:",inline"`
}
@ -133,7 +147,12 @@ func (c *Commit) UnmarshalText(msg []byte) error {
if err := yaml.Unmarshal(msgBody, c); err != nil {
return fmt.Errorf("could not unmarshal Commit message from yaml: %w", err)
} else if reflect.DeepEqual(*c, Commit{}) {
// a basic check, but worthwhile
return errors.New("commit message is malformed, could not unmarshal yaml object")
}
return nil
}

View File

@ -5,7 +5,6 @@ import (
"dehub/yamlutil"
"errors"
"fmt"
"strings"
"gopkg.in/src-d/go-git.v4/plumbing/object"
)
@ -46,11 +45,7 @@ func (r *Repo) NewCommitChange(msg string) (Commit, error) {
// MessageHead implements the method for the CommitInterface interface.
func (cc CommitChange) MessageHead(CommitCommon) (string, error) {
i := strings.Index(cc.Message, "\n")
if i > 0 {
return cc.Message[:i], nil
}
return cc.Message, nil
return abbrevCommitMessage(cc.Message), nil
}
// Hash implements the method for the CommitInterface interface.

46
commit_comment.go Normal file
View File

@ -0,0 +1,46 @@
package dehub
import (
"dehub/yamlutil"
"fmt"
"strings"
"gopkg.in/src-d/go-git.v4/plumbing/object"
)
// CommitComment describes the structure of a comment commit message.
type CommitComment struct {
Message string `yaml:"message"`
MessageHash yamlutil.Blob `yaml:"message_hash"`
}
var _ CommitInterface = CommitComment{}
// NewCommitComment constructs and returns a COmmit populated with a
// CommitComment encompassing the given message. The Credentials of the returned
// Commit will _not_ be filled in.
func (r *Repo) NewCommitComment(msg string) (Commit, error) {
cc := CommitComment{Message: msg}
var err error
if cc.MessageHash, err = cc.Hash(nil, nil); err != nil {
return Commit{}, fmt.Errorf("calculating comment hash: %w", err)
}
return Commit{Comment: &cc}, nil
}
// MessageHead implements the method for the CommitInterface interface.
func (cc CommitComment) MessageHead(common CommitCommon) (string, error) {
msgAbbrev := abbrevCommitMessage(cc.Message)
credAccounts := strings.Join(common.credAccountIDs(), ", ")
return fmt.Sprintf("Comment by %s: %s", credAccounts, msgAbbrev), nil
}
// Hash implements the method for the CommitInterface.
func (cc CommitComment) Hash(_, _ *object.Tree) ([]byte, error) {
return genCommentHash(nil, cc.Message), nil
}
// GetHash implements the method for the CommitInterface.
func (cc CommitComment) GetHash() []byte {
return cc.MessageHash
}

View File

@ -31,6 +31,12 @@ func newHashHelper(h hash.Hash) *hashHelper {
return s
}
func (s *hashHelper) sum(prefix []byte) []byte {
out := make([]byte, len(prefix), len(prefix)+s.Hash.Size())
copy(out, prefix)
return s.Hash.Sum(out)
}
func (s *hashHelper) writeUint(i uint64) {
n := binary.PutUvarint(s.varintBuf, i)
if _, err := s.Write(s.varintBuf[:n]); err != nil {
@ -64,12 +70,22 @@ func (s *hashHelper) writeTreeDiff(from, to *object.Tree) {
}
var changeHashVersion = []byte{0}
var (
changeHashVersion = []byte{0}
commentHashVersion = []byte{0}
)
// if h is nil it then defaultHashHelperAlgo will be used
func genChangeHash(h hash.Hash, msg string, from, to *object.Tree) []byte {
s := newHashHelper(h)
s.writeStr(msg)
s.writeTreeDiff(from, to)
return s.Sum(changeHashVersion)
return s.sum(changeHashVersion)
}
// if h is nil it then defaultHashHelperAlgo will be used
func genCommentHash(h hash.Hash, comment string) []byte {
s := newHashHelper(h)
s.writeStr(comment)
return s.sum(commentHashVersion)
}

View File

@ -124,7 +124,7 @@ func element(val reflect.Value) (reflect.Value, string, []int, error) {
}
}
if fieldVal.IsZero() {
if !fieldVal.IsValid() {
return reflect.Value{}, "", nil, errors.New(`no non-zero fields tagged with "type"`)
}
return fieldVal, typeTag, nonTypeFields, nil