mirror of
https://github.com/go-gitea/gitea.git
synced 2025-07-17 00:01:00 -04:00
Compare commits
11 Commits
ffce336f18
...
787a05a3a2
Author | SHA1 | Date | |
---|---|---|---|
|
787a05a3a2 | ||
|
47bb5f11cb | ||
|
7b9e23cc1d | ||
|
b2359f3df6 | ||
|
5c4075e16d | ||
|
7f9d58fab8 | ||
|
79acf7acc4 | ||
|
807e8e280c | ||
|
2f49b55c19 | ||
|
699f20234b | ||
|
0bfcbcc5e7 |
@ -25,7 +25,7 @@
|
||||
fork_id: 0
|
||||
is_template: false
|
||||
template_id: 0
|
||||
size: 6708
|
||||
size: 7028
|
||||
is_fsck_enabled: true
|
||||
close_issues_via_commit_in_any_branch: false
|
||||
|
||||
|
@ -52,13 +52,16 @@ func listPullRequestStatement(baseRepoID int64, opts *PullRequestsOptions) (*xor
|
||||
|
||||
// GetUnmergedPullRequestsByHeadInfo returns all pull requests that are open and has not been merged
|
||||
// by given head information (repo and branch).
|
||||
func GetUnmergedPullRequestsByHeadInfo(repoID int64, branch string) ([]*PullRequest, error) {
|
||||
// arg `includeClosed` controls whether the SQL returns closed PRs
|
||||
func GetUnmergedPullRequestsByHeadInfo(repoID int64, branch string, includeClosed bool) ([]*PullRequest, error) {
|
||||
prs := make([]*PullRequest, 0, 2)
|
||||
return prs, db.GetEngine(db.DefaultContext).
|
||||
Where("head_repo_id = ? AND head_branch = ? AND has_merged = ? AND issue.is_closed = ? AND flow = ?",
|
||||
repoID, branch, false, false, PullRequestFlowGithub).
|
||||
sess := db.GetEngine(db.DefaultContext).
|
||||
Join("INNER", "issue", "issue.id = pull_request.issue_id").
|
||||
Find(&prs)
|
||||
Where("head_repo_id = ? AND head_branch = ? AND has_merged = ? AND flow = ?", repoID, branch, false, PullRequestFlowGithub)
|
||||
if !includeClosed {
|
||||
sess.Where("issue.is_closed = ?", false)
|
||||
}
|
||||
return prs, sess.Find(&prs)
|
||||
}
|
||||
|
||||
// CanMaintainerWriteToBranch check whether user is a maintainer and could write to the branch
|
||||
@ -71,7 +74,7 @@ func CanMaintainerWriteToBranch(p access_model.Permission, branch string, user *
|
||||
return false
|
||||
}
|
||||
|
||||
prs, err := GetUnmergedPullRequestsByHeadInfo(p.Units[0].RepoID, branch)
|
||||
prs, err := GetUnmergedPullRequestsByHeadInfo(p.Units[0].RepoID, branch, false)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
@ -118,7 +118,7 @@ func TestHasUnmergedPullRequestsByHeadInfo(t *testing.T) {
|
||||
|
||||
func TestGetUnmergedPullRequestsByHeadInfo(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(1, "branch2")
|
||||
prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(1, "branch2", false)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, prs, 1)
|
||||
for _, pr := range prs {
|
||||
|
@ -41,9 +41,8 @@ var RecommendedHashAlgorithms = []string{
|
||||
"pbkdf2_hi",
|
||||
}
|
||||
|
||||
// SetDefaultPasswordHashAlgorithm will take a provided algorithmName and dealias it to
|
||||
// a complete algorithm specification.
|
||||
func SetDefaultPasswordHashAlgorithm(algorithmName string) (string, *PasswordHashAlgorithm) {
|
||||
// hashAlgorithmToSpec converts an algorithm name or a specification to a full algorithm specification
|
||||
func hashAlgorithmToSpec(algorithmName string) string {
|
||||
if algorithmName == "" {
|
||||
algorithmName = DefaultHashAlgorithmName
|
||||
}
|
||||
@ -52,10 +51,26 @@ func SetDefaultPasswordHashAlgorithm(algorithmName string) (string, *PasswordHas
|
||||
algorithmName = alias
|
||||
alias, has = aliasAlgorithmNames[algorithmName]
|
||||
}
|
||||
|
||||
// algorithmName should now be a full algorithm specification
|
||||
// e.g. pbkdf2$50000$50 rather than pbdkf2
|
||||
DefaultHashAlgorithm = Parse(algorithmName)
|
||||
|
||||
return algorithmName, DefaultHashAlgorithm
|
||||
return algorithmName
|
||||
}
|
||||
|
||||
// SetDefaultPasswordHashAlgorithm will take a provided algorithmName and de-alias it to
|
||||
// a complete algorithm specification.
|
||||
func SetDefaultPasswordHashAlgorithm(algorithmName string) (string, *PasswordHashAlgorithm) {
|
||||
algoSpec := hashAlgorithmToSpec(algorithmName)
|
||||
// now we get a full specification, e.g. pbkdf2$50000$50 rather than pbdkf2
|
||||
DefaultHashAlgorithm = Parse(algoSpec)
|
||||
return algoSpec, DefaultHashAlgorithm
|
||||
}
|
||||
|
||||
// ConfigHashAlgorithm will try to find a "recommended algorithm name" defined by RecommendedHashAlgorithms for config
|
||||
// This function is not fast and is only used for the installation page
|
||||
func ConfigHashAlgorithm(algorithm string) string {
|
||||
algorithm = hashAlgorithmToSpec(algorithm)
|
||||
for _, recommAlgo := range RecommendedHashAlgorithms {
|
||||
if algorithm == hashAlgorithmToSpec(recommAlgo) {
|
||||
return recommAlgo
|
||||
}
|
||||
}
|
||||
return algorithm
|
||||
}
|
||||
|
@ -277,11 +277,18 @@ func (repo *Repository) GetPatch(base, head string, w io.Writer) error {
|
||||
|
||||
// GetFilesChangedBetween returns a list of all files that have been changed between the given commits
|
||||
func (repo *Repository) GetFilesChangedBetween(base, head string) ([]string, error) {
|
||||
stdout, _, err := NewCommand(repo.Ctx, "diff", "--name-only").AddDynamicArguments(base + ".." + head).RunStdString(&RunOpts{Dir: repo.Path})
|
||||
stdout, _, err := NewCommand(repo.Ctx, "diff", "--name-only", "-z").AddDynamicArguments(base + ".." + head).RunStdString(&RunOpts{Dir: repo.Path})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return strings.Split(stdout, "\n"), err
|
||||
split := strings.Split(stdout, "\000")
|
||||
|
||||
// Because Git will always emit filenames with a terminal NUL ignore the last entry in the split - which will always be empty.
|
||||
if len(split) > 0 {
|
||||
split = split[:len(split)-1]
|
||||
}
|
||||
|
||||
return split, err
|
||||
}
|
||||
|
||||
// GetDiffFromMergeBase generates and return patch data from merge base to head
|
||||
|
@ -237,7 +237,6 @@ internal_token_failed = Failed to generate internal token: %v
|
||||
secret_key_failed = Failed to generate secret key: %v
|
||||
save_config_failed = Failed to save configuration: %v
|
||||
invalid_admin_setting = Administrator account setting is invalid: %v
|
||||
install_success = Welcome! Thank you for choosing Gitea. Have fun and take care!
|
||||
invalid_log_root_path = The log path is invalid: %v
|
||||
default_keep_email_private = Hide Email Addresses by Default
|
||||
default_keep_email_private_popup = Hide email addresses of new user accounts by default.
|
||||
@ -248,6 +247,7 @@ default_enable_timetracking_popup = Enable time tracking for new repositories by
|
||||
no_reply_address = Hidden Email Domain
|
||||
no_reply_address_helper = Domain name for users with a hidden email address. For example, the username 'joe' will be logged in Git as 'joe@noreply.example.org' if the hidden email domain is set to 'noreply.example.org'.
|
||||
password_algorithm = Password Hash Algorithm
|
||||
invalid_password_algorithm = Invalid password hash algorithm
|
||||
password_algorithm_helper = Set the password hashing algorithm. Algorithms have differing requirements and strength. `argon2` whilst having good characteristics uses a lot of memory and may be inappropriate for small systems.
|
||||
enable_update_checker = Enable Update Checker
|
||||
enable_update_checker_helper = Checks for new version releases periodically by connecting to gitea.io.
|
||||
|
@ -52,7 +52,7 @@ func (s *Service) Register(
|
||||
}
|
||||
|
||||
if runnerToken.IsActive {
|
||||
return nil, errors.New("runner token has already activated")
|
||||
return nil, errors.New("runner token has already been activated")
|
||||
}
|
||||
|
||||
// create new runner
|
||||
|
@ -59,11 +59,6 @@ func Init(ctx goctx.Context) func(next http.Handler) http.Handler {
|
||||
dbTypeNames := getSupportedDbTypeNames()
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
if setting.InstallLock {
|
||||
resp.Header().Add("Refresh", "1; url="+setting.AppURL+"user/login")
|
||||
_ = rnd.HTML(resp, http.StatusOK, string(tplPostInstall), nil)
|
||||
return
|
||||
}
|
||||
locale := middleware.Locale(resp, req)
|
||||
startTime := time.Now()
|
||||
ctx := context.Context{
|
||||
@ -93,6 +88,11 @@ func Init(ctx goctx.Context) func(next http.Handler) http.Handler {
|
||||
|
||||
// Install render installation page
|
||||
func Install(ctx *context.Context) {
|
||||
if setting.InstallLock {
|
||||
InstallDone(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
form := forms.InstallForm{}
|
||||
|
||||
// Database settings
|
||||
@ -162,7 +162,7 @@ func Install(ctx *context.Context) {
|
||||
form.DefaultAllowCreateOrganization = setting.Service.DefaultAllowCreateOrganization
|
||||
form.DefaultEnableTimetracking = setting.Service.DefaultEnableTimetracking
|
||||
form.NoReplyAddress = setting.Service.NoReplyAddress
|
||||
form.PasswordAlgorithm = setting.PasswordHashAlgo
|
||||
form.PasswordAlgorithm = hash.ConfigHashAlgorithm(setting.PasswordHashAlgo)
|
||||
|
||||
middleware.AssignForm(form, ctx.Data)
|
||||
ctx.HTML(http.StatusOK, tplInstall)
|
||||
@ -234,6 +234,11 @@ func checkDatabase(ctx *context.Context, form *forms.InstallForm) bool {
|
||||
|
||||
// SubmitInstall response for submit install items
|
||||
func SubmitInstall(ctx *context.Context) {
|
||||
if setting.InstallLock {
|
||||
InstallDone(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
form := *web.GetForm(ctx).(*forms.InstallForm)
|
||||
@ -277,7 +282,6 @@ func SubmitInstall(ctx *context.Context) {
|
||||
setting.Database.Charset = form.Charset
|
||||
setting.Database.Path = form.DbPath
|
||||
setting.Database.LogSQL = !setting.IsProd
|
||||
setting.PasswordHashAlgo = form.PasswordAlgorithm
|
||||
|
||||
if !checkDatabase(ctx, &form) {
|
||||
return
|
||||
@ -499,6 +503,12 @@ func SubmitInstall(ctx *context.Context) {
|
||||
}
|
||||
|
||||
if len(form.PasswordAlgorithm) > 0 {
|
||||
var algorithm *hash.PasswordHashAlgorithm
|
||||
setting.PasswordHashAlgo, algorithm = hash.SetDefaultPasswordHashAlgorithm(form.PasswordAlgorithm)
|
||||
if algorithm == nil {
|
||||
ctx.RenderWithErr(ctx.Tr("install.invalid_password_algorithm"), tplInstall, &form)
|
||||
return
|
||||
}
|
||||
cfg.Section("security").Key("PASSWORD_HASH_ALGO").SetValue(form.PasswordAlgorithm)
|
||||
}
|
||||
|
||||
@ -571,18 +581,26 @@ func SubmitInstall(ctx *context.Context) {
|
||||
}
|
||||
|
||||
log.Info("First-time run install finished!")
|
||||
InstallDone(ctx)
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("install.install_success"))
|
||||
|
||||
ctx.RespHeader().Add("Refresh", "1; url="+setting.AppURL+"user/login")
|
||||
ctx.HTML(http.StatusOK, tplPostInstall)
|
||||
|
||||
// Now get the http.Server from this request and shut it down
|
||||
// NB: This is not our hammerable graceful shutdown this is http.Server.Shutdown
|
||||
srv := ctx.Value(http.ServerContextKey).(*http.Server)
|
||||
go func() {
|
||||
// Sleep for a while to make sure the user's browser has loaded the post-install page and its assets (images, css, js)
|
||||
// What if this duration is not long enough? That's impossible -- if the user can't load the simple page in time, how could they install or use Gitea in the future ....
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
// Now get the http.Server from this request and shut it down
|
||||
// NB: This is not our hammerable graceful shutdown this is http.Server.Shutdown
|
||||
srv := ctx.Value(http.ServerContextKey).(*http.Server)
|
||||
if err := srv.Shutdown(graceful.GetManager().HammerContext()); err != nil {
|
||||
log.Error("Unable to shutdown the install server! Error: %v", err)
|
||||
}
|
||||
|
||||
// After the HTTP server for "install" shuts down, the `runWeb()` will continue to run the "normal" server
|
||||
}()
|
||||
}
|
||||
|
||||
// InstallDone shows the "post-install" page, makes it easier to develop the page.
|
||||
// The name is not called as "PostInstall" to avoid misinterpretation as a handler for "POST /install"
|
||||
func InstallDone(ctx *context.Context) { //nolint
|
||||
ctx.HTML(http.StatusOK, tplPostInstall)
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ package install
|
||||
import (
|
||||
goctx "context"
|
||||
"fmt"
|
||||
"html"
|
||||
"net/http"
|
||||
"path"
|
||||
|
||||
@ -37,7 +38,7 @@ func installRecovery(ctx goctx.Context) func(next http.Handler) http.Handler {
|
||||
// Why we need this? The first recover will try to render a beautiful
|
||||
// error page for user, but the process can still panic again, then
|
||||
// we have to just recover twice and send a simple error page that
|
||||
// should not panic any more.
|
||||
// should not panic anymore.
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
combinedErr := fmt.Sprintf("PANIC: %v\n%s", err, log.Stack(2))
|
||||
@ -107,8 +108,9 @@ func Routes(ctx goctx.Context) *web.Route {
|
||||
|
||||
r.Use(installRecovery(ctx))
|
||||
r.Use(Init(ctx))
|
||||
r.Get("/", Install)
|
||||
r.Get("/", Install) // it must be on the root, because the "install.js" use the window.location to replace the "localhost" AppURL
|
||||
r.Post("/", web.Bind(forms.InstallForm{}), SubmitInstall)
|
||||
r.Get("/post-install", InstallDone)
|
||||
r.Get("/api/healthz", healthcheck.Check)
|
||||
|
||||
r.NotFound(web.Wrap(installNotFound))
|
||||
@ -116,5 +118,10 @@ func Routes(ctx goctx.Context) *web.Route {
|
||||
}
|
||||
|
||||
func installNotFound(w http.ResponseWriter, req *http.Request) {
|
||||
http.Redirect(w, req, setting.AppURL, http.StatusFound)
|
||||
w.Header().Add("Content-Type", "text/html; charset=utf-8")
|
||||
w.Header().Add("Refresh", fmt.Sprintf("1; url=%s", setting.AppSubURL+"/"))
|
||||
// do not use 30x status, because the "post-install" page needs to use 404/200 to detect if Gitea has been installed.
|
||||
// the fetch API could follow 30x requests to the page with 200 status.
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
_, _ = fmt.Fprintf(w, `Not Found. <a href="%s">Go to default page</a>.`, html.EscapeString(setting.AppSubURL+"/"))
|
||||
}
|
||||
|
@ -1420,11 +1420,12 @@ func ViewIssue(ctx *context.Context) {
|
||||
}
|
||||
|
||||
var (
|
||||
role issues_model.RoleDescriptor
|
||||
ok bool
|
||||
marked = make(map[int64]issues_model.RoleDescriptor)
|
||||
comment *issues_model.Comment
|
||||
participants = make([]*user_model.User, 1, 10)
|
||||
role issues_model.RoleDescriptor
|
||||
ok bool
|
||||
marked = make(map[int64]issues_model.RoleDescriptor)
|
||||
comment *issues_model.Comment
|
||||
participants = make([]*user_model.User, 1, 10)
|
||||
latestCloseCommentID int64
|
||||
)
|
||||
if ctx.Repo.Repository.IsTimetrackerEnabled(ctx) {
|
||||
if ctx.IsSigned {
|
||||
@ -1622,9 +1623,15 @@ func ViewIssue(ctx *context.Context) {
|
||||
comment.Type == issues_model.CommentTypeStopTracking {
|
||||
// drop error since times could be pruned from DB..
|
||||
_ = comment.LoadTime()
|
||||
} else if comment.Type == issues_model.CommentTypeClose {
|
||||
// record ID of latest closed comment.
|
||||
// if PR is closed, the comments whose type is CommentTypePullRequestPush(29) after latestCloseCommentID won't be rendered.
|
||||
latestCloseCommentID = comment.ID
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Data["LatestCloseCommentID"] = latestCloseCommentID
|
||||
|
||||
// Combine multiple label assignments into a single comment
|
||||
combineLabelComments(issue)
|
||||
|
||||
|
@ -587,7 +587,7 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
|
||||
ctx.Data["HeadBranchCommitID"] = headBranchSha
|
||||
ctx.Data["PullHeadCommitID"] = sha
|
||||
|
||||
if pull.HeadRepo == nil || !headBranchExist || headBranchSha != sha {
|
||||
if pull.HeadRepo == nil || !headBranchExist || (!pull.Issue.IsClosed && (headBranchSha != sha)) {
|
||||
ctx.Data["IsPullRequestBroken"] = true
|
||||
if pull.IsSameRepo() {
|
||||
ctx.Data["HeadTarget"] = pull.HeadBranch
|
||||
|
@ -186,7 +186,7 @@ func renderDirectory(ctx *context.Context, treeLink string) {
|
||||
return
|
||||
}
|
||||
|
||||
renderReadmeFile(ctx, readmeFile, treeLink)
|
||||
renderReadmeFile(ctx, readmeFile, fmt.Sprintf("%s/%s", treeLink, readmeFile.name))
|
||||
}
|
||||
|
||||
// localizedExtensions prepends the provided language code with and without a
|
||||
|
@ -499,6 +499,13 @@ func SyncPullMirror(ctx context.Context, repoID int64) bool {
|
||||
theCommits.Commits = theCommits.Commits[:setting.UI.FeedMaxCommitNum]
|
||||
}
|
||||
|
||||
if newCommit, err := gitRepo.GetCommit(newCommitID); err != nil {
|
||||
log.Error("SyncMirrors [repo: %-v]: unable to get commit %s: %v", m.Repo, newCommitID, err)
|
||||
continue
|
||||
} else {
|
||||
theCommits.HeadCommit = repo_module.CommitToPushCommit(newCommit)
|
||||
}
|
||||
|
||||
theCommits.CompareURL = m.Repo.ComposeCompareURL(oldCommitID, newCommitID)
|
||||
|
||||
notification.NotifySyncPushCommits(ctx, m.Repo.MustOwner(ctx), m.Repo, &repo_module.PushUpdateOptions{
|
||||
|
@ -257,7 +257,7 @@ func AddTestPullRequestTask(doer *user_model.User, repoID int64, branch string,
|
||||
// If you don't let it run all the way then you will lose data
|
||||
// TODO: graceful: AddTestPullRequestTask needs to become a queue!
|
||||
|
||||
prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(repoID, branch)
|
||||
prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(repoID, branch, true)
|
||||
if err != nil {
|
||||
log.Error("Find pull requests [head_repo_id: %d, head_branch: %s]: %v", repoID, branch, err)
|
||||
return
|
||||
@ -500,7 +500,7 @@ func (errs errlist) Error() string {
|
||||
|
||||
// CloseBranchPulls close all the pull requests who's head branch is the branch
|
||||
func CloseBranchPulls(doer *user_model.User, repoID int64, branch string) error {
|
||||
prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(repoID, branch)
|
||||
prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(repoID, branch, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -536,7 +536,7 @@ func CloseRepoBranchesPulls(ctx context.Context, doer *user_model.User, repo *re
|
||||
|
||||
var errs errlist
|
||||
for _, branch := range branches {
|
||||
prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(repo.ID, branch.Name)
|
||||
prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(repo.ID, branch.Name, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{{if .Title}}{{.Title | RenderEmojiPlain}} - {{end}}{{if .Repository.Name}}{{.Repository.Name}} - {{end}}{{AppName}}</title>
|
||||
<link rel="manifest" href="data:{{.ManifestData}}">
|
||||
{{if .ManifestData}}<link rel="manifest" href="data:{{.ManifestData}}">{{end}}
|
||||
<meta name="theme-color" content="{{ThemeColorMetaTag}}">
|
||||
<meta name="default-theme" content="{{DefaultTheme}}">
|
||||
<meta name="author" content="{{if .Repository}}{{.Owner.Name}}{{else}}{{MetaAuthor}}{{end}}">
|
||||
|
@ -8,7 +8,7 @@
|
||||
<title>{{.Subject}}</title>
|
||||
</head>
|
||||
|
||||
{{$repo_url := printf "<a href='%s'>%s</a>" (Escape .Issue.Repo.Link) (Escape .Issue.Repo.FullName)}}
|
||||
{{$repo_url := printf "<a href='%s'>%s</a>" (Escape .Issue.Repo.HTMLURL) (Escape .Issue.Repo.FullName)}}
|
||||
{{$link := printf "<a href='%s'>#%d</a>" (Escape .Link) .Issue.Index}}
|
||||
<body>
|
||||
<p>
|
||||
|
@ -20,11 +20,11 @@
|
||||
{{if eq .ActionName "push"}}
|
||||
<p>
|
||||
{{if .Comment.IsForcePush}}
|
||||
{{$oldCommitUrl := printf "%s/commit/%s" .Comment.Issue.PullRequest.BaseRepo.Link .Comment.OldCommit}}
|
||||
{{$oldCommitUrl := printf "%s/commit/%s" .Comment.Issue.PullRequest.BaseRepo.HTMLURL .Comment.OldCommit}}
|
||||
{{$oldShortSha := ShortSha .Comment.OldCommit}}
|
||||
{{$oldCommitLink := printf "<a href='%[1]s'><b>%[2]s</b></a>" (Escape $oldCommitUrl) (Escape $oldShortSha)}}
|
||||
|
||||
{{$newCommitUrl := printf "%s/commit/%s" .Comment.Issue.PullRequest.BaseRepo.Link .Comment.NewCommit}}
|
||||
{{$newCommitUrl := printf "%s/commit/%s" .Comment.Issue.PullRequest.BaseRepo.HTMLURL .Comment.NewCommit}}
|
||||
{{$newShortSha := ShortSha .Comment.NewCommit}}
|
||||
{{$newCommitLink := printf "<a href='%[1]s'><b>%[2]s</b></a>" (Escape $newCommitUrl) (Escape $newShortSha)}}
|
||||
|
||||
@ -72,7 +72,7 @@
|
||||
<ul>
|
||||
{{range .Comment.Commits}}
|
||||
<li>
|
||||
<a href="{{$.Comment.Issue.PullRequest.BaseRepo.Link}}/commit/{{.ID}}">
|
||||
<a href="{{$.Comment.Issue.PullRequest.BaseRepo.HTMLURL}}/commit/{{.ID}}">
|
||||
{{ShortSha .ID.String}}
|
||||
</a> - {{.Summary}}
|
||||
</li>
|
||||
|
@ -11,8 +11,8 @@
|
||||
|
||||
</head>
|
||||
|
||||
{{$release_url := printf "<a href='%s'>%s</a>" (.Release.Link | Escape) (.Release.TagName | Escape)}}
|
||||
{{$repo_url := printf "<a href='%s'>%s</a>" (.Release.Repo.Link | Escape) (.Release.Repo.FullName | Escape)}}
|
||||
{{$release_url := printf "<a href='%s'>%s</a>" (.Release.HTMLURL | Escape) (.Release.TagName | Escape)}}
|
||||
{{$repo_url := printf "<a href='%s'>%s</a>" (.Release.Repo.HTMLURL | Escape) (.Release.Repo.FullName | Escape)}}
|
||||
<body>
|
||||
<p>
|
||||
{{.locale.Tr "mail.release.new.text" .Release.Publisher.Name $release_url $repo_url | Str2html}}
|
||||
|
@ -1,5 +1,5 @@
|
||||
{{template "base/head" .}}
|
||||
<div role="main" aria-label="{{.Title}}" class="page-content install">
|
||||
<div role="main" aria-label="{{.Title}}" class="page-content install post-install">
|
||||
<div class="ui container">
|
||||
<div class="ui grid">
|
||||
<div class="sixteen wide column content">
|
||||
@ -13,7 +13,7 @@
|
||||
</div>
|
||||
<div class="ui stackable middle very relaxed page grid">
|
||||
<div class="sixteen wide center aligned centered column">
|
||||
<p><a href="{{AppSubUrl}}/user/login">{{AppSubUrl}}/user/login</a></p>
|
||||
<p><a id="goto-user-login" href="{{AppSubUrl}}/user/login">{{.locale.Tr "loading"}}</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -697,6 +697,10 @@
|
||||
</span>
|
||||
</div>
|
||||
{{else if and (eq .Type 29) (or (gt .CommitsNum 0) .IsForcePush)}}
|
||||
<!-- If PR is closed, the comments whose type is CommentTypePullRequestPush(29) after latestCloseCommentID won't be rendered. //-->
|
||||
{{if and .Issue.IsClosed (gt .ID $.LatestCloseCommentID)}}
|
||||
{{continue}}
|
||||
{{end}}
|
||||
<div class="timeline-item event" id="{{.HashTag}}">
|
||||
<span class="badge">{{svg "octicon-repo-push"}}</span>
|
||||
<span class="text grey muted-links">
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1 @@
|
||||
78fb907e3a3309eae4fe8fef030874cebbf1cd5e
|
@ -256,3 +256,23 @@ func TestViewRepoDirectory(t *testing.T) {
|
||||
assert.Zero(t, repoTopics.Length())
|
||||
assert.Zero(t, repoSummary.Length())
|
||||
}
|
||||
|
||||
func TestMarkDownImage(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
session := loginUser(t, "user2")
|
||||
|
||||
req := NewRequest(t, "GET", "/user2/repo1/src/branch/home-md-img-check")
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
_, exists := htmlDoc.doc.Find(`img[src="/user2/repo1/media/branch/home-md-img-check/test-fake-img.jpg"]`).Attr("src")
|
||||
assert.True(t, exists, "Repo home page markdown image link check failed")
|
||||
|
||||
req = NewRequest(t, "GET", "/user2/repo1/src/branch/home-md-img-check/README.md")
|
||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
htmlDoc = NewHTMLParser(t, resp.Body)
|
||||
_, exists = htmlDoc.doc.Find(`img[src="/user2/repo1/media/branch/home-md-img-check/test-fake-img.jpg"]`).Attr("src")
|
||||
assert.True(t, exists, "Repo src page markdown image link check failed")
|
||||
}
|
||||
|
@ -2,10 +2,18 @@ import $ from 'jquery';
|
||||
import {hideElem, showElem} from '../utils/dom.js';
|
||||
|
||||
export function initInstall() {
|
||||
if ($('.page-content.install').length === 0) {
|
||||
const $page = $('.page-content.install');
|
||||
if ($page.length === 0) {
|
||||
return;
|
||||
}
|
||||
if ($page.is('.post-install')) {
|
||||
initPostInstall();
|
||||
} else {
|
||||
initPreInstall();
|
||||
}
|
||||
}
|
||||
|
||||
function initPreInstall() {
|
||||
const defaultDbUser = 'gitea';
|
||||
const defaultDbName = 'gitea';
|
||||
|
||||
@ -40,6 +48,18 @@ export function initInstall() {
|
||||
} // else: for SQLite3, the default path is always prepared by backend code (setting)
|
||||
}).trigger('change');
|
||||
|
||||
const $appUrl = $('#app_url');
|
||||
const configAppUrl = $appUrl.val();
|
||||
if (configAppUrl.includes('://localhost')) {
|
||||
$appUrl.val(window.location.href);
|
||||
}
|
||||
|
||||
const $domain = $('#domain');
|
||||
const configDomain = $domain.val().trim();
|
||||
if (configDomain === 'localhost') {
|
||||
$domain.val(window.location.hostname);
|
||||
}
|
||||
|
||||
// TODO: better handling of exclusive relations.
|
||||
$('#offline-mode input').on('change', function () {
|
||||
if ($(this).is(':checked')) {
|
||||
@ -83,3 +103,20 @@ export function initInstall() {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initPostInstall() {
|
||||
const el = document.getElementById('goto-user-login');
|
||||
if (!el) return;
|
||||
|
||||
const targetUrl = el.getAttribute('href');
|
||||
let tid = setInterval(async () => {
|
||||
try {
|
||||
const resp = await fetch(targetUrl);
|
||||
if (tid && resp.status === 200) {
|
||||
clearInterval(tid);
|
||||
tid = null;
|
||||
window.location.href = targetUrl;
|
||||
}
|
||||
} catch {}
|
||||
}, 1000);
|
||||
}
|
||||
|
@ -91,36 +91,8 @@ export function initRepoEditor() {
|
||||
$('#commit-button').text($(this).attr('button_text'));
|
||||
});
|
||||
|
||||
const $editFilename = $('#file-name');
|
||||
$editFilename.on('keyup', function (e) {
|
||||
const $section = $('.breadcrumb span.section');
|
||||
const $divider = $('.breadcrumb div.divider');
|
||||
let value;
|
||||
let parts;
|
||||
|
||||
if (e.keyCode === 8 && getCursorPosition($(this)) === 0 && $section.length > 0) {
|
||||
value = $section.last().find('a').text();
|
||||
$(this).val(value + $(this).val());
|
||||
$(this)[0].setSelectionRange(value.length, value.length);
|
||||
$section.last().remove();
|
||||
$divider.last().remove();
|
||||
}
|
||||
if (e.keyCode === 191) {
|
||||
parts = $(this).val().split('/');
|
||||
for (let i = 0; i < parts.length; ++i) {
|
||||
value = parts[i];
|
||||
if (i < parts.length - 1) {
|
||||
if (value.length) {
|
||||
$(`<span class="section"><a href="#">${htmlEscape(value)}</a></span>`).insertBefore($(this));
|
||||
$('<div class="divider"> / </div>').insertBefore($(this));
|
||||
}
|
||||
} else {
|
||||
$(this).val(value);
|
||||
}
|
||||
$(this)[0].setSelectionRange(0, 0);
|
||||
}
|
||||
}
|
||||
parts = [];
|
||||
const joinTreePath = ($fileNameEl) => {
|
||||
const parts = [];
|
||||
$('.breadcrumb span.section').each(function () {
|
||||
const element = $(this);
|
||||
if (element.find('a').length) {
|
||||
@ -129,9 +101,47 @@ export function initRepoEditor() {
|
||||
parts.push(element.text());
|
||||
}
|
||||
});
|
||||
if ($(this).val()) parts.push($(this).val());
|
||||
if ($fileNameEl.val()) parts.push($fileNameEl.val());
|
||||
$('#tree_path').val(parts.join('/'));
|
||||
}).trigger('keyup');
|
||||
};
|
||||
|
||||
const $editFilename = $('#file-name');
|
||||
$editFilename.on('input', function () {
|
||||
const parts = $(this).val().split('/');
|
||||
|
||||
if (parts.length > 1) {
|
||||
for (let i = 0; i < parts.length; ++i) {
|
||||
const value = parts[i];
|
||||
if (i < parts.length - 1) {
|
||||
if (value.length) {
|
||||
$(`<span class="section"><a href="#">${htmlEscape(value)}</a></span>`).insertBefore($(this));
|
||||
$('<div class="divider"> / </div>').insertBefore($(this));
|
||||
}
|
||||
} else {
|
||||
$(this).val(value);
|
||||
}
|
||||
this.setSelectionRange(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
joinTreePath($(this));
|
||||
});
|
||||
|
||||
$editFilename.on('keydown', function (e) {
|
||||
const $section = $('.breadcrumb span.section');
|
||||
|
||||
// Jump back to last directory once the filename is empty
|
||||
if (e.code === 'Backspace' && getCursorPosition($(this)) === 0 && $section.length > 0) {
|
||||
e.preventDefault();
|
||||
const $divider = $('.breadcrumb div.divider');
|
||||
const value = $section.last().find('a').text();
|
||||
$(this).val(value + $(this).val());
|
||||
this.setSelectionRange(value.length, value.length);
|
||||
$section.last().remove();
|
||||
$divider.last().remove();
|
||||
joinTreePath($(this));
|
||||
}
|
||||
});
|
||||
|
||||
const $editArea = $('.repository.editor textarea#edit_area');
|
||||
if (!$editArea.length) return;
|
||||
|
19
web_src/js/webcomponents/README.md
Normal file
19
web_src/js/webcomponents/README.md
Normal file
@ -0,0 +1,19 @@
|
||||
# Web Components
|
||||
|
||||
This `webcomponents` directory contains the source code for the web components used in the Gitea Web UI.
|
||||
|
||||
https://developer.mozilla.org/en-US/docs/Web/Web_Components
|
||||
|
||||
# Guidelines
|
||||
|
||||
* These components are loaded in `<head>` (before DOM body),
|
||||
so they should have their own dependencies and should be very light,
|
||||
then they won't affect the page loading time too much.
|
||||
* If the component is not a public one, it's suggested to have its own `Gitea` or `gitea-` prefix to avoid conflicts.
|
||||
|
||||
# TODO
|
||||
|
||||
There are still some components that are not migrated to web components yet:
|
||||
|
||||
* `<span class="js-pretty-number">`
|
||||
* `<time data-format>`
|
@ -2121,8 +2121,7 @@ a.ui.label:hover {
|
||||
font: 12px var(--fonts-monospace);
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.blame .code-inner {
|
||||
|
Loading…
x
Reference in New Issue
Block a user