add ability to credential range of change commits

---
type: change
message: |-
  add ability to credential range of change commits

  previously it wasn't possible to generate a credential for the changes of a
  range of commits, which was not super helpful. Now it is!
change_hash: AG8eGxGH30bZYieD7uYtiUtpQL7u357SKPE0rvbk8LpR
credentials:
- type: pgp_signature
  pub_key_id: 95C46FA6A41148AC
  body: iQIzBAABAgAdFiEEJ6tQKp6olvZKJ0lwlcRvpqQRSKwFAl55QyAACgkQlcRvpqQRSKxU6g/+Lu4zVh8dz7ziZCYyOEwn7jrQ+L0UXMUmaHx6sL6eOd6P3m583etkl3EQbSCoTUvEGTcuxKm1bSb1hcd6NNjRaol6IpTaQtm71NQTbfUo7jZi8aq3P9o+tXr2UPqcht1nxcAXQEoOoK7MO2mVj6weWRQ40gFo5xwI/eO8KygZc3uXKSlWswvgs9jTDx9zyTDCMI9JX9S6Ino24K1RIRuZIAfOhUIRLq3vi8JmymhpjMAeyOj/gvBNfwkbUL0zGrikPr4VJbHtZDHsDZUnOB0kM7xZngq1jUe2yuiF+m2XG9eIXIGmP1hY1QTS+JllffZKtK781IsPs3Ncc2szwhqwEMpqYFLvNu6YFOvMaI6+gki/aqM3dzK74kopWBMnqgI18Z2mcgm0EwENnKVWyanMZ8UdfSHqORf+t6pO/fNkt7a0Dra9S/izmJEaalswj3EZmtJ4Z/qi9GW1ab7SUnccZyLTK22YoymgQ4VauYoa0WjmIDKpdnmU0/R7OMJtUJYoMy8aTGnVeNlu7IcaPh7yfGCGvA07h8l6wow0HsFiNoTCO7HW4O0HKLEtu+mcNnQsH2qFTSLcl3EJh7qhiMSCROHC78UIndgNweTKSxQcNynxfBmcZfJyW451RsuTHfwgoTWB2m8ZKVl24QM8tsOa68gSrOYk4IO43AL7I7w7Gas=
  account: mediocregopher
This commit is contained in:
mediocregopher 2020-03-23 17:15:44 -06:00
parent f3226d6171
commit aa1a4969f3
4 changed files with 91 additions and 44 deletions

View File

