From 256b94e9e923edc806fca38c90507c8c1b93c691 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Thu, 30 Jan 2025 09:24:57 +0800 Subject: [PATCH] Support choose email when creating a commit via web UI (#33432) Initial PR for #24469 --- models/user/email_address.go | 10 ++ models/user/user.go | 2 +- options/locale/locale_en-US.ini | 2 + routers/api/v1/repo/file.go | 32 +++---- routers/api/v1/repo/patch.go | 8 +- routers/web/repo/editor.go | 68 ++++++++----- services/forms/repo_form.go | 1 + services/packages/cargo/index.go | 10 +- services/repository/files/cherry_pick.go | 23 +++-- services/repository/files/file.go | 46 --------- services/repository/files/patch.go | 21 ++-- services/repository/files/temp_repo.go | 69 +++++++++++--- services/repository/files/update.go | 25 +++-- services/repository/files/upload.go | 13 ++- templates/repo/editor/commit_form.tmpl | 10 ++ tests/integration/actions_trigger_test.go | 72 +++++++------- tests/integration/editor_test.go | 106 +++++++++++++++++++++ tests/integration/pull_update_test.go | 16 ++-- tests/integration/repofiles_change_test.go | 4 +- 19 files changed, 356 insertions(+), 182 deletions(-) diff --git a/models/user/email_address.go b/models/user/email_address.go index 7c9c6140ac..2ba6a56450 100644 --- a/models/user/email_address.go +++ b/models/user/email_address.go @@ -542,3 +542,13 @@ func IsEmailDomainAllowed(email string) bool { return validation.IsEmailDomainListed(setting.Service.EmailDomainAllowList, email) } + +func GetActivatedEmailAddresses(ctx context.Context, uid int64) ([]string, error) { + emails := make([]string, 0, 2) + if err := db.GetEngine(ctx).Table("email_address").Select("email"). + Where("uid=? AND is_activated=?", uid, true).Asc("id"). + Find(&emails); err != nil { + return nil, err + } + return emails, nil +} diff --git a/models/user/user.go b/models/user/user.go index 75ed7ece8b..e13fb6ab3c 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -214,7 +214,7 @@ func (u *User) GetPlaceholderEmail() string { return fmt.Sprintf("%s@%s", u.LowerName, setting.Service.NoReplyAddress) } -// GetEmail returns an noreply email, if the user has set to keep his +// GetEmail returns a noreply email, if the user has set to keep his // email address private, otherwise the primary email address. func (u *User) GetEmail() string { if u.KeepEmailPrivate { diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 85d2c71ec7..68b7fa2f9f 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1345,6 +1345,8 @@ editor.new_branch_name_desc = New branch nameā€¦ editor.cancel = Cancel editor.filename_cannot_be_empty = The filename cannot be empty. editor.filename_is_invalid = The filename is invalid: "%s". +editor.commit_email = Commit email +editor.invalid_commit_email = The email for the commit is invalid. editor.branch_does_not_exist = Branch "%s" does not exist in this repository. editor.branch_already_exists = Branch "%s" already exists in this repository. editor.directory_is_a_file = Directory name "%s" is already used as a filename in this repository. diff --git a/routers/api/v1/repo/file.go b/routers/api/v1/repo/file.go index 3eefd2ae29..045db7a291 100644 --- a/routers/api/v1/repo/file.go +++ b/routers/api/v1/repo/file.go @@ -489,12 +489,12 @@ func ChangeFiles(ctx *context.APIContext) { OldBranch: apiOpts.BranchName, NewBranch: apiOpts.NewBranchName, Committer: &files_service.IdentityOptions{ - Name: apiOpts.Committer.Name, - Email: apiOpts.Committer.Email, + GitUserName: apiOpts.Committer.Name, + GitUserEmail: apiOpts.Committer.Email, }, Author: &files_service.IdentityOptions{ - Name: apiOpts.Author.Name, - Email: apiOpts.Author.Email, + GitUserName: apiOpts.Author.Name, + GitUserEmail: apiOpts.Author.Email, }, Dates: &files_service.CommitDateOptions{ Author: apiOpts.Dates.Author, @@ -586,12 +586,12 @@ func CreateFile(ctx *context.APIContext) { OldBranch: apiOpts.BranchName, NewBranch: apiOpts.NewBranchName, Committer: &files_service.IdentityOptions{ - Name: apiOpts.Committer.Name, - Email: apiOpts.Committer.Email, + GitUserName: apiOpts.Committer.Name, + GitUserEmail: apiOpts.Committer.Email, }, Author: &files_service.IdentityOptions{ - Name: apiOpts.Author.Name, - Email: apiOpts.Author.Email, + GitUserName: apiOpts.Author.Name, + GitUserEmail: apiOpts.Author.Email, }, Dates: &files_service.CommitDateOptions{ Author: apiOpts.Dates.Author, @@ -689,12 +689,12 @@ func UpdateFile(ctx *context.APIContext) { OldBranch: apiOpts.BranchName, NewBranch: apiOpts.NewBranchName, Committer: &files_service.IdentityOptions{ - Name: apiOpts.Committer.Name, - Email: apiOpts.Committer.Email, + GitUserName: apiOpts.Committer.Name, + GitUserEmail: apiOpts.Committer.Email, }, Author: &files_service.IdentityOptions{ - Name: apiOpts.Author.Name, - Email: apiOpts.Author.Email, + GitUserName: apiOpts.Author.Name, + GitUserEmail: apiOpts.Author.Email, }, Dates: &files_service.CommitDateOptions{ Author: apiOpts.Dates.Author, @@ -848,12 +848,12 @@ func DeleteFile(ctx *context.APIContext) { OldBranch: apiOpts.BranchName, NewBranch: apiOpts.NewBranchName, Committer: &files_service.IdentityOptions{ - Name: apiOpts.Committer.Name, - Email: apiOpts.Committer.Email, + GitUserName: apiOpts.Committer.Name, + GitUserEmail: apiOpts.Committer.Email, }, Author: &files_service.IdentityOptions{ - Name: apiOpts.Author.Name, - Email: apiOpts.Author.Email, + GitUserName: apiOpts.Author.Name, + GitUserEmail: apiOpts.Author.Email, }, Dates: &files_service.CommitDateOptions{ Author: apiOpts.Dates.Author, diff --git a/routers/api/v1/repo/patch.go b/routers/api/v1/repo/patch.go index 5e24dcf891..95d7631da7 100644 --- a/routers/api/v1/repo/patch.go +++ b/routers/api/v1/repo/patch.go @@ -58,12 +58,12 @@ func ApplyDiffPatch(ctx *context.APIContext) { OldBranch: apiOpts.BranchName, NewBranch: apiOpts.NewBranchName, Committer: &files.IdentityOptions{ - Name: apiOpts.Committer.Name, - Email: apiOpts.Committer.Email, + GitUserName: apiOpts.Committer.Name, + GitUserEmail: apiOpts.Committer.Email, }, Author: &files.IdentityOptions{ - Name: apiOpts.Author.Name, - Email: apiOpts.Author.Email, + GitUserName: apiOpts.Author.Name, + GitUserEmail: apiOpts.Author.Email, }, Dates: &files.CommitDateOptions{ Author: apiOpts.Dates.Author, diff --git a/routers/web/repo/editor.go b/routers/web/repo/editor.go index 85f407ab8d..48e041fb1d 100644 --- a/routers/web/repo/editor.go +++ b/routers/web/repo/editor.go @@ -13,6 +13,7 @@ import ( git_model "code.gitea.io/gitea/models/git" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" + user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/json" @@ -102,10 +103,32 @@ func getParentTreeFields(treePath string) (treeNames, treePaths []string) { return treeNames, treePaths } -func editFile(ctx *context.Context, isNewFile bool) { - ctx.Data["PageIsViewCode"] = true +func getCandidateEmailAddresses(ctx *context.Context) []string { + emails, err := user_model.GetActivatedEmailAddresses(ctx, ctx.Doer.ID) + if err != nil { + log.Error("getCandidateEmailAddresses: GetActivatedEmailAddresses: %v", err) + } + + if ctx.Doer.KeepEmailPrivate { + emails = append([]string{ctx.Doer.GetPlaceholderEmail()}, emails...) + } + return emails +} + +func editFileCommon(ctx *context.Context, isNewFile bool) { ctx.Data["PageIsEdit"] = true ctx.Data["IsNewFile"] = isNewFile + ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL() + ctx.Data["PreviewableExtensions"] = strings.Join(markup.PreviewableExtensions(), ",") + ctx.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",") + ctx.Data["IsEditingFileOnly"] = ctx.FormString("return_uri") != "" + ctx.Data["ReturnURI"] = ctx.FormString("return_uri") + ctx.Data["CommitCandidateEmails"] = getCandidateEmailAddresses(ctx) + ctx.Data["CommitDefaultEmail"] = ctx.Doer.GetEmail() +} + +func editFile(ctx *context.Context, isNewFile bool) { + editFileCommon(ctx, isNewFile) canCommit := renderCommitRights(ctx) treePath := cleanUploadFileName(ctx.Repo.TreePath) @@ -174,28 +197,19 @@ func editFile(ctx *context.Context, isNewFile bool) { ctx.Data["FileContent"] = content } } else { - // Append filename from query, or empty string to allow user name the new file. + // Append filename from query, or empty string to allow username the new file. treeNames = append(treeNames, fileName) } ctx.Data["TreeNames"] = treeNames ctx.Data["TreePaths"] = treePaths - ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL() ctx.Data["commit_summary"] = "" ctx.Data["commit_message"] = "" - if canCommit { - ctx.Data["commit_choice"] = frmCommitChoiceDirect - } else { - ctx.Data["commit_choice"] = frmCommitChoiceNewBranch - } + ctx.Data["commit_choice"] = util.Iif(canCommit, frmCommitChoiceDirect, frmCommitChoiceNewBranch) ctx.Data["new_branch_name"] = GetUniquePatchBranchName(ctx) ctx.Data["last_commit"] = ctx.Repo.CommitID - ctx.Data["PreviewableExtensions"] = strings.Join(markup.PreviewableExtensions(), ",") - ctx.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",") - ctx.Data["EditorconfigJson"] = GetEditorConfig(ctx, treePath) - ctx.Data["IsEditingFileOnly"] = ctx.FormString("return_uri") != "" - ctx.Data["ReturnURI"] = ctx.FormString("return_uri") + ctx.Data["EditorconfigJson"] = GetEditorConfig(ctx, treePath) ctx.HTML(http.StatusOK, tplEditFile) } @@ -224,6 +238,9 @@ func NewFile(ctx *context.Context) { } func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile bool) { + editFileCommon(ctx, isNewFile) + ctx.Data["PageHasPosted"] = true + canCommit := renderCommitRights(ctx) treeNames, treePaths := getParentTreeFields(form.TreePath) branchName := ctx.Repo.BranchName @@ -231,21 +248,15 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b branchName = form.NewBranchName } - ctx.Data["PageIsEdit"] = true - ctx.Data["PageHasPosted"] = true - ctx.Data["IsNewFile"] = isNewFile ctx.Data["TreePath"] = form.TreePath ctx.Data["TreeNames"] = treeNames ctx.Data["TreePaths"] = treePaths - ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/branch/" + util.PathEscapeSegments(ctx.Repo.BranchName) ctx.Data["FileContent"] = form.Content ctx.Data["commit_summary"] = form.CommitSummary ctx.Data["commit_message"] = form.CommitMessage ctx.Data["commit_choice"] = form.CommitChoice ctx.Data["new_branch_name"] = form.NewBranchName ctx.Data["last_commit"] = ctx.Repo.CommitID - ctx.Data["PreviewableExtensions"] = strings.Join(markup.PreviewableExtensions(), ",") - ctx.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",") ctx.Data["EditorconfigJson"] = GetEditorConfig(ctx, form.TreePath) if ctx.HasError() { @@ -253,7 +264,7 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b return } - // Cannot commit to a an existing branch if user doesn't have rights + // Cannot commit to an existing branch if user doesn't have rights if branchName == ctx.Repo.BranchName && !canCommit { ctx.Data["Err_NewBranchName"] = true ctx.Data["commit_choice"] = frmCommitChoiceNewBranch @@ -276,6 +287,17 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b message += "\n\n" + form.CommitMessage } + gitCommitter := &files_service.IdentityOptions{} + if form.CommitEmail != "" { + if util.SliceContainsString(getCandidateEmailAddresses(ctx), form.CommitEmail, true) { + gitCommitter.GitUserEmail = form.CommitEmail + } else { + ctx.Data["Err_CommitEmail"] = true + ctx.RenderWithErr(ctx.Tr("repo.editor.invalid_commit_email"), tplEditFile, &form) + return + } + } + operation := "update" if isNewFile { operation = "create" @@ -294,7 +316,9 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b ContentReader: strings.NewReader(strings.ReplaceAll(form.Content, "\r", "")), }, }, - Signoff: form.Signoff, + Signoff: form.Signoff, + Author: gitCommitter, + Committer: gitCommitter, }); err != nil { // This is where we handle all the errors thrown by files_service.ChangeRepoFiles if git.IsErrNotExist(err) { diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index 1a1c6585db..40e15f2d5c 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -720,6 +720,7 @@ type EditRepoFileForm struct { NewBranchName string `binding:"GitRefName;MaxSize(100)"` LastCommit string Signoff bool + CommitEmail string } // Validate validates the fields diff --git a/services/packages/cargo/index.go b/services/packages/cargo/index.go index e8a8313625..88a463e4c6 100644 --- a/services/packages/cargo/index.go +++ b/services/packages/cargo/index.go @@ -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 } diff --git a/services/repository/files/cherry_pick.go b/services/repository/files/cherry_pick.go index 10545e9e03..3457283803 100644 --- a/services/repository/files/cherry_pick.go +++ b/services/repository/files/cherry_pick.go @@ -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 } diff --git a/services/repository/files/file.go b/services/repository/files/file.go index d7ca8e79e5..2caa1b4946 100644 --- a/services/repository/files/file.go +++ b/services/repository/files/file.go @@ -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 diff --git a/services/repository/files/patch.go b/services/repository/files/patch.go index 38c17b4073..78c275f01c 100644 --- a/services/repository/files/patch.go +++ b/services/repository/files/patch.go @@ -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 } diff --git a/services/repository/files/temp_repo.go b/services/repository/files/temp_repo.go index 138af991f9..cf1402397b 100644 --- a/services/repository/files/temp_repo.go +++ b/services/repository/files/temp_repo.go @@ -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: ") diff --git a/services/repository/files/update.go b/services/repository/files/update.go index a2763105b0..a707ea8bb6 100644 --- a/services/repository/files/update.go +++ b/services/repository/files/update.go @@ -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 } diff --git a/services/repository/files/upload.go b/services/repository/files/upload.go index cbfaf49d13..af32bc4c85 100644 --- a/services/repository/files/upload.go +++ b/services/repository/files/upload.go @@ -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 } diff --git a/templates/repo/editor/commit_form.tmpl b/templates/repo/editor/commit_form.tmpl index c050324e93..8f46c47b96 100644 --- a/templates/repo/editor/commit_form.tmpl +++ b/templates/repo/editor/commit_form.tmpl @@ -66,6 +66,16 @@ {{end}} + {{if and .CommitCandidateEmails (gt (len .CommitCandidateEmails) 1)}} +
+ + +
+ {{end}}