2020-03-04 23:34:02 +00:00
|
|
|
package dehub
|
|
|
|
|
|
|
|
import (
|
2020-03-22 23:56:00 +00:00
|
|
|
"bytes"
|
2020-03-29 21:16:36 +00:00
|
|
|
"dehub.dev/src/dehub.git/fs"
|
|
|
|
"dehub.dev/src/dehub.git/sigcred"
|
|
|
|
"dehub.dev/src/dehub.git/yamlutil"
|
2020-03-04 23:34:02 +00:00
|
|
|
"errors"
|
normalize how git commits are interacted with, including changing VerifyComit -> VerifyCommits
---
type: change
message: |-
normalize how git commits are interacted with, including changing VerifyComit -> VerifyCommits
This commit attempts to normalize git commit interactions in order to reduce
the amount of manual `GitRepo.CommitObject`, `GitRepo.TreeObject`,
`Commit.UnmarshalText`, and `Commit.Interface` calls are done, by creating a
single structure (`GitCommit`) which holds the output of those calls, and is
only created by a single method (`GetGitCommit`), which is then used by a bunch
of other methods to expand its functionality, including implementing a range
request which can be used by verify and the pre-receive hook (though it's only
used by the hook, currently).
change_hash: AMae4PL6+jrxhn2KEGHejstcdT37Gw/jjkl/UuovHcgd
credentials:
- type: pgp_signature
pub_key_id: 95C46FA6A41148AC
body: iQIzBAABAgAdFiEEJ6tQKp6olvZKJ0lwlcRvpqQRSKwFAl5uhvoACgkQlcRvpqQRSKzJrhAAqi2LEQVTyVktfsOBv/CZmefclLLqWTChVoeIZt2EAGDDGygmrx88hI0SEAviOzPMn0kiZFDeY5k7ICJMhJ9RVDU9WjH7fbOboMJW19rVhx6Ke/M2ERtrT0OFLRmFVJVDM0P8SEheQvR3HE/iiypBICVCtp+meHEq9mOJWZlZnoCqMaulAy/Nnq4N1VD0yPPlr16+yxMqedKHcgKbcH8K61ltNAjXDT+tCWwCq1huA5MVSuTm5EwqIeKPN6JKgwATv8Ku2GhYZWHSGUwecP1J3x2XTDPeChCQVDpC232Pxwk8z/D36F3J/XOfkdl0QYQ077xL1IJfYOnuuHir47CokDf3G0XCQnJ/+X4pZdtP387rc045o/2bhUi2U4eJ5HgS7Hvyi6EApT0Czv7SeJePTvdnRUYse8ZYuIwYXj5GWWxnbKQzLpyjcHdQc2a3B3RN84zXqqAOS6ObFrFPZQIfz2rfQojZN8kvcmUvYhJXSaT65XmqFjyJ4n6grrEnK/N+MfbnpzyF/yvlzxWPqGFQOQj9meosbTAdgZbmdwYqa5r1ee8DmlkzNJJxze96h503a733yciN8Ef4hGZNlRV6YFegkK/cCgKaA4NCEALKb1t0Uri5gnPldXk4HsPF+23GANbE7mjytY8ra3fhXG4VhaFt/WsLg3Bu7djQ0H74y+g=
account: mediocregopher
2020-03-15 19:50:24 +00:00
|
|
|
"fmt"
|
2020-03-22 23:56:00 +00:00
|
|
|
"sort"
|
|
|
|
"strings"
|
2020-03-04 23:34:02 +00:00
|
|
|
|
2020-03-22 23:56:00 +00:00
|
|
|
"gopkg.in/src-d/go-git.v4/plumbing"
|
2020-03-04 23:34:02 +00:00
|
|
|
"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) {
|
normalize how git commits are interacted with, including changing VerifyComit -> VerifyCommits
---
type: change
message: |-
normalize how git commits are interacted with, including changing VerifyComit -> VerifyCommits
This commit attempts to normalize git commit interactions in order to reduce
the amount of manual `GitRepo.CommitObject`, `GitRepo.TreeObject`,
`Commit.UnmarshalText`, and `Commit.Interface` calls are done, by creating a
single structure (`GitCommit`) which holds the output of those calls, and is
only created by a single method (`GetGitCommit`), which is then used by a bunch
of other methods to expand its functionality, including implementing a range
request which can be used by verify and the pre-receive hook (though it's only
used by the hook, currently).
change_hash: AMae4PL6+jrxhn2KEGHejstcdT37Gw/jjkl/UuovHcgd
credentials:
- type: pgp_signature
pub_key_id: 95C46FA6A41148AC
body: iQIzBAABAgAdFiEEJ6tQKp6olvZKJ0lwlcRvpqQRSKwFAl5uhvoACgkQlcRvpqQRSKzJrhAAqi2LEQVTyVktfsOBv/CZmefclLLqWTChVoeIZt2EAGDDGygmrx88hI0SEAviOzPMn0kiZFDeY5k7ICJMhJ9RVDU9WjH7fbOboMJW19rVhx6Ke/M2ERtrT0OFLRmFVJVDM0P8SEheQvR3HE/iiypBICVCtp+meHEq9mOJWZlZnoCqMaulAy/Nnq4N1VD0yPPlr16+yxMqedKHcgKbcH8K61ltNAjXDT+tCWwCq1huA5MVSuTm5EwqIeKPN6JKgwATv8Ku2GhYZWHSGUwecP1J3x2XTDPeChCQVDpC232Pxwk8z/D36F3J/XOfkdl0QYQ077xL1IJfYOnuuHir47CokDf3G0XCQnJ/+X4pZdtP387rc045o/2bhUi2U4eJ5HgS7Hvyi6EApT0Czv7SeJePTvdnRUYse8ZYuIwYXj5GWWxnbKQzLpyjcHdQc2a3B3RN84zXqqAOS6ObFrFPZQIfz2rfQojZN8kvcmUvYhJXSaT65XmqFjyJ4n6grrEnK/N+MfbnpzyF/yvlzxWPqGFQOQj9meosbTAdgZbmdwYqa5r1ee8DmlkzNJJxze96h503a733yciN8Ef4hGZNlRV6YFegkK/cCgKaA4NCEALKb1t0Uri5gnPldXk4HsPF+23GANbE7mjytY8ra3fhXG4VhaFt/WsLg3Bu7djQ0H74y+g=
account: mediocregopher
2020-03-15 19:50:24 +00:00
|
|
|
headTree := new(object.Tree)
|
2020-03-22 23:54:25 +00:00
|
|
|
if head, err := r.GetGitHead(); err != nil && !errors.Is(err, ErrHeadIsZero) {
|
normalize how git commits are interacted with, including changing VerifyComit -> VerifyCommits
---
type: change
message: |-
normalize how git commits are interacted with, including changing VerifyComit -> VerifyCommits
This commit attempts to normalize git commit interactions in order to reduce
the amount of manual `GitRepo.CommitObject`, `GitRepo.TreeObject`,
`Commit.UnmarshalText`, and `Commit.Interface` calls are done, by creating a
single structure (`GitCommit`) which holds the output of those calls, and is
only created by a single method (`GetGitCommit`), which is then used by a bunch
of other methods to expand its functionality, including implementing a range
request which can be used by verify and the pre-receive hook (though it's only
used by the hook, currently).
change_hash: AMae4PL6+jrxhn2KEGHejstcdT37Gw/jjkl/UuovHcgd
credentials:
- type: pgp_signature
pub_key_id: 95C46FA6A41148AC
body: iQIzBAABAgAdFiEEJ6tQKp6olvZKJ0lwlcRvpqQRSKwFAl5uhvoACgkQlcRvpqQRSKzJrhAAqi2LEQVTyVktfsOBv/CZmefclLLqWTChVoeIZt2EAGDDGygmrx88hI0SEAviOzPMn0kiZFDeY5k7ICJMhJ9RVDU9WjH7fbOboMJW19rVhx6Ke/M2ERtrT0OFLRmFVJVDM0P8SEheQvR3HE/iiypBICVCtp+meHEq9mOJWZlZnoCqMaulAy/Nnq4N1VD0yPPlr16+yxMqedKHcgKbcH8K61ltNAjXDT+tCWwCq1huA5MVSuTm5EwqIeKPN6JKgwATv8Ku2GhYZWHSGUwecP1J3x2XTDPeChCQVDpC232Pxwk8z/D36F3J/XOfkdl0QYQ077xL1IJfYOnuuHir47CokDf3G0XCQnJ/+X4pZdtP387rc045o/2bhUi2U4eJ5HgS7Hvyi6EApT0Czv7SeJePTvdnRUYse8ZYuIwYXj5GWWxnbKQzLpyjcHdQc2a3B3RN84zXqqAOS6ObFrFPZQIfz2rfQojZN8kvcmUvYhJXSaT65XmqFjyJ4n6grrEnK/N+MfbnpzyF/yvlzxWPqGFQOQj9meosbTAdgZbmdwYqa5r1ee8DmlkzNJJxze96h503a733yciN8Ef4hGZNlRV6YFegkK/cCgKaA4NCEALKb1t0Uri5gnPldXk4HsPF+23GANbE7mjytY8ra3fhXG4VhaFt/WsLg3Bu7djQ0H74y+g=
account: mediocregopher
2020-03-15 19:50:24 +00:00
|
|
|
return Commit{}, fmt.Errorf("getting HEAD commit: %w", err)
|
|
|
|
} else if err == nil {
|
|
|
|
headTree = head.GitTree
|
2020-03-04 23:34:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
_, 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.
|
2020-03-21 17:22:15 +00:00
|
|
|
func (cc CommitChange) MessageHead(CommitCommon) (string, error) {
|
2020-03-21 18:24:38 +00:00
|
|
|
return abbrevCommitMessage(cc.Message), nil
|
2020-03-04 23:34:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
2020-03-22 23:56:00 +00:00
|
|
|
|
|
|
|
// CombineCommitChanges takes all changes in the given range and combines them
|
|
|
|
// into a single change Commit. The resulting Commit will have the same message
|
|
|
|
// as the latest change commit in the range, and will contain all Credentials
|
|
|
|
// for the resulting change hash that it finds in the range as well.
|
|
|
|
//
|
|
|
|
// The combined commit is then committed to the repo with the given revision as
|
|
|
|
// its parent. If the diff between start/end and onto/end is different then this
|
|
|
|
// will return an error, as the change hash which has been accredited in
|
|
|
|
// start/end will be different than the one which needs to be accredited in
|
|
|
|
// onto/end.
|
2020-03-23 22:49:01 +00:00
|
|
|
func (r *Repo) CombineCommitChanges(commits []GitCommit, onto plumbing.ReferenceName) (GitCommit, error) {
|
2020-03-23 23:15:44 +00:00
|
|
|
info, err := r.changeRangeInfo(commits)
|
|
|
|
if err != nil {
|
|
|
|
return GitCommit{}, err
|
2020-03-23 22:49:01 +00:00
|
|
|
}
|
|
|
|
|
2020-03-23 23:15:44 +00:00
|
|
|
authors := make([]string, 0, len(info.authors))
|
|
|
|
for author := range info.authors {
|
2020-03-23 22:49:01 +00:00
|
|
|
authors = append(authors, author)
|
2020-03-22 23:56:00 +00:00
|
|
|
}
|
2020-03-23 22:49:01 +00:00
|
|
|
sort.Strings(authors)
|
2020-03-22 23:56:00 +00:00
|
|
|
|
|
|
|
ontoBranchName, err := r.ReferenceToBranchName(onto)
|
|
|
|
if err != nil {
|
|
|
|
return GitCommit{}, 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 := r.GetGitRevision(plumbing.Revision(onto))
|
|
|
|
if err != nil {
|
|
|
|
return GitCommit{}, fmt.Errorf("resolving revision %q: %w", onto, err)
|
|
|
|
}
|
|
|
|
ontoTree := ontoCommit.GitTree
|
2020-03-23 23:15:44 +00:00
|
|
|
ontoEndChangeHash := genChangeHash(nil, info.msg, ontoTree, info.endTree)
|
|
|
|
if !bytes.Equal(ontoEndChangeHash, info.changeHash) {
|
2020-03-22 23:56:00 +00:00
|
|
|
// TODO figure out what files to show as being the "problem files" in
|
|
|
|
// the error message
|
|
|
|
return GitCommit{}, fmt.Errorf("rebasing onto %q would cause the change hash to change, aborting combine", onto)
|
|
|
|
}
|
|
|
|
|
|
|
|
var creds []sigcred.Credential
|
2020-03-23 22:49:01 +00:00
|
|
|
for _, commit := range commits {
|
2020-03-23 23:15:44 +00:00
|
|
|
if bytes.Equal(commit.Interface.GetHash(), info.changeHash) {
|
2020-03-22 23:56:00 +00:00
|
|
|
creds = append(creds, commit.Commit.Common.Credentials...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// this is mostly to make tests easier
|
|
|
|
sort.Slice(creds, func(i, j int) bool {
|
|
|
|
return creds[i].AccountID < creds[j].AccountID
|
|
|
|
})
|
|
|
|
|
|
|
|
commit := Commit{
|
|
|
|
Change: &CommitChange{
|
2020-03-23 23:15:44 +00:00
|
|
|
Message: info.msg,
|
|
|
|
ChangeHash: info.changeHash,
|
2020-03-22 23:56:00 +00:00
|
|
|
},
|
|
|
|
Common: CommitCommon{Credentials: creds},
|
|
|
|
}
|
|
|
|
|
|
|
|
gitCommit, err := r.CommitBare(CommitBareParams{
|
|
|
|
Commit: commit,
|
2020-03-23 22:49:01 +00:00
|
|
|
Author: strings.Join(authors, ","),
|
2020-03-22 23:56:00 +00:00
|
|
|
ParentHash: ontoCommit.GitCommit.Hash,
|
2020-03-23 23:15:44 +00:00
|
|
|
GitTree: info.endTree,
|
2020-03-22 23:56:00 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return GitCommit{}, fmt.Errorf("storing commit: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// set the onto branch to this new commit
|
|
|
|
newHeadRef := plumbing.NewHashReference(ontoBranchName, gitCommit.GitCommit.Hash)
|
|
|
|
if err := r.GitRepo.Storer.SetReference(newHeadRef); err != nil {
|
|
|
|
return GitCommit{}, fmt.Errorf("setting reference %q to new commit hash %q: %w",
|
|
|
|
ontoBranchName, gitCommit.GitCommit.Hash, err)
|
|
|
|
}
|
|
|
|
return gitCommit, nil
|
|
|
|
}
|