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