Support choose email when creating a commit via web UI (#33432)
Initial PR for #24469
This commit is contained in:
parent
ac2d97cb61
commit
256b94e9e9
19 changed files with 356 additions and 182 deletions
|
@ -720,6 +720,7 @@ type EditRepoFileForm struct {
|
|||
NewBranchName string `binding:"GitRefName;MaxSize(100)"`
|
||||
LastCommit string
|
||||
Signoff bool
|
||||
CommitEmail string
|
||||
}
|
||||
|
||||
// Validate validates the fields
|
||||
|
|
|
@ -11,7 +11,6 @@ import (
|
|||
"io"
|
||||
"path"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
packages_model "code.gitea.io/gitea/models/packages"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
|
@ -296,8 +295,13 @@ func alterRepositoryContent(ctx context.Context, doer *user_model.User, repo *re
|
|||
return err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
commitHash, err := t.CommitTreeWithDate(lastCommitID, doer, doer, treeHash, commitMessage, false, now, now)
|
||||
commitOpts := &files_service.CommitTreeUserOptions{
|
||||
ParentCommitID: lastCommitID,
|
||||
TreeHash: treeHash,
|
||||
CommitMessage: commitMessage,
|
||||
DoerUser: doer,
|
||||
}
|
||||
commitHash, err := t.CommitTree(commitOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -32,15 +32,13 @@ func (err ErrCommitIDDoesNotMatch) Error() string {
|
|||
return fmt.Sprintf("file CommitID does not match [given: %s, expected: %s]", err.GivenCommitID, err.CurrentCommitID)
|
||||
}
|
||||
|
||||
// CherryPick cherrypicks or reverts a commit to the given repository
|
||||
// CherryPick cherry-picks or reverts a commit to the given repository
|
||||
func CherryPick(ctx context.Context, repo *repo_model.Repository, doer *user_model.User, revert bool, opts *ApplyDiffPatchOptions) (*structs.FileResponse, error) {
|
||||
if err := opts.Validate(ctx, repo, doer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
message := strings.TrimSpace(opts.Message)
|
||||
|
||||
author, committer := GetAuthorAndCommitterUsers(opts.Author, opts.Committer, doer)
|
||||
|
||||
t, err := NewTemporaryUploadRepository(ctx, repo)
|
||||
if err != nil {
|
||||
log.Error("NewTemporaryUploadRepository failed: %v", err)
|
||||
|
@ -112,12 +110,21 @@ func CherryPick(ctx context.Context, repo *repo_model.Repository, doer *user_mod
|
|||
}
|
||||
|
||||
// Now commit the tree
|
||||
var commitHash string
|
||||
if opts.Dates != nil {
|
||||
commitHash, err = t.CommitTreeWithDate("HEAD", author, committer, treeHash, message, opts.Signoff, opts.Dates.Author, opts.Dates.Committer)
|
||||
} else {
|
||||
commitHash, err = t.CommitTree("HEAD", author, committer, treeHash, message, opts.Signoff)
|
||||
commitOpts := &CommitTreeUserOptions{
|
||||
ParentCommitID: "HEAD",
|
||||
TreeHash: treeHash,
|
||||
CommitMessage: message,
|
||||
SignOff: opts.Signoff,
|
||||
DoerUser: doer,
|
||||
AuthorIdentity: opts.Author,
|
||||
AuthorTime: nil,
|
||||
CommitterIdentity: opts.Committer,
|
||||
CommitterTime: nil,
|
||||
}
|
||||
if opts.Dates != nil {
|
||||
commitOpts.AuthorTime, commitOpts.CommitterTime = &opts.Dates.Author, &opts.Dates.Committer
|
||||
}
|
||||
commitHash, err := t.CommitTree(commitOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@ import (
|
|||
"time"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
@ -111,51 +110,6 @@ func GetFileCommitResponse(repo *repo_model.Repository, commit *git.Commit) (*ap
|
|||
return fileCommit, nil
|
||||
}
|
||||
|
||||
// GetAuthorAndCommitterUsers Gets the author and committer user objects from the IdentityOptions
|
||||
func GetAuthorAndCommitterUsers(author, committer *IdentityOptions, doer *user_model.User) (authorUser, committerUser *user_model.User) {
|
||||
// Committer and author are optional. If they are not the doer (not same email address)
|
||||
// then we use bogus User objects for them to store their FullName and Email.
|
||||
// If only one of the two are provided, we set both of them to it.
|
||||
// If neither are provided, both are the doer.
|
||||
if committer != nil && committer.Email != "" {
|
||||
if doer != nil && strings.EqualFold(doer.Email, committer.Email) {
|
||||
committerUser = doer // the committer is the doer, so will use their user object
|
||||
if committer.Name != "" {
|
||||
committerUser.FullName = committer.Name
|
||||
}
|
||||
} else {
|
||||
committerUser = &user_model.User{
|
||||
FullName: committer.Name,
|
||||
Email: committer.Email,
|
||||
}
|
||||
}
|
||||
}
|
||||
if author != nil && author.Email != "" {
|
||||
if doer != nil && strings.EqualFold(doer.Email, author.Email) {
|
||||
authorUser = doer // the author is the doer, so will use their user object
|
||||
if authorUser.Name != "" {
|
||||
authorUser.FullName = author.Name
|
||||
}
|
||||
} else {
|
||||
authorUser = &user_model.User{
|
||||
FullName: author.Name,
|
||||
Email: author.Email,
|
||||
}
|
||||
}
|
||||
}
|
||||
if authorUser == nil {
|
||||
if committerUser != nil {
|
||||
authorUser = committerUser // No valid author was given so use the committer
|
||||
} else if doer != nil {
|
||||
authorUser = doer // No valid author was given and no valid committer so use the doer
|
||||
}
|
||||
}
|
||||
if committerUser == nil {
|
||||
committerUser = authorUser // No valid committer so use the author as the committer (was set to a valid user above)
|
||||
}
|
||||
return authorUser, committerUser
|
||||
}
|
||||
|
||||
// ErrFilenameInvalid represents a "FilenameInvalid" kind of error.
|
||||
type ErrFilenameInvalid struct {
|
||||
Path string
|
||||
|
|
|
@ -126,8 +126,6 @@ func ApplyDiffPatch(ctx context.Context, repo *repo_model.Repository, doer *user
|
|||
|
||||
message := strings.TrimSpace(opts.Message)
|
||||
|
||||
author, committer := GetAuthorAndCommitterUsers(opts.Author, opts.Committer, doer)
|
||||
|
||||
t, err := NewTemporaryUploadRepository(ctx, repo)
|
||||
if err != nil {
|
||||
log.Error("NewTemporaryUploadRepository failed: %v", err)
|
||||
|
@ -187,12 +185,21 @@ func ApplyDiffPatch(ctx context.Context, repo *repo_model.Repository, doer *user
|
|||
}
|
||||
|
||||
// Now commit the tree
|
||||
var commitHash string
|
||||
if opts.Dates != nil {
|
||||
commitHash, err = t.CommitTreeWithDate("HEAD", author, committer, treeHash, message, opts.Signoff, opts.Dates.Author, opts.Dates.Committer)
|
||||
} else {
|
||||
commitHash, err = t.CommitTree("HEAD", author, committer, treeHash, message, opts.Signoff)
|
||||
commitOpts := &CommitTreeUserOptions{
|
||||
ParentCommitID: "HEAD",
|
||||
TreeHash: treeHash,
|
||||
CommitMessage: message,
|
||||
SignOff: opts.Signoff,
|
||||
DoerUser: doer,
|
||||
AuthorIdentity: opts.Author,
|
||||
AuthorTime: nil,
|
||||
CommitterIdentity: opts.Committer,
|
||||
CommitterTime: nil,
|
||||
}
|
||||
if opts.Dates != nil {
|
||||
commitOpts.AuthorTime, commitOpts.CommitterTime = &opts.Dates.Author, &opts.Dates.Committer
|
||||
}
|
||||
commitHash, err := t.CommitTree(commitOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/log"
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
||||
"code.gitea.io/gitea/services/gitdiff"
|
||||
)
|
||||
|
@ -225,15 +226,53 @@ func (t *TemporaryUploadRepository) GetLastCommitByRef(ref string) (string, erro
|
|||
return strings.TrimSpace(stdout), nil
|
||||
}
|
||||
|
||||
// CommitTree creates a commit from a given tree for the user with provided message
|
||||
func (t *TemporaryUploadRepository) CommitTree(parent string, author, committer *user_model.User, treeHash, message string, signoff bool) (string, error) {
|
||||
return t.CommitTreeWithDate(parent, author, committer, treeHash, message, signoff, time.Now(), time.Now())
|
||||
type CommitTreeUserOptions struct {
|
||||
ParentCommitID string
|
||||
TreeHash string
|
||||
CommitMessage string
|
||||
SignOff bool
|
||||
|
||||
DoerUser *user_model.User
|
||||
|
||||
AuthorIdentity *IdentityOptions // if nil, use doer
|
||||
AuthorTime *time.Time // if nil, use now
|
||||
CommitterIdentity *IdentityOptions
|
||||
CommitterTime *time.Time
|
||||
}
|
||||
|
||||
// CommitTreeWithDate creates a commit from a given tree for the user with provided message
|
||||
func (t *TemporaryUploadRepository) CommitTreeWithDate(parent string, author, committer *user_model.User, treeHash, message string, signoff bool, authorDate, committerDate time.Time) (string, error) {
|
||||
authorSig := author.NewGitSig()
|
||||
committerSig := committer.NewGitSig()
|
||||
func makeGitUserSignature(doer *user_model.User, identity, other *IdentityOptions) *git.Signature {
|
||||
gitSig := &git.Signature{}
|
||||
if identity != nil {
|
||||
gitSig.Name, gitSig.Email = identity.GitUserName, identity.GitUserEmail
|
||||
}
|
||||
if other != nil {
|
||||
gitSig.Name = util.IfZero(gitSig.Name, other.GitUserName)
|
||||
gitSig.Email = util.IfZero(gitSig.Email, other.GitUserEmail)
|
||||
}
|
||||
if gitSig.Name == "" {
|
||||
gitSig.Name = doer.GitName()
|
||||
}
|
||||
if gitSig.Email == "" {
|
||||
gitSig.Email = doer.GetEmail()
|
||||
}
|
||||
return gitSig
|
||||
}
|
||||
|
||||
// CommitTree creates a commit from a given tree for the user with provided message
|
||||
func (t *TemporaryUploadRepository) CommitTree(opts *CommitTreeUserOptions) (string, error) {
|
||||
authorSig := makeGitUserSignature(opts.DoerUser, opts.AuthorIdentity, opts.CommitterIdentity)
|
||||
committerSig := makeGitUserSignature(opts.DoerUser, opts.CommitterIdentity, opts.AuthorIdentity)
|
||||
|
||||
authorDate := opts.AuthorTime
|
||||
committerDate := opts.CommitterTime
|
||||
if authorDate == nil && committerDate == nil {
|
||||
authorDate = util.ToPointer(time.Now())
|
||||
committerDate = authorDate
|
||||
} else if authorDate == nil {
|
||||
authorDate = committerDate
|
||||
} else if committerDate == nil {
|
||||
committerDate = authorDate
|
||||
}
|
||||
|
||||
// Because this may call hooks we should pass in the environment
|
||||
env := append(os.Environ(),
|
||||
|
@ -244,21 +283,21 @@ func (t *TemporaryUploadRepository) CommitTreeWithDate(parent string, author, co
|
|||
)
|
||||
|
||||
messageBytes := new(bytes.Buffer)
|
||||
_, _ = messageBytes.WriteString(message)
|
||||
_, _ = messageBytes.WriteString(opts.CommitMessage)
|
||||
_, _ = messageBytes.WriteString("\n")
|
||||
|
||||
cmdCommitTree := git.NewCommand(t.ctx, "commit-tree").AddDynamicArguments(treeHash)
|
||||
if parent != "" {
|
||||
cmdCommitTree.AddOptionValues("-p", parent)
|
||||
cmdCommitTree := git.NewCommand(t.ctx, "commit-tree").AddDynamicArguments(opts.TreeHash)
|
||||
if opts.ParentCommitID != "" {
|
||||
cmdCommitTree.AddOptionValues("-p", opts.ParentCommitID)
|
||||
}
|
||||
|
||||
var sign bool
|
||||
var keyID string
|
||||
var signer *git.Signature
|
||||
if parent != "" {
|
||||
sign, keyID, signer, _ = asymkey_service.SignCRUDAction(t.ctx, t.repo.RepoPath(), author, t.basePath, parent)
|
||||
if opts.ParentCommitID != "" {
|
||||
sign, keyID, signer, _ = asymkey_service.SignCRUDAction(t.ctx, t.repo.RepoPath(), opts.DoerUser, t.basePath, opts.ParentCommitID)
|
||||
} else {
|
||||
sign, keyID, signer, _ = asymkey_service.SignInitialCommit(t.ctx, t.repo.RepoPath(), author)
|
||||
sign, keyID, signer, _ = asymkey_service.SignInitialCommit(t.ctx, t.repo.RepoPath(), opts.DoerUser)
|
||||
}
|
||||
if sign {
|
||||
cmdCommitTree.AddOptionFormat("-S%s", keyID)
|
||||
|
@ -279,7 +318,7 @@ func (t *TemporaryUploadRepository) CommitTreeWithDate(parent string, author, co
|
|||
cmdCommitTree.AddArguments("--no-gpg-sign")
|
||||
}
|
||||
|
||||
if signoff {
|
||||
if opts.SignOff {
|
||||
// Signed-off-by
|
||||
_, _ = messageBytes.WriteString("\n")
|
||||
_, _ = messageBytes.WriteString("Signed-off-by: ")
|
||||
|
|
|
@ -27,8 +27,8 @@ import (
|
|||
|
||||
// IdentityOptions for a person's identity like an author or committer
|
||||
type IdentityOptions struct {
|
||||
Name string
|
||||
Email string
|
||||
GitUserName string // to match "git config user.name"
|
||||
GitUserEmail string // to match "git config user.email"
|
||||
}
|
||||
|
||||
// CommitDateOptions store dates for GIT_AUTHOR_DATE and GIT_COMMITTER_DATE
|
||||
|
@ -160,8 +160,6 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
|
|||
|
||||
message := strings.TrimSpace(opts.Message)
|
||||
|
||||
author, committer := GetAuthorAndCommitterUsers(opts.Author, opts.Committer, doer)
|
||||
|
||||
t, err := NewTemporaryUploadRepository(ctx, repo)
|
||||
if err != nil {
|
||||
log.Error("NewTemporaryUploadRepository failed: %v", err)
|
||||
|
@ -262,12 +260,21 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
|
|||
}
|
||||
|
||||
// Now commit the tree
|
||||
var commitHash string
|
||||
if opts.Dates != nil {
|
||||
commitHash, err = t.CommitTreeWithDate(opts.LastCommitID, author, committer, treeHash, message, opts.Signoff, opts.Dates.Author, opts.Dates.Committer)
|
||||
} else {
|
||||
commitHash, err = t.CommitTree(opts.LastCommitID, author, committer, treeHash, message, opts.Signoff)
|
||||
commitOpts := &CommitTreeUserOptions{
|
||||
ParentCommitID: opts.LastCommitID,
|
||||
TreeHash: treeHash,
|
||||
CommitMessage: message,
|
||||
SignOff: opts.Signoff,
|
||||
DoerUser: doer,
|
||||
AuthorIdentity: opts.Author,
|
||||
AuthorTime: nil,
|
||||
CommitterIdentity: opts.Committer,
|
||||
CommitterTime: nil,
|
||||
}
|
||||
if opts.Dates != nil {
|
||||
commitOpts.AuthorTime, commitOpts.CommitterTime = &opts.Dates.Author, &opts.Dates.Committer
|
||||
}
|
||||
commitHash, err := t.CommitTree(commitOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -128,12 +128,15 @@ func UploadRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
|
|||
return err
|
||||
}
|
||||
|
||||
// make author and committer the doer
|
||||
author := doer
|
||||
committer := doer
|
||||
|
||||
// Now commit the tree
|
||||
commitHash, err := t.CommitTree(opts.LastCommitID, author, committer, treeHash, opts.Message, opts.Signoff)
|
||||
commitOpts := &CommitTreeUserOptions{
|
||||
ParentCommitID: opts.LastCommitID,
|
||||
TreeHash: treeHash,
|
||||
CommitMessage: opts.Message,
|
||||
SignOff: opts.Signoff,
|
||||
DoerUser: doer,
|
||||
}
|
||||
commitHash, err := t.CommitTree(commitOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue