Merge pull request '[v1.22/gitea] week 16 cherry pick to v7.0' (#3235) from earl-warren/forgejo:wip-v7.0-gitea-cherry-pick into v7.0/forgejo

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/3235
Reviewed-by: Gergely Nagy <algernon@noreply.codeberg.org>
This commit is contained in:
Earl Warren 2024-04-16 09:31:46 +00:00
commit 335abbbc9d
60 changed files with 341 additions and 311 deletions

View file

@ -114,7 +114,7 @@ func showWebStartupMessage(msg string) {
log.Info("* WorkPath: %s", setting.AppWorkPath) log.Info("* WorkPath: %s", setting.AppWorkPath)
log.Info("* CustomPath: %s", setting.CustomPath) log.Info("* CustomPath: %s", setting.CustomPath)
log.Info("* ConfigFile: %s", setting.CustomConf) log.Info("* ConfigFile: %s", setting.CustomConf)
log.Info("%s", msg) log.Info("%s", msg) // show startup message
} }
func serveInstall(ctx *cli.Context) error { func serveInstall(ctx *cli.Context) error {

View file

@ -44,6 +44,9 @@ func (schedules ScheduleList) LoadTriggerUser(ctx context.Context) error {
schedule.TriggerUser = user_model.NewActionsUser() schedule.TriggerUser = user_model.NewActionsUser()
} else { } else {
schedule.TriggerUser = users[schedule.TriggerUserID] schedule.TriggerUser = users[schedule.TriggerUserID]
if schedule.TriggerUser == nil {
schedule.TriggerUser = user_model.NewGhostUser()
}
} }
} }
return nil return nil

View file

@ -11,6 +11,7 @@ import (
auth_model "code.gitea.io/gitea/models/auth" auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@ -227,7 +228,9 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask
if runner.RepoID != 0 { if runner.RepoID != 0 {
jobCond = builder.Eq{"repo_id": runner.RepoID} jobCond = builder.Eq{"repo_id": runner.RepoID}
} else if runner.OwnerID != 0 { } else if runner.OwnerID != 0 {
jobCond = builder.In("repo_id", builder.Select("id").From("repository").Where(builder.Eq{"owner_id": runner.OwnerID})) jobCond = builder.In("repo_id", builder.Select("`repository`.id").From("repository").
Join("INNER", "repo_unit", "`repository`.id = `repo_unit`.repo_id").
Where(builder.Eq{"`repository`.owner_id": runner.OwnerID, "`repo_unit`.type": unit.TypeActions}))
} }
if jobCond.IsValid() { if jobCond.IsValid() {
jobCond = builder.In("run_id", builder.Select("id").From("action_run").Where(jobCond)) jobCond = builder.In("run_id", builder.Select("id").From("action_run").Where(jobCond))

View file

@ -76,23 +76,14 @@ func calcFingerprintNative(publicKeyContent string) (string, error) {
// CalcFingerprint calculate public key's fingerprint // CalcFingerprint calculate public key's fingerprint
func CalcFingerprint(publicKeyContent string) (string, error) { func CalcFingerprint(publicKeyContent string) (string, error) {
// Call the method based on configuration // Call the method based on configuration
var ( useNative := setting.SSH.KeygenPath == ""
fnName, fp string calcFn := util.Iif(useNative, calcFingerprintNative, calcFingerprintSSHKeygen)
err error fp, err := calcFn(publicKeyContent)
)
if len(setting.SSH.KeygenPath) == 0 {
fnName = "calcFingerprintNative"
fp, err = calcFingerprintNative(publicKeyContent)
} else {
fnName = "calcFingerprintSSHKeygen"
fp, err = calcFingerprintSSHKeygen(publicKeyContent)
}
if err != nil { if err != nil {
if IsErrKeyUnableVerify(err) { if IsErrKeyUnableVerify(err) {
log.Info("%s", publicKeyContent)
return "", err return "", err
} }
return "", fmt.Errorf("%s: %w", fnName, err) return "", fmt.Errorf("CalcFingerprint(%s): %w", util.Iif(useNative, "native", "ssh-keygen"), err)
} }
return fp, nil return fp, nil
} }

View file

@ -53,7 +53,7 @@ func (repo *Repository) IsDependenciesEnabled(ctx context.Context) bool {
var u *RepoUnit var u *RepoUnit
var err error var err error
if u, err = repo.GetUnit(ctx, unit.TypeIssues); err != nil { if u, err = repo.GetUnit(ctx, unit.TypeIssues); err != nil {
log.Trace("%s", err) log.Trace("IsDependenciesEnabled: %v", err)
return setting.Service.DefaultEnableDependencies return setting.Service.DefaultEnableDependencies
} }
return u.IssuesConfig().EnableDependencies return u.IssuesConfig().EnableDependencies

View file

@ -142,35 +142,39 @@ type remoteAddress struct {
Password string Password string
} }
func mirrorRemoteAddress(ctx context.Context, m *repo_model.Repository, remoteName string, ignoreOriginalURL bool) remoteAddress { func mirrorRemoteAddress(ctx context.Context, m *repo_model.Repository, remoteName string) remoteAddress {
a := remoteAddress{} ret := remoteAddress{}
remoteURL, err := git.GetRemoteAddress(ctx, m.RepoPath(), remoteName)
remoteURL := m.OriginalURL if err != nil {
if ignoreOriginalURL || remoteURL == "" { log.Error("GetRemoteURL %v", err)
var err error return ret
remoteURL, err = git.GetRemoteAddress(ctx, m.RepoPath(), remoteName)
if err != nil {
log.Error("GetRemoteURL %v", err)
return a
}
} }
u, err := giturl.Parse(remoteURL) u, err := giturl.Parse(remoteURL)
if err != nil { if err != nil {
log.Error("giturl.Parse %v", err) log.Error("giturl.Parse %v", err)
return a return ret
} }
if u.Scheme != "ssh" && u.Scheme != "file" { if u.Scheme != "ssh" && u.Scheme != "file" {
if u.User != nil { if u.User != nil {
a.Username = u.User.Username() ret.Username = u.User.Username()
a.Password, _ = u.User.Password() ret.Password, _ = u.User.Password()
} }
u.User = nil
} }
a.Address = u.String()
return a // The URL stored in the git repo could contain authentication,
// erase it, or it will be shown in the UI.
u.User = nil
ret.Address = u.String()
// Why not use m.OriginalURL to set ret.Address?
// It should be OK to use it, since m.OriginalURL should be the same as the authentication-erased URL from the Git repository.
// However, the old code has already stored authentication in m.OriginalURL when updating mirror settings.
// That means we need to use "giturl.Parse" for m.OriginalURL again to ensure authentication is erased.
// Instead of doing this, why not directly use the authentication-erased URL from the Git repository?
// It should be the same as long as there are no bugs.
return ret
} }
func FilenameIsImage(filename string) bool { func FilenameIsImage(filename string) bool {

View file

@ -212,3 +212,11 @@ func ToFloat64(number any) (float64, error) {
func ToPointer[T any](val T) *T { func ToPointer[T any](val T) *T {
return &val return &val
} }
// Iif is an "inline-if", it returns "trueVal" if "condition" is true, otherwise "falseVal"
func Iif[T any](condition bool, trueVal, falseVal T) T {
if condition {
return trueVal
}
return falseVal
}

View file

@ -311,7 +311,7 @@ func SearchIssues(ctx *context.APIContext) {
ctx.SetLinkHeader(int(total), limit) ctx.SetLinkHeader(int(total), limit)
ctx.SetTotalCountHeader(total) ctx.SetTotalCountHeader(total)
ctx.JSON(http.StatusOK, convert.ToAPIIssueList(ctx, issues)) ctx.JSON(http.StatusOK, convert.ToAPIIssueList(ctx, ctx.Doer, issues))
} }
// ListIssues list the issues of a repository // ListIssues list the issues of a repository
@ -548,7 +548,7 @@ func ListIssues(ctx *context.APIContext) {
ctx.SetLinkHeader(int(total), listOptions.PageSize) ctx.SetLinkHeader(int(total), listOptions.PageSize)
ctx.SetTotalCountHeader(total) ctx.SetTotalCountHeader(total)
ctx.JSON(http.StatusOK, convert.ToAPIIssueList(ctx, issues)) ctx.JSON(http.StatusOK, convert.ToAPIIssueList(ctx, ctx.Doer, issues))
} }
func getUserIDForFilter(ctx *context.APIContext, queryName string) int64 { func getUserIDForFilter(ctx *context.APIContext, queryName string) int64 {
@ -614,7 +614,7 @@ func GetIssue(ctx *context.APIContext) {
ctx.NotFound() ctx.NotFound()
return return
} }
ctx.JSON(http.StatusOK, convert.ToAPIIssue(ctx, issue)) ctx.JSON(http.StatusOK, convert.ToAPIIssue(ctx, ctx.Doer, issue))
} }
// CreateIssue create an issue of a repository // CreateIssue create an issue of a repository
@ -737,7 +737,7 @@ func CreateIssue(ctx *context.APIContext) {
ctx.Error(http.StatusInternalServerError, "GetIssueByID", err) ctx.Error(http.StatusInternalServerError, "GetIssueByID", err)
return return
} }
ctx.JSON(http.StatusCreated, convert.ToAPIIssue(ctx, issue)) ctx.JSON(http.StatusCreated, convert.ToAPIIssue(ctx, ctx.Doer, issue))
} }
// EditIssue modify an issue of a repository // EditIssue modify an issue of a repository
@ -913,7 +913,7 @@ func EditIssue(ctx *context.APIContext) {
ctx.InternalServerError(err) ctx.InternalServerError(err)
return return
} }
ctx.JSON(http.StatusCreated, convert.ToAPIIssue(ctx, issue)) ctx.JSON(http.StatusCreated, convert.ToAPIIssue(ctx, ctx.Doer, issue))
} }
func DeleteIssue(ctx *context.APIContext) { func DeleteIssue(ctx *context.APIContext) {

View file

@ -108,7 +108,7 @@ func ListIssueAttachments(ctx *context.APIContext) {
return return
} }
ctx.JSON(http.StatusOK, convert.ToAPIIssue(ctx, issue).Attachments) ctx.JSON(http.StatusOK, convert.ToAPIIssue(ctx, ctx.Doer, issue).Attachments)
} }
// CreateIssueAttachment creates an attachment and saves the given file // CreateIssueAttachment creates an attachment and saves the given file

View file

@ -153,7 +153,7 @@ func GetIssueDependencies(ctx *context.APIContext) {
blockerIssues = append(blockerIssues, &blocker.Issue) blockerIssues = append(blockerIssues, &blocker.Issue)
} }
ctx.JSON(http.StatusOK, convert.ToAPIIssueList(ctx, blockerIssues)) ctx.JSON(http.StatusOK, convert.ToAPIIssueList(ctx, ctx.Doer, blockerIssues))
} }
// CreateIssueDependency create a new issue dependencies // CreateIssueDependency create a new issue dependencies
@ -214,7 +214,7 @@ func CreateIssueDependency(ctx *context.APIContext) {
return return
} }
ctx.JSON(http.StatusCreated, convert.ToAPIIssue(ctx, target)) ctx.JSON(http.StatusCreated, convert.ToAPIIssue(ctx, ctx.Doer, target))
} }
// RemoveIssueDependency remove an issue dependency // RemoveIssueDependency remove an issue dependency
@ -275,7 +275,7 @@ func RemoveIssueDependency(ctx *context.APIContext) {
return return
} }
ctx.JSON(http.StatusCreated, convert.ToAPIIssue(ctx, target)) ctx.JSON(http.StatusCreated, convert.ToAPIIssue(ctx, ctx.Doer, target))
} }
// GetIssueBlocks list issues that are blocked by this issue // GetIssueBlocks list issues that are blocked by this issue
@ -381,7 +381,7 @@ func GetIssueBlocks(ctx *context.APIContext) {
issues = append(issues, &depMeta.Issue) issues = append(issues, &depMeta.Issue)
} }
ctx.JSON(http.StatusOK, convert.ToAPIIssueList(ctx, issues)) ctx.JSON(http.StatusOK, convert.ToAPIIssueList(ctx, ctx.Doer, issues))
} }
// CreateIssueBlocking block the issue given in the body by the issue in path // CreateIssueBlocking block the issue given in the body by the issue in path
@ -438,7 +438,7 @@ func CreateIssueBlocking(ctx *context.APIContext) {
return return
} }
ctx.JSON(http.StatusCreated, convert.ToAPIIssue(ctx, dependency)) ctx.JSON(http.StatusCreated, convert.ToAPIIssue(ctx, ctx.Doer, dependency))
} }
// RemoveIssueBlocking unblock the issue given in the body by the issue in path // RemoveIssueBlocking unblock the issue given in the body by the issue in path
@ -495,7 +495,7 @@ func RemoveIssueBlocking(ctx *context.APIContext) {
return return
} }
ctx.JSON(http.StatusCreated, convert.ToAPIIssue(ctx, dependency)) ctx.JSON(http.StatusCreated, convert.ToAPIIssue(ctx, ctx.Doer, dependency))
} }
func getParamsIssue(ctx *context.APIContext) *issues_model.Issue { func getParamsIssue(ctx *context.APIContext) *issues_model.Issue {

View file

@ -207,7 +207,7 @@ func ListPinnedIssues(ctx *context.APIContext) {
return return
} }
ctx.JSON(http.StatusOK, convert.ToAPIIssueList(ctx, issues)) ctx.JSON(http.StatusOK, convert.ToAPIIssueList(ctx, ctx.Doer, issues))
} }
// ListPinnedPullRequests returns a list of all pinned PRs // ListPinnedPullRequests returns a list of all pinned PRs

View file

@ -138,7 +138,7 @@ func ListTrackedTimes(ctx *context.APIContext) {
} }
ctx.SetTotalCountHeader(count) ctx.SetTotalCountHeader(count)
ctx.JSON(http.StatusOK, convert.ToTrackedTimeList(ctx, trackedTimes)) ctx.JSON(http.StatusOK, convert.ToTrackedTimeList(ctx, ctx.Doer, trackedTimes))
} }
// AddTime add time manual to the given issue // AddTime add time manual to the given issue
@ -225,7 +225,7 @@ func AddTime(ctx *context.APIContext) {
ctx.Error(http.StatusInternalServerError, "LoadAttributes", err) ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
return return
} }
ctx.JSON(http.StatusOK, convert.ToTrackedTime(ctx, trackedTime)) ctx.JSON(http.StatusOK, convert.ToTrackedTime(ctx, user, trackedTime))
} }
// ResetIssueTime reset time manual to the given issue // ResetIssueTime reset time manual to the given issue
@ -455,7 +455,7 @@ func ListTrackedTimesByUser(ctx *context.APIContext) {
ctx.Error(http.StatusInternalServerError, "LoadAttributes", err) ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
return return
} }
ctx.JSON(http.StatusOK, convert.ToTrackedTimeList(ctx, trackedTimes)) ctx.JSON(http.StatusOK, convert.ToTrackedTimeList(ctx, ctx.Doer, trackedTimes))
} }
// ListTrackedTimesByRepository lists all tracked times of the repository // ListTrackedTimesByRepository lists all tracked times of the repository
@ -567,7 +567,7 @@ func ListTrackedTimesByRepository(ctx *context.APIContext) {
} }
ctx.SetTotalCountHeader(count) ctx.SetTotalCountHeader(count)
ctx.JSON(http.StatusOK, convert.ToTrackedTimeList(ctx, trackedTimes)) ctx.JSON(http.StatusOK, convert.ToTrackedTimeList(ctx, ctx.Doer, trackedTimes))
} }
// ListMyTrackedTimes lists all tracked times of the current user // ListMyTrackedTimes lists all tracked times of the current user
@ -629,5 +629,5 @@ func ListMyTrackedTimes(ctx *context.APIContext) {
} }
ctx.SetTotalCountHeader(count) ctx.SetTotalCountHeader(count)
ctx.JSON(http.StatusOK, convert.ToTrackedTimeList(ctx, trackedTimes)) ctx.JSON(http.StatusOK, convert.ToTrackedTimeList(ctx, ctx.Doer, trackedTimes))
} }

View file

