Skip to content

Commit 8e87598

Browse files
committed
Merge branch 'main' into lunny/locale_file_json
2 parents b6372c9 + 53dfbbb commit 8e87598

File tree

26 files changed

+447
-258
lines changed

26 files changed

+447
-258
lines changed

modules/actions/workflows.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -377,20 +377,28 @@ func matchIssuesEvent(issuePayload *api.IssuePayload, evt *jobparser.Event) bool
377377
// Actions with the same name:
378378
// opened, edited, closed, reopened, assigned, unassigned, milestoned, demilestoned
379379
// Actions need to be converted:
380-
// label_updated -> labeled
380+
// label_updated -> labeled (when adding) or unlabeled (when removing)
381381
// label_cleared -> unlabeled
382382
// Unsupported activity types:
383383
// deleted, transferred, pinned, unpinned, locked, unlocked
384384

385-
action := issuePayload.Action
386-
switch action {
385+
actions := []string{}
386+
switch issuePayload.Action {
387387
case api.HookIssueLabelUpdated:
388-
action = "labeled"
388+
if len(issuePayload.Changes.AddedLabels) > 0 {
389+
actions = append(actions, "labeled")
390+
}
391+
if len(issuePayload.Changes.RemovedLabels) > 0 {
392+
actions = append(actions, "unlabeled")
393+
}
389394
case api.HookIssueLabelCleared:
390-
action = "unlabeled"
395+
actions = append(actions, "unlabeled")
396+
default:
397+
actions = append(actions, string(issuePayload.Action))
391398
}
399+
392400
for _, val := range vals {
393-
if glob.MustCompile(val, '/').Match(string(action)) {
401+
if slices.ContainsFunc(actions, glob.MustCompile(val, '/').Match) {
394402
matchTimes++
395403
break
396404
}

modules/actions/workflows_test.go

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,3 +154,184 @@ func TestDetectMatched(t *testing.T) {
154154
})
155155
}
156156
}
157+
158+
func TestMatchIssuesEvent(t *testing.T) {
159+
testCases := []struct {
160+
desc string
161+
payload *api.IssuePayload
162+
yamlOn string
163+
expected bool
164+
eventType string
165+
}{
166+
{
167+
desc: "Label deletion should trigger unlabeled event",
168+
payload: &api.IssuePayload{
169+
Action: api.HookIssueLabelUpdated,
170+
Issue: &api.Issue{
171+
Labels: []*api.Label{},
172+
},
173+
Changes: &api.ChangesPayload{
174+
RemovedLabels: []*api.Label{
175+
{ID: 123, Name: "deleted-label"},
176+
},
177+
},
178+
},
179+
yamlOn: "on:\n issues:\n types: [unlabeled]",
180+
expected: true,
181+
eventType: "unlabeled",
182+
},
183+
{
184+
desc: "Label deletion with existing labels should trigger unlabeled event",
185+
payload: &api.IssuePayload{
186+
Action: api.HookIssueLabelUpdated,
187+
Issue: &api.Issue{
188+
Labels: []*api.Label{
189+
{ID: 456, Name: "existing-label"},
190+
},
191+
},
192+
Changes: &api.ChangesPayload{
193+
AddedLabels: nil,
194+
RemovedLabels: []*api.Label{
195+
{ID: 123, Name: "deleted-label"},
196+
},
197+
},
198+
},
199+
yamlOn: "on:\n issues:\n types: [unlabeled]",
200+
expected: true,
201+
eventType: "unlabeled",
202+
},
203+
{
204+
desc: "Label addition should trigger labeled event",
205+
payload: &api.IssuePayload{
206+
Action: api.HookIssueLabelUpdated,
207+
Issue: &api.Issue{
208+
Labels: []*api.Label{
209+
{ID: 123, Name: "new-label"},
210+
},
211+
},
212+
Changes: &api.ChangesPayload{
213+
AddedLabels: []*api.Label{
214+
{ID: 123, Name: "new-label"},
215+
},
216+
RemovedLabels: []*api.Label{}, // Empty array, no labels removed
217+
},
218+
},
219+
yamlOn: "on:\n issues:\n types: [labeled]",
220+
expected: true,
221+
eventType: "labeled",
222+
},
223+
{
224+
desc: "Label clear should trigger unlabeled event",
225+
payload: &api.IssuePayload{
226+
Action: api.HookIssueLabelCleared,
227+
Issue: &api.Issue{
228+
Labels: []*api.Label{},
229+
},
230+
},
231+
yamlOn: "on:\n issues:\n types: [unlabeled]",
232+
expected: true,
233+
eventType: "unlabeled",
234+
},
235+
{
236+
desc: "Both adding and removing labels should trigger labeled event",
237+
payload: &api.IssuePayload{
238+
Action: api.HookIssueLabelUpdated,
239+
Issue: &api.Issue{
240+
Labels: []*api.Label{
241+
{ID: 789, Name: "new-label"},
242+
},
243+
},
244+
Changes: &api.ChangesPayload{
245+
AddedLabels: []*api.Label{
246+
{ID: 789, Name: "new-label"},
247+
},
248+
RemovedLabels: []*api.Label{
249+
{ID: 123, Name: "deleted-label"},
250+
},
251+
},
252+
},
253+
yamlOn: "on:\n issues:\n types: [labeled]",
254+
expected: true,
255+
eventType: "labeled",
256+
},
257+
{
258+
desc: "Both adding and removing labels should trigger unlabeled event",
259+
payload: &api.IssuePayload{
260+
Action: api.HookIssueLabelUpdated,
261+
Issue: &api.Issue{
262+
Labels: []*api.Label{
263+
{ID: 789, Name: "new-label"},
264+
},
265+
},
266+
Changes: &api.ChangesPayload{
267+
AddedLabels: []*api.Label{
268+
{ID: 789, Name: "new-label"},
269+
},
270+
RemovedLabels: []*api.Label{
271+
{ID: 123, Name: "deleted-label"},
272+
},
273+
},
274+
},
275+
yamlOn: "on:\n issues:\n types: [unlabeled]",
276+
expected: true,
277+
eventType: "unlabeled",
278+
},
279+
{
280+
desc: "Both adding and removing labels should trigger both events",
281+
payload: &api.IssuePayload{
282+
Action: api.HookIssueLabelUpdated,
283+
Issue: &api.Issue{
284+
Labels: []*api.Label{
285+
{ID: 789, Name: "new-label"},
286+
},
287+
},
288+
Changes: &api.ChangesPayload{
289+
AddedLabels: []*api.Label{
290+
{ID: 789, Name: "new-label"},
291+
},
292+
RemovedLabels: []*api.Label{
293+
{ID: 123, Name: "deleted-label"},
294+
},
295+
},
296+
},
297+
yamlOn: "on:\n issues:\n types: [labeled, unlabeled]",
298+
expected: true,
299+
eventType: "multiple",
300+
},
301+
}
302+
303+
for _, tc := range testCases {
304+
t.Run(tc.desc, func(t *testing.T) {
305+
evts, err := GetEventsFromContent([]byte(tc.yamlOn))
306+
assert.NoError(t, err)
307+
assert.Len(t, evts, 1)
308+
309+
// Test if the event matches as expected
310+
assert.Equal(t, tc.expected, matchIssuesEvent(tc.payload, evts[0]))
311+
312+
// For extra validation, check that action mapping works correctly
313+
if tc.eventType == "multiple" {
314+
// Skip direct action mapping validation for multiple events case
315+
// as one action can map to multiple event types
316+
return
317+
}
318+
319+
// Determine expected action for single event case
320+
var expectedAction string
321+
switch tc.payload.Action {
322+
case api.HookIssueLabelUpdated:
323+
if tc.eventType == "labeled" {
324+
expectedAction = "labeled"
325+
} else if tc.eventType == "unlabeled" {
326+
expectedAction = "unlabeled"
327+
}
328+
case api.HookIssueLabelCleared:
329+
expectedAction = "unlabeled"
330+
default:
331+
expectedAction = string(tc.payload.Action)
332+
}
333+
334+
assert.Equal(t, expectedAction, tc.eventType, "Event type should match expected")
335+
})
336+
}
337+
}

