From aa1a4969f3609db10be9d2a5d91f3d8d5b445472 Mon Sep 17 00:00:00 2001 From: mediocregopher <> Date: Mon, 23 Mar 2020 17:15:44 -0600 Subject: [PATCH] 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 --- cmd/dehub/cmd_commit.go | 35 +++++++++++++++++++++++--------- commit.go | 43 +++++++++++++++++++++++++++++++++++++++ commit_change.go | 45 ++++++++++------------------------------- commit_credential.go | 12 +++++++++++ 4 files changed, 91 insertions(+), 44 deletions(-) diff --git a/cmd/dehub/cmd_commit.go b/cmd/dehub/cmd_commit.go index 790d6db..38e11bf 100644 --- a/cmd/dehub/cmd_commit.go +++ b/cmd/dehub/cmd_commit.go @@ -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") } - gitCommit, err := repo.GetGitRevision(plumbing.Revision(*rev)) - if err != nil { - return nil, fmt.Errorf("resolving revision %q: %w", *rev, err) + 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 diff --git a/commit.go b/commit.go index b8dd8eb..4bcd7c1 100644 --- a/commit.go +++ b/commit.go @@ -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 +} diff --git a/commit_change.go b/commit_change.go index 8b005f0..8ff815b 100644 --- a/commit_change.go +++ b/commit_change.go @@ -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) diff --git a/commit_credential.go b/commit_credential.go index ea7acb4..6224e8c 100644 --- a/commit_credential.go +++ b/commit_credential.go @@ -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)