@ -27,7 +27,7 @@ func GenerateActionsRunnerToken(ctx *context.PrivateContext) {
defer rd.Close() defer rd.Close()
if err := json.NewDecoder(rd).Decode(&genRequest); err != nil { if err := json.NewDecoder(rd).Decode(&genRequest); err != nil {
log.Error("%v", err) log.Error("JSON Decode failed: %v", err)
ctx.JSON(http.StatusInternalServerError, private.Response{ ctx.JSON(http.StatusInternalServerError, private.Response{
Err: err.Error(), Err: err.Error(),
}) })
@ -36,7 +36,7 @@ func GenerateActionsRunnerToken(ctx *context.PrivateContext) {
owner, repo, err := parseScope(ctx, genRequest.Scope) owner, repo, err := parseScope(ctx, genRequest.Scope)
if err != nil { if err != nil {
log.Error("%v", err) log.Error("parseScope failed: %v", err)
ctx.JSON(http.StatusInternalServerError, private.Response{ ctx.JSON(http.StatusInternalServerError, private.Response{
Err: err.Error(), Err: err.Error(),
}) })
@ -46,18 +46,18 @@ func GenerateActionsRunnerToken(ctx *context.PrivateContext) {
if errors.Is(err, util.ErrNotExist) || (token != nil && !token.IsActive) { if errors.Is(err, util.ErrNotExist) || (token != nil && !token.IsActive) {
token, err = actions_model.NewRunnerToken(ctx, owner, repo) token, err = actions_model.NewRunnerToken(ctx, owner, repo)
if err != nil { if err != nil {
err := fmt.Sprintf("error while creating runner token: %v", err) errMsg := fmt.Sprintf("error while creating runner token: %v", err)
log.Error("%v", err) log.Error("NewRunnerToken failed: %v", errMsg)
ctx.JSON(http.StatusInternalServerError, private.Response{ ctx.JSON(http.StatusInternalServerError, private.Response{
Err: err, Err: errMsg,
}) })
return return
} }
} else if err != nil { } else if err != nil {
err := fmt.Sprintf("could not get unactivated runner token: %v", err) errMsg := fmt.Sprintf("could not get unactivated runner token: %v", err)
log.Error("%v", err) log.Error("GetLatestRunnerToken failed: %v", errMsg)
ctx.JSON(http.StatusInternalServerError, private.Response{ ctx.JSON(http.StatusInternalServerError, private.Response{
Err: err, Err: errMsg,
}) })
return return
} }

View file

@ -47,7 +47,7 @@ func verifyCommits(oldCommitID, newCommitID string, repo *git.Repository, env []
_ = stdoutWriter.Close() _ = stdoutWriter.Close()
err := readAndVerifyCommitsFromShaReader(stdoutReader, repo, env) err := readAndVerifyCommitsFromShaReader(stdoutReader, repo, env)
if err != nil { if err != nil {
log.Error("%v", err) log.Error("readAndVerifyCommitsFromShaReader failed: %v", err)
cancel() cancel()
} }
_ = stdoutReader.Close() _ = stdoutReader.Close()
@ -66,7 +66,6 @@ func readAndVerifyCommitsFromShaReader(input io.ReadCloser, repo *git.Repository
line := scanner.Text() line := scanner.Text()
err := readAndVerifyCommit(line, repo, env) err := readAndVerifyCommit(line, repo, env)
if err != nil { if err != nil {
log.Error("%v", err)
return err return err
} }
} }

View file

@ -35,7 +35,7 @@ func SendEmail(ctx *context.PrivateContext) {
defer rd.Close() defer rd.Close()
if err := json.NewDecoder(rd).Decode(&mail); err != nil { if err := json.NewDecoder(rd).Decode(&mail); err != nil {
log.Error("%v", err) log.Error("JSON Decode failed: %v", err)
ctx.JSON(http.StatusInternalServerError, private.Response{ ctx.JSON(http.StatusInternalServerError, private.Response{
Err: err.Error(), Err: err.Error(),
}) })

View file

@ -403,7 +403,6 @@ func EditUserPost(ctx *context.Context) {
ctx.Data["Err_Password"] = true ctx.Data["Err_Password"] = true
ctx.RenderWithErr(ctx.Tr("auth.password_pwned"), tplUserEdit, &form) ctx.RenderWithErr(ctx.Tr("auth.password_pwned"), tplUserEdit, &form)
case password.IsErrIsPwnedRequest(err): case password.IsErrIsPwnedRequest(err):
log.Error("%s", err.Error())
ctx.Data["Err_Password"] = true ctx.Data["Err_Password"] = true
ctx.RenderWithErr(ctx.Tr("auth.password_pwned_err"), tplUserEdit, &form) ctx.RenderWithErr(ctx.Tr("auth.password_pwned_err"), tplUserEdit, &form)
default: default:

View file

@ -215,7 +215,6 @@ func ResetPasswdPost(ctx *context.Context) {
case errors.Is(err, password.ErrIsPwned): case errors.Is(err, password.ErrIsPwned):
ctx.RenderWithErr(ctx.Tr("auth.password_pwned"), tplResetPassword, nil) ctx.RenderWithErr(ctx.Tr("auth.password_pwned"), tplResetPassword, nil)
case password.IsErrIsPwnedRequest(err): case password.IsErrIsPwnedRequest(err):
log.Error("%s", err.Error())
ctx.RenderWithErr(ctx.Tr("auth.password_pwned_err"), tplResetPassword, nil) ctx.RenderWithErr(ctx.Tr("auth.password_pwned_err"), tplResetPassword, nil)
default: default:
ctx.ServerError("UpdateAuth", err) ctx.ServerError("UpdateAuth", err)
@ -299,7 +298,6 @@ func MustChangePasswordPost(ctx *context.Context) {
ctx.Data["Err_Password"] = true ctx.Data["Err_Password"] = true
ctx.RenderWithErr(ctx.Tr("auth.password_pwned"), tplMustChangePassword, &form) ctx.RenderWithErr(ctx.Tr("auth.password_pwned"), tplMustChangePassword, &form)
case password.IsErrIsPwnedRequest(err): case password.IsErrIsPwnedRequest(err):
log.Error("%s", err.Error())
ctx.Data["Err_Password"] = true ctx.Data["Err_Password"] = true
ctx.RenderWithErr(ctx.Tr("auth.password_pwned_err"), tplMustChangePassword, &form) ctx.RenderWithErr(ctx.Tr("auth.password_pwned_err"), tplMustChangePassword, &form)
default: default:

View file

@ -7,7 +7,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"net/http" "net/http"
"net/url"
"strconv" "strconv"
"strings" "strings"
@ -195,14 +194,15 @@ func NewProjectPost(ctx *context.Context) {
// ChangeProjectStatus updates the status of a project between "open" and "close" // ChangeProjectStatus updates the status of a project between "open" and "close"
func ChangeProjectStatus(ctx *context.Context) { func ChangeProjectStatus(ctx *context.Context) {
toClose := false var toClose bool
switch ctx.Params(":action") { switch ctx.Params(":action") {
case "open": case "open":
toClose = false toClose = false
case "close": case "close":
toClose = true toClose = true
default: default:
ctx.Redirect(ctx.ContextUser.HomeLink() + "/-/projects") ctx.JSONRedirect(ctx.ContextUser.HomeLink() + "/-/projects")
return
} }
id := ctx.ParamsInt64(":id") id := ctx.ParamsInt64(":id")
@ -210,7 +210,7 @@ func ChangeProjectStatus(ctx *context.Context) {
ctx.NotFoundOrServerError("ChangeProjectStatusByRepoIDAndID", project_model.IsErrProjectNotExist, err) ctx.NotFoundOrServerError("ChangeProjectStatusByRepoIDAndID", project_model.IsErrProjectNotExist, err)
return return
} }
ctx.Redirect(ctx.ContextUser.HomeLink() + "/-/projects?state=" + url.QueryEscape(ctx.Params(":action"))) ctx.JSONRedirect(fmt.Sprintf("%s/-/projects/%d", ctx.ContextUser.HomeLink(), id))
} }
// DeleteProject delete a project // DeleteProject delete a project

View file

@ -2179,7 +2179,7 @@ func GetIssueInfo(ctx *context.Context) {
} }
} }
ctx.JSON(http.StatusOK, convert.ToIssue(ctx, issue)) ctx.JSON(http.StatusOK, convert.ToIssue(ctx, ctx.Doer, issue))
} }
// UpdateIssueTitle change issue's title // UpdateIssueTitle change issue's title
@ -2713,7 +2713,7 @@ func SearchIssues(ctx *context.Context) {
} }
ctx.SetTotalCountHeader(total) ctx.SetTotalCountHeader(total)
ctx.JSON(http.StatusOK, convert.ToIssueList(ctx, issues)) ctx.JSON(http.StatusOK, convert.ToIssueList(ctx, ctx.Doer, issues))
} }
func getUserIDForFilter(ctx *context.Context, queryName string) int64 { func getUserIDForFilter(ctx *context.Context, queryName string) int64 {
@ -2883,7 +2883,7 @@ func ListIssues(ctx *context.Context) {
} }
ctx.SetTotalCountHeader(total) ctx.SetTotalCountHeader(total)
ctx.JSON(http.StatusOK, convert.ToIssueList(ctx, issues)) ctx.JSON(http.StatusOK, convert.ToIssueList(ctx, ctx.Doer, issues))
} }
func BatchDeleteIssues(ctx *context.Context) { func BatchDeleteIssues(ctx *context.Context) {

View file

@ -7,7 +7,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"net/http" "net/http"
"net/url"
"strings" "strings"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
@ -180,14 +179,10 @@ func ChangeProjectStatus(ctx *context.Context) {
id := ctx.ParamsInt64(":id") id := ctx.ParamsInt64(":id")
if err := project_model.ChangeProjectStatusByRepoIDAndID(ctx, ctx.Repo.Repository.ID, id, toClose); err != nil { if err := project_model.ChangeProjectStatusByRepoIDAndID(ctx, ctx.Repo.Repository.ID, id, toClose); err != nil {
if project_model.IsErrProjectNotExist(err) { ctx.NotFoundOrServerError("ChangeProjectStatusByRepoIDAndID", project_model.IsErrProjectNotExist, err)
ctx.NotFound("", err)
} else {
ctx.ServerError("ChangeProjectStatusByIDAndRepoID", err)
}
return return
} }
ctx.JSONRedirect(ctx.Repo.RepoLink + "/projects?state=" + url.QueryEscape(ctx.Params(":action"))) ctx.JSONRedirect(fmt.Sprintf("%s/projects/%d", ctx.Repo.RepoLink, id))
} }
// DeleteProject delete a project // DeleteProject delete a project

View file

@ -74,7 +74,6 @@ func AccountPost(ctx *context.Context) {
case errors.Is(err, password.ErrIsPwned): case errors.Is(err, password.ErrIsPwned):
ctx.Flash.Error(ctx.Tr("auth.password_pwned")) ctx.Flash.Error(ctx.Tr("auth.password_pwned"))
case password.IsErrIsPwnedRequest(err): case password.IsErrIsPwnedRequest(err):
log.Error("%s", err.Error())
ctx.Flash.Error(ctx.Tr("auth.password_pwned_err")) ctx.Flash.Error(ctx.Tr("auth.password_pwned_err"))
default: default:
ctx.ServerError("UpdateAuth", err) ctx.ServerError("UpdateAuth", err)

View file

@ -49,7 +49,7 @@ func (n *actionsNotifier) NewIssue(ctx context.Context, issue *issues_model.Issu
newNotifyInputFromIssue(issue, webhook_module.HookEventIssues).WithPayload(&api.IssuePayload{ newNotifyInputFromIssue(issue, webhook_module.HookEventIssues).WithPayload(&api.IssuePayload{
Action: api.HookIssueOpened, Action: api.HookIssueOpened,
Index: issue.Index, Index: issue.Index,
Issue: convert.ToAPIIssue(ctx, issue), Issue: convert.ToAPIIssue(ctx, issue.Poster, issue),
Repository: convert.ToRepo(ctx, issue.Repo, permission), Repository: convert.ToRepo(ctx, issue.Repo, permission),
Sender: convert.ToUser(ctx, issue.Poster, nil), Sender: convert.ToUser(ctx, issue.Poster, nil),
}).Notify(withMethod(ctx, "NewIssue")) }).Notify(withMethod(ctx, "NewIssue"))
@ -89,7 +89,7 @@ func (n *actionsNotifier) IssueChangeContent(ctx context.Context, doer *user_mod
WithPayload(&api.IssuePayload{ WithPayload(&api.IssuePayload{
Action: api.HookIssueEdited, Action: api.HookIssueEdited,
Index: issue.Index, Index: issue.Index,
Issue: convert.ToAPIIssue(ctx, issue), Issue: convert.ToAPIIssue(ctx, doer, issue),
Repository: convert.ToRepo(ctx, issue.Repo, permission), Repository: convert.ToRepo(ctx, issue.Repo, permission),
Sender: convert.ToUser(ctx, doer, nil), Sender: convert.ToUser(ctx, doer, nil),
}). }).
@ -127,7 +127,7 @@ func (n *actionsNotifier) IssueChangeStatus(ctx context.Context, doer *user_mode
} }
apiIssue := &api.IssuePayload{ apiIssue := &api.IssuePayload{
Index: issue.Index, Index: issue.Index,
Issue: convert.ToAPIIssue(ctx, issue), Issue: convert.ToAPIIssue(ctx, doer, issue),
Repository: convert.ToRepo(ctx, issue.Repo, permission), Repository: convert.ToRepo(ctx, issue.Repo, permission),
Sender: convert.ToUser(ctx, doer, nil), Sender: convert.ToUser(ctx, doer, nil),
} }
@ -229,7 +229,7 @@ func notifyIssueChange(ctx context.Context, doer *user_model.User, issue *issues
WithPayload(&api.IssuePayload{ WithPayload(&api.IssuePayload{
Action: action, Action: action,
Index: issue.Index, Index: issue.Index,
Issue: convert.ToAPIIssue(ctx, issue), Issue: convert.ToAPIIssue(ctx, doer, issue),
Repository: convert.ToRepo(ctx, issue.Repo, permission), Repository: convert.ToRepo(ctx, issue.Repo, permission),
Sender: convert.ToUser(ctx, doer, nil), Sender: convert.ToUser(ctx, doer, nil),
}). }).
@ -293,7 +293,7 @@ func notifyIssueCommentChange(ctx context.Context, doer *user_model.User, commen
payload := &api.IssueCommentPayload{ payload := &api.IssueCommentPayload{
Action: action, Action: action,
Issue: convert.ToAPIIssue(ctx, comment.Issue), Issue: convert.ToAPIIssue(ctx, doer, comment.Issue),
Comment: convert.ToAPIComment(ctx, comment.Issue.Repo, comment), Comment: convert.ToAPIComment(ctx, comment.Issue.Repo, comment),
Repository: convert.ToRepo(ctx, comment.Issue.Repo, permission), Repository: convert.ToRepo(ctx, comment.Issue.Repo, permission),
Sender: convert.ToUser(ctx, doer, nil), Sender: convert.ToUser(ctx, doer, nil),

View file

@ -560,12 +560,9 @@ func DetectAndHandleSchedules(ctx context.Context, repo *repo_model.Repository)
} }
// We need a notifyInput to call handleSchedules // We need a notifyInput to call handleSchedules
// Here we use the commit author as the Doer of the notifyInput // if repo is a mirror, commit author maybe an external user,
commitUser, err := user_model.GetUserByEmail(ctx, commit.Author.Email) // so we use action user as the Doer of the notifyInput
if err != nil { notifyInput := newNotifyInput(repo, user_model.NewActionsUser(), webhook_module.HookEventSchedule)
return fmt.Errorf("get user by email: %w", err)
}
notifyInput := newNotifyInput(repo, commitUser, webhook_module.HookEventSchedule)
return handleSchedules(ctx, scheduleWorkflows, commit, notifyInput, repo.DefaultBranch) return handleSchedules(ctx, scheduleWorkflows, commit, notifyInput, repo.DefaultBranch)
} }

View file

@ -79,11 +79,11 @@ func VerifyCaptcha(ctx *Context, tpl base.TplName, form any) {
case setting.CfTurnstile: case setting.CfTurnstile:
valid, err = turnstile.Verify(ctx, ctx.Req.Form.Get(cfTurnstileResponseField)) valid, err = turnstile.Verify(ctx, ctx.Req.Form.Get(cfTurnstileResponseField))
default: default:
ctx.ServerError("Unknown Captcha Type", fmt.Errorf("Unknown Captcha Type: %s", setting.Service.CaptchaType)) ctx.ServerError("Unknown Captcha Type", fmt.Errorf("unknown Captcha Type: %s", setting.Service.CaptchaType))
return return
} }
if err != nil { if err != nil {
log.Debug("%v", err) log.Debug("Captcha Verify failed: %v", err)
} }
if !valid { if !valid {

View file

@ -18,19 +18,19 @@ import (
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
) )
func ToIssue(ctx context.Context, issue *issues_model.Issue) *api.Issue { func ToIssue(ctx context.Context, doer *user_model.User, issue *issues_model.Issue) *api.Issue {
return toIssue(ctx, issue, WebAssetDownloadURL) return toIssue(ctx, doer, issue, WebAssetDownloadURL)
} }
// ToAPIIssue converts an Issue to API format // ToAPIIssue converts an Issue to API format
// it assumes some fields assigned with values: // it assumes some fields assigned with values:
// Required - Poster, Labels, // Required - Poster, Labels,
// Optional - Milestone, Assignee, PullRequest // Optional - Milestone, Assignee, PullRequest
func ToAPIIssue(ctx context.Context, issue *issues_model.Issue) *api.Issue { func ToAPIIssue(ctx context.Context, doer *user_model.User, issue *issues_model.Issue) *api.Issue {
return toIssue(ctx, issue, APIAssetDownloadURL) return toIssue(ctx, doer, issue, APIAssetDownloadURL)
} }
func toIssue(ctx context.Context, issue *issues_model.Issue, getDownloadURL func(repo *repo_model.Repository, attach *repo_model.Attachment) string) *api.Issue { func toIssue(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, getDownloadURL func(repo *repo_model.Repository, attach *repo_model.Attachment) string) *api.Issue {
if err := issue.LoadLabels(ctx); err != nil { if err := issue.LoadLabels(ctx); err != nil {
return &api.Issue{} return &api.Issue{}
} }
@ -44,7 +44,7 @@ func toIssue(ctx context.Context, issue *issues_model.Issue, getDownloadURL func
apiIssue := &api.Issue{ apiIssue := &api.Issue{
ID: issue.ID, ID: issue.ID,
Index: issue.Index, Index: issue.Index,
Poster: ToUser(ctx, issue.Poster, nil), Poster: ToUser(ctx, issue.Poster, doer),
Title: issue.Title, Title: issue.Title,
Body: issue.Content, Body: issue.Content,
Attachments: toAttachments(issue.Repo, issue.Attachments, getDownloadURL), Attachments: toAttachments(issue.Repo, issue.Attachments, getDownloadURL),
@ -114,25 +114,25 @@ func toIssue(ctx context.Context, issue *issues_model.Issue, getDownloadURL func
} }
// ToIssueList converts an IssueList to API format // ToIssueList converts an IssueList to API format
func ToIssueList(ctx context.Context, il issues_model.IssueList) []*api.Issue { func ToIssueList(ctx context.Context, doer *user_model.User, il issues_model.IssueList) []*api.Issue {
result := make([]*api.Issue, len(il)) result := make([]*api.Issue, len(il))
for i := range il { for i := range il {
result[i] = ToIssue(ctx, il[i]) result[i] = ToIssue(ctx, doer, il[i])
} }
return result return result
} }
// ToAPIIssueList converts an IssueList to API format // ToAPIIssueList converts an IssueList to API format
func ToAPIIssueList(ctx context.Context, il issues_model.IssueList) []*api.Issue { func ToAPIIssueList(ctx context.Context, doer *user_model.User, il issues_model.IssueList) []*api.Issue {
result := make([]*api.Issue, len(il)) result := make([]*api.Issue, len(il))
for i := range il { for i := range il {
result[i] = ToAPIIssue(ctx, il[i]) result[i] = ToAPIIssue(ctx, doer, il[i])
} }
return result return result
} }
// ToTrackedTime converts TrackedTime to API format // ToTrackedTime converts TrackedTime to API format
func ToTrackedTime(ctx context.Context, t *issues_model.TrackedTime) (apiT *api.TrackedTime) { func ToTrackedTime(ctx context.Context, doer *user_model.User, t *issues_model.TrackedTime) (apiT *api.TrackedTime) {
apiT = &api.TrackedTime{ apiT = &api.TrackedTime{
ID: t.ID, ID: t.ID,
IssueID: t.IssueID, IssueID: t.IssueID,
@ -141,7 +141,7 @@ func ToTrackedTime(ctx context.Context, t *issues_model.TrackedTime) (apiT *api.
Created: t.Created, Created: t.Created,
} }
if t.Issue != nil { if t.Issue != nil {
apiT.Issue = ToAPIIssue(ctx, t.Issue) apiT.Issue = ToAPIIssue(ctx, doer, t.Issue)
} }
if t.User != nil { if t.User != nil {
apiT.UserName = t.User.Name apiT.UserName = t.User.Name
@ -192,10 +192,10 @@ func ToStopWatches(ctx context.Context, sws []*issues_model.Stopwatch) (api.Stop
} }
// ToTrackedTimeList converts TrackedTimeList to API format // ToTrackedTimeList converts TrackedTimeList to API format
func ToTrackedTimeList(ctx context.Context, tl issues_model.TrackedTimeList) api.TrackedTimeList { func ToTrackedTimeList(ctx context.Context, doer *user_model.User, tl issues_model.TrackedTimeList) api.TrackedTimeList {
result := make([]*api.TrackedTime, 0, len(tl)) result := make([]*api.TrackedTime, 0, len(tl))
for _, t := range tl { for _, t := range tl {
result = append(result, ToTrackedTime(ctx, t)) result = append(result, ToTrackedTime(ctx, doer, t))
} }
return result return result
} }

View file

@ -120,7 +120,7 @@ func ToTimelineComment(ctx context.Context, repo *repo_model.Repository, c *issu
return nil return nil
} }
comment.TrackedTime = ToTrackedTime(ctx, c.Time) comment.TrackedTime = ToTrackedTime(ctx, doer, c.Time)
} }
if c.RefIssueID != 0 { if c.RefIssueID != 0 {
@ -129,7 +129,7 @@ func ToTimelineComment(ctx context.Context, repo *repo_model.Repository, c *issu
log.Error("GetIssueByID(%d): %v", c.RefIssueID, err) log.Error("GetIssueByID(%d): %v", c.RefIssueID, err)
return nil return nil
} }
comment.RefIssue = ToAPIIssue(ctx, issue) comment.RefIssue = ToAPIIssue(ctx, doer, issue)
} }
if c.RefCommentID != 0 { if c.RefCommentID != 0 {
@ -180,7 +180,7 @@ func ToTimelineComment(ctx context.Context, repo *repo_model.Repository, c *issu
} }
if c.DependentIssue != nil { if c.DependentIssue != nil {
comment.DependentIssue = ToAPIIssue(ctx, c.DependentIssue) comment.DependentIssue = ToAPIIssue(ctx, doer, c.DependentIssue)
} }
return comment return comment

View file

@ -33,7 +33,7 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u
return nil return nil
} }
apiIssue := ToAPIIssue(ctx, pr.Issue) apiIssue := ToAPIIssue(ctx, doer, pr.Issue)
if err := pr.LoadBaseRepo(ctx); err != nil { if err := pr.LoadBaseRepo(ctx); err != nil {
log.Error("GetRepositoryById[%d]: %v", pr.ID, err) log.Error("GetRepositoryById[%d]: %v", pr.ID, err)
return nil return nil

View file

@ -72,6 +72,11 @@ func (g *GitBucketDownloader) LogString() string {
// NewGitBucketDownloader creates a GitBucket downloader // NewGitBucketDownloader creates a GitBucket downloader
func NewGitBucketDownloader(ctx context.Context, baseURL, userName, password, token, repoOwner, repoName string) *GitBucketDownloader { func NewGitBucketDownloader(ctx context.Context, baseURL, userName, password, token, repoOwner, repoName string) *GitBucketDownloader {
githubDownloader := NewGithubDownloaderV3(ctx, baseURL, userName, password, token, repoOwner, repoName) githubDownloader := NewGithubDownloaderV3(ctx, baseURL, userName, password, token, repoOwner, repoName)
// Gitbucket 4.40 uses different internal hard-coded perPage values.
// Issues, PRs, and other major parts use 25. Release page uses 10.
// Some API doesn't support paging yet. Sounds difficult, but using
// minimum number among them worked out very well.
githubDownloader.maxPerPage = 10
githubDownloader.SkipReactions = true githubDownloader.SkipReactions = true
githubDownloader.SkipReviews = true githubDownloader.SkipReviews = true
return &GitBucketDownloader{ return &GitBucketDownloader{

View file

@ -13,6 +13,7 @@ import (
system_model "code.gitea.io/gitea/models/system" system_model "code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/modules/cache" "code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
giturl "code.gitea.io/gitea/modules/git/url"
"code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
@ -30,10 +31,15 @@ const gitShortEmptySha = "0000000"
// UpdateAddress writes new address to Git repository and database // UpdateAddress writes new address to Git repository and database
func UpdateAddress(ctx context.Context, m *repo_model.Mirror, addr string) error { func UpdateAddress(ctx context.Context, m *repo_model.Mirror, addr string) error {
u, err := giturl.Parse(addr)
if err != nil {
return fmt.Errorf("invalid addr: %v", err)
}
remoteName := m.GetRemoteName() remoteName := m.GetRemoteName()
repoPath := m.GetRepository(ctx).RepoPath() repoPath := m.GetRepository(ctx).RepoPath()
// Remove old remote // Remove old remote
_, _, err := git.NewCommand(ctx, "remote", "rm").AddDynamicArguments(remoteName).RunStdString(&git.RunOpts{Dir: repoPath}) _, _, err = git.NewCommand(ctx, "remote", "rm").AddDynamicArguments(remoteName).RunStdString(&git.RunOpts{Dir: repoPath})
if err != nil && !strings.HasPrefix(err.Error(), "exit status 128 - fatal: No such remote ") { if err != nil && !strings.HasPrefix(err.Error(), "exit status 128 - fatal: No such remote ") {
return err return err
} }
@ -70,7 +76,9 @@ func UpdateAddress(ctx context.Context, m *repo_model.Mirror, addr string) error
} }
} }
m.Repo.OriginalURL = addr // erase authentication before storing in database
u.User = nil
m.Repo.OriginalURL = u.String()
return repo_model.UpdateRepositoryCols(ctx, m.Repo, "original_url") return repo_model.UpdateRepositoryCols(ctx, m.Repo, "original_url")
} }
@ -449,19 +457,17 @@ func SyncPullMirror(ctx context.Context, repoID int64) bool {
return false return false
} }
var gitRepo *git.Repository gitRepo, err := gitrepo.OpenRepository(ctx, m.Repo)
if len(results) == 0 { if err != nil {
log.Trace("SyncMirrors [repo: %-v]: no branches updated", m.Repo) log.Error("SyncMirrors [repo: %-v]: unable to OpenRepository: %v", m.Repo, err)
} else { return false
log.Trace("SyncMirrors [repo: %-v]: %d branches updated", m.Repo, len(results)) }
gitRepo, err = gitrepo.OpenRepository(ctx, m.Repo) defer gitRepo.Close()
if err != nil {
log.Error("SyncMirrors [repo: %-v]: unable to OpenRepository: %v", m.Repo, err)
return false
}
defer gitRepo.Close()
log.Trace("SyncMirrors [repo: %-v]: %d branches updated", m.Repo, len(results))
if len(results) > 0 {
if ok := checkAndUpdateEmptyRepository(ctx, m, gitRepo, results); !ok { if ok := checkAndUpdateEmptyRepository(ctx, m, gitRepo, results); !ok {
log.Error("SyncMirrors [repo: %-v]: checkAndUpdateEmptyRepository: %v", m.Repo, err)
return false return false
} }
} }
@ -534,16 +540,24 @@ func SyncPullMirror(ctx context.Context, repoID int64) bool {
} }
log.Trace("SyncMirrors [repo: %-v]: done notifying updated branches/tags - now updating last commit time", m.Repo) log.Trace("SyncMirrors [repo: %-v]: done notifying updated branches/tags - now updating last commit time", m.Repo)
// Get latest commit date and update to current repository updated time isEmpty, err := gitRepo.IsEmpty()
commitDate, err := git.GetLatestCommitTime(ctx, m.Repo.RepoPath())
if err != nil { if err != nil {
log.Error("SyncMirrors [repo: %-v]: unable to GetLatestCommitDate: %v", m.Repo, err) log.Error("SyncMirrors [repo: %-v]: unable to check empty git repo: %v", m.Repo, err)
return false return false
} }
if !isEmpty {
// Get latest commit date and update to current repository updated time
commitDate, err := git.GetLatestCommitTime(ctx, m.Repo.RepoPath())
if err != nil {
log.Error("SyncMirrors [repo: %-v]: unable to GetLatestCommitDate: %v", m.Repo, err)
return false
}
if err = repo_model.UpdateRepositoryUpdatedTime(ctx, m.RepoID, commitDate); err != nil {
log.Error("SyncMirrors [repo: %-v]: unable to update repository 'updated_unix': %v", m.Repo, err)
return false
}
if err = repo_model.UpdateRepositoryUpdatedTime(ctx, m.RepoID, commitDate); err != nil {
log.Error("SyncMirrors [repo: %-v]: unable to update repository 'updated_unix': %v", m.Repo, err)
return false
} }
log.Trace("SyncMirrors [repo: %-v]: Successfully updated", m.Repo) log.Trace("SyncMirrors [repo: %-v]: Successfully updated", m.Repo)

View file

@ -91,7 +91,7 @@ func AutoMergePullRequest(ctx context.Context, doer *user_model.User, pr *issues
// NewPullRequest notifies new pull request to notifiers // NewPullRequest notifies new pull request to notifiers
func NewPullRequest(ctx context.Context, pr *issues_model.PullRequest, mentions []*user_model.User) { func NewPullRequest(ctx context.Context, pr *issues_model.PullRequest, mentions []*user_model.User) {
if err := pr.LoadIssue(ctx); err != nil { if err := pr.LoadIssue(ctx); err != nil {
log.Error("%v", err) log.Error("LoadIssue failed: %v", err)
return return
} }
if err := pr.Issue.LoadPoster(ctx); err != nil { if err := pr.Issue.LoadPoster(ctx); err != nil {
@ -112,7 +112,7 @@ func PullRequestSynchronized(ctx context.Context, doer *user_model.User, pr *iss
// PullRequestReview notifies new pull request review // PullRequestReview notifies new pull request review
func PullRequestReview(ctx context.Context, pr *issues_model.PullRequest, review *issues_model.Review, comment *issues_model.Comment, mentions []*user_model.User) { func PullRequestReview(ctx context.Context, pr *issues_model.PullRequest, review *issues_model.Review, comment *issues_model.Comment, mentions []*user_model.User) {
if err := review.LoadReviewer(ctx); err != nil { if err := review.LoadReviewer(ctx); err != nil {
log.Error("%v", err) log.Error("LoadReviewer failed: %v", err)
return return
} }
for _, notifier := range notifiers { for _, notifier := range notifiers {

View file

@ -28,7 +28,7 @@ func CherryPick(ctx context.Context, repo *repo_model.Repository, doer *user_mod
t, err := NewTemporaryUploadRepository(ctx, repo) t, err := NewTemporaryUploadRepository(ctx, repo)
if err != nil { if err != nil {
log.Error("%v", err) log.Error("NewTemporaryUploadRepository failed: %v", err)
} }
defer t.Close() defer t.Close()
if err := t.Clone(opts.OldBranch, false); err != nil { if err := t.Clone(opts.OldBranch, false); err != nil {

View file

@ -111,7 +111,7 @@ func ApplyDiffPatch(ctx context.Context, repo *repo_model.Repository, doer *user
t, err := NewTemporaryUploadRepository(ctx, repo) t, err := NewTemporaryUploadRepository(ctx, repo)
if err != nil { if err != nil {
log.Error("%v", err) log.Error("NewTemporaryUploadRepository failed: %v", err)
} }
defer t.Close() defer t.Close()
if err := t.Clone(opts.OldBranch, true); err != nil { if err := t.Clone(opts.OldBranch, true); err != nil {

View file

@ -143,7 +143,7 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
t, err := NewTemporaryUploadRepository(ctx, repo) t, err := NewTemporaryUploadRepository(ctx, repo)
if err != nil { if err != nil {
log.Error("%v", err) log.Error("NewTemporaryUploadRepository failed: %v", err)
} }
defer t.Close() defer t.Close()
hasOldBranch := true hasOldBranch := true

View file

@ -67,7 +67,7 @@ func (m *webhookNotifier) IssueClearLabels(ctx context.Context, doer *user_model
err = PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, webhook_module.HookEventIssueLabel, &api.IssuePayload{ err = PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, webhook_module.HookEventIssueLabel, &api.IssuePayload{
Action: api.HookIssueLabelCleared, Action: api.HookIssueLabelCleared,
Index: issue.Index, Index: issue.Index,
Issue: convert.ToAPIIssue(ctx, issue), Issue: convert.ToAPIIssue(ctx, doer, issue),
Repository: convert.ToRepo(ctx, issue.Repo, permission), Repository: convert.ToRepo(ctx, issue.Repo, permission),
Sender: convert.ToUser(ctx, doer, nil), Sender: convert.ToUser(ctx, doer, nil),
}) })
@ -168,7 +168,7 @@ func (m *webhookNotifier) IssueChangeAssignee(ctx context.Context, doer *user_mo
permission, _ := access_model.GetUserRepoPermission(ctx, issue.Repo, doer) permission, _ := access_model.GetUserRepoPermission(ctx, issue.Repo, doer)
apiIssue := &api.IssuePayload{ apiIssue := &api.IssuePayload{
Index: issue.Index, Index: issue.Index,
Issue: convert.ToAPIIssue(ctx, issue), Issue: convert.ToAPIIssue(ctx, doer, issue),
Repository: convert.ToRepo(ctx, issue.Repo, permission), Repository: convert.ToRepo(ctx, issue.Repo, permission),
Sender: convert.ToUser(ctx, doer, nil), Sender: convert.ToUser(ctx, doer, nil),
} }
@ -214,7 +214,7 @@ func (m *webhookNotifier) IssueChangeTitle(ctx context.Context, doer *user_model
From: oldTitle, From: oldTitle,
}, },
}, },
Issue: convert.ToAPIIssue(ctx, issue), Issue: convert.ToAPIIssue(ctx, doer, issue),
Repository: convert.ToRepo(ctx, issue.Repo, permission), Repository: convert.ToRepo(ctx, issue.Repo, permission),
Sender: convert.ToUser(ctx, doer, nil), Sender: convert.ToUser(ctx, doer, nil),
}) })
@ -250,7 +250,7 @@ func (m *webhookNotifier) IssueChangeStatus(ctx context.Context, doer *user_mode
} else { } else {
apiIssue := &api.IssuePayload{ apiIssue := &api.IssuePayload{
Index: issue.Index, Index: issue.Index,
Issue: convert.ToAPIIssue(ctx, issue), Issue: convert.ToAPIIssue(ctx, doer, issue),
Repository: convert.ToRepo(ctx, issue.Repo, permission), Repository: convert.ToRepo(ctx, issue.Repo, permission),
Sender: convert.ToUser(ctx, doer, nil), Sender: convert.ToUser(ctx, doer, nil),
CommitID: commitID, CommitID: commitID,
@ -281,7 +281,7 @@ func (m *webhookNotifier) NewIssue(ctx context.Context, issue *issues_model.Issu
if err := PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, webhook_module.HookEventIssues, &api.IssuePayload{ if err := PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, webhook_module.HookEventIssues, &api.IssuePayload{
Action: api.HookIssueOpened, Action: api.HookIssueOpened,
Index: issue.Index, Index: issue.Index,
Issue: convert.ToAPIIssue(ctx, issue), Issue: convert.ToAPIIssue(ctx, issue.Poster, issue),
Repository: convert.ToRepo(ctx, issue.Repo, permission), Repository: convert.ToRepo(ctx, issue.Repo, permission),
Sender: convert.ToUser(ctx, issue.Poster, nil), Sender: convert.ToUser(ctx, issue.Poster, nil),
}); err != nil { }); err != nil {
@ -349,7 +349,7 @@ func (m *webhookNotifier) IssueChangeContent(ctx context.Context, doer *user_mod
From: oldContent, From: oldContent,
}, },
}, },
Issue: convert.ToAPIIssue(ctx, issue), Issue: convert.ToAPIIssue(ctx, doer, issue),
Repository: convert.ToRepo(ctx, issue.Repo, permission), Repository: convert.ToRepo(ctx, issue.Repo, permission),
Sender: convert.ToUser(ctx, doer, nil), Sender: convert.ToUser(ctx, doer, nil),
}) })
@ -384,7 +384,7 @@ func (m *webhookNotifier) UpdateComment(ctx context.Context, doer *user_model.Us
permission, _ := access_model.GetUserRepoPermission(ctx, c.Issue.Repo, doer) permission, _ := access_model.GetUserRepoPermission(ctx, c.Issue.Repo, doer)
if err := PrepareWebhooks(ctx, EventSource{Repository: c.Issue.Repo}, eventType, &api.IssueCommentPayload{ if err := PrepareWebhooks(ctx, EventSource{Repository: c.Issue.Repo}, eventType, &api.IssueCommentPayload{
Action: api.HookIssueCommentEdited, Action: api.HookIssueCommentEdited,
Issue: convert.ToAPIIssue(ctx, c.Issue), Issue: convert.ToAPIIssue(ctx, doer, c.Issue),
Comment: convert.ToAPIComment(ctx, c.Issue.Repo, c), Comment: convert.ToAPIComment(ctx, c.Issue.Repo, c),
Changes: &api.ChangesPayload{ Changes: &api.ChangesPayload{
Body: &api.ChangesFromPayload{ Body: &api.ChangesFromPayload{
@ -412,7 +412,7 @@ func (m *webhookNotifier) CreateIssueComment(ctx context.Context, doer *user_mod
permission, _ := access_model.GetUserRepoPermission(ctx, repo, doer) permission, _ := access_model.GetUserRepoPermission(ctx, repo, doer)
if err := PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, eventType, &api.IssueCommentPayload{ if err := PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, eventType, &api.IssueCommentPayload{
Action: api.HookIssueCommentCreated, Action: api.HookIssueCommentCreated,
Issue: convert.ToAPIIssue(ctx, issue), Issue: convert.ToAPIIssue(ctx, doer, issue),
Comment: convert.ToAPIComment(ctx, repo, comment), Comment: convert.ToAPIComment(ctx, repo, comment),
Repository: convert.ToRepo(ctx, repo, permission), Repository: convert.ToRepo(ctx, repo, permission),
Sender: convert.ToUser(ctx, doer, nil), Sender: convert.ToUser(ctx, doer, nil),
@ -449,7 +449,7 @@ func (m *webhookNotifier) DeleteComment(ctx context.Context, doer *user_model.Us
permission, _ := access_model.GetUserRepoPermission(ctx, comment.Issue.Repo, doer) permission, _ := access_model.GetUserRepoPermission(ctx, comment.Issue.Repo, doer)
if err := PrepareWebhooks(ctx, EventSource{Repository: comment.Issue.Repo}, eventType, &api.IssueCommentPayload{ if err := PrepareWebhooks(ctx, EventSource{Repository: comment.Issue.Repo}, eventType, &api.IssueCommentPayload{
Action: api.HookIssueCommentDeleted, Action: api.HookIssueCommentDeleted,
Issue: convert.ToAPIIssue(ctx, comment.Issue), Issue: convert.ToAPIIssue(ctx, doer, comment.Issue),
Comment: convert.ToAPIComment(ctx, comment.Issue.Repo, comment), Comment: convert.ToAPIComment(ctx, comment.Issue.Repo, comment),
Repository: convert.ToRepo(ctx, comment.Issue.Repo, permission), Repository: convert.ToRepo(ctx, comment.Issue.Repo, permission),
Sender: convert.ToUser(ctx, doer, nil), Sender: convert.ToUser(ctx, doer, nil),
@ -533,7 +533,7 @@ func (m *webhookNotifier) IssueChangeLabels(ctx context.Context, doer *user_mode
err = PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, webhook_module.HookEventIssueLabel, &api.IssuePayload{ err = PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, webhook_module.HookEventIssueLabel, &api.IssuePayload{
Action: api.HookIssueLabelUpdated, Action: api.HookIssueLabelUpdated,
Index: issue.Index, Index: issue.Index,
Issue: convert.ToAPIIssue(ctx, issue), Issue: convert.ToAPIIssue(ctx, doer, issue),
Repository: convert.ToRepo(ctx, issue.Repo, permission), Repository: convert.ToRepo(ctx, issue.Repo, permission),
Sender: convert.ToUser(ctx, doer, nil), Sender: convert.ToUser(ctx, doer, nil),
}) })
@ -575,7 +575,7 @@ func (m *webhookNotifier) IssueChangeMilestone(ctx context.Context, doer *user_m
err = PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, webhook_module.HookEventIssueMilestone, &api.IssuePayload{ err = PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, webhook_module.HookEventIssueMilestone, &api.IssuePayload{
Action: hookAction, Action: hookAction,
Index: issue.Index, Index: issue.Index,
Issue: convert.ToAPIIssue(ctx, issue), Issue: convert.ToAPIIssue(ctx, doer, issue),
Repository: convert.ToRepo(ctx, issue.Repo, permission), Repository: convert.ToRepo(ctx, issue.Repo, permission),
Sender: convert.ToUser(ctx, doer, nil), Sender: convert.ToUser(ctx, doer, nil),
}) })

View file

@ -209,7 +209,7 @@ func updateWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model
if isOldWikiExist { if isOldWikiExist {
err := gitRepo.RemoveFilesFromIndex(oldWikiPath) err := gitRepo.RemoveFilesFromIndex(oldWikiPath)
if err != nil { if err != nil {
log.Error("%v", err) log.Error("RemoveFilesFromIndex failed: %v", err)
return err return err
} }
} }
@ -219,18 +219,18 @@ func updateWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model
objectHash, err := gitRepo.HashObject(strings.NewReader(content)) objectHash, err := gitRepo.HashObject(strings.NewReader(content))
if err != nil { if err != nil {
log.Error("%v", err) log.Error("HashObject failed: %v", err)
return err return err
} }
if err := gitRepo.AddObjectToIndex("100644", objectHash, newWikiPath); err != nil { if err := gitRepo.AddObjectToIndex("100644", objectHash, newWikiPath); err != nil {
log.Error("%v", err) log.Error("AddObjectToIndex failed: %v", err)
return err return err
} }
tree, err := gitRepo.WriteTree() tree, err := gitRepo.WriteTree()
if err != nil { if err != nil {
log.Error("%v", err) log.Error("WriteTree failed: %v", err)
return err return err
} }
@ -255,7 +255,7 @@ func updateWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model
commitHash, err := gitRepo.CommitTree(doer.NewGitSig(), committer, tree, commitTreeOpts) commitHash, err := gitRepo.CommitTree(doer.NewGitSig(), committer, tree, commitTreeOpts)
if err != nil { if err != nil {
log.Error("%v", err) log.Error("CommitTree failed: %v", err)
return err return err
} }
@ -270,11 +270,11 @@ func updateWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model
0, 0,
), ),
}); err != nil { }); err != nil {
log.Error("%v", err) log.Error("Push failed: %v", err)
if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) { if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) {
return err return err
} }
return fmt.Errorf("Push: %w", err) return fmt.Errorf("failed to push: %w", err)
} }
return nil return nil

View file

@ -62,10 +62,7 @@
<div class="ui modal admin" id="detail-modal"> <div class="ui modal admin" id="detail-modal">
<div class="header">{{ctx.Locale.Tr "admin.notices.view_detail_header"}}</div> <div class="header">{{ctx.Locale.Tr "admin.notices.view_detail_header"}}</div>
<div class="content"> <div class="content"><pre></pre></div>
<div class="sub header"></div>
<pre></pre>
</div>
</div> </div>
{{template "admin/layout_footer" .}} {{template "admin/layout_footer" .}}

View file

@ -13,11 +13,9 @@
<div class="ui four wide column"> <div class="ui four wide column">
{{template "shared/user/profile_big_avatar" .}} {{template "shared/user/profile_big_avatar" .}}
</div> </div>
<div class="ui twelve wide column"> <div class="ui twelve wide column tw-mb-4">
<div class="tw-mb-4">
{{template "user/overview/header" .}} {{template "user/overview/header" .}}
</div> {{template "projects/list" .}}
{{template "projects/list" .}}
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,7 +1,7 @@
{{template "base/head" .}} {{template "base/head" .}}
<div role="main" aria-label="{{.Title}}" class="page-content repository projects view-project"> <div role="main" aria-label="{{.Title}}" class="page-content repository projects view-project">
{{template "shared/user/org_profile_avatar" .}} {{template "shared/user/org_profile_avatar" .}}
<div class="ui container"> <div class="ui container tw-mb-4">
{{template "user/overview/header" .}} {{template "user/overview/header" .}}
</div> </div>
<div class="ui container fluid padded"> <div class="ui container fluid padded">

View file

@ -18,10 +18,10 @@
{{end}} {{end}}
{{end}} {{end}}
<div class="ui top attached header clearing segment tw-relative commit-header {{$class}}"> <div class="ui top attached header clearing segment tw-relative commit-header {{$class}}">
<div class="tw-flex tw-mb-4 tw-flex-wrap"> <div class="tw-flex tw-mb-4 tw-gap-1">
<h3 class="tw-mb-0 tw-flex-1"><span class="commit-summary" title="{{.Commit.Summary}}">{{RenderCommitMessage $.Context .Commit.Message ($.Repository.ComposeMetas ctx)}}</span>{{template "repo/commit_statuses" dict "Status" .CommitStatus "Statuses" .CommitStatuses}}</h3> <h3 class="tw-mb-0 tw-flex-1"><span class="commit-summary" title="{{.Commit.Summary}}">{{RenderCommitMessage $.Context .Commit.Message ($.Repository.ComposeMetas ctx)}}</span>{{template "repo/commit_statuses" dict "Status" .CommitStatus "Statuses" .CommitStatuses}}</h3>
{{if not $.PageIsWiki}} {{if not $.PageIsWiki}}
<div> <div class="commit-header-buttons">
<a class="ui primary tiny button" href="{{.SourcePath}}"> <a class="ui primary tiny button" href="{{.SourcePath}}">
{{ctx.Locale.Tr "repo.diff.browse_source"}} {{ctx.Locale.Tr "repo.diff.browse_source"}}
</a> </a>

View file

@ -111,7 +111,7 @@
{{$isReviewFile := and $.IsSigned $.PageIsPullFiles (not $.IsArchived) $.IsShowingAllCommits}} {{$isReviewFile := and $.IsSigned $.PageIsPullFiles (not $.IsArchived) $.IsShowingAllCommits}}
<div class="diff-file-box diff-box file-content {{TabSizeClass $.Editorconfig $file.Name}} tw-mt-0" id="diff-{{$file.NameHash}}" data-old-filename="{{$file.OldName}}" data-new-filename="{{$file.Name}}" {{if or ($file.ShouldBeHidden) (not $isExpandable)}}data-folded="true"{{end}}> <div class="diff-file-box diff-box file-content {{TabSizeClass $.Editorconfig $file.Name}} tw-mt-0" id="diff-{{$file.NameHash}}" data-old-filename="{{$file.OldName}}" data-new-filename="{{$file.Name}}" {{if or ($file.ShouldBeHidden) (not $isExpandable)}}data-folded="true"{{end}}>
<h4 class="diff-file-header sticky-2nd-row ui top attached header tw-font-normal tw-flex tw-items-center tw-justify-between tw-flex-wrap"> <h4 class="diff-file-header sticky-2nd-row ui top attached header tw-font-normal tw-flex tw-items-center tw-justify-between tw-flex-wrap">
<div class="diff-file-name tw-flex tw-items-center gt-gap-2 tw-flex-wrap"> <div class="diff-file-name tw-flex tw-flex-1 tw-items-center tw-gap-1 tw-flex-wrap">
<button class="fold-file btn interact-bg tw-p-1{{if not $isExpandable}} tw-invisible{{end}}"> <button class="fold-file btn interact-bg tw-p-1{{if not $isExpandable}} tw-invisible{{end}}">
{{if $file.ShouldBeHidden}} {{if $file.ShouldBeHidden}}
{{svg "octicon-chevron-right" 18}} {{svg "octicon-chevron-right" 18}}
@ -128,21 +128,23 @@
{{template "repo/diff/stats" dict "file" . "root" $}} {{template "repo/diff/stats" dict "file" . "root" $}}
{{end}} {{end}}
</div> </div>
<span class="file tw-font-mono"><a class="muted file-link" title="{{if $file.IsRenamed}}{{$file.OldName}}{{end}}{{$file.Name}}" href="#diff-{{$file.NameHash}}">{{if $file.IsRenamed}}{{$file.OldName}}{{end}}{{$file.Name}}</a>{{if .IsLFSFile}} ({{ctx.Locale.Tr "repo.stored_lfs"}}){{end}}</span> <span class="file tw-flex tw-items-center tw-font-mono tw-flex-1"><a class="muted file-link" title="{{if $file.IsRenamed}}{{$file.OldName}}{{end}}{{$file.Name}}" href="#diff-{{$file.NameHash}}">{{if $file.IsRenamed}}{{$file.OldName}}{{end}}{{$file.Name}}</a>
<button class="btn interact-fg tw-p-2" data-clipboard-text="{{$file.Name}}">{{svg "octicon-copy" 14}}</button> {{if .IsLFSFile}} ({{ctx.Locale.Tr "repo.stored_lfs"}}){{end}}
{{if $file.IsGenerated}} <button class="btn interact-fg tw-p-2" data-clipboard-text="{{$file.Name}}">{{svg "octicon-copy" 14}}</button>
<span class="ui label">{{ctx.Locale.Tr "repo.diff.generated"}}</span> {{if $file.IsGenerated}}
{{end}} <span class="ui label">{{ctx.Locale.Tr "repo.diff.generated"}}</span>
{{if $file.IsVendored}} {{end}}
<span class="ui label">{{ctx.Locale.Tr "repo.diff.vendored"}}</span> {{if $file.IsVendored}}
{{end}} <span class="ui label">{{ctx.Locale.Tr "repo.diff.vendored"}}</span>
{{if and $file.Mode $file.OldMode}} {{end}}
{{$old := ctx.Locale.Tr ($file.ModeTranslationKey $file.OldMode)}} {{if and $file.Mode $file.OldMode}}
{{$new := ctx.Locale.Tr ($file.ModeTranslationKey $file.Mode)}} {{$old := ctx.Locale.Tr ($file.ModeTranslationKey $file.OldMode)}}
<span class="tw-ml-4 tw-font-mono">{{ctx.Locale.Tr "git.filemode.changed_filemode" $old $new}}</span> {{$new := ctx.Locale.Tr ($file.ModeTranslationKey $file.Mode)}}
{{else if $file.Mode}} <span class="tw-mx-2 tw-font-mono tw-whitespace-nowrap">{{ctx.Locale.Tr "git.filemode.changed_filemode" $old $new}}</span>
<span class="tw-ml-4 tw-font-mono">{{ctx.Locale.Tr ($file.ModeTranslationKey $file.Mode)}}</span> {{else if $file.Mode}}
{{end}} <span class="tw-mx-2 tw-font-mono tw-whitespace-nowrap">{{ctx.Locale.Tr ($file.ModeTranslationKey $file.Mode)}}</span>
{{end}}
</span>
</div> </div>
<div class="diff-file-header-actions tw-flex tw-items-center tw-gap-1 tw-flex-wrap"> <div class="diff-file-header-actions tw-flex tw-items-center tw-gap-1 tw-flex-wrap">
{{if $showFileViewToggle}} {{if $showFileViewToggle}}

View file

@ -59,8 +59,11 @@
</div> </div>
{{end}} {{end}}
{{template "repo/sub_menu" .}} {{template "repo/sub_menu" .}}
{{$n := len .TreeNames}}
{{$l := Eval $n "-" 1}}
{{$isHomepage := (eq $n 0)}}
<div class="repo-button-row"> <div class="repo-button-row">
<div class="tw-flex tw-items-center tw-flex-wrap tw-gap-y-2"> <div class="tw-flex tw-items-center tw-gap-y-2">
{{template "repo/branch_dropdown" dict "root" . "ContainerClasses" "tw-mr-1"}} {{template "repo/branch_dropdown" dict "root" . "ContainerClasses" "tw-mr-1"}}
{{if and .CanCompareOrPull .IsViewBranch (not .Repository.IsArchived)}} {{if and .CanCompareOrPull .IsViewBranch (not .Repository.IsArchived)}}
{{$cmpBranch := ""}} {{$cmpBranch := ""}}
@ -75,9 +78,7 @@
</a> </a>
{{end}} {{end}}
<!-- Show go to file and breadcrumbs if not on home page --> <!-- Show go to file and breadcrumbs if not on home page -->
{{$n := len .TreeNames}} {{if $isHomepage}}
{{$l := Eval $n "-" 1}}
{{if eq $n 0}}
<a href="{{.Repository.Link}}/find/{{.BranchNameSubURL}}" class="ui compact basic button">{{ctx.Locale.Tr "repo.find_file.go_to_file"}}</a> <a href="{{.Repository.Link}}/find/{{.BranchNameSubURL}}" class="ui compact basic button">{{ctx.Locale.Tr "repo.find_file.go_to_file"}}</a>
{{end}} {{end}}
@ -101,20 +102,20 @@
</button> </button>
{{end}} {{end}}
{{if and (eq $n 0) (.Repository.IsTemplate)}} {{if and $isHomepage (.Repository.IsTemplate)}}
<a role="button" class="ui primary compact button" href="{{AppSubUrl}}/repo/create?template_id={{.Repository.ID}}"> <a role="button" class="ui primary compact button" href="{{AppSubUrl}}/repo/create?template_id={{.Repository.ID}}">
{{ctx.Locale.Tr "repo.use_template"}} {{ctx.Locale.Tr "repo.use_template"}}
</a> </a>
{{end}} {{end}}
{{if ne $n 0}} {{if (not $isHomepage)}}
<span class="breadcrumb repo-path tw-ml-1"> <span class="breadcrumb repo-path tw-ml-1">
<a class="section" href="{{.RepoLink}}/src/{{.BranchNameSubURL}}" title="{{.Repository.Name}}">{{StringUtils.EllipsisString .Repository.Name 30}}</a> <a class="section" href="{{.RepoLink}}/src/{{.BranchNameSubURL}}" title="{{.Repository.Name}}">{{StringUtils.EllipsisString .Repository.Name 30}}</a>
{{- range $i, $v := .TreeNames -}} {{- range $i, $v := .TreeNames -}}
<span class="breadcrumb-divider">/</span> <span class="breadcrumb-divider">/</span>
{{- if eq $i $l -}} {{- if eq $i $l -}}
<span class="active section" title="{{$v}}">{{StringUtils.EllipsisString $v 30}}</span> <span class="active section" title="{{$v}}">{{$v}}</span>
{{- else -}} {{- else -}}
{{$p := index $.Paths $i}}<span class="section"><a href="{{$.BranchLink}}/{{PathEscapeSegments $p}}" title="{{$v}}">{{StringUtils.EllipsisString $v 30}}</a></span> {{$p := index $.Paths $i}}<span class="section"><a href="{{$.BranchLink}}/{{PathEscapeSegments $p}}" title="{{$v}}">{{$v}}</a></span>
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}
</span> </span>
@ -122,7 +123,7 @@
</div> </div>
<div class="tw-flex tw-items-center"> <div class="tw-flex tw-items-center">
<!-- Only show clone panel in repository home page --> <!-- Only show clone panel in repository home page -->
{{if eq $n 0}} {{if $isHomepage}}
<div class="clone-panel ui action tiny input"> <div class="clone-panel ui action tiny input">
{{template "repo/clone_buttons" .}} {{template "repo/clone_buttons" .}}
<button class="ui small jump dropdown icon button" data-tooltip-content="{{ctx.Locale.Tr "repo.more_operations"}}"> <button class="ui small jump dropdown icon button" data-tooltip-content="{{ctx.Locale.Tr "repo.more_operations"}}">
@ -145,7 +146,7 @@
</div> </div>
{{template "repo/cite/cite_modal" .}} {{template "repo/cite/cite_modal" .}}
{{end}} {{end}}
{{if and (ne $n 0) (not .IsViewFile) (not .IsBlame)}} {{if and (not $isHomepage) (not .IsViewFile) (not .IsBlame)}}
<a class="ui button" href="{{.RepoLink}}/commits/{{.BranchNameSubURL}}/{{.TreePath | PathEscapeSegments}}"> <a class="ui button" href="{{.RepoLink}}/commits/{{.BranchNameSubURL}}/{{.TreePath | PathEscapeSegments}}">
{{svg "octicon-history" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.file_history"}} {{svg "octicon-history" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.file_history"}}
</a> </a>

View file

@ -156,7 +156,7 @@
<label for="interval">{{ctx.Locale.Tr "repo.mirror_interval" .MinimumMirrorInterval}}</label> <label for="interval">{{ctx.Locale.Tr "repo.mirror_interval" .MinimumMirrorInterval}}</label>
<input id="interval" name="interval" value="{{.PullMirror.Interval}}"> <input id="interval" name="interval" value="{{.PullMirror.Interval}}">
</div> </div>
{{$address := MirrorRemoteAddress $.Context .Repository .PullMirror.GetRemoteName false}} {{$address := MirrorRemoteAddress $.Context .Repository .PullMirror.GetRemoteName}}
<div class="field {{if .Err_MirrorAddress}}error{{end}}"> <div class="field {{if .Err_MirrorAddress}}error{{end}}">
<label for="mirror_address">{{ctx.Locale.Tr "repo.mirror_address"}}</label> <label for="mirror_address">{{ctx.Locale.Tr "repo.mirror_address"}}</label>
<input id="mirror_address" name="mirror_address" value="{{$address.Address}}" required> <input id="mirror_address" name="mirror_address" value="{{$address.Address}}" required>

View file

@ -11,13 +11,13 @@
{{end}} {{end}}
{{if not .ReadmeInList}} {{if not .ReadmeInList}}
<div id="repo-file-commit-box" class="ui top attached header list-header tw-mb-4"> <div id="repo-file-commit-box" class="ui top attached header list-header tw-mb-4 tw-flex tw-justify-between">
<div> <div class="latest-commit">
{{template "repo/latest_commit" .}} {{template "repo/latest_commit" .}}
</div> </div>
{{if .LatestCommit}} {{if .LatestCommit}}
{{if .LatestCommit.Committer}} {{if .LatestCommit.Committer}}
<div class="ui text grey right age"> <div class="text grey age">
{{TimeSince .LatestCommit.Committer.When ctx.Locale}} {{TimeSince .LatestCommit.Committer.When ctx.Locale}}
</div> </div>
{{end}} {{end}}

View file

@ -1,8 +1,12 @@
<table id="repo-files-table" class="ui single line table tw-mt-0" {{if .HasFilesWithoutLatestCommit}}hx-indicator="tr.notready td.message span" hx-trigger="load" hx-swap="morph" hx-post="{{.LastCommitLoaderURL}}"{{end}}> <table id="repo-files-table" class="ui single line table tw-mt-0" {{if .HasFilesWithoutLatestCommit}}hx-indicator="tr.notready td.message span" hx-trigger="load" hx-swap="morph" hx-post="{{.LastCommitLoaderURL}}"{{end}}>
<thead> <thead>
<tr class="commit-list"> <tr class="commit-list">
<th colspan="2"> <th class="tw-overflow-hidden" colspan="2">
{{template "repo/latest_commit" .}} <div class="tw-flex">
<div class="latest-commit">
{{template "repo/latest_commit" .}}
</div>
</div>
</th> </th>
<th class="text grey right age">{{if .LatestCommit}}{{if .LatestCommit.Committer}}{{TimeSince .LatestCommit.Committer.When ctx.Locale}}{{end}}{{end}}</th> <th class="text grey right age">{{if .LatestCommit}}{{if .LatestCommit.Committer}}{{TimeSince .LatestCommit.Committer.When ctx.Locale}}{{end}}{{end}}</th>
</tr> </tr>

View file

@ -1,7 +1,7 @@
<div role="main" aria-label="{{.Title}}" class="page-content user notification" id="notification_div" data-sequence-number="{{.SequenceNumber}}"> <div role="main" aria-label="{{.Title}}" class="page-content user notification" id="notification_div" data-sequence-number="{{.SequenceNumber}}">
<div class="ui container"> <div class="ui container">
{{$notificationUnreadCount := call .NotificationUnreadCount}} {{$notificationUnreadCount := call .NotificationUnreadCount}}
<div class="tw-flex tw-items-center tw-justify-between tw-mb-4"> <div class="tw-flex tw-items-center tw-justify-between tw-mb-[--page-spacing]">
<div class="small-menu-items ui compact tiny menu"> <div class="small-menu-items ui compact tiny menu">
<a class="{{if eq .Status 1}}active {{end}}item" href="{{AppSubUrl}}/notifications?q=unread"> <a class="{{if eq .Status 1}}active {{end}}item" href="{{AppSubUrl}}/notifications?q=unread">
{{ctx.Locale.Tr "notification.unread"}} {{ctx.Locale.Tr "notification.unread"}}

View file

@ -13,11 +13,9 @@
<div class="ui four wide column"> <div class="ui four wide column">
{{template "shared/user/profile_big_avatar" .}} {{template "shared/user/profile_big_avatar" .}}
</div> </div>
<div class="ui twelve wide column"> <div class="ui twelve wide column tw-mb-4">
<div class="tw-mb-4">
{{template "user/overview/header" .}} {{template "user/overview/header" .}}
</div> {{template "package/shared/versionlist" .}}
{{template "package/shared/versionlist" .}}
</div> </div>
</div> </div>
</div> </div>

View file

@ -13,11 +13,9 @@
<div class="ui four wide column"> <div class="ui four wide column">
{{template "shared/user/profile_big_avatar" .}} {{template "shared/user/profile_big_avatar" .}}
</div> </div>
<div class="ui twelve wide column"> <div class="ui twelve wide column tw-mb-4">
<div class="tw-mb-4">
{{template "user/overview/header" .}} {{template "user/overview/header" .}}
</div> {{template "package/shared/list" .}}
{{template "package/shared/list" .}}
</div> </div>
</div> </div>
</div> </div>

View file

@ -6,11 +6,8 @@
<div class="ui four wide column"> <div class="ui four wide column">
{{template "shared/user/profile_big_avatar" .}} {{template "shared/user/profile_big_avatar" .}}
</div> </div>
<div class="ui twelve wide column"> <div class="ui twelve wide column tw-mb-4">
<div class="tw-mb-4"> {{template "user/overview/header" .}}
{{template "user/overview/header" .}}
</div>
{{if eq .TabName "activity"}} {{if eq .TabName "activity"}}
{{if .ContextUser.KeepActivityPrivate}} {{if .ContextUser.KeepActivityPrivate}}
<div class="ui info message"> <div class="ui info message">

View file

@ -44,7 +44,7 @@
} }
.run-list-item-right { .run-list-item-right {
flex: 0 0 15%; flex: 0 0 min(20%, 130px);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 3px; gap: 3px;

View file

@ -24,7 +24,21 @@
--repo-header-issue-min-height: 41px; --repo-header-issue-min-height: 41px;
--min-height-textarea: 132px; /* padding + 6 lines + border = calc(1.57142em + 6lh + 2px), but lh is not fully supported */ --min-height-textarea: 132px; /* padding + 6 lines + border = calc(1.57142em + 6lh + 2px), but lh is not fully supported */
--tab-size: 4; --tab-size: 4;
--checkbox-size: 16px; /* height and width of checkbox and radio inputs */ --checkbox-size: 15px; /* height and width of checkbox and radio inputs */
--page-spacing: 16px; /* space between page elements */
--page-margin-x: 32px; /* minimum space on left and right side of page */
}
@media (min-width: 768px) and (max-width: 1200px) {
:root {
--page-margin-x: 16px;
}
}
@media (max-width: 767.98px) {
:root {
--page-margin-x: 8px;
}
} }
:root * { :root * {
@ -668,11 +682,14 @@ img.ui.avatar,
margin-bottom: 14px; margin-bottom: 14px;
} }
/* add padding to all content when there is no .secondary.nav. this uses padding instead of /* add margin to all pages when there is no .secondary.nav */
margin because with the negative margin on .ui.grid we would have to set margin-top: 0,
but that does not work universally for all pages */
.page-content > :first-child:not(.secondary-nav) { .page-content > :first-child:not(.secondary-nav) {
padding-top: 14px; margin-top: var(--page-spacing);
}
/* if .ui.grid is the first child the first grid-column has 'padding-top: 1rem' which we need
to compensate here */
.page-content > :first-child.ui.grid {
margin-top: calc(var(--page-spacing) - 1rem);
} }
.ui.pagination.menu .active.item { .ui.pagination.menu .active.item {
@ -1330,6 +1347,7 @@ overflow-menu .ui.label {
white-space: pre-wrap; white-space: pre-wrap;
word-break: break-all; word-break: break-all;
overflow-wrap: anywhere; overflow-wrap: anywhere;
line-height: inherit; /* needed for inline code preview in markup */
} }
.blame .code-inner { .blame .code-inner {

View file

@ -249,21 +249,6 @@ textarea:focus,
.user.signup form .optional .title { .user.signup form .optional .title {
margin-left: 250px !important; margin-left: 250px !important;
} }
.user.activate form .inline.field > input,
.user.forgot.password form .inline.field > input,
.user.reset.password form .inline.field > input,
.user.link-account form .inline.field > input,
.user.signin form .inline.field > input,
.user.signup form .inline.field > input,
.user.activate form .inline.field > textarea,
.user.forgot.password form .inline.field > textarea,
.user.reset.password form .inline.field > textarea,
.user.link-account form .inline.field > textarea,
.user.signin form .inline.field > textarea,
.user.signup form .inline.field > textarea,
.oauth-login-link {
width: 50%;
}
} }
@media (max-width: 767.98px) { @media (max-width: 767.98px) {
@ -310,14 +295,7 @@ textarea:focus,
.user.reset.password form .inline.field > label, .user.reset.password form .inline.field > label,
.user.link-account form .inline.field > label, .user.link-account form .inline.field > label,
.user.signin form .inline.field > label, .user.signin form .inline.field > label,
.user.signup form .inline.field > label, .user.signup form .inline.field > label {
.user.activate form input,
.user.forgot.password form input,
.user.reset.password form input,
.user.link-account form input,
.user.signin form input,
.user.signup form input,
.oauth-login-link {
width: 100% !important; width: 100% !important;
} }
} }
@ -435,9 +413,9 @@ textarea:focus,
.repository.new.repo form label, .repository.new.repo form label,
.repository.new.migrate form label, .repository.new.migrate form label,
.repository.new.fork form label, .repository.new.fork form label,
.repository.new.repo form input, .repository.new.repo form .inline.field > input,
.repository.new.migrate form input, .repository.new.migrate form .inline.field > input,
.repository.new.fork form input, .repository.new.fork form .inline.field > input,
.repository.new.fork form .field a, .repository.new.fork form .field a,
.repository.new.repo form .selection.dropdown, .repository.new.repo form .selection.dropdown,
.repository.new.migrate form .selection.dropdown, .repository.new.migrate form .selection.dropdown,

View file

@ -20,7 +20,7 @@ input[type="radio"] {
.ui.checkbox input[type="checkbox"], .ui.checkbox input[type="checkbox"],
.ui.checkbox input[type="radio"] { .ui.checkbox input[type="radio"] {
position: absolute; position: absolute;
top: 0; top: 1px;
left: 0; left: 0;
width: var(--checkbox-size); width: var(--checkbox-size);
height: var(--checkbox-size); height: var(--checkbox-size);

View file

@ -49,30 +49,11 @@
/* overwrite width of containers inside the main page content div (div with class "page-content") */ /* overwrite width of containers inside the main page content div (div with class "page-content") */
.page-content .ui.ui.ui.container:not(.fluid) { .page-content .ui.ui.ui.container:not(.fluid) {
width: 1280px; width: 1280px;
max-width: calc(100% - 64px); max-width: calc(100% - calc(2 * var(--page-margin-x)));
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
} }
.ui.container.fluid.padded { .ui.container.fluid.padded {
padding: 0 32px; padding: 0 var(--page-margin-x);
}
/* enable fluid page widths for medium size viewports */
@media (min-width: 768px) and (max-width: 1200px) {
.page-content .ui.ui.ui.container:not(.fluid) {
max-width: calc(100% - 32px);
}
.ui.container.fluid.padded {
padding: 0 16px;
}
}
@media (max-width: 767.98px) {
.page-content .ui.ui.ui.container:not(.fluid) {
max-width: calc(100% - 16px);
}
.ui.container.fluid.padded {
padding: 0 8px;
}
} }

View file

@ -2,7 +2,8 @@
.flex-container { .flex-container {
display: flex !important; display: flex !important;
gap: 16px; gap: var(--page-spacing);
margin-top: var(--page-spacing);
} }
.flex-container-nav { .flex-container-nav {

View file

@ -177,12 +177,44 @@
} }
} }
.repository.file.list .repo-path { .commit-summary {
word-break: break-word; flex: 1;
overflow-wrap: anywhere;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
} }
.repository.file.list #repo-files-table { .commit-header .commit-summary,
table-layout: fixed; td .commit-summary {
white-space: normal;
}
.latest-commit {
display: flex;
flex: 1;
align-items: center;
overflow: hidden;
text-overflow: ellipsis;
}
@media (max-width: 767.98px) {
.latest-commit .sha {
display: none;
}
.latest-commit .commit-summary {
margin-left: 8px;
}
}
.repo-path {
display: flex;
overflow-wrap: anywhere;
}
/* this is what limits the commit table width to a value that works on all viewport sizes */
#repo-files-table th:first-of-type {
max-width: calc(calc(min(100vw, 1280px)) - 145px - calc(2 * var(--page-margin-x)));
} }
.repository.file.list #repo-files-table thead th { .repository.file.list #repo-files-table thead th {
@ -262,7 +294,6 @@
} }
.repository.file.list #repo-files-table td.age { .repository.file.list #repo-files-table td.age {
width: 120px;
color: var(--color-text-light-1); color: var(--color-text-light-1);
} }
@ -1223,10 +1254,6 @@
margin: 0; margin: 0;
} }
.repository #commits-table td.message {
text-overflow: unset;
}
.repository #commits-table.ui.basic.striped.table tbody tr:nth-child(2n) { .repository #commits-table.ui.basic.striped.table tbody tr:nth-child(2n) {
background-color: var(--color-light) !important; background-color: var(--color-light) !important;
} }
@ -2153,6 +2180,20 @@
display: inline-block !important; display: inline-block !important;
} }
.commit-header-buttons {
display: flex;
gap: 4px;
align-items: flex-start;
white-space: nowrap;
}
@media (max-width: 767.98px) {
.commit-header-buttons {
flex-direction: column;
align-items: stretch;
}
}
.settings.webhooks .list > .item:not(:first-child), .settings.webhooks .list > .item:not(:first-child),
.settings.githooks .list > .item:not(:first-child), .settings.githooks .list > .item:not(:first-child),
.settings.actions .list > .item:not(:first-child) { .settings.actions .list > .item:not(:first-child) {
@ -2469,7 +2510,7 @@ tbody.commit-list {
.author-wrapper { .author-wrapper {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
max-width: calc(100% - 50px); max-width: 100%;
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: middle;
} }
@ -2494,10 +2535,6 @@ tbody.commit-list {
tr.commit-list { tr.commit-list {
width: 100%; width: 100%;
} }
th .message-wrapper {
display: block;
max-width: calc(100vw - 70px);
}
.author-wrapper { .author-wrapper {
max-width: 80px; max-width: 80px;
} }
@ -2507,27 +2544,18 @@ tbody.commit-list {
tr.commit-list { tr.commit-list {
width: 723px; width: 723px;
} }
th .message-wrapper {
max-width: 120px;
}
} }
@media (min-width: 992px) and (max-width: 1200px) { @media (min-width: 992px) and (max-width: 1200px) {
tr.commit-list { tr.commit-list {
width: 933px; width: 933px;
} }
th .message-wrapper {
max-width: 350px;
}
} }
@media (min-width: 1201px) { @media (min-width: 1201px) {
tr.commit-list { tr.commit-list {
width: 1127px; width: 1127px;
} }
th .message-wrapper {
max-width: 525px;
}
} }
.commit-list .commit-status-link { .commit-list .commit-status-link {
@ -2854,7 +2882,7 @@ tbody.commit-list {
.repository.file.list #repo-files-table .entry td.message, .repository.file.list #repo-files-table .entry td.message,
.repository.file.list #repo-files-table .commit-list td.message, .repository.file.list #repo-files-table .commit-list td.message,
.repository.file.list #repo-files-table .entry span.commit-summary, .repository.file.list #repo-files-table .entry span.commit-summary,
.repository.file.list #repo-files-table .commit-list span.commit-summary { .repository.file.list #repo-files-table .commit-list tr span.commit-summary {
display: none !important; display: none !important;
} }
.repository.view.issue .comment-list .timeline, .repository.view.issue .comment-list .timeline,

View file

@ -65,7 +65,7 @@
--color-console-fg-subtle: #bec4c8; --color-console-fg-subtle: #bec4c8;
--color-console-bg: #171b1e; --color-console-bg: #171b1e;
--color-console-border: #2e353b; --color-console-border: #2e353b;
--color-console-hover-bg: #e8e8ff16; --color-console-hover-bg: #292d31;
--color-console-active-bg: #2e353b; --color-console-active-bg: #2e353b;
--color-console-menu-bg: #252b30; --color-console-menu-bg: #252b30;
--color-console-menu-border: #424b51; --color-console-menu-border: #424b51;

View file

@ -63,12 +63,12 @@
/* console colors - used for actions console and console files */ /* console colors - used for actions console and console files */
--color-console-fg: #f8f8f9; --color-console-fg: #f8f8f9;
--color-console-fg-subtle: #bec4c8; --color-console-fg-subtle: #bec4c8;
--color-console-bg: #181b1d; --color-console-bg: #171b1e;
--color-console-border: #313538; --color-console-border: #2e353b;
--color-console-hover-bg: #ffffff16; --color-console-hover-bg: #292d31;
--color-console-active-bg: #313538; --color-console-active-bg: #2e353b;
--color-console-menu-bg: #272b2e; --color-console-menu-bg: #252b30;
--color-console-menu-border: #464a4d; --color-console-menu-border: #424b51;
/* named colors */ /* named colors */
--color-red: #db2828; --color-red: #db2828;
--color-orange: #f2711c; --color-orange: #f2711c;

View file

@ -526,8 +526,16 @@ export function initRepositoryActionView() {
.action-summary { .action-summary {
display: flex; display: flex;
flex-wrap: wrap;
gap: 5px; gap: 5px;
margin: 0 0 0 28px; margin-left: 28px;
}
@media (max-width: 767.98px) {
.action-commit-summary {
margin-left: 0;
margin-top: 8px;
}
} }
/* ================ */ /* ================ */
@ -540,6 +548,14 @@ export function initRepositoryActionView() {
top: 12px; top: 12px;
max-height: 100vh; max-height: 100vh;
overflow-y: auto; overflow-y: auto;
background: var(--color-body);
z-index: 2; /* above .job-info-header */
}
@media (max-width: 767.98px) {
.action-view-left {
position: static; /* can not sticky because multiple jobs would overlap into right view */
}
} }
.job-artifacts-title { .job-artifacts-title {
@ -701,7 +717,9 @@ export function initRepositoryActionView() {
position: sticky; position: sticky;
top: 0; top: 0;
height: 60px; height: 60px;
z-index: 1; z-index: 1; /* above .job-step-container */
background: var(--color-console-bg);
border-radius: 3px;
} }
.job-info-header:has(+ .job-step-container) { .job-info-header:has(+ .job-step-container) {
@ -739,7 +757,7 @@ export function initRepositoryActionView() {
.job-step-container .job-step-summary.step-expandable:hover { .job-step-container .job-step-summary.step-expandable:hover {
color: var(--color-console-fg); color: var(--color-console-fg);
background-color: var(--color-console-hover-bg); background: var(--color-console-hover-bg);
} }
.job-step-container .job-step-summary .step-summary-msg { .job-step-container .job-step-summary .step-summary-msg {
@ -757,17 +775,15 @@ export function initRepositoryActionView() {
top: 60px; top: 60px;
} }
@media (max-width: 768px) { @media (max-width: 767.98px) {
.action-view-body { .action-view-body {
flex-direction: column; flex-direction: column;
} }
.action-view-left, .action-view-right { .action-view-left, .action-view-right {
width: 100%; width: 100%;
} }
.action-view-left { .action-view-left {
max-width: none; max-width: none;
overflow-y: hidden;
} }
} }
</style> </style>

View file

@ -207,13 +207,13 @@ export function initAdminCommon() {
// Notice // Notice
if (document.querySelector('.admin.notice')) { if (document.querySelector('.admin.notice')) {
const $detailModal = document.getElementById('detail-modal'); const detailModal = document.getElementById('detail-modal');
// Attach view detail modals // Attach view detail modals
$('.view-detail').on('click', function () { $('.view-detail').on('click', function () {
$detailModal.find('.content pre').text($(this).parents('tr').find('.notice-description').text()); const description = this.closest('tr').querySelector('.notice-description').textContent;
$detailModal.find('.sub.header').text(this.closest('tr')?.querySelector('relative-time')?.getAttribute('title')); detailModal.querySelector('.content pre').textContent = description;
$detailModal.modal('show'); $(detailModal).modal('show');
return false; return false;
}); });

View file

@ -435,12 +435,10 @@ export function initRepoPullRequestReview() {
offset += $('.diff-detail-box').outerHeight() + $(diffHeader).outerHeight(); offset += $('.diff-detail-box').outerHeight() + $(diffHeader).outerHeight();
} }
document.getElementById(`show-outdated-${id}`).classList.add('tw-hidden'); hideElem(`#show-outdated-${id}`);
document.getElementById(`code-comments-${id}`).classList.remove('tw-hidden'); showElem(`#code-comments-${id}, #code-preview-${id}, #hide-outdated-${id}`);
document.getElementById(`code-preview-${id}`).classList.remove('tw-hidden');
document.getElementById(`hide-outdated-${id}`).classList.remove('tw-hidden');
// if the comment box is folded, expand it // if the comment box is folded, expand it
if (ancestorDiffBox.getAttribute('data-folded') === 'true') { if (ancestorDiffBox?.getAttribute('data-folded') === 'true') {
setFileFolding(ancestorDiffBox, ancestorDiffBox.querySelector('.fold-file'), false); setFileFolding(ancestorDiffBox, ancestorDiffBox.querySelector('.fold-file'), false);
} }