modules/git/repo_commit_gogit.go

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,6 @@ func (repo *Repository) GetRefCommitID(name string) (string, error) {
3838
return ref.Hash().String(), nil
3939
}
4040

41-
// SetReference sets the commit ID string of given reference (e.g. branch or tag).
42-
func (repo *Repository) SetReference(name, commitID string) error {
43-
return repo.gogitRepo.Storer.SetReference(plumbing.NewReferenceFromStrings(name, commitID))
44-
}
45-
46-
// RemoveReference removes the given reference (e.g. branch or tag).
47-
func (repo *Repository) RemoveReference(name string) error {
48-
return repo.gogitRepo.Storer.RemoveReference(plumbing.ReferenceName(name))
49-
}
50-
5141
// ConvertToHash returns a Hash object from a potential ID string
5242
func (repo *Repository) ConvertToGitID(commitID string) (ObjectID, error) {
5343
objectFormat, err := repo.GetObjectFormat()

modules/git/repo_commit_nogogit.go

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -51,18 +51,6 @@ func (repo *Repository) GetRefCommitID(name string) (string, error) {
5151
return string(shaBs), nil
5252
}
5353

54-
// SetReference sets the commit ID string of given reference (e.g. branch or tag).
55-
func (repo *Repository) SetReference(name, commitID string) error {
56-
_, _, err := gitcmd.NewCommand("update-ref").AddDynamicArguments(name, commitID).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
57-
return err
58-
}
59-
60-
// RemoveReference removes the given reference (e.g. branch or tag).
61-
func (repo *Repository) RemoveReference(name string) error {
62-
_, _, err := gitcmd.NewCommand("update-ref", "--no-deref", "-d").AddDynamicArguments(name).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
63-
return err
64-
}
65-
6654
// IsCommitExist returns true if given commit exists in current repository.
6755
func (repo *Repository) IsCommitExist(name string) bool {
6856
if err := ensureValidGitRepository(repo.Ctx, repo.Path); err != nil {

modules/git/repo_compare_test.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99
"path/filepath"
1010
"testing"
1111

12+
"code.gitea.io/gitea/modules/git/gitcmd"
13+
1214
"github.com/stretchr/testify/assert"
1315
)
1416

@@ -99,7 +101,9 @@ func TestReadWritePullHead(t *testing.T) {
99101

100102
// Write a fake sha1 with only 40 zeros
101103
newCommit := "feaf4ba6bc635fec442f46ddd4512416ec43c2c2"
102-
err = repo.SetReference(PullPrefix+"1/head", newCommit)
104+
_, _, err = gitcmd.NewCommand("update-ref").
105+
AddDynamicArguments(PullPrefix+"1/head", newCommit).
106+
RunStdString(t.Context(), &gitcmd.RunOpts{Dir: repo.Path})
103107
if err != nil {
104108
assert.NoError(t, err)
105109
return
@@ -116,7 +120,9 @@ func TestReadWritePullHead(t *testing.T) {
116120
assert.Equal(t, headContents, newCommit)
117121

118122
// Remove file after the test
119-
err = repo.RemoveReference(PullPrefix + "1/head")
123+
_, _, err = gitcmd.NewCommand("update-ref", "--no-deref", "-d").
124+
AddDynamicArguments(PullPrefix+"1/head").
125+
RunStdString(t.Context(), &gitcmd.RunOpts{Dir: repo.Path})
120126
assert.NoError(t, err)
121127
}
122128

modules/gitrepo/ref.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package gitrepo
5+
6+
import (
7+
"context"
8+
9+
"code.gitea.io/gitea/modules/git/gitcmd"
10+
)
11+
12+
func UpdateRef(ctx context.Context, repo Repository, refName, newCommitID string) error {
13+
_, _, err := gitcmd.NewCommand("update-ref").AddDynamicArguments(refName, newCommitID).RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath(repo)})
14+
return err
15+
}
16+
17+
func RemoveRef(ctx context.Context, repo Repository, refName string) error {
18+
_, _, err := gitcmd.NewCommand("update-ref", "--no-deref", "-d").
19+
AddDynamicArguments(refName).RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath(repo)})
20+
return err
21+
}

