aboutsummaryrefslogtreecommitdiffstats
path: root/services
diff options
context:
space:
mode:
Diffstat (limited to 'services')
-rw-r--r--services/actions/context.go161
-rw-r--r--services/actions/context_test.go29
-rw-r--r--services/actions/init_test.go4
-rw-r--r--services/actions/notifier.go8
-rw-r--r--services/auth/auth_test.go22
-rw-r--r--services/automerge/automerge.go11
-rw-r--r--services/context/access_log.go103
-rw-r--r--services/context/access_log_test.go71
-rw-r--r--services/context/api.go3
-rw-r--r--services/context/base.go24
-rw-r--r--services/context/context_model.go20
-rw-r--r--services/context/permission.go78
-rw-r--r--services/context/repo.go340
-rw-r--r--services/context/response.go36
-rw-r--r--services/convert/issue.go3
-rw-r--r--services/convert/issue_comment.go2
-rw-r--r--services/convert/utils.go2
-rw-r--r--services/convert/utils_test.go10
-rw-r--r--services/doctor/dbconsistency_test.go5
-rw-r--r--services/feed/notifier.go2
-rw-r--r--services/forms/repo_form.go81
-rw-r--r--services/gitdiff/csv_test.go13
-rw-r--r--services/gitdiff/gitdiff.go6
-rw-r--r--services/gitdiff/gitdiff_test.go6
-rw-r--r--services/issue/issue.go5
-rw-r--r--services/mailer/mail_test.go4
-rw-r--r--services/migrations/gitea_downloader_test.go9
-rw-r--r--services/mirror/mirror_pull.go15
-rw-r--r--services/pull/pull.go105
-rw-r--r--services/repository/archiver/archiver.go57
-rw-r--r--services/repository/archiver/archiver_test.go27
-rw-r--r--services/repository/branch.go101
-rw-r--r--services/repository/merge_upstream.go84
-rw-r--r--services/repository/push.go3
-rw-r--r--services/webhook/notifier.go12
-rw-r--r--services/webhook/slack.go6
-rw-r--r--services/webhook/webhook.go10
-rw-r--r--services/wiki/wiki_test.go21
38 files changed, 853 insertions, 646 deletions
diff --git a/services/actions/context.go b/services/actions/context.go
new file mode 100644
index 0000000000..d14728fae4
--- /dev/null
+++ b/services/actions/context.go
@@ -0,0 +1,161 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package actions
+
+import (
+ "context"
+ "fmt"
+
+ actions_model "code.gitea.io/gitea/models/actions"
+ "code.gitea.io/gitea/models/db"
+ actions_module "code.gitea.io/gitea/modules/actions"
+ "code.gitea.io/gitea/modules/container"
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/json"
+ "code.gitea.io/gitea/modules/setting"
+)
+
+// GenerateGiteaContext generate the gitea context without token and gitea_runtime_token
+// job can be nil when generating a context for parsing workflow-level expressions
+func GenerateGiteaContext(run *actions_model.ActionRun, job *actions_model.ActionRunJob) map[string]any {
+ event := map[string]any{}
+ _ = json.Unmarshal([]byte(run.EventPayload), &event)
+
+ baseRef := ""
+ headRef := ""
+ ref := run.Ref
+ sha := run.CommitSHA
+ if pullPayload, err := run.GetPullRequestEventPayload(); err == nil && pullPayload.PullRequest != nil && pullPayload.PullRequest.Base != nil && pullPayload.PullRequest.Head != nil {
+ baseRef = pullPayload.PullRequest.Base.Ref
+ headRef = pullPayload.PullRequest.Head.Ref
+
+ // if the TriggerEvent is pull_request_target, ref and sha need to be set according to the base of pull request
+ // In GitHub's documentation, ref should be the branch or tag that triggered workflow. But when the TriggerEvent is pull_request_target,
+ // the ref will be the base branch.
+ if run.TriggerEvent == actions_module.GithubEventPullRequestTarget {
+ ref = git.BranchPrefix + pullPayload.PullRequest.Base.Name
+ sha = pullPayload.PullRequest.Base.Sha
+ }
+ }
+
+ refName := git.RefName(ref)
+
+ gitContext := map[string]any{
+ // standard contexts, see https://docs.github.com/en/actions/learn-github-actions/contexts#github-context
+ "action": "", // string, The name of the action currently running, or the id of a step. GitHub removes special characters, and uses the name __run when the current step runs a script without an id. If you use the same action more than once in the same job, the name will include a suffix with the sequence number with underscore before it. For example, the first script you run will have the name __run, and the second script will be named __run_2. Similarly, the second invocation of actions/checkout will be actionscheckout2.
+ "action_path": "", // string, The path where an action is located. This property is only supported in composite actions. You can use this path to access files located in the same repository as the action.
+ "action_ref": "", // string, For a step executing an action, this is the ref of the action being executed. For example, v2.
+ "action_repository": "", // string, For a step executing an action, this is the owner and repository name of the action. For example, actions/checkout.
+ "action_status": "", // string, For a composite action, the current result of the composite action.
+ "actor": run.TriggerUser.Name, // string, The username of the user that triggered the initial workflow run. If the workflow run is a re-run, this value may differ from github.triggering_actor. Any workflow re-runs will use the privileges of github.actor, even if the actor initiating the re-run (github.triggering_actor) has different privileges.
+ "api_url": setting.AppURL + "api/v1", // string, The URL of the GitHub REST API.
+ "base_ref": baseRef, // string, The base_ref or target branch of the pull request in a workflow run. This property is only available when the event that triggers a workflow run is either pull_request or pull_request_target.
+ "env": "", // string, Path on the runner to the file that sets environment variables from workflow commands. This file is unique to the current step and is a different file for each step in a job. For more information, see "Workflow commands for GitHub Actions."
+ "event": event, // object, The full event webhook payload. You can access individual properties of the event using this context. This object is identical to the webhook payload of the event that triggered the workflow run, and is different for each event. The webhooks for each GitHub Actions event is linked in "Events that trigger workflows." For example, for a workflow run triggered by the push event, this object contains the contents of the push webhook payload.
+ "event_name": run.TriggerEvent, // string, The name of the event that triggered the workflow run.
+ "event_path": "", // string, The path to the file on the runner that contains the full event webhook payload.
+ "graphql_url": "", // string, The URL of the GitHub GraphQL API.
+ "head_ref": headRef, // string, The head_ref or source branch of the pull request in a workflow run. This property is only available when the event that triggers a workflow run is either pull_request or pull_request_target.
+ "job": "", // string, The job_id of the current job.
+ "ref": ref, // string, The fully-formed ref of the branch or tag that triggered the workflow run. For workflows triggered by push, this is the branch or tag ref that was pushed. For workflows triggered by pull_request, this is the pull request merge branch. For workflows triggered by release, this is the release tag created. For other triggers, this is the branch or tag ref that triggered the workflow run. This is only set if a branch or tag is available for the event type. The ref given is fully-formed, meaning that for branches the format is refs/heads/<branch_name>, for pull requests it is refs/pull/<pr_number>/merge, and for tags it is refs/tags/<tag_name>. For example, refs/heads/feature-branch-1.
+ "ref_name": refName.ShortName(), // string, The short ref name of the branch or tag that triggered the workflow run. This value matches the branch or tag name shown on GitHub. For example, feature-branch-1.
+ "ref_protected": false, // boolean, true if branch protections are configured for the ref that triggered the workflow run.
+ "ref_type": string(refName.RefType()), // string, The type of ref that triggered the workflow run. Valid values are branch or tag.
+ "path": "", // string, Path on the runner to the file that sets system PATH variables from workflow commands. This file is unique to the current step and is a different file for each step in a job. For more information, see "Workflow commands for GitHub Actions."
+ "repository": run.Repo.OwnerName + "/" + run.Repo.Name, // string, The owner and repository name. For example, Codertocat/Hello-World.
+ "repository_owner": run.Repo.OwnerName, // string, The repository owner's name. For example, Codertocat.
+ "repositoryUrl": run.Repo.HTMLURL(), // string, The Git URL to the repository. For example, git://github.com/codertocat/hello-world.git.
+ "retention_days": "", // string, The number of days that workflow run logs and artifacts are kept.
+ "run_id": "", // string, A unique number for each workflow run within a repository. This number does not change if you re-run the workflow run.
+ "run_number": fmt.Sprint(run.Index), // string, A unique number for each run of a particular workflow in a repository. This number begins at 1 for the workflow's first run, and increments with each new run. This number does not change if you re-run the workflow run.
+ "run_attempt": "", // string, A unique number for each attempt of a particular workflow run in a repository. This number begins at 1 for the workflow run's first attempt, and increments with each re-run.
+ "secret_source": "Actions", // string, The source of a secret used in a workflow. Possible values are None, Actions, Dependabot, or Codespaces.
+ "server_url": setting.AppURL, // string, The URL of the GitHub server. For example: https://github.com.
+ "sha": sha, // string, The commit SHA that triggered the workflow. The value of this commit SHA depends on the event that triggered the workflow. For more information, see "Events that trigger workflows." For example, ffac537e6cbbf934b08745a378932722df287a53.
+ "triggering_actor": "", // string, The username of the user that initiated the workflow run. If the workflow run is a re-run, this value may differ from github.actor. Any workflow re-runs will use the privileges of github.actor, even if the actor initiating the re-run (github.triggering_actor) has different privileges.
+ "workflow": run.WorkflowID, // string, The name of the workflow. If the workflow file doesn't specify a name, the value of this property is the full path of the workflow file in the repository.
+ "workspace": "", // string, The default working directory on the runner for steps, and the default location of your repository when using the checkout action.
+
+ // additional contexts
+ "gitea_default_actions_url": setting.Actions.DefaultActionsURL.URL(),
+ }
+
+ if job != nil {
+ gitContext["job"] = job.JobID
+ gitContext["run_id"] = fmt.Sprint(job.RunID)
+ gitContext["run_attempt"] = fmt.Sprint(job.Attempt)
+ }
+
+ return gitContext
+}
+
+type TaskNeed struct {
+ Result actions_model.Status
+ Outputs map[string]string
+}
+
+// FindTaskNeeds finds the `needs` for the task by the task's job
+func FindTaskNeeds(ctx context.Context, job *actions_model.ActionRunJob) (map[string]*TaskNeed, error) {
+ if len(job.Needs) == 0 {
+ return nil, nil
+ }
+ needs := container.SetOf(job.Needs...)
+
+ jobs, err := db.Find[actions_model.ActionRunJob](ctx, actions_model.FindRunJobOptions{RunID: job.RunID})
+ if err != nil {
+ return nil, fmt.Errorf("FindRunJobs: %w", err)
+ }
+
+ jobIDJobs := make(map[string][]*actions_model.ActionRunJob)
+ for _, job := range jobs {
+ jobIDJobs[job.JobID] = append(jobIDJobs[job.JobID], job)
+ }
+
+ ret := make(map[string]*TaskNeed, len(needs))
+ for jobID, jobsWithSameID := range jobIDJobs {
+ if !needs.Contains(jobID) {
+ continue
+ }
+ var jobOutputs map[string]string
+ for _, job := range jobsWithSameID {
+ if job.TaskID == 0 || !job.Status.IsDone() {
+ // it shouldn't happen, or the job has been rerun
+ continue
+ }
+ got, err := actions_model.FindTaskOutputByTaskID(ctx, job.TaskID)
+ if err != nil {
+ return nil, fmt.Errorf("FindTaskOutputByTaskID: %w", err)
+ }
+ outputs := make(map[string]string, len(got))
+ for _, v := range got {
+ outputs[v.OutputKey] = v.OutputValue
+ }
+ if len(jobOutputs) == 0 {
+ jobOutputs = outputs
+ } else {
+ jobOutputs = mergeTwoOutputs(outputs, jobOutputs)
+ }
+ }
+ ret[jobID] = &TaskNeed{
+ Outputs: jobOutputs,
+ Result: actions_model.AggregateJobStatus(jobsWithSameID),
+ }
+ }
+ return ret, nil
+}
+
+// mergeTwoOutputs merges two outputs from two different ActionRunJobs
+// Values with the same output name may be overridden. The user should ensure the output names are unique.
+// See https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#using-job-outputs-in-a-matrix-job
+func mergeTwoOutputs(o1, o2 map[string]string) map[string]string {
+ ret := make(map[string]string, len(o1))
+ for k1, v1 := range o1 {
+ if len(v1) > 0 {
+ ret[k1] = v1
+ } else {
+ ret[k1] = o2[k1]
+ }
+ }
+ return ret
+}
diff --git a/services/actions/context_test.go b/services/actions/context_test.go
new file mode 100644
index 0000000000..6ed094b289
--- /dev/null
+++ b/services/actions/context_test.go
@@ -0,0 +1,29 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package actions
+
+import (
+ "context"
+ "testing"
+
+ actions_model "code.gitea.io/gitea/models/actions"
+ "code.gitea.io/gitea/models/unittest"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestFindTaskNeeds(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+
+ task := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 51})
+ job := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: task.JobID})
+
+ ret, err := FindTaskNeeds(context.Background(), job)
+ assert.NoError(t, err)
+ assert.Len(t, ret, 1)
+ assert.Contains(t, ret, "job1")
+ assert.Len(t, ret["job1"].Outputs, 2)
+ assert.Equal(t, "abc", ret["job1"].Outputs["output_a"])
+ assert.Equal(t, "bbb", ret["job1"].Outputs["output_b"])
+}
diff --git a/services/actions/init_test.go b/services/actions/init_test.go
index 59c321ccd7..7ef0702204 100644
--- a/services/actions/init_test.go
+++ b/services/actions/init_test.go
@@ -17,9 +17,7 @@ import (
)
func TestMain(m *testing.M) {
- unittest.MainTest(m, &unittest.TestOptions{
- FixtureFiles: []string{"action_runner_token.yml"},
- })
+ unittest.MainTest(m)
os.Exit(m.Run())
}
diff --git a/services/actions/notifier.go b/services/actions/notifier.go
index 67e33e7cce..1a23b4e0c5 100644
--- a/services/actions/notifier.go
+++ b/services/actions/notifier.go
@@ -563,9 +563,9 @@ func (n *actionsNotifier) CreateRef(ctx context.Context, pusher *user_model.User
newNotifyInput(repo, pusher, webhook_module.HookEventCreate).
WithRef(refFullName.String()).
WithPayload(&api.CreatePayload{
- Ref: refFullName.String(),
+ Ref: refFullName.String(), // HINT: here is inconsistent with the Webhook's payload: webhook uses ShortName
Sha: refID,
- RefType: refFullName.RefType(),
+ RefType: string(refFullName.RefType()),
Repo: apiRepo,
Sender: apiPusher,
}).
@@ -580,8 +580,8 @@ func (n *actionsNotifier) DeleteRef(ctx context.Context, pusher *user_model.User
newNotifyInput(repo, pusher, webhook_module.HookEventDelete).
WithPayload(&api.DeletePayload{
- Ref: refFullName.String(),
- RefType: refFullName.RefType(),
+ Ref: refFullName.String(), // HINT: here is inconsistent with the Webhook's payload: webhook uses ShortName
+ RefType: string(refFullName.RefType()),
PusherType: api.PusherTypeUser,
Repo: apiRepo,
Sender: apiPusher,
diff --git a/services/auth/auth_test.go b/services/auth/auth_test.go
index 3adaa28664..f1e9e6753f 100644
--- a/services/auth/auth_test.go
+++ b/services/auth/auth_test.go
@@ -9,6 +9,8 @@ import (
"testing"
"code.gitea.io/gitea/modules/setting"
+
+ "github.com/stretchr/testify/assert"
)
func Test_isGitRawOrLFSPath(t *testing.T) {
@@ -108,26 +110,22 @@ func Test_isGitRawOrLFSPath(t *testing.T) {
t.Run(tt.path, func(t *testing.T) {
req, _ := http.NewRequest("POST", "http://localhost"+tt.path, nil)
setting.LFS.StartServer = false
- if got := isGitRawOrAttachOrLFSPath(req); got != tt.want {
- t.Errorf("isGitOrLFSPath() = %v, want %v", got, tt.want)
- }
+ assert.Equal(t, tt.want, isGitRawOrAttachOrLFSPath(req))
+
setting.LFS.StartServer = true
- if got := isGitRawOrAttachOrLFSPath(req); got != tt.want {
- t.Errorf("isGitOrLFSPath() = %v, want %v", got, tt.want)
- }
+ assert.Equal(t, tt.want, isGitRawOrAttachOrLFSPath(req))
})
}
for _, tt := range lfsTests {
t.Run(tt, func(t *testing.T) {
req, _ := http.NewRequest("POST", tt, nil)
setting.LFS.StartServer = false
- if got := isGitRawOrAttachOrLFSPath(req); got != setting.LFS.StartServer {
- t.Errorf("isGitOrLFSPath(%q) = %v, want %v, %v", tt, got, setting.LFS.StartServer, gitRawOrAttachPathRe.MatchString(tt))
- }
+ got := isGitRawOrAttachOrLFSPath(req)
+ assert.Equalf(t, setting.LFS.StartServer, got, "isGitOrLFSPath(%q) = %v, want %v, %v", tt, got, setting.LFS.StartServer, gitRawOrAttachPathRe.MatchString(tt))
+
setting.LFS.StartServer = true
- if got := isGitRawOrAttachOrLFSPath(req); got != setting.LFS.StartServer {
- t.Errorf("isGitOrLFSPath(%q) = %v, want %v", tt, got, setting.LFS.StartServer)
- }
+ got = isGitRawOrAttachOrLFSPath(req)
+ assert.Equalf(t, setting.LFS.StartServer, got, "isGitOrLFSPath(%q) = %v, want %v", tt, got, setting.LFS.StartServer)
})
}
setting.LFS.StartServer = origLFSStartServer
diff --git a/services/automerge/automerge.go b/services/automerge/automerge.go
index a1ee204882..bdb0493ae8 100644
--- a/services/automerge/automerge.go
+++ b/services/automerge/automerge.go
@@ -24,6 +24,7 @@ import (
"code.gitea.io/gitea/modules/queue"
notify_service "code.gitea.io/gitea/services/notify"
pull_service "code.gitea.io/gitea/services/pull"
+ repo_service "code.gitea.io/gitea/services/repository"
)
// prAutoMergeQueue represents a queue to handle update pull request tests
@@ -63,9 +64,9 @@ func addToQueue(pr *issues_model.PullRequest, sha string) {
}
// ScheduleAutoMerge if schedule is false and no error, pull can be merged directly
-func ScheduleAutoMerge(ctx context.Context, doer *user_model.User, pull *issues_model.PullRequest, style repo_model.MergeStyle, message string) (scheduled bool, err error) {
+func ScheduleAutoMerge(ctx context.Context, doer *user_model.User, pull *issues_model.PullRequest, style repo_model.MergeStyle, message string, deleteBranchAfterMerge bool) (scheduled bool, err error) {
err = db.WithTx(ctx, func(ctx context.Context) error {
- if err := pull_model.ScheduleAutoMerge(ctx, doer, pull.ID, style, message); err != nil {
+ if err := pull_model.ScheduleAutoMerge(ctx, doer, pull.ID, style, message, deleteBranchAfterMerge); err != nil {
return err
}
scheduled = true
@@ -303,4 +304,10 @@ func handlePullRequestAutoMerge(pullID int64, sha string) {
// on the pull request page. But this should not be finished in a bug fix PR which will be backport to release branch.
return
}
+
+ if pr.Flow == issues_model.PullRequestFlowGithub && scheduledPRM.DeleteBranchAfterMerge {
+ if err := repo_service.DeleteBranch(ctx, doer, pr.HeadRepo, headGitRepo, pr.HeadBranch, pr); err != nil {
+ log.Error("DeletePullRequestHeadBranch: %v", err)
+ }
+ }
}
diff --git a/services/context/access_log.go b/services/context/access_log.go
index 0926748ac5..001d93a362 100644
--- a/services/context/access_log.go
+++ b/services/context/access_log.go
@@ -18,13 +18,14 @@ import (
"code.gitea.io/gitea/modules/web/middleware"
)
-type routerLoggerOptions struct {
- req *http.Request
+type accessLoggerTmplData struct {
Identity *string
Start *time.Time
- ResponseWriter http.ResponseWriter
- Ctx map[string]any
- RequestID *string
+ ResponseWriter struct {
+ Status, Size int
+ }
+ Ctx map[string]any
+ RequestID *string
}
const keyOfRequestIDInTemplate = ".RequestID"
@@ -51,51 +52,65 @@ func parseRequestIDFromRequestHeader(req *http.Request) string {
return requestID
}
+type accessLogRecorder struct {
+ logger log.BaseLogger
+ logTemplate *template.Template
+ needRequestID bool
+}
+
+func (lr *accessLogRecorder) record(start time.Time, respWriter ResponseWriter, req *http.Request) {
+ var requestID string
+ if lr.needRequestID {
+ requestID = parseRequestIDFromRequestHeader(req)
+ }
+
+ reqHost, _, err := net.SplitHostPort(req.RemoteAddr)
+ if err != nil {
+ reqHost = req.RemoteAddr
+ }
+
+ identity := "-"
+ data := middleware.GetContextData(req.Context())
+ if signedUser, ok := data[middleware.ContextDataKeySignedUser].(*user_model.User); ok {
+ identity = signedUser.Name
+ }
+ buf := bytes.NewBuffer([]byte{})
+ tmplData := accessLoggerTmplData{
+ Identity: &identity,
+ Start: &start,
+ Ctx: map[string]any{
+ "RemoteAddr": req.RemoteAddr,
+ "RemoteHost": reqHost,
+ "Req": req,
+ },
+ RequestID: &requestID,
+ }
+ tmplData.ResponseWriter.Status = respWriter.WrittenStatus()
+ tmplData.ResponseWriter.Size = respWriter.WrittenSize()
+ err = lr.logTemplate.Execute(buf, tmplData)
+ if err != nil {
+ log.Error("Could not execute access logger template: %v", err.Error())
+ }
+
+ lr.logger.Log(1, log.INFO, "%s", buf.String())
+}
+
+func newAccessLogRecorder() *accessLogRecorder {
+ return &accessLogRecorder{
+ logger: log.GetLogger("access"),
+ logTemplate: template.Must(template.New("log").Parse(setting.Log.AccessLogTemplate)),
+ needRequestID: len(setting.Log.RequestIDHeaders) > 0 && strings.Contains(setting.Log.AccessLogTemplate, keyOfRequestIDInTemplate),
+ }
+}
+
// AccessLogger returns a middleware to log access logger
func AccessLogger() func(http.Handler) http.Handler {
- logger := log.GetLogger("access")
- needRequestID := len(setting.Log.RequestIDHeaders) > 0 && strings.Contains(setting.Log.AccessLogTemplate, keyOfRequestIDInTemplate)
- logTemplate, _ := template.New("log").Parse(setting.Log.AccessLogTemplate)
+ recorder := newAccessLogRecorder()
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
start := time.Now()
-
- var requestID string
- if needRequestID {
- requestID = parseRequestIDFromRequestHeader(req)
- }
-
- reqHost, _, err := net.SplitHostPort(req.RemoteAddr)
- if err != nil {
- reqHost = req.RemoteAddr
- }
-
next.ServeHTTP(w, req)
- rw := w.(ResponseWriter)
-
- identity := "-"
- data := middleware.GetContextData(req.Context())
- if signedUser, ok := data[middleware.ContextDataKeySignedUser].(*user_model.User); ok {
- identity = signedUser.Name
- }
- buf := bytes.NewBuffer([]byte{})
- err = logTemplate.Execute(buf, routerLoggerOptions{
- req: req,
- Identity: &identity,
- Start: &start,
- ResponseWriter: rw,
- Ctx: map[string]any{
- "RemoteAddr": req.RemoteAddr,
- "RemoteHost": reqHost,
- "Req": req,
- },
- RequestID: &requestID,
- })
- if err != nil {
- log.Error("Could not execute access logger template: %v", err.Error())
- }
-
- logger.Info("%s", buf.String())
+ recorder.record(start, w.(ResponseWriter), req)
})
}
}
diff --git a/services/context/access_log_test.go b/services/context/access_log_test.go
new file mode 100644
index 0000000000..bd3e47e0cc
--- /dev/null
+++ b/services/context/access_log_test.go
@@ -0,0 +1,71 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package context
+
+import (
+ "fmt"
+ "net/http"
+ "net/url"
+ "testing"
+ "time"
+
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
+
+ "github.com/stretchr/testify/assert"
+)
+
+type testAccessLoggerMock struct {
+ logs []string
+}
+
+func (t *testAccessLoggerMock) Log(skip int, level log.Level, format string, v ...any) {
+ t.logs = append(t.logs, fmt.Sprintf(format, v...))
+}
+
+func (t *testAccessLoggerMock) GetLevel() log.Level {
+ return log.INFO
+}
+
+type testAccessLoggerResponseWriterMock struct{}
+
+func (t testAccessLoggerResponseWriterMock) Header() http.Header {
+ return nil
+}
+
+func (t testAccessLoggerResponseWriterMock) Before(f func(ResponseWriter)) {}
+
+func (t testAccessLoggerResponseWriterMock) WriteHeader(statusCode int) {}
+
+func (t testAccessLoggerResponseWriterMock) Write(bytes []byte) (int, error) {
+ return 0, nil
+}
+
+func (t testAccessLoggerResponseWriterMock) Flush() {}
+
+func (t testAccessLoggerResponseWriterMock) WrittenStatus() int {
+ return http.StatusOK
+}
+
+func (t testAccessLoggerResponseWriterMock) WrittenSize() int {
+ return 123123
+}
+
+func TestAccessLogger(t *testing.T) {
+ setting.Log.AccessLogTemplate = `{{.Ctx.RemoteHost}} - {{.Identity}} {{.Start.Format "[02/Jan/2006:15:04:05 -0700]" }} "{{.Ctx.Req.Method}} {{.Ctx.Req.URL.RequestURI}} {{.Ctx.Req.Proto}}" {{.ResponseWriter.Status}} {{.ResponseWriter.Size}} "{{.Ctx.Req.Referer}}" "{{.Ctx.Req.UserAgent}}"`
+ recorder := newAccessLogRecorder()
+ mockLogger := &testAccessLoggerMock{}
+ recorder.logger = mockLogger
+ req := &http.Request{
+ RemoteAddr: "remote-addr",
+ Method: "GET",
+ Proto: "https",
+ URL: &url.URL{Path: "/path"},
+ }
+ req.Header = http.Header{}
+ req.Header.Add("Referer", "referer")
+ req.Header.Add("User-Agent", "user-agent")
+ recorder.record(time.Date(2000, 1, 2, 3, 4, 5, 0, time.UTC), &testAccessLoggerResponseWriterMock{}, req)
+ assert.Equal(t, []string{`remote-addr - - [02/Jan/2000:03:04:05 +0000] "GET /path https" 200 123123 "referer" "user-agent"`}, mockLogger.logs)
+}
diff --git a/services/context/api.go b/services/context/api.go
index bda705cb48..3a3cbe670e 100644
--- a/services/context/api.go
+++ b/services/context/api.go
@@ -293,8 +293,7 @@ func RepoRefForAPI(next http.Handler) http.Handler {
return
}
- // NOTICE: the "ref" here for internal usage only (e.g. woodpecker)
- refName, _ := getRefNameLegacy(ctx.Base, ctx.Repo, ctx.FormTrim("ref"))
+ refName, _ := getRefNameLegacy(ctx.Base, ctx.Repo, ctx.PathParam("*"), ctx.FormTrim("ref"))
var err error
if ctx.Repo.GitRepo.IsBranchExist(refName) {
diff --git a/services/context/base.go b/services/context/base.go
index 7a39353e09..5db84f42a5 100644
--- a/services/context/base.go
+++ b/services/context/base.go
@@ -4,7 +4,6 @@
package context
import (
- "context"
"fmt"
"html/template"
"io"
@@ -25,8 +24,7 @@ type BaseContextKeyType struct{}
var BaseContextKey BaseContextKeyType
type Base struct {
- context.Context
- reqctx.RequestDataStore
+ reqctx.RequestContext
Resp ResponseWriter
Req *http.Request
@@ -172,19 +170,19 @@ func (b *Base) TrN(cnt any, key1, keyN string, args ...any) template.HTML {
}
func NewBaseContext(resp http.ResponseWriter, req *http.Request) *Base {
- ds := reqctx.GetRequestDataStore(req.Context())
+ reqCtx := reqctx.FromContext(req.Context())
b := &Base{
- Context: req.Context(),
- RequestDataStore: ds,
- Req: req,
- Resp: WrapResponseWriter(resp),
- Locale: middleware.Locale(resp, req),
- Data: ds.GetData(),
+ RequestContext: reqCtx,
+
+ Req: req,
+ Resp: WrapResponseWriter(resp),
+ Locale: middleware.Locale(resp, req),
+ Data: reqCtx.GetData(),
}
b.Req = b.Req.WithContext(b)
- ds.SetContextValue(BaseContextKey, b)
- ds.SetContextValue(translation.ContextKey, b.Locale)
- ds.SetContextValue(httplib.RequestContextKey, b.Req)
+ reqCtx.SetContextValue(BaseContextKey, b)
+ reqCtx.SetContextValue(translation.ContextKey, b.Locale)
+ reqCtx.SetContextValue(httplib.RequestContextKey, b.Req)
return b
}
diff --git a/services/context/context_model.go b/services/context/context_model.go
index 4f70aac516..3a1776102f 100644
--- a/services/context/context_model.go
+++ b/services/context/context_model.go
@@ -3,27 +3,7 @@
package context
-import (
- "code.gitea.io/gitea/models/unit"
-)
-
// IsUserSiteAdmin returns true if current user is a site admin
func (ctx *Context) IsUserSiteAdmin() bool {
return ctx.IsSigned && ctx.Doer.IsAdmin
}
-
-// IsUserRepoAdmin returns true if current user is admin in current repo
-func (ctx *Context) IsUserRepoAdmin() bool {
- return ctx.Repo.IsAdmin()
-}
-
-// IsUserRepoWriter returns true if current user has write privilege in current repo
-func (ctx *Context) IsUserRepoWriter(unitTypes []unit.Type) bool {
- for _, unitType := range unitTypes {
- if ctx.Repo.CanWrite(unitType) {
- return true
- }
- }
-
- return false
-}
diff --git a/services/context/permission.go b/services/context/permission.go
index 9338587257..0d69ccc4a4 100644
--- a/services/context/permission.go
+++ b/services/context/permission.go
@@ -9,31 +9,20 @@ import (
auth_model "code.gitea.io/gitea/models/auth"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
- "code.gitea.io/gitea/modules/log"
)
// RequireRepoAdmin returns a middleware for requiring repository admin permission
func RequireRepoAdmin() func(ctx *Context) {
return func(ctx *Context) {
if !ctx.IsSigned || !ctx.Repo.IsAdmin() {
- ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
+ ctx.NotFound("RequireRepoAdmin denies the request", nil)
return
}
}
}
-// RequireRepoWriter returns a middleware for requiring repository write to the specify unitType
-func RequireRepoWriter(unitType unit.Type) func(ctx *Context) {
- return func(ctx *Context) {
- if !ctx.Repo.CanWrite(unitType) {
- ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
- return
- }
- }
-}
-
-// CanEnableEditor checks if the user is allowed to write to the branch of the repo
-func CanEnableEditor() func(ctx *Context) {
+// CanWriteToBranch checks if the user is allowed to write to the branch of the repo
+func CanWriteToBranch() func(ctx *Context) {
return func(ctx *Context) {
if !ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, ctx.Repo.BranchName) {
ctx.NotFound("CanWriteToBranch denies permission", nil)
@@ -42,75 +31,30 @@ func CanEnableEditor() func(ctx *Context) {
}
}
-// RequireRepoWriterOr returns a middleware for requiring repository write to one of the unit permission
-func RequireRepoWriterOr(unitTypes ...unit.Type) func(ctx *Context) {
+// RequireUnitWriter returns a middleware for requiring repository write to one of the unit permission
+func RequireUnitWriter(unitTypes ...unit.Type) func(ctx *Context) {
return func(ctx *Context) {
for _, unitType := range unitTypes {
if ctx.Repo.CanWrite(unitType) {
return
}
}
- ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
+ ctx.NotFound("RequireUnitWriter denies the request", nil)
}
}
-// RequireRepoReader returns a middleware for requiring repository read to the specify unitType
-func RequireRepoReader(unitType unit.Type) func(ctx *Context) {
- return func(ctx *Context) {
- if !ctx.Repo.CanRead(unitType) {
- if unitType == unit.TypeCode && canWriteAsMaintainer(ctx) {
- return
- }
- if log.IsTrace() {
- if ctx.IsSigned {
- log.Trace("Permission Denied: User %-v cannot read %-v in Repo %-v\n"+
- "User in Repo has Permissions: %-+v",
- ctx.Doer,
- unitType,
- ctx.Repo.Repository,
- ctx.Repo.Permission)
- } else {
- log.Trace("Permission Denied: Anonymous user cannot read %-v in Repo %-v\n"+
- "Anonymous user in Repo has Permissions: %-+v",
- unitType,
- ctx.Repo.Repository,
- ctx.Repo.Permission)
- }
- }
- ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
- return
- }
- }
-}
-
-// RequireRepoReaderOr returns a middleware for requiring repository write to one of the unit permission
-func RequireRepoReaderOr(unitTypes ...unit.Type) func(ctx *Context) {
+// RequireUnitReader returns a middleware for requiring repository write to one of the unit permission
+func RequireUnitReader(unitTypes ...unit.Type) func(ctx *Context) {
return func(ctx *Context) {
for _, unitType := range unitTypes {
if ctx.Repo.CanRead(unitType) {
return
}
- }
- if log.IsTrace() {
- var format string
- var args []any
- if ctx.IsSigned {
- format = "Permission Denied: User %-v cannot read ["
- args = append(args, ctx.Doer)
- } else {
- format = "Permission Denied: Anonymous user cannot read ["
- }
- for _, unit := range unitTypes {
- format += "%-v, "
- args = append(args, unit)
+ if unitType == unit.TypeCode && canWriteAsMaintainer(ctx) {
+ return
}
-
- format = format[:len(format)-2] + "] in Repo %-v\n" +
- "User in Repo has Permissions: %-+v"
- args = append(args, ctx.Repo.Repository, ctx.Repo.Permission)
- log.Trace(format, args...)
}
- ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
+ ctx.NotFound("RequireUnitReader denies the request", nil)
}
}
diff --git a/services/context/repo.go b/services/context/repo.go
index 4de905ef2c..6cd70d139b 100644
--- a/services/context/repo.go
+++ b/services/context/repo.go
@@ -46,22 +46,21 @@ type PullRequest struct {
// Repository contains information to operate a repository
type Repository struct {
access_model.Permission
- IsWatching bool
- IsViewBranch bool
- IsViewTag bool
- IsViewCommit bool
- Repository *repo_model.Repository
- Owner *user_model.User
+
+ Repository *repo_model.Repository
+ Owner *user_model.User
+
+ RepoLink string
+ GitRepo *git.Repository
+
+ // RefFullName is the full ref name that the user is viewing
+ RefFullName git.RefName
+ BranchName string // it is the RefFullName's short name if its type is "branch"
+ TreePath string
+
+ // Commit it is always set to the commit for the branch or tag, or just the commit that the user is viewing
Commit *git.Commit
- Tag *git.Tag
- GitRepo *git.Repository
- RefName string
- BranchName string
- TagName string
- TreePath string
CommitID string
- RepoLink string
- CloneLink repo_model.CloneLink
CommitsCount int64
PullRequest *PullRequest
@@ -74,7 +73,7 @@ func (r *Repository) CanWriteToBranch(ctx context.Context, user *user_model.User
// CanEnableEditor returns true if repository is editable and user has proper access level.
func (r *Repository) CanEnableEditor(ctx context.Context, user *user_model.User) bool {
- return r.IsViewBranch && r.CanWriteToBranch(ctx, user, r.BranchName) && r.Repository.CanEnableEditor() && !r.Repository.IsArchived
+ return r.RefFullName.IsBranch() && r.CanWriteToBranch(ctx, user, r.BranchName) && r.Repository.CanEnableEditor() && !r.Repository.IsArchived
}
// CanCreateBranch returns true if repository is editable and user has proper access level.
@@ -149,7 +148,7 @@ func (r *Repository) CanCommitToBranch(ctx context.Context, doer *user_model.Use
}, err
}
-// CanUseTimetracker returns whether or not a user can use the timetracker.
+// CanUseTimetracker returns whether a user can use the timetracker.
func (r *Repository) CanUseTimetracker(ctx context.Context, issue *issues_model.Issue, user *user_model.User) bool {
// Checking for following:
// 1. Is timetracker enabled
@@ -169,15 +168,9 @@ func (r *Repository) GetCommitsCount() (int64, error) {
if r.Commit == nil {
return 0, nil
}
- var contextName string
- if r.IsViewBranch {
- contextName = r.BranchName
- } else if r.IsViewTag {
- contextName = r.TagName
- } else {
- contextName = r.CommitID
- }
- return cache.GetInt64(r.Repository.GetCommitsCountCacheKey(contextName, r.IsViewBranch || r.IsViewTag), func() (int64, error) {
+ contextName := r.RefFullName.ShortName()
+ isRef := r.RefFullName.IsBranch() || r.RefFullName.IsTag()
+ return cache.GetInt64(r.Repository.GetCommitsCountCacheKey(contextName, isRef), func() (int64, error) {
return r.Commit.CommitsCount()
})
}
@@ -199,33 +192,13 @@ func (r *Repository) GetCommitGraphsCount(ctx context.Context, hidePRRefs bool,
})
}
-// BranchNameSubURL sub-URL for the BranchName field
-func (r *Repository) BranchNameSubURL() string {
- switch {
- case r.IsViewBranch:
- return "branch/" + util.PathEscapeSegments(r.BranchName)
- case r.IsViewTag:
- return "tag/" + util.PathEscapeSegments(r.TagName)
- case r.IsViewCommit:
- return "commit/" + util.PathEscapeSegments(r.CommitID)
- }
- log.Error("Unknown view type for repo: %v", r)
- return ""
-}
-
-// FileExists returns true if a file exists in the given repo branch
-func (r *Repository) FileExists(path, branch string) (bool, error) {
- if branch == "" {
- branch = r.Repository.DefaultBranch
- }
- commit, err := r.GitRepo.GetBranchCommit(branch)
- if err != nil {
- return false, err
- }
- if _, err := commit.GetTreeEntryByPath(path); err != nil {
- return false, err
- }
- return true, nil
+// RefTypeNameSubURL makes a sub-url for the current ref (branch/tag/commit) field, for example:
+// * "branch/master"
+// * "tag/v1.0.0"
+// * "commit/123456"
+// It is usually used to construct a link like ".../src/{{RefTypeNameSubURL}}/{{PathEscapeSegments TreePath}}"
+func (r *Repository) RefTypeNameSubURL() string {
+ return r.RefFullName.RefWebLinkPath()
}
// GetEditorconfig returns the .editorconfig definition if found in the
@@ -398,33 +371,25 @@ func repoAssignment(ctx *Context, repo *repo_model.Repository) {
// RepoAssignment returns a middleware to handle repository assignment
func RepoAssignment(ctx *Context) {
- if _, repoAssignmentOnce := ctx.Data["repoAssignmentExecuted"]; repoAssignmentOnce {
- // FIXME: it should panic in dev/test modes to have a clear behavior
- if !setting.IsProd || setting.IsInTesting {
- panic("RepoAssignment should not be executed twice")
- }
- return
+ if ctx.Data["Repository"] != nil {
+ setting.PanicInDevOrTesting("RepoAssignment should not be executed twice")
}
- ctx.Data["repoAssignmentExecuted"] = true
-
- var (
- owner *user_model.User
- err error
- )
+ var err error
userName := ctx.PathParam("username")
repoName := ctx.PathParam("reponame")
repoName = strings.TrimSuffix(repoName, ".git")
if setting.Other.EnableFeed {
+ ctx.Data["EnableFeed"] = true
repoName = strings.TrimSuffix(repoName, ".rss")
repoName = strings.TrimSuffix(repoName, ".atom")
}
// Check if the user is the same as the repository owner
if ctx.IsSigned && ctx.Doer.LowerName == strings.ToLower(userName) {
- owner = ctx.Doer
+ ctx.Repo.Owner = ctx.Doer
} else {
- owner, err = user_model.GetUserByName(ctx, userName)
+ ctx.Repo.Owner, err = user_model.GetUserByName(ctx, userName)
if err != nil {
if user_model.IsErrUserNotExist(err) {
// go-get does not support redirects
@@ -447,10 +412,8 @@ func RepoAssignment(ctx *Context) {
return
}
}
- ctx.Repo.Owner = owner
- ctx.ContextUser = owner
+ ctx.ContextUser = ctx.Repo.Owner
ctx.Data["ContextUser"] = ctx.ContextUser
- ctx.Data["Username"] = ctx.Repo.Owner.Name
// redirect link to wiki
if strings.HasSuffix(repoName, ".wiki") {
@@ -473,10 +436,10 @@ func RepoAssignment(ctx *Context) {
}
// Get repository.
- repo, err := repo_model.GetRepositoryByName(ctx, owner.ID, repoName)
+ repo, err := repo_model.GetRepositoryByName(ctx, ctx.Repo.Owner.ID, repoName)
if err != nil {
if repo_model.IsErrRepoNotExist(err) {
- redirectRepoID, err := repo_model.LookupRedirect(ctx, owner.ID, repoName)
+ redirectRepoID, err := repo_model.LookupRedirect(ctx, ctx.Repo.Owner.ID, repoName)
if err == nil {
RedirectToRepo(ctx.Base, redirectRepoID)
} else if repo_model.IsErrRedirectNotExist(err) {
@@ -493,7 +456,7 @@ func RepoAssignment(ctx *Context) {
}
return
}
- repo.Owner = owner
+ repo.Owner = ctx.Repo.Owner
repoAssignment(ctx, repo)
if ctx.Written() {
@@ -502,12 +465,7 @@ func RepoAssignment(ctx *Context) {
ctx.Repo.RepoLink = repo.Link()
ctx.Data["RepoLink"] = ctx.Repo.RepoLink
- ctx.Data["RepoRelPath"] = ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name
-
- if setting.Other.EnableFeed {
- ctx.Data["EnableFeed"] = true
- ctx.Data["FeedURL"] = ctx.Repo.RepoLink
- }
+ ctx.Data["FeedURL"] = ctx.Repo.RepoLink
unit, err := ctx.Repo.Repository.GetUnit(ctx, unit_model.TypeExternalTracker)
if err == nil {
@@ -534,12 +492,9 @@ func RepoAssignment(ctx *Context) {
return
}
- ctx.Data["Title"] = owner.Name + "/" + repo.Name
+ ctx.Data["Title"] = repo.Owner.Name + "/" + repo.Name
ctx.Data["Repository"] = repo
ctx.Data["Owner"] = ctx.Repo.Repository.Owner
- ctx.Data["IsRepositoryOwner"] = ctx.Repo.IsOwner()
- ctx.Data["IsRepositoryAdmin"] = ctx.Repo.IsAdmin()
- ctx.Data["RepoOwnerIsOrganization"] = repo.Owner.IsOrganization()
ctx.Data["CanWriteCode"] = ctx.Repo.CanWrite(unit_model.TypeCode)
ctx.Data["CanWriteIssues"] = ctx.Repo.CanWrite(unit_model.TypeIssues)
ctx.Data["CanWritePulls"] = ctx.Repo.CanWrite(unit_model.TypePullRequests)
@@ -607,7 +562,6 @@ func RepoAssignment(ctx *Context) {
// Disable everything when the repo is being created
if ctx.Repo.Repository.IsBeingCreated() || ctx.Repo.Repository.IsBroken() {
- ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
if !isHomeOrSettings {
ctx.Redirect(ctx.Repo.RepoLink)
}
@@ -615,9 +569,7 @@ func RepoAssignment(ctx *Context) {
}
if ctx.Repo.GitRepo != nil {
- if !setting.IsProd || setting.IsInTesting {
- panic("RepoAssignment: GitRepo should be nil")
- }
+ setting.PanicInDevOrTesting("RepoAssignment: GitRepo should be nil")
_ = ctx.Repo.GitRepo.Close()
ctx.Repo.GitRepo = nil
}
@@ -627,7 +579,6 @@ func RepoAssignment(ctx *Context) {
if strings.Contains(err.Error(), "repository does not exist") || strings.Contains(err.Error(), "no such file or directory") {
log.Error("Repository %-v has a broken repository on the file system: %s Error: %v", ctx.Repo.Repository, ctx.Repo.Repository.RepoPath(), err)
ctx.Repo.Repository.MarkAsBrokenEmpty()
- ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
// Only allow access to base of repo or settings
if !isHomeOrSettings {
ctx.Redirect(ctx.Repo.RepoLink)
@@ -640,7 +591,6 @@ func RepoAssignment(ctx *Context) {
// Stop at this point when the repo is empty.
if ctx.Repo.Repository.IsEmpty {
- ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
return
}
@@ -666,22 +616,6 @@ func RepoAssignment(ctx *Context) {
ctx.Data["BranchesCount"] = branchesTotal
- // If no branch is set in the request URL, try to guess a default one.
- if len(ctx.Repo.BranchName) == 0 {
- if len(ctx.Repo.Repository.DefaultBranch) > 0 && ctx.Repo.GitRepo.IsBranchExist(ctx.Repo.Repository.DefaultBranch) {
- ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch
- } else {
- ctx.Repo.BranchName, _ = gitrepo.GetDefaultBranch(ctx, ctx.Repo.Repository)
- if ctx.Repo.BranchName == "" {
- // If it still can't get a default branch, fall back to default branch from setting.
- // Something might be wrong. Either site admin should fix the repo sync or Gitea should fix a potential bug.
- ctx.Repo.BranchName = setting.Repository.DefaultBranch
- }
- }
- ctx.Repo.RefName = ctx.Repo.BranchName
- }
- ctx.Data["BranchName"] = ctx.Repo.BranchName
-
// People who have push access or have forked repository can propose a new pull request.
canPush := ctx.Repo.CanWrite(unit_model.TypeCode) ||
(ctx.IsSigned && repo_model.HasForkedRepo(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID))
@@ -724,32 +658,19 @@ func RepoAssignment(ctx *Context) {
}
if ctx.FormString("go-get") == "1" {
- ctx.Data["GoGetImport"] = ComposeGoGetImport(ctx, owner.Name, repo.Name)
+ ctx.Data["GoGetImport"] = ComposeGoGetImport(ctx, repo.Owner.Name, repo.Name)
fullURLPrefix := repo.HTMLURL() + "/src/branch/" + util.PathEscapeSegments(ctx.Repo.BranchName)
ctx.Data["GoDocDirectory"] = fullURLPrefix + "{/dir}"
ctx.Data["GoDocFile"] = fullURLPrefix + "{/dir}/{file}#L{line}"
}
}
-// RepoRefType type of repo reference
-type RepoRefType int
-
-const (
- // RepoRefUnknown is for legacy support, makes the code to "guess" the ref type
- RepoRefUnknown RepoRefType = iota
- RepoRefBranch
- RepoRefTag
- RepoRefCommit
- RepoRefBlob
-)
-
const headRefName = "HEAD"
-// RepoRef handles repository reference names when the ref name is not
-// explicitly given
func RepoRef() func(*Context) {
- // since no ref name is explicitly specified, ok to just use branch
- return RepoRefByType(RepoRefBranch)
+ // old code does: return RepoRefByType(git.RefTypeBranch)
+ // in most cases, it is an abuse, so we just disable it completely and fix the abuses one by one (if there is anything wrong)
+ return nil
}
func getRefNameFromPath(repo *Repository, path string, isExist func(string) bool) string {
@@ -765,37 +686,29 @@ func getRefNameFromPath(repo *Repository, path string, isExist func(string) bool
return ""
}
-func getRefNameLegacy(ctx *Base, repo *Repository, optionalExtraRef ...string) (string, RepoRefType) {
- extraRef := util.OptionalArg(optionalExtraRef)
- reqPath := ctx.PathParam("*")
- reqPath = path.Join(extraRef, reqPath)
-
- if refName := getRefName(ctx, repo, RepoRefBranch); refName != "" {
- return refName, RepoRefBranch
+func getRefNameLegacy(ctx *Base, repo *Repository, reqPath, extraRef string) (string, git.RefType) {
+ reqRefPath := path.Join(extraRef, reqPath)
+ reqRefPathParts := strings.Split(reqRefPath, "/")
+ if refName := getRefName(ctx, repo, reqRefPath, git.RefTypeBranch); refName != "" {
+ return refName, git.RefTypeBranch
}
- if refName := getRefName(ctx, repo, RepoRefTag); refName != "" {
- return refName, RepoRefTag
+ if refName := getRefName(ctx, repo, reqRefPath, git.RefTypeTag); refName != "" {
+ return refName, git.RefTypeTag
}
-
- // For legacy support only full commit sha
- parts := strings.Split(reqPath, "/")
- if git.IsStringLikelyCommitID(git.ObjectFormatFromName(repo.Repository.ObjectFormatName), parts[0]) {
+ if git.IsStringLikelyCommitID(git.ObjectFormatFromName(repo.Repository.ObjectFormatName), reqRefPathParts[0]) {
// FIXME: this logic is different from other types. Ideally, it should also try to GetCommit to check if it exists
- repo.TreePath = strings.Join(parts[1:], "/")
- return parts[0], RepoRefCommit
- }
-
- if refName := getRefName(ctx, repo, RepoRefBlob); len(refName) > 0 {
- return refName, RepoRefBlob
+ repo.TreePath = strings.Join(reqRefPathParts[1:], "/")
+ return reqRefPathParts[0], git.RefTypeCommit
}
+ // FIXME: the old code falls back to default branch if "ref" doesn't exist, there could be an edge case:
+ // "README?ref=no-such" would read the README file from the default branch, but the user might expect a 404
repo.TreePath = reqPath
- return repo.Repository.DefaultBranch, RepoRefBranch
+ return repo.Repository.DefaultBranch, git.RefTypeBranch
}
-func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string {
- path := ctx.PathParam("*")
- switch pathType {
- case RepoRefBranch:
+func getRefName(ctx *Base, repo *Repository, path string, refType git.RefType) string {
+ switch refType {
+ case git.RefTypeBranch:
ref := getRefNameFromPath(repo, path, repo.GitRepo.IsBranchExist)
if len(ref) == 0 {
// check if ref is HEAD
@@ -825,9 +738,9 @@ func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string {
}
return ref
- case RepoRefTag:
+ case git.RefTypeTag:
return getRefNameFromPath(repo, path, repo.GitRepo.IsTagExist)
- case RepoRefCommit:
+ case git.RefTypeCommit:
parts := strings.Split(path, "/")
if git.IsStringLikelyCommitID(repo.GetObjectFormat(), parts[0], 7) {
// FIXME: this logic is different from other types. Ideally, it should also try to GetCommit to check if it exists
@@ -844,66 +757,73 @@ func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string {
repo.TreePath = strings.Join(parts[1:], "/")
return commit.ID.String()
}
- case RepoRefBlob:
- _, err := repo.GitRepo.GetBlob(path)
- if err != nil {
- return ""
- }
- return path
default:
- panic(fmt.Sprintf("Unrecognized path type: %v", pathType))
+ panic(fmt.Sprintf("Unrecognized ref type: %v", refType))
}
return ""
}
-type RepoRefByTypeOptions struct {
- IgnoreNotExistErr bool
+func repoRefFullName(typ git.RefType, shortName string) git.RefName {
+ switch typ {
+ case git.RefTypeBranch:
+ return git.RefNameFromBranch(shortName)
+ case git.RefTypeTag:
+ return git.RefNameFromTag(shortName)
+ case git.RefTypeCommit:
+ return git.RefNameFromCommit(shortName)
+ default:
+ setting.PanicInDevOrTesting("Unknown RepoRefType: %v", typ)
+ return git.RefNameFromBranch("main") // just a dummy result, it shouldn't happen
+ }
+}
+
+func RepoRefByDefaultBranch() func(*Context) {
+ return func(ctx *Context) {
+ ctx.Repo.RefFullName = git.RefNameFromBranch(ctx.Repo.Repository.DefaultBranch)
+ ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch
+ ctx.Repo.Commit, _ = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.BranchName)
+ ctx.Repo.CommitsCount, _ = ctx.Repo.GetCommitsCount()
+ ctx.Data["RefFullName"] = ctx.Repo.RefFullName
+ ctx.Data["BranchName"] = ctx.Repo.BranchName
+ ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
+ }
}
// RepoRefByType handles repository reference name for a specific type
// of repository reference
-func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func(*Context) {
- opt := util.OptionalArg(opts)
+func RepoRefByType(detectRefType git.RefType) func(*Context) {
return func(ctx *Context) {
+ var err error
refType := detectRefType
// Empty repository does not have reference information.
if ctx.Repo.Repository.IsEmpty {
// assume the user is viewing the (non-existent) default branch
- ctx.Repo.IsViewBranch = true
ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch
+ ctx.Repo.RefFullName = git.RefNameFromBranch(ctx.Repo.BranchName)
+ // these variables are used by the template to "add/upload" new files
+ ctx.Data["BranchName"] = ctx.Repo.BranchName
ctx.Data["TreePath"] = ""
return
}
- var (
- refName string
- err error
- )
-
- if ctx.Repo.GitRepo == nil {
- ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx.Repo.Repository)
- if err != nil {
- ctx.ServerError(fmt.Sprintf("Open Repository %v failed", ctx.Repo.Repository.FullName()), err)
- return
- }
- }
-
// Get default branch.
- if len(ctx.PathParam("*")) == 0 {
- refName = ctx.Repo.Repository.DefaultBranch
- if !ctx.Repo.GitRepo.IsBranchExist(refName) {
+ var refShortName string
+ reqPath := ctx.PathParam("*")
+ if reqPath == "" {
+ refShortName = ctx.Repo.Repository.DefaultBranch
+ if !ctx.Repo.GitRepo.IsBranchExist(refShortName) {
brs, _, err := ctx.Repo.GitRepo.GetBranches(0, 1)
if err == nil && len(brs) != 0 {
- refName = brs[0].Name
+ refShortName = brs[0].Name
} else if len(brs) == 0 {
log.Error("No branches in non-empty repository %s", ctx.Repo.GitRepo.Path)
} else {
log.Error("GetBranches error: %v", err)
}
}
- ctx.Repo.RefName = refName
- ctx.Repo.BranchName = refName
- ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
+ ctx.Repo.RefFullName = git.RefNameFromBranch(refShortName)
+ ctx.Repo.BranchName = refShortName
+ ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refShortName)
if err == nil {
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
} else if strings.Contains(err.Error(), "fatal: not a git repository") || strings.Contains(err.Error(), "object does not exist") {
@@ -913,39 +833,37 @@ func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func
ctx.ServerError("GetBranchCommit", err)
return
}
- ctx.Repo.IsViewBranch = true
- } else {
- guessLegacyPath := refType == RepoRefUnknown
+ } else { // there is a path in request
+ guessLegacyPath := refType == ""
if guessLegacyPath {
- refName, refType = getRefNameLegacy(ctx.Base, ctx.Repo)
+ refShortName, refType = getRefNameLegacy(ctx.Base, ctx.Repo, reqPath, "")
} else {
- refName = getRefName(ctx.Base, ctx.Repo, refType)
+ refShortName = getRefName(ctx.Base, ctx.Repo, reqPath, refType)
}
- ctx.Repo.RefName = refName
+ ctx.Repo.RefFullName = repoRefFullName(refType, refShortName)
isRenamedBranch, has := ctx.Data["IsRenamedBranch"].(bool)
if isRenamedBranch && has {
renamedBranchName := ctx.Data["RenamedBranchName"].(string)
- ctx.Flash.Info(ctx.Tr("repo.branch.renamed", refName, renamedBranchName))
- link := setting.AppSubURL + strings.Replace(ctx.Req.URL.EscapedPath(), util.PathEscapeSegments(refName), util.PathEscapeSegments(renamedBranchName), 1)
+ ctx.Flash.Info(ctx.Tr("repo.branch.renamed", refShortName, renamedBranchName))
+ link := setting.AppSubURL + strings.Replace(ctx.Req.URL.EscapedPath(), util.PathEscapeSegments(refShortName), util.PathEscapeSegments(renamedBranchName), 1)
ctx.Redirect(link)
return
}
- if refType == RepoRefBranch && ctx.Repo.GitRepo.IsBranchExist(refName) {
- ctx.Repo.IsViewBranch = true
- ctx.Repo.BranchName = refName
+ if refType == git.RefTypeBranch && ctx.Repo.GitRepo.IsBranchExist(refShortName) {
+ ctx.Repo.BranchName = refShortName
+ ctx.Repo.RefFullName = git.RefNameFromBranch(refShortName)
- ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
+ ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refShortName)
if err != nil {
ctx.ServerError("GetBranchCommit", err)
return
}
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
- } else if refType == RepoRefTag && ctx.Repo.GitRepo.IsTagExist(refName) {
- ctx.Repo.IsViewTag = true
- ctx.Repo.TagName = refName
+ } else if refType == git.RefTypeTag && ctx.Repo.GitRepo.IsTagExist(refShortName) {
+ ctx.Repo.RefFullName = git.RefNameFromTag(refShortName)
- ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetTagCommit(refName)
+ ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetTagCommit(refShortName)
if err != nil {
if git.IsErrNotExist(err) {
ctx.NotFound("GetTagCommit", err)
@@ -955,25 +873,22 @@ func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func
return
}
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
- } else if git.IsStringLikelyCommitID(ctx.Repo.GetObjectFormat(), refName, 7) {
- ctx.Repo.IsViewCommit = true
- ctx.Repo.CommitID = refName
+ } else if git.IsStringLikelyCommitID(ctx.Repo.GetObjectFormat(), refShortName, 7) {
+ ctx.Repo.RefFullName = git.RefNameFromCommit(refShortName)
+ ctx.Repo.CommitID = refShortName
- ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName)
+ ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refShortName)
if err != nil {
ctx.NotFound("GetCommit", err)
return
}
// If short commit ID add canonical link header
- if len(refName) < ctx.Repo.GetObjectFormat().FullLength() {
- canonicalURL := util.URLJoin(httplib.GuessCurrentAppURL(ctx), strings.Replace(ctx.Req.URL.RequestURI(), util.PathEscapeSegments(refName), url.PathEscape(ctx.Repo.Commit.ID.String()), 1))
+ if len(refShortName) < ctx.Repo.GetObjectFormat().FullLength() {
+ canonicalURL := util.URLJoin(httplib.GuessCurrentAppURL(ctx), strings.Replace(ctx.Req.URL.RequestURI(), util.PathEscapeSegments(refShortName), url.PathEscape(ctx.Repo.Commit.ID.String()), 1))
ctx.RespHeader().Set("Link", fmt.Sprintf(`<%s>; rel="canonical"`, canonicalURL))
}
} else {
- if opt.IgnoreNotExistErr {
- return
- }
- ctx.NotFound("RepoRef invalid repo", fmt.Errorf("branch or tag not exist: %s", refName))
+ ctx.NotFound("RepoRef invalid repo", fmt.Errorf("branch or tag not exist: %s", refShortName))
return
}
@@ -983,22 +898,21 @@ func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func
redirect := path.Join(
ctx.Repo.RepoLink,
util.PathEscapeSegments(prefix),
- ctx.Repo.BranchNameSubURL(),
+ ctx.Repo.RefTypeNameSubURL(),
util.PathEscapeSegments(ctx.Repo.TreePath))
ctx.Redirect(redirect)
return
}
}
+ ctx.Data["RefFullName"] = ctx.Repo.RefFullName
+ ctx.Data["RefTypeNameSubURL"] = ctx.Repo.RefTypeNameSubURL()
+ ctx.Data["TreePath"] = ctx.Repo.TreePath
+
ctx.Data["BranchName"] = ctx.Repo.BranchName
- ctx.Data["RefName"] = ctx.Repo.RefName
- ctx.Data["BranchNameSubURL"] = ctx.Repo.BranchNameSubURL()
- ctx.Data["TagName"] = ctx.Repo.TagName
+
ctx.Data["CommitID"] = ctx.Repo.CommitID
- ctx.Data["TreePath"] = ctx.Repo.TreePath
- ctx.Data["IsViewBranch"] = ctx.Repo.IsViewBranch
- ctx.Data["IsViewTag"] = ctx.Repo.IsViewTag
- ctx.Data["IsViewCommit"] = ctx.Repo.IsViewCommit
+
ctx.Data["CanCreateBranch"] = ctx.Repo.CanCreateBranch() // only used by the branch selector dropdown: AllowCreateNewRef
ctx.Repo.CommitsCount, err = ctx.Repo.GetCommitsCount()
diff --git a/services/context/response.go b/services/context/response.go
index 2f271f211b..c7368ebc6f 100644
--- a/services/context/response.go
+++ b/services/context/response.go
@@ -11,31 +11,29 @@ import (
// ResponseWriter represents a response writer for HTTP
type ResponseWriter interface {
- http.ResponseWriter
- http.Flusher
- web_types.ResponseStatusProvider
-
- Before(func(ResponseWriter))
+ http.ResponseWriter // provides Header/Write/WriteHeader
+ http.Flusher // provides Flush
+ web_types.ResponseStatusProvider // provides WrittenStatus
- Status() int // used by access logger template
- Size() int // used by access logger template
+ Before(fn func(ResponseWriter))
+ WrittenSize() int
}
-var _ ResponseWriter = &Response{}
+var _ ResponseWriter = (*Response)(nil)
// Response represents a response
type Response struct {
http.ResponseWriter
written int
status int
- befores []func(ResponseWriter)
+ beforeFuncs []func(ResponseWriter)
beforeExecuted bool
}
// Write writes bytes to HTTP endpoint
func (r *Response) Write(bs []byte) (int, error) {
if !r.beforeExecuted {
- for _, before := range r.befores {
+ for _, before := range r.beforeFuncs {
before(r)
}
r.beforeExecuted = true
@@ -51,18 +49,14 @@ func (r *Response) Write(bs []byte) (int, error) {
return size, nil
}
-func (r *Response) Status() int {
- return r.status
-}
-
-func (r *Response) Size() int {
+func (r *Response) WrittenSize() int {
return r.written
}
// WriteHeader write status code
func (r *Response) WriteHeader(statusCode int) {
if !r.beforeExecuted {
- for _, before := range r.befores {
+ for _, before := range r.beforeFuncs {
before(r)
}
r.beforeExecuted = true
@@ -87,17 +81,13 @@ func (r *Response) WrittenStatus() int {
// Before allows for a function to be called before the ResponseWriter has been written to. This is
// useful for setting headers or any other operations that must happen before a response has been written.
-func (r *Response) Before(f func(ResponseWriter)) {
- r.befores = append(r.befores, f)
+func (r *Response) Before(fn func(ResponseWriter)) {
+ r.beforeFuncs = append(r.beforeFuncs, fn)
}
func WrapResponseWriter(resp http.ResponseWriter) *Response {
if v, ok := resp.(*Response); ok {
return v
}
- return &Response{
- ResponseWriter: resp,
- status: 0,
- befores: make([]func(ResponseWriter), 0),
- }
+ return &Response{ResponseWriter: resp}
}
diff --git a/services/convert/issue.go b/services/convert/issue.go
index e3124efd64..37935accca 100644
--- a/services/convert/issue.go
+++ b/services/convert/issue.go
@@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/util"
)
func ToIssue(ctx context.Context, doer *user_model.User, issue *issues_model.Issue) *api.Issue {
@@ -186,7 +187,7 @@ func ToStopWatches(ctx context.Context, sws []*issues_model.Stopwatch) (api.Stop
result = append(result, api.StopWatch{
Created: sw.CreatedUnix.AsTime(),
Seconds: sw.Seconds(),
- Duration: sw.Duration(),
+ Duration: util.SecToHours(sw.Seconds()),
IssueIndex: issue.Index,
IssueTitle: issue.Title,
RepoOwnerName: repo.OwnerName,
diff --git a/services/convert/issue_comment.go b/services/convert/issue_comment.go
index b8527ae233..9ad584a62f 100644
--- a/services/convert/issue_comment.go
+++ b/services/convert/issue_comment.go
@@ -74,7 +74,7 @@ func ToTimelineComment(ctx context.Context, repo *repo_model.Repository, c *issu
c.Content[0] == '|' {
// TimeTracking Comments from v1.21 on store the seconds instead of an formatted string
// so we check for the "|" delimiter and convert new to legacy format on demand
- c.Content = util.SecToTime(c.Content[1:])
+ c.Content = util.SecToHours(c.Content[1:])
}
if c.Type == issues_model.CommentTypeChangeTimeEstimate {
diff --git a/services/convert/utils.go b/services/convert/utils.go
index 5e9d32cc8e..b59884ec50 100644
--- a/services/convert/utils.go
+++ b/services/convert/utils.go
@@ -36,6 +36,8 @@ func ToGitServiceType(value string) structs.GitServiceType {
return structs.OneDevService
case "gitbucket":
return structs.GitBucketService
+ case "codebase":
+ return structs.CodebaseService
case "codecommit":
return structs.CodeCommitService
default:
diff --git a/services/convert/utils_test.go b/services/convert/utils_test.go
index 1ac03a3097..a8363ec6bd 100644
--- a/services/convert/utils_test.go
+++ b/services/convert/utils_test.go
@@ -21,6 +21,8 @@ func TestToGitServiceType(t *testing.T) {
typ string
enum int
}{{
+ typ: "trash", enum: 1,
+ }, {
typ: "github", enum: 2,
}, {
typ: "gitea", enum: 3,
@@ -29,7 +31,13 @@ func TestToGitServiceType(t *testing.T) {
}, {
typ: "gogs", enum: 5,
}, {
- typ: "trash", enum: 1,
+ typ: "onedev", enum: 6,
+ }, {
+ typ: "gitbucket", enum: 7,
+ }, {
+ typ: "codebase", enum: 8,
+ }, {
+ typ: "codecommit", enum: 9,
}}
for _, test := range tc {
assert.EqualValues(t, test.enum, ToGitServiceType(test.typ))
diff --git a/services/doctor/dbconsistency_test.go b/services/doctor/dbconsistency_test.go
index 4e4ac535b7..eb427dee73 100644
--- a/services/doctor/dbconsistency_test.go
+++ b/services/doctor/dbconsistency_test.go
@@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/modules/log"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestConsistencyCheck(t *testing.T) {
@@ -21,9 +22,7 @@ func TestConsistencyCheck(t *testing.T) {
idx := slices.IndexFunc(checks, func(check consistencyCheck) bool {
return check.Name == "Orphaned OAuth2Application without existing User"
})
- if !assert.NotEqual(t, -1, idx) {
- return
- }
+ require.NotEqual(t, -1, idx)
_ = db.TruncateBeans(db.DefaultContext, &auth.OAuth2Application{}, &user.User{})
_ = db.TruncateBeans(db.DefaultContext, &auth.OAuth2Application{}, &auth.OAuth2Application{})
diff --git a/services/feed/notifier.go b/services/feed/notifier.go
index 702eb5ad53..3aaf885c9a 100644
--- a/services/feed/notifier.go
+++ b/services/feed/notifier.go
@@ -469,7 +469,7 @@ func (a *actionNotifier) NewRelease(ctx context.Context, rel *repo_model.Release
Repo: rel.Repo,
IsPrivate: rel.Repo.IsPrivate,
Content: rel.Title,
- RefName: rel.TagName, // FIXME: use a full ref name?
+ RefName: git.RefNameFromTag(rel.TagName).String(), // Other functions in this file all use "refFullName.String()"
}); err != nil {
log.Error("NotifyWatchers: %v", err)
}
diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go
index b14171787e..1a1c6585db 100644
--- a/services/forms/repo_form.go
+++ b/services/forms/repo_form.go
@@ -110,41 +110,51 @@ type RepoSettingForm struct {
EnablePrune bool
// Advanced settings
- EnableCode bool
- EnableWiki bool
- EnableExternalWiki bool
- DefaultWikiBranch string
- DefaultWikiEveryoneAccess string
- ExternalWikiURL string
+ EnableCode bool
+ DefaultCodeEveryoneAccess string
+
+ EnableWiki bool
+ EnableExternalWiki bool
+ DefaultWikiBranch string
+ DefaultWikiEveryoneAccess string
+ ExternalWikiURL string
+
EnableIssues bool
+ DefaultIssuesEveryoneAccess string
EnableExternalTracker bool
ExternalTrackerURL string
TrackerURLFormat string
TrackerIssueStyle string
ExternalTrackerRegexpPattern string
EnableCloseIssuesViaCommitInAnyBranch bool
- EnableProjects bool
- ProjectsMode string
- EnableReleases bool
- EnablePackages bool
- EnablePulls bool
- EnableActions bool
- PullsIgnoreWhitespace bool
- PullsAllowMerge bool
- PullsAllowRebase bool
- PullsAllowRebaseMerge bool
- PullsAllowSquash bool
- PullsAllowFastForwardOnly bool
- PullsAllowManualMerge bool
- PullsDefaultMergeStyle string
- EnableAutodetectManualMerge bool
- PullsAllowRebaseUpdate bool
- DefaultDeleteBranchAfterMerge bool
- DefaultAllowMaintainerEdit bool
- EnableTimetracker bool
- AllowOnlyContributorsToTrackTime bool
- EnableIssueDependencies bool
- IsArchived bool
+
+ EnableProjects bool
+ ProjectsMode string
+
+ EnableReleases bool
+
+ EnablePackages bool
+
+ EnablePulls bool
+ PullsIgnoreWhitespace bool
+ PullsAllowMerge bool
+ PullsAllowRebase bool
+ PullsAllowRebaseMerge bool
+ PullsAllowSquash bool
+ PullsAllowFastForwardOnly bool
+ PullsAllowManualMerge bool
+ PullsDefaultMergeStyle string
+ EnableAutodetectManualMerge bool
+ PullsAllowRebaseUpdate bool
+ DefaultDeleteBranchAfterMerge bool
+ DefaultAllowMaintainerEdit bool
+ EnableTimetracker bool
+ AllowOnlyContributorsToTrackTime bool
+ EnableIssueDependencies bool
+
+ EnableActions bool
+
+ IsArchived bool
// Signing Settings
TrustModel string
@@ -209,26 +219,18 @@ type ProtectBranchPriorityForm struct {
IDs []int64
}
-// __ __ ___. .__ __
-// / \ / \ ____\_ |__ | |__ ____ ____ | | __
-// \ \/\/ // __ \| __ \| | \ / _ \ / _ \| |/ /
-// \ /\ ___/| \_\ \ Y ( <_> | <_> ) <
-// \__/\ / \___ >___ /___| /\____/ \____/|__|_ \
-// \/ \/ \/ \/ \/
-
// WebhookForm form for changing web hook
type WebhookForm struct {
Events string
Create bool
Delete bool
Fork bool
+ Push bool
Issues bool
IssueAssign bool
IssueLabel bool
IssueMilestone bool
IssueComment bool
- Release bool
- Push bool
PullRequest bool
PullRequestAssign bool
PullRequestLabel bool
@@ -239,6 +241,7 @@ type WebhookForm struct {
PullRequestReviewRequest bool
Wiki bool
Repository bool
+ Release bool
Package bool
Active bool
BranchFilter string `binding:"GlobPattern"`
@@ -651,8 +654,8 @@ type NewReleaseForm struct {
Target string `form:"tag_target" binding:"Required;MaxSize(255)"`
Title string `binding:"MaxSize(255)"`
Content string
- Draft string
- TagOnly string
+ Draft bool
+ TagOnly bool
Prerelease bool
AddTagMsg bool
Files []string
diff --git a/services/gitdiff/csv_test.go b/services/gitdiff/csv_test.go
index c006a7c2bd..f91e0e9aa7 100644
--- a/services/gitdiff/csv_test.go
+++ b/services/gitdiff/csv_test.go
@@ -192,23 +192,18 @@ c,d,e`,
for n, c := range cases {
diff, err := ParsePatch(db.DefaultContext, setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, strings.NewReader(c.diff), "")
- if err != nil {
- t.Errorf("ParsePatch failed: %s", err)
- }
+ assert.NoError(t, err)
var baseReader *csv.Reader
if len(c.base) > 0 {
baseReader, err = csv_module.CreateReaderAndDetermineDelimiter(nil, strings.NewReader(c.base))
- if err != nil {
- t.Errorf("CreateReaderAndDetermineDelimiter failed: %s", err)
- }
+ assert.NoError(t, err)
}
+
var headReader *csv.Reader
if len(c.head) > 0 {
headReader, err = csv_module.CreateReaderAndDetermineDelimiter(nil, strings.NewReader(c.head))
- if err != nil {
- t.Errorf("CreateReaderAndDetermineDelimiter failed: %s", err)
- }
+ assert.NoError(t, err)
}
result, err := CreateCsvDiff(diff.Files[0], baseReader, headReader)
diff --git a/services/gitdiff/gitdiff.go b/services/gitdiff/gitdiff.go
index f42686bb71..f046e59678 100644
--- a/services/gitdiff/gitdiff.go
+++ b/services/gitdiff/gitdiff.go
@@ -1136,7 +1136,10 @@ func GetDiff(ctx context.Context, gitRepo *git.Repository, opts *DiffOptions, fi
} else {
actualBeforeCommitID := opts.BeforeCommitID
if len(actualBeforeCommitID) == 0 {
- parentCommit, _ := commit.Parent(0)
+ parentCommit, err := commit.Parent(0)
+ if err != nil {
+ return nil, err
+ }
actualBeforeCommitID = parentCommit.ID.String()
}
@@ -1145,7 +1148,6 @@ func GetDiff(ctx context.Context, gitRepo *git.Repository, opts *DiffOptions, fi
AddDynamicArguments(actualBeforeCommitID, opts.AfterCommitID)
opts.BeforeCommitID = actualBeforeCommitID
- var err error
beforeCommit, err = gitRepo.GetCommit(opts.BeforeCommitID)
if err != nil {
return nil, err
diff --git a/services/gitdiff/gitdiff_test.go b/services/gitdiff/gitdiff_test.go
index 2351c5da87..1017d188dd 100644
--- a/services/gitdiff/gitdiff_test.go
+++ b/services/gitdiff/gitdiff_test.go
@@ -19,6 +19,7 @@ import (
dmp "github.com/sergi/go-diff/diffmatchpatch"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestDiffToHTML(t *testing.T) {
@@ -628,9 +629,8 @@ func TestDiffLine_GetCommentSide(t *testing.T) {
func TestGetDiffRangeWithWhitespaceBehavior(t *testing.T) {
gitRepo, err := git.OpenRepository(git.DefaultContext, "./testdata/academic-module")
- if !assert.NoError(t, err) {
- return
- }
+ require.NoError(t, err)
+
defer gitRepo.Close()
for _, behavior := range []git.TrustedCmdArgs{{"-w"}, {"--ignore-space-at-eol"}, {"-b"}, nil} {
diffs, err := GetDiff(db.DefaultContext, gitRepo,
diff --git a/services/issue/issue.go b/services/issue/issue.go
index c6a52cc0fe..091b7c02d7 100644
--- a/services/issue/issue.go
+++ b/services/issue/issue.go
@@ -250,8 +250,9 @@ func GetRefEndNamesAndURLs(issues []*issues_model.Issue, repoLink string) (map[i
issueRefURLs := make(map[int64]string, len(issues))
for _, issue := range issues {
if issue.Ref != "" {
- issueRefEndNames[issue.ID] = git.RefName(issue.Ref).ShortName()
- issueRefURLs[issue.ID] = git.RefURL(repoLink, issue.Ref)
+ ref := git.RefName(issue.Ref)
+ issueRefEndNames[issue.ID] = ref.ShortName()
+ issueRefURLs[issue.ID] = repoLink + "/src/" + ref.RefWebLinkPath()
}
}
return issueRefEndNames, issueRefURLs
diff --git a/services/mailer/mail_test.go b/services/mailer/mail_test.go
index 185b72f069..36cef486c9 100644
--- a/services/mailer/mail_test.go
+++ b/services/mailer/mail_test.go
@@ -390,9 +390,7 @@ func TestGenerateMessageIDForIssue(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := generateMessageIDForIssue(tt.args.issue, tt.args.comment, tt.args.actionType)
- if !strings.HasPrefix(got, tt.prefix) {
- t.Errorf("generateMessageIDForIssue() = %v, want %v", got, tt.prefix)
- }
+ assert.True(t, strings.HasPrefix(got, tt.prefix), "%v, want %v", got, tt.prefix)
})
}
}
diff --git a/services/migrations/gitea_downloader_test.go b/services/migrations/gitea_downloader_test.go
index c37c70947e..6f6ef99d96 100644
--- a/services/migrations/gitea_downloader_test.go
+++ b/services/migrations/gitea_downloader_test.go
@@ -14,6 +14,7 @@ import (
base "code.gitea.io/gitea/modules/migration"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestGiteaDownloadRepo(t *testing.T) {
@@ -29,12 +30,8 @@ func TestGiteaDownloadRepo(t *testing.T) {
}
downloader, err := NewGiteaDownloader(context.Background(), "https://gitea.com", "gitea/test_repo", "", "", giteaToken)
- if downloader == nil {
- t.Fatal("NewGitlabDownloader is nil")
- }
- if !assert.NoError(t, err) {
- t.Fatal("NewGitlabDownloader error occur")
- }
+ require.NoError(t, err, "NewGiteaDownloader error occur")
+ require.NotNil(t, downloader, "NewGiteaDownloader is nil")
repo, err := downloader.GetRepoInfo()
assert.NoError(t, err)
diff --git a/services/mirror/mirror_pull.go b/services/mirror/mirror_pull.go
index 948222a436..24605cfae0 100644
--- a/services/mirror/mirror_pull.go
+++ b/services/mirror/mirror_pull.go
@@ -87,6 +87,7 @@ type mirrorSyncResult struct {
/*
// * [new tag] v0.1.8 -> v0.1.8
// * [new branch] master -> origin/master
+// * [new ref] refs/pull/2/head -> refs/pull/2/head"
// - [deleted] (none) -> origin/test // delete a branch
// - [deleted] (none) -> 1 // delete a tag
// 957a993..a87ba5f test -> origin/test
@@ -117,6 +118,11 @@ func parseRemoteUpdateOutput(output, remoteName string) []*mirrorSyncResult {
refName: git.RefNameFromBranch(refName),
oldCommitID: gitShortEmptySha,
})
+ case strings.HasPrefix(lines[i], " * [new ref]"): // new reference
+ results = append(results, &mirrorSyncResult{
+ refName: git.RefName(refName),
+ oldCommitID: gitShortEmptySha,
+ })
case strings.HasPrefix(lines[i], " - "): // Delete reference
isTag := !strings.HasPrefix(refName, remoteName+"/")
var refFullName git.RefName
@@ -159,8 +165,15 @@ func parseRemoteUpdateOutput(output, remoteName string) []*mirrorSyncResult {
log.Error("Expect two SHAs but not what found: %q", lines[i])
continue
}
+ var refFullName git.RefName
+ if strings.HasPrefix(refName, "refs/") {
+ refFullName = git.RefName(refName)
+ } else {
+ refFullName = git.RefNameFromBranch(strings.TrimPrefix(refName, remoteName+"/"))
+ }
+
results = append(results, &mirrorSyncResult{
- refName: git.RefNameFromBranch(strings.TrimPrefix(refName, remoteName+"/")),
+ refName: refFullName,
oldCommitID: shas[0],
newCommitID: shas[1],
})
diff --git a/services/pull/pull.go b/services/pull/pull.go
index 52abf35cec..5d3758eca6 100644
--- a/services/pull/pull.go
+++ b/services/pull/pull.go
@@ -6,6 +6,7 @@ package pull
import (
"bytes"
"context"
+ "errors"
"fmt"
"io"
"os"
@@ -636,33 +637,9 @@ func UpdateRef(ctx context.Context, pr *issues_model.PullRequest) (err error) {
return err
}
-type errlist []error
-
-func (errs errlist) Error() string {
- if len(errs) > 0 {
- var buf strings.Builder
- for i, err := range errs {
- if i > 0 {
- buf.WriteString(", ")
- }
- buf.WriteString(err.Error())
- }
- return buf.String()
- }
- return ""
-}
-
-// RetargetChildrenOnMerge retarget children pull requests on merge if possible
-func RetargetChildrenOnMerge(ctx context.Context, doer *user_model.User, pr *issues_model.PullRequest) error {
- if setting.Repository.PullRequest.RetargetChildrenOnMerge && pr.BaseRepoID == pr.HeadRepoID {
- return RetargetBranchPulls(ctx, doer, pr.HeadRepoID, pr.HeadBranch, pr.BaseBranch)
- }
- return nil
-}
-
-// RetargetBranchPulls change target branch for all pull requests whose base branch is the branch
+// retargetBranchPulls change target branch for all pull requests whose base branch is the branch
// Both branch and targetBranch must be in the same repo (for security reasons)
-func RetargetBranchPulls(ctx context.Context, doer *user_model.User, repoID int64, branch, targetBranch string) error {
+func retargetBranchPulls(ctx context.Context, doer *user_model.User, repoID int64, branch, targetBranch string) error {
prs, err := issues_model.GetUnmergedPullRequestsByBaseInfo(ctx, repoID, branch)
if err != nil {
return err
@@ -672,7 +649,7 @@ func RetargetBranchPulls(ctx context.Context, doer *user_model.User, repoID int6
return err
}
- var errs errlist
+ var errs []error
for _, pr := range prs {
if err = pr.Issue.LoadRepo(ctx); err != nil {
errs = append(errs, err)
@@ -682,40 +659,75 @@ func RetargetBranchPulls(ctx context.Context, doer *user_model.User, repoID int6
errs = append(errs, err)
}
}
-
- if len(errs) > 0 {
- return errs
- }
- return nil
+ return errors.Join(errs...)
}
-// CloseBranchPulls close all the pull requests who's head branch is the branch
-func CloseBranchPulls(ctx context.Context, doer *user_model.User, repoID int64, branch string) error {
- prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(ctx, repoID, branch)
+// AdjustPullsCausedByBranchDeleted close all the pull requests who's head branch is the branch
+// Or Close all the plls who's base branch is the branch if setting.Repository.PullRequest.RetargetChildrenOnMerge is false.
+// If it's true, Retarget all these pulls to the default branch.
+func AdjustPullsCausedByBranchDeleted(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, branch string) error {
+ // branch as head branch
+ prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(ctx, repo.ID, branch)
if err != nil {
return err
}
- prs2, err := issues_model.GetUnmergedPullRequestsByBaseInfo(ctx, repoID, branch)
+ if err := issues_model.PullRequestList(prs).LoadAttributes(ctx); err != nil {
+ return err
+ }
+ issues_model.PullRequestList(prs).SetHeadRepo(repo)
+ if err := issues_model.PullRequestList(prs).LoadRepositories(ctx); err != nil {
+ return err
+ }
+
+ var errs []error
+ for _, pr := range prs {
+ if err = issue_service.CloseIssue(ctx, pr.Issue, doer, ""); err != nil && !issues_model.IsErrIssueIsClosed(err) && !issues_model.IsErrDependenciesLeft(err) {
+ errs = append(errs, err)
+ }
+ if err == nil {
+ if err := issues_model.AddDeletePRBranchComment(ctx, doer, pr.BaseRepo, pr.Issue.ID, pr.HeadBranch); err != nil {
+ log.Error("AddDeletePRBranchComment: %v", err)
+ errs = append(errs, err)
+ }
+ }
+ }
+
+ if setting.Repository.PullRequest.RetargetChildrenOnMerge {
+ if err := retargetBranchPulls(ctx, doer, repo.ID, branch, repo.DefaultBranch); err != nil {
+ log.Error("retargetBranchPulls failed: %v", err)
+ errs = append(errs, err)
+ }
+ return errors.Join(errs...)
+ }
+
+ // branch as base branch
+ prs, err = issues_model.GetUnmergedPullRequestsByBaseInfo(ctx, repo.ID, branch)
if err != nil {
return err
}
- prs = append(prs, prs2...)
if err := issues_model.PullRequestList(prs).LoadAttributes(ctx); err != nil {
return err
}
+ issues_model.PullRequestList(prs).SetBaseRepo(repo)
+ if err := issues_model.PullRequestList(prs).LoadRepositories(ctx); err != nil {
+ return err
+ }
- var errs errlist
+ errs = nil
for _, pr := range prs {
- if err = issue_service.CloseIssue(ctx, pr.Issue, doer, ""); err != nil && !issues_model.IsErrIssueIsClosed(err) && !issues_model.IsErrDependenciesLeft(err) {
+ if err = issues_model.AddDeletePRBranchComment(ctx, doer, pr.BaseRepo, pr.Issue.ID, pr.BaseBranch); err != nil {
+ log.Error("AddDeletePRBranchComment: %v", err)
errs = append(errs, err)
}
+ if err == nil {
+ if err = issue_service.CloseIssue(ctx, pr.Issue, doer, ""); err != nil && !issues_model.IsErrIssueIsClosed(err) && !issues_model.IsErrDependenciesLeft(err) {
+ errs = append(errs, err)
+ }
+ }
}
- if len(errs) > 0 {
- return errs
- }
- return nil
+ return errors.Join(errs...)
}
// CloseRepoBranchesPulls close all pull requests which head branches are in the given repository, but only whose base repo is not in the given repository
@@ -725,7 +737,7 @@ func CloseRepoBranchesPulls(ctx context.Context, doer *user_model.User, repo *re
return err
}
- var errs errlist
+ var errs []error
for _, branch := range branches {
prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(ctx, repo.ID, branch.Name)
if err != nil {
@@ -748,10 +760,7 @@ func CloseRepoBranchesPulls(ctx context.Context, doer *user_model.User, repo *re
}
}
- if len(errs) > 0 {
- return errs
- }
- return nil
+ return errors.Join(errs...)
}
var commitMessageTrailersPattern = regexp.MustCompile(`(?:^|\n\n)(?:[\w-]+[ \t]*:[^\n]+\n*(?:[ \t]+[^\n]+\n*)*)+$`)
diff --git a/services/repository/archiver/archiver.go b/services/repository/archiver/archiver.go
index e1addbed33..d39acc080d 100644
--- a/services/repository/archiver/archiver.go
+++ b/services/repository/archiver/archiver.go
@@ -31,19 +31,20 @@ import (
// handle elsewhere.
type ArchiveRequest struct {
RepoID int64
- refName string
Type git.ArchiveType
CommitID string
+
+ archiveRefShortName string // the ref short name to download the archive, for example: "master", "v1.0.0", "commit id"
}
// ErrUnknownArchiveFormat request archive format is not supported
type ErrUnknownArchiveFormat struct {
- RequestFormat string
+ RequestNameType string
}
// Error implements error
func (err ErrUnknownArchiveFormat) Error() string {
- return fmt.Sprintf("unknown format: %s", err.RequestFormat)
+ return fmt.Sprintf("unknown format: %s", err.RequestNameType)
}
// Is implements error
@@ -54,12 +55,12 @@ func (ErrUnknownArchiveFormat) Is(err error) bool {
// RepoRefNotFoundError is returned when a requested reference (commit, tag) was not found.
type RepoRefNotFoundError struct {
- RefName string
+ RefShortName string
}
// Error implements error.
func (e RepoRefNotFoundError) Error() string {
- return fmt.Sprintf("unrecognized repository reference: %s", e.RefName)
+ return fmt.Sprintf("unrecognized repository reference: %s", e.RefShortName)
}
func (e RepoRefNotFoundError) Is(err error) bool {
@@ -67,43 +68,23 @@ func (e RepoRefNotFoundError) Is(err error) bool {
return ok
}
-func ParseFileName(uri string) (ext string, tp git.ArchiveType, err error) {
- switch {
- case strings.HasSuffix(uri, ".zip"):
- ext = ".zip"
- tp = git.ZIP
- case strings.HasSuffix(uri, ".tar.gz"):
- ext = ".tar.gz"
- tp = git.TARGZ
- case strings.HasSuffix(uri, ".bundle"):
- ext = ".bundle"
- tp = git.BUNDLE
- default:
- return "", 0, ErrUnknownArchiveFormat{RequestFormat: uri}
- }
- return ext, tp, nil
-}
-
// NewRequest creates an archival request, based on the URI. The
// resulting ArchiveRequest is suitable for being passed to Await()
// if it's determined that the request still needs to be satisfied.
-func NewRequest(repoID int64, repo *git.Repository, refName string, fileType git.ArchiveType) (*ArchiveRequest, error) {
- if fileType < git.ZIP || fileType > git.BUNDLE {
- return nil, ErrUnknownArchiveFormat{RequestFormat: fileType.String()}
- }
-
- r := &ArchiveRequest{
- RepoID: repoID,
- refName: refName,
- Type: fileType,
+func NewRequest(repoID int64, repo *git.Repository, archiveRefExt string) (*ArchiveRequest, error) {
+ // here the archiveRefShortName is not a clear ref, it could be a tag, branch or commit id
+ archiveRefShortName, archiveType := git.SplitArchiveNameType(archiveRefExt)
+ if archiveType == git.ArchiveUnknown {
+ return nil, ErrUnknownArchiveFormat{archiveRefExt}
}
// Get corresponding commit.
- commitID, err := repo.ConvertToGitID(r.refName)
+ commitID, err := repo.ConvertToGitID(archiveRefShortName)
if err != nil {
- return nil, RepoRefNotFoundError{RefName: r.refName}
+ return nil, RepoRefNotFoundError{RefShortName: archiveRefShortName}
}
+ r := &ArchiveRequest{RepoID: repoID, archiveRefShortName: archiveRefShortName, Type: archiveType}
r.CommitID = commitID.String()
return r, nil
}
@@ -111,11 +92,11 @@ func NewRequest(repoID int64, repo *git.Repository, refName string, fileType git
// GetArchiveName returns the name of the caller, based on the ref used by the
// caller to create this request.
func (aReq *ArchiveRequest) GetArchiveName() string {
- return strings.ReplaceAll(aReq.refName, "/", "-") + "." + aReq.Type.String()
+ return strings.ReplaceAll(aReq.archiveRefShortName, "/", "-") + "." + aReq.Type.String()
}
// Await awaits the completion of an ArchiveRequest. If the archive has
-// already been prepared the method returns immediately. Otherwise an archiver
+// already been prepared the method returns immediately. Otherwise, an archiver
// process will be started and its completion awaited. On success the returned
// RepoArchiver may be used to download the archive. Note that even if the
// context is cancelled/times out a started archiver will still continue to run
@@ -208,8 +189,8 @@ func doArchive(ctx context.Context, r *ArchiveRequest) (*repo_model.RepoArchiver
rd, w := io.Pipe()
defer func() {
- w.Close()
- rd.Close()
+ _ = w.Close()
+ _ = rd.Close()
}()
done := make(chan error, 1) // Ensure that there is some capacity which will ensure that the goroutine below can always finish
repo, err := repo_model.GetRepositoryByID(ctx, archiver.RepoID)
@@ -230,7 +211,7 @@ func doArchive(ctx context.Context, r *ArchiveRequest) (*repo_model.RepoArchiver
}
}()
- if archiver.Type == git.BUNDLE {
+ if archiver.Type == git.ArchiveBundle {
err = gitRepo.CreateBundle(
ctx,
archiver.CommitID,
diff --git a/services/repository/archiver/archiver_test.go b/services/repository/archiver/archiver_test.go
index 1d0c6e513d..522f90558a 100644
--- a/services/repository/archiver/archiver_test.go
+++ b/services/repository/archiver/archiver_test.go
@@ -9,7 +9,6 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unittest"
- "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/services/contexttest"
_ "code.gitea.io/gitea/models/actions"
@@ -31,47 +30,47 @@ func TestArchive_Basic(t *testing.T) {
contexttest.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close()
- bogusReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit, git.ZIP)
+ bogusReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip")
assert.NoError(t, err)
assert.NotNil(t, bogusReq)
assert.EqualValues(t, firstCommit+".zip", bogusReq.GetArchiveName())
// Check a series of bogus requests.
// Step 1, valid commit with a bad extension.
- bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit, 100)
+ bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".unknown")
assert.Error(t, err)
assert.Nil(t, bogusReq)
// Step 2, missing commit.
- bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "dbffff", git.ZIP)
+ bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "dbffff.zip")
assert.Error(t, err)
assert.Nil(t, bogusReq)
// Step 3, doesn't look like branch/tag/commit.
- bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "db", git.ZIP)
+ bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "db.zip")
assert.Error(t, err)
assert.Nil(t, bogusReq)
- bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "master", git.ZIP)
+ bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "master.zip")
assert.NoError(t, err)
assert.NotNil(t, bogusReq)
assert.EqualValues(t, "master.zip", bogusReq.GetArchiveName())
- bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "test/archive", git.ZIP)
+ bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "test/archive.zip")
assert.NoError(t, err)
assert.NotNil(t, bogusReq)
assert.EqualValues(t, "test-archive.zip", bogusReq.GetArchiveName())
// Now two valid requests, firstCommit with valid extensions.
- zipReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit, git.ZIP)
+ zipReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip")
assert.NoError(t, err)
assert.NotNil(t, zipReq)
- tgzReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit, git.TARGZ)
+ tgzReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".tar.gz")
assert.NoError(t, err)
assert.NotNil(t, tgzReq)
- secondReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, secondCommit, git.ZIP)
+ secondReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, secondCommit+".bundle")
assert.NoError(t, err)
assert.NotNil(t, secondReq)
@@ -91,7 +90,7 @@ func TestArchive_Basic(t *testing.T) {
// Sleep two seconds to make sure the queue doesn't change.
time.Sleep(2 * time.Second)
- zipReq2, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit, git.ZIP)
+ zipReq2, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip")
assert.NoError(t, err)
// This zipReq should match what's sitting in the queue, as we haven't
// let it release yet. From the consumer's point of view, this looks like
@@ -106,12 +105,12 @@ func TestArchive_Basic(t *testing.T) {
// Now we'll submit a request and TimedWaitForCompletion twice, before and
// after we release it. We should trigger both the timeout and non-timeout
// cases.
- timedReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, secondCommit, git.TARGZ)
+ timedReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, secondCommit+".tar.gz")
assert.NoError(t, err)
assert.NotNil(t, timedReq)
doArchive(db.DefaultContext, timedReq)
- zipReq2, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit, git.ZIP)
+ zipReq2, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip")
assert.NoError(t, err)
// Now, we're guaranteed to have released the original zipReq from the queue.
// Ensure that we don't get handed back the released entry somehow, but they
@@ -129,6 +128,6 @@ func TestArchive_Basic(t *testing.T) {
}
func TestErrUnknownArchiveFormat(t *testing.T) {
- err := ErrUnknownArchiveFormat{RequestFormat: "master"}
+ err := ErrUnknownArchiveFormat{RequestNameType: "xxx"}
assert.ErrorIs(t, err, ErrUnknownArchiveFormat{})
}
diff --git a/services/repository/branch.go b/services/repository/branch.go
index fc476298ca..c80d367bbd 100644
--- a/services/repository/branch.go
+++ b/services/repository/branch.go
@@ -26,6 +26,7 @@ import (
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/queue"
repo_module "code.gitea.io/gitea/modules/repository"
+ "code.gitea.io/gitea/modules/reqctx"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
webhook_module "code.gitea.io/gitea/modules/webhook"
@@ -416,6 +417,29 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, doer *user_m
return "from_not_exist", nil
}
+ perm, err := access_model.GetUserRepoPermission(ctx, repo, doer)
+ if err != nil {
+ return "", err
+ }
+
+ isDefault := from == repo.DefaultBranch
+ if isDefault && !perm.IsAdmin() {
+ return "", repo_model.ErrUserDoesNotHaveAccessToRepo{
+ UserID: doer.ID,
+ RepoName: repo.LowerName,
+ }
+ }
+
+ // If from == rule name, admins are allowed to modify them.
+ if protectedBranch, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, from); err != nil {
+ return "", err
+ } else if protectedBranch != nil && !perm.IsAdmin() {
+ return "", repo_model.ErrUserDoesNotHaveAccessToRepo{
+ UserID: doer.ID,
+ RepoName: repo.LowerName,
+ }
+ }
+
if err := git_model.RenameBranch(ctx, repo, from, to, func(ctx context.Context, isDefault bool) error {
err2 := gitRepo.RenameBranch(from, to)
if err2 != nil {
@@ -489,7 +513,7 @@ func CanDeleteBranch(ctx context.Context, repo *repo_model.Repository, branchNam
}
// DeleteBranch delete branch
-func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, branchName string) error {
+func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, branchName string, pr *issues_model.PullRequest) error {
err := repo.MustNotBeArchived()
if err != nil {
return err
@@ -519,6 +543,12 @@ func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.R
}
}
+ if pr != nil {
+ if err := issues_model.AddDeletePRBranchComment(ctx, doer, pr.BaseRepo, pr.Issue.ID, pr.HeadBranch); err != nil {
+ return fmt.Errorf("DeleteBranch: %v", err)
+ }
+ }
+
return gitRepo.DeleteBranch(branchName, git.DeleteBranchOptions{
Force: true,
})
@@ -636,3 +666,72 @@ func SetRepoDefaultBranch(ctx context.Context, repo *repo_model.Repository, gitR
return nil
}
+
+// BranchDivergingInfo contains the information about the divergence of a head branch to the base branch.
+type BranchDivergingInfo struct {
+ // whether the base branch contains new commits which are not in the head branch
+ BaseHasNewCommits bool
+
+ // behind/after are number of commits that the head branch is behind/after the base branch, it's 0 if it's unable to calculate.
+ // there could be a case that BaseHasNewCommits=true while the behind/after are both 0 (unable to calculate).
+ HeadCommitsBehind int
+ HeadCommitsAhead int
+}
+
+// GetBranchDivergingInfo returns the information about the divergence of a patch branch to the base branch.
+func GetBranchDivergingInfo(ctx reqctx.RequestContext, baseRepo *repo_model.Repository, baseBranch string, headRepo *repo_model.Repository, headBranch string) (*BranchDivergingInfo, error) {
+ headGitBranch, err := git_model.GetBranch(ctx, headRepo.ID, headBranch)
+ if err != nil {
+ return nil, err
+ }
+ if headGitBranch.IsDeleted {
+ return nil, git_model.ErrBranchNotExist{
+ BranchName: headBranch,
+ }
+ }
+ baseGitBranch, err := git_model.GetBranch(ctx, baseRepo.ID, baseBranch)
+ if err != nil {
+ return nil, err
+ }
+ if baseGitBranch.IsDeleted {
+ return nil, git_model.ErrBranchNotExist{
+ BranchName: baseBranch,
+ }
+ }
+
+ info := &BranchDivergingInfo{}
+ if headGitBranch.CommitID == baseGitBranch.CommitID {
+ return info, nil
+ }
+
+ // if the fork repo has new commits, this call will fail because they are not in the base repo
+ // exit status 128 - fatal: Invalid symmetric difference expression aaaaaaaaaaaa...bbbbbbbbbbbb
+ // so at the moment, we first check the update time, then check whether the fork branch has base's head
+ diff, err := git.GetDivergingCommits(ctx, baseRepo.RepoPath(), baseGitBranch.CommitID, headGitBranch.CommitID)
+ if err != nil {
+ info.BaseHasNewCommits = baseGitBranch.UpdatedUnix > headGitBranch.UpdatedUnix
+ if headRepo.IsFork && info.BaseHasNewCommits {
+ return info, nil
+ }
+ // if the base's update time is before the fork, check whether the base's head is in the fork
+ headGitRepo, err := gitrepo.RepositoryFromRequestContextOrOpen(ctx, headRepo)
+ if err != nil {
+ return nil, err
+ }
+ headCommit, err := headGitRepo.GetCommit(headGitBranch.CommitID)
+ if err != nil {
+ return nil, err
+ }
+ baseCommitID, err := git.NewIDFromString(baseGitBranch.CommitID)
+ if err != nil {
+ return nil, err
+ }
+ hasPreviousCommit, _ := headCommit.HasPreviousCommit(baseCommitID)
+ info.BaseHasNewCommits = !hasPreviousCommit
+ return info, nil
+ }
+
+ info.HeadCommitsBehind, info.HeadCommitsAhead = diff.Behind, diff.Ahead
+ info.BaseHasNewCommits = info.HeadCommitsBehind > 0
+ return info, nil
+}
diff --git a/services/repository/merge_upstream.go b/services/repository/merge_upstream.go
index 85ca8f7e31..34e01df723 100644
--- a/services/repository/merge_upstream.go
+++ b/services/repository/merge_upstream.go
@@ -4,35 +4,38 @@
package repository
import (
- "context"
+ "errors"
"fmt"
- git_model "code.gitea.io/gitea/models/git"
issue_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
repo_module "code.gitea.io/gitea/modules/repository"
+ "code.gitea.io/gitea/modules/reqctx"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/pull"
)
-type UpstreamDivergingInfo struct {
- BaseIsNewer bool
- CommitsBehind int
- CommitsAhead int
-}
-
-func MergeUpstream(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, branch string) (mergeStyle string, err error) {
+// MergeUpstream merges the base repository's default branch into the fork repository's current branch.
+func MergeUpstream(ctx reqctx.RequestContext, doer *user_model.User, repo *repo_model.Repository, branch string) (mergeStyle string, err error) {
if err = repo.MustNotBeArchived(); err != nil {
return "", err
}
if err = repo.GetBaseRepo(ctx); err != nil {
return "", err
}
+ divergingInfo, err := GetUpstreamDivergingInfo(ctx, repo, branch)
+ if err != nil {
+ return "", err
+ }
+ if !divergingInfo.BaseBranchHasNewCommits {
+ return "up-to-date", nil
+ }
+
err = git.Push(ctx, repo.BaseRepo.RepoPath(), git.PushOptions{
Remote: repo.RepoPath(),
- Branch: fmt.Sprintf("%s:%s", branch, branch),
+ Branch: fmt.Sprintf("%s:%s", divergingInfo.BaseBranchName, branch),
Env: repo_module.PushingEnvironment(doer, repo),
})
if err == nil {
@@ -64,7 +67,7 @@ func MergeUpstream(ctx context.Context, doer *user_model.User, repo *repo_model.
BaseRepoID: repo.BaseRepo.ID,
BaseRepo: repo.BaseRepo,
HeadBranch: branch, // maybe HeadCommitID is not needed
- BaseBranch: branch,
+ BaseBranch: divergingInfo.BaseBranchName,
}
fakeIssue.PullRequest = fakePR
err = pull.Update(ctx, fakePR, doer, "merge upstream", false)
@@ -74,42 +77,47 @@ func MergeUpstream(ctx context.Context, doer *user_model.User, repo *repo_model.
return "merge", nil
}
-func GetUpstreamDivergingInfo(ctx context.Context, repo *repo_model.Repository, branch string) (*UpstreamDivergingInfo, error) {
- if !repo.IsFork {
+// UpstreamDivergingInfo is also used in templates, so it needs to search for all references before changing it.
+type UpstreamDivergingInfo struct {
+ BaseBranchName string
+ BaseBranchHasNewCommits bool
+ HeadBranchCommitsBehind int
+}
+
+// GetUpstreamDivergingInfo returns the information about the divergence between the fork repository's branch and the base repository's default branch.
+func GetUpstreamDivergingInfo(ctx reqctx.RequestContext, forkRepo *repo_model.Repository, forkBranch string) (*UpstreamDivergingInfo, error) {
+ if !forkRepo.IsFork {
return nil, util.NewInvalidArgumentErrorf("repo is not a fork")
}
- if repo.IsArchived {
+ if forkRepo.IsArchived {
return nil, util.NewInvalidArgumentErrorf("repo is archived")
}
- if err := repo.GetBaseRepo(ctx); err != nil {
+ if err := forkRepo.GetBaseRepo(ctx); err != nil {
return nil, err
}
- forkBranch, err := git_model.GetBranch(ctx, repo.ID, branch)
- if err != nil {
- return nil, err
- }
-
- baseBranch, err := git_model.GetBranch(ctx, repo.BaseRepo.ID, branch)
- if err != nil {
- return nil, err
- }
-
- info := &UpstreamDivergingInfo{}
- if forkBranch.CommitID == baseBranch.CommitID {
- return info, nil
+ // Do the best to follow the GitHub's behavior, suppose there is a `branch-a` in fork repo:
+ // * if `branch-a` exists in base repo: try to sync `base:branch-a` to `fork:branch-a`
+ // * if `branch-a` doesn't exist in base repo: try to sync `base:main` to `fork:branch-a`
+ info, err := GetBranchDivergingInfo(ctx, forkRepo.BaseRepo, forkBranch, forkRepo, forkBranch)
+ if err == nil {
+ return &UpstreamDivergingInfo{
+ BaseBranchName: forkBranch,
+ BaseBranchHasNewCommits: info.BaseHasNewCommits,
+ HeadBranchCommitsBehind: info.HeadCommitsBehind,
+ }, nil
}
-
- // TODO: if the fork repo has new commits, this call will fail:
- // exit status 128 - fatal: Invalid symmetric difference expression aaaaaaaaaaaa...bbbbbbbbbbbb
- // so at the moment, we are not able to handle this case, should be improved in the future
- diff, err := git.GetDivergingCommits(ctx, repo.BaseRepo.RepoPath(), baseBranch.CommitID, forkBranch.CommitID)
- if err != nil {
- info.BaseIsNewer = baseBranch.UpdatedUnix > forkBranch.UpdatedUnix
- return info, nil
+ if errors.Is(err, util.ErrNotExist) {
+ info, err = GetBranchDivergingInfo(ctx, forkRepo.BaseRepo, forkRepo.BaseRepo.DefaultBranch, forkRepo, forkBranch)
+ if err == nil {
+ return &UpstreamDivergingInfo{
+ BaseBranchName: forkRepo.BaseRepo.DefaultBranch,
+ BaseBranchHasNewCommits: info.BaseHasNewCommits,
+ HeadBranchCommitsBehind: info.HeadCommitsBehind,
+ }, nil
+ }
}
- info.CommitsBehind, info.CommitsAhead = diff.Behind, diff.Ahead
- return info, nil
+ return nil, err
}
diff --git a/services/repository/push.go b/services/repository/push.go
index 06ad65e48f..0ea51f9c07 100644
--- a/services/repository/push.go
+++ b/services/repository/push.go
@@ -275,7 +275,8 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
}
} else {
notify_service.DeleteRef(ctx, pusher, repo, opts.RefFullName)
- if err = pull_service.CloseBranchPulls(ctx, pusher, repo.ID, branch); err != nil {
+
+ if err := pull_service.AdjustPullsCausedByBranchDeleted(ctx, pusher, repo, branch); err != nil {
// close all related pulls
log.Error("close related pull request failed: %v", err)
}
diff --git a/services/webhook/notifier.go b/services/webhook/notifier.go
index a3d5cb34b1..2fce4b351e 100644
--- a/services/webhook/notifier.go
+++ b/services/webhook/notifier.go
@@ -763,12 +763,10 @@ func (m *webhookNotifier) PullRequestReviewRequest(ctx context.Context, doer *us
func (m *webhookNotifier) CreateRef(ctx context.Context, pusher *user_model.User, repo *repo_model.Repository, refFullName git.RefName, refID string) {
apiPusher := convert.ToUser(ctx, pusher, nil)
apiRepo := convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeNone})
- refName := refFullName.ShortName()
-
if err := PrepareWebhooks(ctx, EventSource{Repository: repo}, webhook_module.HookEventCreate, &api.CreatePayload{
- Ref: refName, // FIXME: should it be a full ref name?
+ Ref: refFullName.ShortName(), // FIXME: should it be a full ref name? But it will break the existing webhooks?
Sha: refID,
- RefType: refFullName.RefType(),
+ RefType: string(refFullName.RefType()),
Repo: apiRepo,
Sender: apiPusher,
}); err != nil {
@@ -800,11 +798,9 @@ func (m *webhookNotifier) PullRequestSynchronized(ctx context.Context, doer *use
func (m *webhookNotifier) DeleteRef(ctx context.Context, pusher *user_model.User, repo *repo_model.Repository, refFullName git.RefName) {
apiPusher := convert.ToUser(ctx, pusher, nil)
apiRepo := convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeOwner})
- refName := refFullName.ShortName()
-
if err := PrepareWebhooks(ctx, EventSource{Repository: repo}, webhook_module.HookEventDelete, &api.DeletePayload{
- Ref: refName, // FIXME: should it be a full ref name?
- RefType: refFullName.RefType(),
+ Ref: refFullName.ShortName(), // FIXME: should it be a full ref name? But it will break the existing webhooks?
+ RefType: string(refFullName.RefType()),
PusherType: api.PusherTypeUser,
Repo: apiRepo,
Sender: apiPusher,
diff --git a/services/webhook/slack.go b/services/webhook/slack.go
index c905e7a89f..2a49df2453 100644
--- a/services/webhook/slack.go
+++ b/services/webhook/slack.go
@@ -84,9 +84,9 @@ func SlackLinkFormatter(url, text string) string {
// SlackLinkToRef slack-formatter link to a repo ref
func SlackLinkToRef(repoURL, ref string) string {
// FIXME: SHA1 hardcoded here
- url := git.RefURL(repoURL, ref)
- refName := git.RefName(ref).ShortName()
- return SlackLinkFormatter(url, refName)
+ refName := git.RefName(ref)
+ url := repoURL + "/src/" + refName.RefWebLinkPath()
+ return SlackLinkFormatter(url, refName.ShortName())
}
// Create implements payloadConvertor Create method
diff --git a/services/webhook/webhook.go b/services/webhook/webhook.go
index e0e8fa2fc1..b4609e8a51 100644
--- a/services/webhook/webhook.go
+++ b/services/webhook/webhook.go
@@ -137,14 +137,8 @@ func PrepareWebhook(ctx context.Context, w *webhook_model.Webhook, event webhook
return nil
}
- for _, e := range w.EventCheckers() {
- if event == e.Type {
- if !e.Has() {
- return nil
- }
-
- break
- }
+ if !w.HasEvent(event) {
+ return nil
}
// Avoid sending "0 new commits" to non-integration relevant webhooks (e.g. slack, discord, etc.).
diff --git a/services/wiki/wiki_test.go b/services/wiki/wiki_test.go
index 0a18cffa25..e8b89f5e97 100644
--- a/services/wiki/wiki_test.go
+++ b/services/wiki/wiki_test.go
@@ -17,6 +17,7 @@ import (
_ "code.gitea.io/gitea/models/actions"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestMain(m *testing.M) {
@@ -166,9 +167,8 @@ func TestRepository_AddWikiPage(t *testing.T) {
assert.NoError(t, AddWikiPage(git.DefaultContext, doer, repo, webPath, wikiContent, commitMsg))
// Now need to show that the page has been added:
gitRepo, err := gitrepo.OpenWikiRepository(git.DefaultContext, repo)
- if !assert.NoError(t, err) {
- return
- }
+ require.NoError(t, err)
+
defer gitRepo.Close()
masterTree, err := gitRepo.GetTree(repo.DefaultWikiBranch)
assert.NoError(t, err)
@@ -238,9 +238,8 @@ func TestRepository_DeleteWikiPage(t *testing.T) {
// Now need to show that the page has been added:
gitRepo, err := gitrepo.OpenWikiRepository(git.DefaultContext, repo)
- if !assert.NoError(t, err) {
- return
- }
+ require.NoError(t, err)
+
defer gitRepo.Close()
masterTree, err := gitRepo.GetTree(repo.DefaultWikiBranch)
assert.NoError(t, err)
@@ -253,9 +252,8 @@ func TestPrepareWikiFileName(t *testing.T) {
unittest.PrepareTestEnv(t)
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
gitRepo, err := gitrepo.OpenWikiRepository(git.DefaultContext, repo)
- if !assert.NoError(t, err) {
- return
- }
+ require.NoError(t, err)
+
defer gitRepo.Close()
tests := []struct {
@@ -307,9 +305,8 @@ func TestPrepareWikiFileName_FirstPage(t *testing.T) {
assert.NoError(t, err)
gitRepo, err := git.OpenRepository(git.DefaultContext, tmpDir)
- if !assert.NoError(t, err) {
- return
- }
+ require.NoError(t, err)
+
defer gitRepo.Close()
existence, newWikiPath, err := prepareGitPath(gitRepo, "master", "Home")