2020-04-26 20:23:03 +00:00
|
|
|
package dehub
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"sort"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"dehub.dev/src/dehub.git/fs"
|
|
|
|
"dehub.dev/src/dehub.git/sigcred"
|
|
|
|
|
|
|
|
"gopkg.in/src-d/go-git.v4/plumbing"
|
|
|
|
"gopkg.in/src-d/go-git.v4/plumbing/object"
|
|
|
|
)
|
|
|
|
|
|
|
|
// PayloadChange describes the structure of a change payload.
|
|
|
|
type PayloadChange struct {
|
|
|
|
Description string `yaml:"description"`
|
|
|
|
|
|
|
|
// LegacyMessage is no longer used, use Description instead
|
|
|
|
LegacyMessage string `yaml:"message,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
var _ Payload = PayloadChange{}
|
|
|
|
|
|
|
|
// NewPayloadChange constructs a PayloadUnion populated with a PayloadChange
|
|
|
|
// encompassing the currently staged file changes. The Credentials of the
|
|
|
|
// returned PayloadUnion will _not_ be filled in.
|
|
|
|
func (proj *Project) NewPayloadChange(description string) (PayloadUnion, error) {
|
|
|
|
headTree := new(object.Tree)
|
|
|
|
if head, err := proj.GetHeadCommit(); err != nil && !errors.Is(err, ErrHeadIsZero) {
|
|
|
|
return PayloadUnion{}, fmt.Errorf("getting HEAD commit: %w", err)
|
|
|
|
} else if err == nil {
|
|
|
|
headTree = head.TreeObject
|
|
|
|
}
|
|
|
|
|
|
|
|
_, stagedTree, err := fs.FromStagedChangesTree(proj.GitRepo)
|
|
|
|
if err != nil {
|
|
|
|
return PayloadUnion{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
changedFiles, err := ChangedFilesBetweenTrees(headTree, stagedTree)
|
|
|
|
if err != nil {
|
|
|
|
return PayloadUnion{}, fmt.Errorf("calculating diff between HEAD and staged changes: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
payCh := PayloadChange{Description: description}
|
|
|
|
fingerprint, err := payCh.Fingerprint(changedFiles)
|
|
|
|
if err != nil {
|
|
|
|
return PayloadUnion{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return PayloadUnion{
|
|
|
|
Change: &payCh,
|
|
|
|
Common: PayloadCommon{Fingerprint: fingerprint},
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// MessageHead implements the method for the Payload interface.
|
2020-05-12 04:09:01 +00:00
|
|
|
func (payCh PayloadChange) MessageHead(PayloadCommon) string {
|
|
|
|
return payCh.Description
|
2020-04-26 20:23:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Fingerprint implements the method for the Payload interface.
|
|
|
|
func (payCh PayloadChange) Fingerprint(changedFiles []ChangedFile) ([]byte, error) {
|
|
|
|
return genChangeFingerprint(nil, payCh.Description, changedFiles), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
|
|
|
func (payCh *PayloadChange) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|
|
|
var wrap struct {
|
|
|
|
Inner PayloadChange `yaml:",inline"`
|
|
|
|
}
|
|
|
|
if err := unmarshal(&wrap); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
*payCh = wrap.Inner
|
|
|
|
if payCh.LegacyMessage != "" {
|
|
|
|
payCh.Description = payCh.LegacyMessage
|
|
|
|
payCh.LegacyMessage = ""
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// CombinePayloadChanges takes all changes in the given range, combines them
|
|
|
|
// into a single PayloadChange, and commits it. The resulting payload will have
|
|
|
|
// the same message as the latest change payload in the range. If the
|
|
|
|
// fingerprint of the PayloadChange produced by this method has any matching
|
|
|
|
// Credentials in the range, those will be included in the payload as well.
|
|
|
|
//
|
|
|
|
// The combined commit is committed to the project with the given revision as
|
|
|
|
// its parent. If the diff across the given range and the diff from onto to the
|
|
|
|
// end of the range are different then this will return an error.
|
|
|
|
func (proj *Project) CombinePayloadChanges(commits []Commit, onto plumbing.ReferenceName) (Commit, error) {
|
|
|
|
info, err := proj.changeRangeInfo(commits)
|
|
|
|
if err != nil {
|
|
|
|
return Commit{}, err
|
|
|
|
}
|
|
|
|
|
2020-05-14 04:19:04 +00:00
|
|
|
commitsFingerprint, err := info.changeFingerprint(info.changeDescription)
|
|
|
|
if err != nil {
|
|
|
|
return Commit{}, err
|
|
|
|
}
|
|
|
|
|
2020-04-26 20:23:03 +00:00
|
|
|
authors := make([]string, 0, len(info.authors))
|
|
|
|
for author := range info.authors {
|
|
|
|
authors = append(authors, author)
|
|
|
|
}
|
|
|
|
sort.Strings(authors)
|
|
|
|
|
|
|
|
ontoBranchName, err := proj.ReferenceToBranchName(onto)
|
|
|
|
if err != nil {
|
|
|
|
return Commit{}, fmt.Errorf("resolving %q into a branch name: %w", onto, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// now determine the change hash from onto->end, to ensure that it remains
|
|
|
|
// the same as from start->end
|
|
|
|
ontoCommit, err := proj.GetCommitByRevision(plumbing.Revision(onto))
|
|
|
|
if err != nil {
|
|
|
|
return Commit{}, fmt.Errorf("resolving revision %q: %w", onto, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
ontoEndChangedFiles, err := ChangedFilesBetweenTrees(ontoCommit.TreeObject, info.endTree)
|
|
|
|
if err != nil {
|
|
|
|
return Commit{}, fmt.Errorf("calculating file changes between %q and %q: %w",
|
|
|
|
ontoCommit.Hash, commits[len(commits)-1].Hash, err)
|
|
|
|
}
|
|
|
|
|
2020-05-14 04:19:04 +00:00
|
|
|
ontoEndChangeFingerprint := genChangeFingerprint(nil, info.changeDescription, ontoEndChangedFiles)
|
|
|
|
if !bytes.Equal(ontoEndChangeFingerprint, commitsFingerprint) {
|
2020-04-26 20:23:03 +00:00
|
|
|
// TODO figure out what files to show as being the "problem files" in
|
|
|
|
// the error message
|
|
|
|
return Commit{}, fmt.Errorf("combining onto %q would produce a different change fingerprint, aborting combine", onto.Short())
|
|
|
|
}
|
|
|
|
|
|
|
|
var creds []sigcred.CredentialUnion
|
|
|
|
for _, commit := range commits {
|
2020-05-14 04:19:04 +00:00
|
|
|
if bytes.Equal(commit.Payload.Common.Fingerprint, commitsFingerprint) {
|
2020-04-26 20:23:03 +00:00
|
|
|
creds = append(creds, commit.Payload.Common.Credentials...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// this is mostly to make tests easier
|
|
|
|
sort.Slice(creds, func(i, j int) bool {
|
|
|
|
return creds[i].AccountID < creds[j].AccountID
|
|
|
|
})
|
|
|
|
|
|
|
|
payUn := PayloadUnion{
|
|
|
|
Change: &PayloadChange{
|
2020-05-14 04:19:04 +00:00
|
|
|
Description: info.changeDescription,
|
2020-04-26 20:23:03 +00:00
|
|
|
},
|
|
|
|
Common: PayloadCommon{
|
2020-05-14 04:19:04 +00:00
|
|
|
Fingerprint: commitsFingerprint,
|
2020-04-26 20:23:03 +00:00
|
|
|
Credentials: creds,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
commit, err := proj.CommitDirect(CommitDirectParams{
|
|
|
|
PayloadUnion: payUn,
|
|
|
|
Author: strings.Join(authors, ","),
|
|
|
|
ParentHash: ontoCommit.Hash,
|
|
|
|
GitTree: info.endTree,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return Commit{}, fmt.Errorf("storing commit: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// set the onto branch to this new commit
|
|
|
|
newHeadRef := plumbing.NewHashReference(ontoBranchName, commit.Hash)
|
|
|
|
if err := proj.GitRepo.Storer.SetReference(newHeadRef); err != nil {
|
|
|
|
return Commit{}, fmt.Errorf("setting reference %q to new commit hash %q: %w",
|
|
|
|
ontoBranchName, commit.Hash, err)
|
|
|
|
}
|
|
|
|
return commit, nil
|
|
|
|
}
|