From eeb74ea22be3199d9e92c9147b557a22af9061e5 Mon Sep 17 00:00:00 2001 From: mediocregopher <> Date: Sat, 21 Mar 2020 12:24:38 -0600 Subject: [PATCH] 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 --- ROADMAP.md | 1 - cmd/dehub/cmd_commit.go | 24 ++++++++++++++++++++ cmd/dehub/tmp_file.go | 2 +- commit.go | 19 ++++++++++++++++ commit_change.go | 7 +----- commit_comment.go | 46 +++++++++++++++++++++++++++++++++++++++ change_hash.go => hash.go | 20 +++++++++++++++-- typeobj/typeobj.go | 2 +- 8 files changed, 110 insertions(+), 11 deletions(-) create mode 100644 commit_comment.go rename change_hash.go => hash.go (76%) diff --git a/ROADMAP.md b/ROADMAP.md index 725a636..d801ac3 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -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 diff --git a/cmd/dehub/cmd_commit.go b/cmd/dehub/cmd_commit.go index 2810efd..eb2b19f 100644 --- a/cmd/dehub/cmd_commit.go +++ b/cmd/dehub/cmd_commit.go @@ -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) } diff --git a/cmd/dehub/tmp_file.go b/cmd/dehub/tmp_file.go index 0354f8f..51437e5 100644 --- a/cmd/dehub/tmp_file.go +++ b/cmd/dehub/tmp_file.go @@ -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) diff --git a/commit.go b/commit.go index 8b6611f..7808d95 100644 --- a/commit.go +++ b/commit.go @@ -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 } diff --git a/commit_change.go b/commit_change.go index 7608184..341639c 100644 --- a/commit_change.go +++ b/commit_change.go @@ -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. diff --git a/commit_comment.go b/commit_comment.go new file mode 100644 index 0000000..920855d --- /dev/null +++ b/commit_comment.go @@ -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 +} diff --git a/change_hash.go b/hash.go similarity index 76% rename from change_hash.go rename to hash.go index 8c4feb0..d969242 100644 --- a/change_hash.go +++ b/hash.go @@ -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) } diff --git a/typeobj/typeobj.go b/typeobj/typeobj.go index fed9225..debe79a 100644 --- a/typeobj/typeobj.go +++ b/typeobj/typeobj.go @@ -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