@ -102,23 +102,38 @@ func cmdCommit(ctx context.Context, cmd *dcmd.Cmd) {
cmd.SubCmd("credential", "Commit credential of a different commit",
func(ctx context.Context, cmd *dcmd.Cmd) {
flag := cmd.FlagSet()
rev := flag.String("rev", "", "Revision of commit to accredit")
startRev := flag.String("start", "", "Revision of the starting commit to accredit (when accrediting a range of changes)")
endRev := flag.String("end", "HEAD", "Revision of the ending commit to accredit (when accrediting a range of changes)")
rev := flag.String("rev", "", "Revision of commit to accredit (when accrediting a single commit)")
cmd.Run(func() (context.Context, error) {
if *rev == "" {
return nil, errors.New("-rev is required")
if *rev == "" && *startRev == "" {
return nil, errors.New("-rev or -start is required")
} else if hasStaged {
return nil, errors.New("credential commit cannot have any files changed")
}
var credCommit dehub.Commit
if *rev != "" {
gitCommit, err := repo.GetGitRevision(plumbing.Revision(*rev))
if err != nil {
return nil, fmt.Errorf("resolving revision %q: %w", *rev, err)
} else if credCommit, err = repo.NewCommitCredential(gitCommit.Interface.GetHash()); err != nil {
return nil, fmt.Errorf("constructing credential commit: %w", err)
}
} else {
gitCommits, err := repo.GetGitRevisionRange(
plumbing.Revision(*startRev),
plumbing.Revision(*endRev),
)
if err != nil {
return nil, fmt.Errorf("resolving revisions %q to %q: %w",
*startRev, *endRev, err)
} else if credCommit, err = repo.NewCommitCredentialFromChanges(gitCommits); err != nil {
return nil, fmt.Errorf("constructing credential commit: %w", err)
}
}
credCommit, err := repo.NewCommitCredential(gitCommit.Interface.GetHash())
if err != nil {
return nil, fmt.Errorf("constructing credential commit: %w", err)
} else if err := accreditAndCommit(credCommit); err != nil {
if err := accreditAndCommit(credCommit); err != nil {
return nil, err
}
return nil, nil

View File

@ -439,3 +439,46 @@ func (r *Repo) verifyCommit(branch plumbing.ReferenceName, gitCommit GitCommit,
return nil
}
type changeRangeInfo struct {
lastChangeCommit GitCommit
authors map[string]struct{}
msg string
startTree, endTree *object.Tree
changeHash []byte
}
// changeRangeInfo returns various pieces of information about a range of
// commits' changes.
func (r *Repo) changeRangeInfo(commits []GitCommit) (changeRangeInfo, error) {
info := changeRangeInfo{
authors: map[string]struct{}{},
}
var lastChangeCommitOk bool
for _, commit := range commits {
if _, ok := commit.Interface.(*CommitChange); ok {
info.lastChangeCommit = commit
lastChangeCommitOk = true
for _, cred := range commit.Commit.Common.Credentials {
info.authors[cred.AccountID] = struct{}{}
}
}
}
if !lastChangeCommitOk {
return changeRangeInfo{}, errors.New("no change commits found")
}
// startTree has to be the tree of the parent of the first commit, which
// isn't included in commits. Determine it the hard way.
var err error
if info.startTree, err = r.parentTree(commits[0].GitCommit); err != nil {
return changeRangeInfo{}, fmt.Errorf("getting tree of parent of %q: %w",
commits[0].GitCommit.Hash, err)
}
info.msg = info.lastChangeCommit.Commit.Change.Message
info.endTree = info.lastChangeCommit.GitTree
info.changeHash = genChangeHash(nil, info.msg, info.startTree, info.endTree)
return info, nil
}

View File

@ -74,40 +74,17 @@ func (cc CommitChange) GetHash() []byte {
// start/end will be different than the one which needs to be accredited in
// onto/end.
func (r *Repo) CombineCommitChanges(commits []GitCommit, onto plumbing.ReferenceName) (GitCommit, error) {
authorsSet := map[string]struct{}{}
var lastChangeCommit GitCommit
var lastChangeCommitOk bool
for _, commit := range commits {
if _, ok := commit.Interface.(*CommitChange); ok {
lastChangeCommit = commit
lastChangeCommitOk = true
for _, cred := range commit.Commit.Common.Credentials {
authorsSet[cred.AccountID] = struct{}{}
}
}
}
if !lastChangeCommitOk {
return GitCommit{}, errors.New("no change commits in range")
info, err := r.changeRangeInfo(commits)
if err != nil {
return GitCommit{}, err
}
authors := make([]string, 0, len(authorsSet))
for author := range authorsSet {
authors := make([]string, 0, len(info.authors))
for author := range info.authors {
authors = append(authors, author)
}
sort.Strings(authors)
// startTree has to be the tree of startRev, which isn't included in
// commits. Determine it the hard way.
startTree, err := r.parentTree(commits[0].GitCommit)
if err != nil {
return GitCommit{}, fmt.Errorf("getting tree of parent of %q: %w",
commits[0].GitCommit.Hash, err)
}
msg := lastChangeCommit.Commit.Change.Message
endTree := lastChangeCommit.GitTree
changeHash := genChangeHash(nil, msg, startTree, endTree)
ontoBranchName, err := r.ReferenceToBranchName(onto)
if err != nil {
return GitCommit{}, fmt.Errorf("resolving %q into a branch name: %w", onto, err)
@ -120,8 +97,8 @@ func (r *Repo) CombineCommitChanges(commits []GitCommit, onto plumbing.Reference
return GitCommit{}, fmt.Errorf("resolving revision %q: %w", onto, err)
}
ontoTree := ontoCommit.GitTree
ontoEndChangeHash := genChangeHash(nil, msg, ontoTree, endTree)
if !bytes.Equal(ontoEndChangeHash, changeHash) {
ontoEndChangeHash := genChangeHash(nil, info.msg, ontoTree, info.endTree)
if !bytes.Equal(ontoEndChangeHash, info.changeHash) {
// 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)
@ -129,7 +106,7 @@ func (r *Repo) CombineCommitChanges(commits []GitCommit, onto plumbing.Reference
var creds []sigcred.Credential
for _, commit := range commits {
if bytes.Equal(commit.Interface.GetHash(), changeHash) {
if bytes.Equal(commit.Interface.GetHash(), info.changeHash) {
creds = append(creds, commit.Commit.Common.Credentials...)
}
}
@ -141,8 +118,8 @@ func (r *Repo) CombineCommitChanges(commits []GitCommit, onto plumbing.Reference
commit := Commit{
Change: &CommitChange{
Message: msg,
ChangeHash: changeHash,
Message: info.msg,
ChangeHash: info.changeHash,
},
Common: CommitCommon{Credentials: creds},
}
@ -151,7 +128,7 @@ func (r *Repo) CombineCommitChanges(commits []GitCommit, onto plumbing.Reference
Commit: commit,
Author: strings.Join(authors, ","),
ParentHash: ontoCommit.GitCommit.Hash,
GitTree: endTree,
GitTree: info.endTree,
})
if err != nil {
return GitCommit{}, fmt.Errorf("storing commit: %w", err)

View File

@ -27,6 +27,18 @@ func (r *Repo) NewCommitCredential(hash []byte) (Commit, error) {
}, nil
}
// NewCommitCredentialFromChanges constructs and returns a Comit populated with
// a CommitCredential for all changes in the given range of GitCommits. The
// message of the last change commit in the range is used when generating the
// hash.
func (r *Repo) NewCommitCredentialFromChanges(commits []GitCommit) (Commit, error) {
info, err := r.changeRangeInfo(commits)
if err != nil {
return Commit{}, err
}
return r.NewCommitCredential(info.changeHash)
}
// MessageHead implements the method for the CommitInterface interface.
func (cc CommitCredential) MessageHead(common CommitCommon) (string, error) {
hash64 := base64.StdEncoding.EncodeToString(cc.CredentialedHash)