modules/setting/repository.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ var (
5454
AllowForkWithoutMaximumLimit bool
5555
AllowForkIntoSameOwner bool
5656

57+
// StreamArchives makes Gitea stream git archive files to the client directly instead of creating an archive first.
58+
// Ideally all users should use this streaming method. However, at the moment we don't know whether there are
59+
// any users who still need the old behavior, so we introduce this option, intentionally not documenting it.
60+
// After one or two releases, if no one complains, we will remove this option and always use streaming.
61+
StreamArchives bool
62+
5763
// Repository editor settings
5864
Editor struct {
5965
LineWrapExtensions []string
@@ -167,6 +173,7 @@ var (
167173
DisableStars: false,
168174
DefaultBranch: "main",
169175
AllowForkWithoutMaximumLimit: true,
176+
StreamArchives: true,
170177

171178
// Repository editor settings
172179
Editor: struct {

modules/structs/hook.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,10 @@ type ChangesPayload struct {
418418
Body *ChangesFromPayload `json:"body,omitempty"`
419419
// Changes made to the reference
420420
Ref *ChangesFromPayload `json:"ref,omitempty"`
421+
// Changes made to the labels added
422+
AddedLabels []*Label `json:"added_labels"`
423+
// Changes made to the labels removed
424+
RemovedLabels []*Label `json:"removed_labels"`
421425
}
422426

423427
// PullRequestPayload represents a payload information of pull request event.

options/locale/locale_ja-JP.json

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,13 @@
513513
"repo.transfer.body": "承認または拒否するには %s を開きます。 もしくは単に無視してもかまいません。",
514514
"repo.collaborator.added.subject": "%s が %s にあなたを追加しました",
515515
"repo.collaborator.added.text": "あなたは次のリポジトリの共同作業者に追加されました:",
516+
"repo.actions.run.failed": "実行に失敗しました",
517+
"repo.actions.run.succeeded": "実行に成功しました",
518+
"repo.actions.run.cancelled": "実行がキャンセルされました",
519+
"repo.actions.jobs.all_succeeded": "すべてのジョブが成功しました",
520+
"repo.actions.jobs.all_failed": "すべてのジョブが失敗しました",
521+
"repo.actions.jobs.some_not_successful": "いくつかのジョブが成功しませんでした",
522+
"repo.actions.jobs.all_cancelled": "すべてのジョブがキャンセルされました",
516523
"team_invite.subject": "%[1]s さんが %[2]s への参加にあなたを招待しました",
517524
"team_invite.text_1": "%[1]s さんが、組織 %[3]s 内のチーム %[2]s への参加に、あなたを招待しました。",
518525
"team_invite.text_2": "下のリンクをクリックしてチームに参加してください。",
@@ -3569,10 +3576,14 @@
35693576
"swift.install2": "そして次のコマンドを実行します:",
35703577
"vagrant.install": "Vagrant ボックスを追加するには、次のコマンドを実行します。",
35713578
"settings.link": "このパッケージをリポジトリにリンク",
3579+
"settings.link.description": "パッケージをリポジトリにリンクすると、リポジトリのパッケージ一覧にそのパッケージが表示されます。 リンクできるのは、同じオーナーが所有するリポジトリのみです。 入力欄を空にするとリンクを削除します。",
35723580
"settings.link.select": "リポジトリを選択",
35733581
"settings.link.button": "リポジトリのリンクを更新",
3574-
"settings.link.success": "リポジトリのリンクが正常に更新されました",
3582+
"settings.link.success": "リポジトリのリンクを更新しました",
35753583
"settings.link.error": "リポジトリのリンクの更新に失敗しました。",
3584+
"settings.link.repo_not_found": "リポジトリ %s が見つかりません。",
3585+
"settings.unlink.error": "リポジトリのリンクの削除に失敗しました。",
3586+
"settings.unlink.success": "リポジトリのリンクを削除しました。",
35763587
"settings.delete": "パッケージ削除",
35773588
"settings.delete.description": "パッケージの削除は恒久的で元に戻すことはできません。",
35783589
"settings.delete.notice": "%s (%s) を削除しようとしています。この操作は元に戻せません。よろしいですか?",

routers/api/v1/api.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1247,7 +1247,7 @@ func Routes() *web.Router {
12471247
}, reqToken())
12481248
m.Get("/raw/*", context.ReferencesGitRepo(), context.RepoRefForAPI, reqRepoReader(unit.TypeCode), repo.GetRawFile)
12491249
m.Get("/media/*", context.ReferencesGitRepo(), context.RepoRefForAPI, reqRepoReader(unit.TypeCode), repo.GetRawFileOrLFS)
1250-
m.Methods("HEAD,GET", "/archive/*", reqRepoReader(unit.TypeCode), repo.GetArchive)
1250+
m.Methods("HEAD,GET", "/archive/*", reqRepoReader(unit.TypeCode), context.ReferencesGitRepo(true), repo.GetArchive)
12511251
m.Combo("/forks").Get(repo.ListForks).
12521252
Post(reqToken(), reqRepoReader(unit.TypeCode), bind(api.CreateForkOption{}), repo.CreateFork)
12531253
m.Post("/merge-upstream", reqToken(), mustNotBeArchived, reqRepoWriter(unit.TypeCode), bind(api.MergeUpstreamRequest{}), repo.MergeUpstream)
@@ -1466,7 +1466,7 @@ func Routes() *web.Router {
14661466
m.Delete("", repo.DeleteAvatar)
14671467
}, reqAdmin(), reqToken())
14681468

1469-
m.Methods("HEAD,GET", "/{ball_type:tarball|zipball|bundle}/*", reqRepoReader(unit.TypeCode), repo.DownloadArchive)
1469+
m.Methods("HEAD,GET", "/{ball_type:tarball|zipball|bundle}/*", reqRepoReader(unit.TypeCode), context.ReferencesGitRepo(true), repo.DownloadArchive)
14701470
}, repoAssignment(), checkTokenPublicOnly())
14711471
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
14721472

0 commit comments

Comments
 (0)