mirror of
https://codeberg.org/forgejo/forgejo
synced 2024-11-22 09:54:24 +01:00
Compare commits
23 commits
16facb456c
...
6a2b45ab8f
Author | SHA1 | Date | |
---|---|---|---|
6a2b45ab8f | |||
4d75ce4f76 | |||
b2ff717263 | |||
bd117a617f | |||
c5c2f62f5b | |||
8879e02af6 | |||
c71510dae1 | |||
2db03d0d02 | |||
25bf32c838 | |||
1ece127e55 | |||
3f49b7e81f | |||
5d3db32a45 | |||
cf9826c678 | |||
b666a1ee93 | |||
569a67327c | |||
146824badc | |||
eaa66f85f6 | |||
969a6ab24a | |||
7d59060dc6 | |||
308812a82e | |||
fc26becba4 | |||
013cc1dee4 | |||
2cccc02e76 |
|
@ -7,6 +7,7 @@
|
|||
target_url: https://example.com/builds/
|
||||
description: My awesome CI-service
|
||||
context: ci/awesomeness
|
||||
context_hash: c65f4d64a3b14a3eced0c9b36799e66e1bd5ced7
|
||||
creator_id: 2
|
||||
|
||||
-
|
||||
|
@ -18,6 +19,7 @@
|
|||
target_url: https://example.com/coverage/
|
||||
description: My awesome Coverage service
|
||||
context: cov/awesomeness
|
||||
context_hash: 3929ac7bccd3fa1bf9b38ddedb77973b1b9a8cfe
|
||||
creator_id: 2
|
||||
|
||||
-
|
||||
|
@ -29,6 +31,7 @@
|
|||
target_url: https://example.com/coverage/
|
||||
description: My awesome Coverage service
|
||||
context: cov/awesomeness
|
||||
context_hash: 3929ac7bccd3fa1bf9b38ddedb77973b1b9a8cfe
|
||||
creator_id: 2
|
||||
|
||||
-
|
||||
|
@ -40,6 +43,7 @@
|
|||
target_url: https://example.com/builds/
|
||||
description: My awesome CI-service
|
||||
context: ci/awesomeness
|
||||
context_hash: c65f4d64a3b14a3eced0c9b36799e66e1bd5ced7
|
||||
creator_id: 2
|
||||
|
||||
-
|
||||
|
@ -51,15 +55,41 @@
|
|||
target_url: https://example.com/builds/
|
||||
description: My awesome deploy service
|
||||
context: deploy/awesomeness
|
||||
context_hash: ae9547713a6665fc4261d0756904932085a41cf2
|
||||
creator_id: 2
|
||||
|
||||
-
|
||||
id: 6
|
||||
index: 6
|
||||
index: 1
|
||||
repo_id: 62
|
||||
state: "failure"
|
||||
sha: "774f93df12d14931ea93259ae93418da4482fcc1"
|
||||
target_url: "/user2/test_workflows/actions"
|
||||
description: My awesome deploy service
|
||||
context: deploy/awesomeness
|
||||
context_hash: ae9547713a6665fc4261d0756904932085a41cf2
|
||||
creator_id: 2
|
||||
|
||||
-
|
||||
id: 7
|
||||
index: 6
|
||||
repo_id: 1
|
||||
state: "pending"
|
||||
sha: "1234123412341234123412341234123412341234"
|
||||
target_url: https://example.com/builds/
|
||||
description: My awesome deploy service
|
||||
context: deploy/awesomeness
|
||||
context_hash: ae9547713a6665fc4261d0756904932085a41cf2
|
||||
creator_id: 2
|
||||
|
||||
-
|
||||
id: 8
|
||||
index: 2
|
||||
repo_id: 62
|
||||
state: "error"
|
||||
sha: "774f93df12d14931ea93259ae93418da4482fcc1"
|
||||
target_url: "/user2/test_workflows/actions"
|
||||
description: "My awesome deploy service - v2"
|
||||
context: deploy/awesomeness
|
||||
context_hash: ae9547713a6665fc4261d0756904932085a41cf2
|
||||
creator_id: 2
|
||||
|
|
|
@ -288,27 +288,18 @@ func GetLatestCommitStatus(ctx context.Context, repoID int64, sha string, listOp
|
|||
|
||||
// GetLatestCommitStatusForPairs returns all statuses with a unique context for a given list of repo-sha pairs
|
||||
func GetLatestCommitStatusForPairs(ctx context.Context, repoSHAs []RepoSHA) (map[int64][]*CommitStatus, error) {
|
||||
type result struct {
|
||||
Index int64
|
||||
RepoID int64
|
||||
SHA string
|
||||
}
|
||||
|
||||
results := make([]result, 0, len(repoSHAs))
|
||||
|
||||
getBase := func() *xorm.Session {
|
||||
return db.GetEngine(ctx).Table(&CommitStatus{})
|
||||
}
|
||||
results := []*CommitStatus{}
|
||||
|
||||
// Create a disjunction of conditions for each repoID and SHA pair
|
||||
conds := make([]builder.Cond, 0, len(repoSHAs))
|
||||
for _, repoSHA := range repoSHAs {
|
||||
conds = append(conds, builder.Eq{"repo_id": repoSHA.RepoID, "sha": repoSHA.SHA})
|
||||
}
|
||||
sess := getBase().Where(builder.Or(conds...)).
|
||||
Select("max( `index` ) as `index`, repo_id, sha").
|
||||
GroupBy("context_hash, repo_id, sha").OrderBy("max( `index` ) desc")
|
||||
|
||||
sess := db.GetEngine(ctx).Table(&CommitStatus{}).
|
||||
Select("MAX(`index`) AS `index`, *").
|
||||
Where(builder.Or(conds...)).
|
||||
GroupBy("context_hash, repo_id, sha").OrderBy("MAX(`index`) DESC")
|
||||
err := sess.Find(&results)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -316,27 +307,9 @@ func GetLatestCommitStatusForPairs(ctx context.Context, repoSHAs []RepoSHA) (map
|
|||
|
||||
repoStatuses := make(map[int64][]*CommitStatus)
|
||||
|
||||
if len(results) > 0 {
|
||||
statuses := make([]*CommitStatus, 0, len(results))
|
||||
|
||||
conds = make([]builder.Cond, 0, len(results))
|
||||
for _, result := range results {
|
||||
cond := builder.Eq{
|
||||
"`index`": result.Index,
|
||||
"repo_id": result.RepoID,
|
||||
"sha": result.SHA,
|
||||
}
|
||||
conds = append(conds, cond)
|
||||
}
|
||||
err = getBase().Where(builder.Or(conds...)).Find(&statuses)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Group the statuses by repo ID
|
||||
for _, status := range statuses {
|
||||
repoStatuses[status.RepoID] = append(repoStatuses[status.RepoID], status)
|
||||
}
|
||||
// Group the statuses by repo ID
|
||||
for _, status := range results {
|
||||
repoStatuses[status.RepoID] = append(repoStatuses[status.RepoID], status)
|
||||
}
|
||||
|
||||
return repoStatuses, nil
|
||||
|
|
|
@ -35,8 +35,8 @@ func TestGetCommitStatuses(t *testing.T) {
|
|||
SHA: sha1,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 5, int(maxResults))
|
||||
assert.Len(t, statuses, 5)
|
||||
assert.EqualValues(t, 6, maxResults)
|
||||
assert.Len(t, statuses, 6)
|
||||
|
||||
assert.Equal(t, "ci/awesomeness", statuses[0].Context)
|
||||
assert.Equal(t, structs.CommitStatusPending, statuses[0].State)
|
||||
|
@ -58,13 +58,17 @@ func TestGetCommitStatuses(t *testing.T) {
|
|||
assert.Equal(t, structs.CommitStatusError, statuses[4].State)
|
||||
assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/statuses/1234123412341234123412341234123412341234", statuses[4].APIURL(db.DefaultContext))
|
||||
|
||||
assert.Equal(t, "deploy/awesomeness", statuses[5].Context)
|
||||
assert.Equal(t, structs.CommitStatusPending, statuses[5].State)
|
||||
assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/statuses/1234123412341234123412341234123412341234", statuses[5].APIURL(db.DefaultContext))
|
||||
|
||||
statuses, maxResults, err = db.FindAndCount[git_model.CommitStatus](db.DefaultContext, &git_model.CommitStatusOptions{
|
||||
ListOptions: db.ListOptions{Page: 2, PageSize: 50},
|
||||
RepoID: repo1.ID,
|
||||
SHA: sha1,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 5, int(maxResults))
|
||||
assert.EqualValues(t, 6, maxResults)
|
||||
assert.Empty(t, statuses)
|
||||
}
|
||||
|
||||
|
@ -265,3 +269,148 @@ func TestCommitStatusesHideActionsURL(t *testing.T) {
|
|||
assert.Empty(t, statuses[0].TargetURL)
|
||||
assert.Equal(t, "https://mycicd.org/1", statuses[1].TargetURL)
|
||||
}
|
||||
|
||||
func TestGetLatestCommitStatusForPairs(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
t.Run("All", func(t *testing.T) {
|
||||
pairs, err := git_model.GetLatestCommitStatusForPairs(db.DefaultContext, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.EqualValues(t, map[int64][]*git_model.CommitStatus{
|
||||
1: {
|
||||
{
|
||||
ID: 7,
|
||||
Index: 6,
|
||||
RepoID: 1,
|
||||
State: structs.CommitStatusPending,
|
||||
SHA: "1234123412341234123412341234123412341234",
|
||||
TargetURL: "https://example.com/builds/",
|
||||
Description: "My awesome deploy service",
|
||||
ContextHash: "ae9547713a6665fc4261d0756904932085a41cf2",
|
||||
Context: "deploy/awesomeness",
|
||||
CreatorID: 2,
|
||||
},
|
||||
{
|
||||
ID: 4,
|
||||
Index: 4,
|
||||
State: structs.CommitStatusFailure,
|
||||
TargetURL: "https://example.com/builds/",
|
||||
Description: "My awesome CI-service",
|
||||
Context: "ci/awesomeness",
|
||||
CreatorID: 2,
|
||||
RepoID: 1,
|
||||
SHA: "1234123412341234123412341234123412341234",
|
||||
ContextHash: "c65f4d64a3b14a3eced0c9b36799e66e1bd5ced7",
|
||||
},
|
||||
{
|
||||
ID: 3,
|
||||
Index: 3,
|
||||
State: structs.CommitStatusSuccess,
|
||||
TargetURL: "https://example.com/coverage/",
|
||||
Description: "My awesome Coverage service",
|
||||
Context: "cov/awesomeness",
|
||||
CreatorID: 2,
|
||||
RepoID: 1,
|
||||
SHA: "1234123412341234123412341234123412341234",
|
||||
ContextHash: "3929ac7bccd3fa1bf9b38ddedb77973b1b9a8cfe",
|
||||
},
|
||||
},
|
||||
62: {
|
||||
{
|
||||
ID: 8,
|
||||
Index: 2,
|
||||
RepoID: 62,
|
||||
State: structs.CommitStatusError,
|
||||
TargetURL: "/user2/test_workflows/actions",
|
||||
Description: "My awesome deploy service - v2",
|
||||
Context: "deploy/awesomeness",
|
||||
SHA: "774f93df12d14931ea93259ae93418da4482fcc1",
|
||||
ContextHash: "ae9547713a6665fc4261d0756904932085a41cf2",
|
||||
CreatorID: 2,
|
||||
},
|
||||
},
|
||||
}, pairs)
|
||||
})
|
||||
|
||||
t.Run("Repo 1", func(t *testing.T) {
|
||||
pairs, err := git_model.GetLatestCommitStatusForPairs(db.DefaultContext, []git_model.RepoSHA{{1, "1234123412341234123412341234123412341234"}})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.EqualValues(t, map[int64][]*git_model.CommitStatus{
|
||||
1: {
|
||||
{
|
||||
ID: 7,
|
||||
Index: 6,
|
||||
RepoID: 1,
|
||||
State: structs.CommitStatusPending,
|
||||
SHA: "1234123412341234123412341234123412341234",
|
||||
TargetURL: "https://example.com/builds/",
|
||||
Description: "My awesome deploy service",
|
||||
ContextHash: "ae9547713a6665fc4261d0756904932085a41cf2",
|
||||
Context: "deploy/awesomeness",
|
||||
CreatorID: 2,
|
||||
},
|
||||
{
|
||||
ID: 4,
|
||||
Index: 4,
|
||||
State: structs.CommitStatusFailure,
|
||||
TargetURL: "https://example.com/builds/",
|
||||
Description: "My awesome CI-service",
|
||||
Context: "ci/awesomeness",
|
||||
CreatorID: 2,
|
||||
RepoID: 1,
|
||||
SHA: "1234123412341234123412341234123412341234",
|
||||
ContextHash: "c65f4d64a3b14a3eced0c9b36799e66e1bd5ced7",
|
||||
},
|
||||
{
|
||||
ID: 3,
|
||||
Index: 3,
|
||||
State: structs.CommitStatusSuccess,
|
||||
TargetURL: "https://example.com/coverage/",
|
||||
Description: "My awesome Coverage service",
|
||||
Context: "cov/awesomeness",
|
||||
CreatorID: 2,
|
||||
RepoID: 1,
|
||||
SHA: "1234123412341234123412341234123412341234",
|
||||
ContextHash: "3929ac7bccd3fa1bf9b38ddedb77973b1b9a8cfe",
|
||||
},
|
||||
},
|
||||
}, pairs)
|
||||
})
|
||||
t.Run("Repo 62", func(t *testing.T) {
|
||||
pairs, err := git_model.GetLatestCommitStatusForPairs(db.DefaultContext, []git_model.RepoSHA{{62, "774f93df12d14931ea93259ae93418da4482fcc1"}})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.EqualValues(t, map[int64][]*git_model.CommitStatus{
|
||||
62: {
|
||||
{
|
||||
ID: 8,
|
||||
Index: 2,
|
||||
RepoID: 62,
|
||||
State: structs.CommitStatusError,
|
||||
TargetURL: "/user2/test_workflows/actions",
|
||||
Description: "My awesome deploy service - v2",
|
||||
Context: "deploy/awesomeness",
|
||||
SHA: "774f93df12d14931ea93259ae93418da4482fcc1",
|
||||
ContextHash: "ae9547713a6665fc4261d0756904932085a41cf2",
|
||||
CreatorID: 2,
|
||||
},
|
||||
},
|
||||
}, pairs)
|
||||
})
|
||||
|
||||
t.Run("Repo 62 nonexistant sha", func(t *testing.T) {
|
||||
pairs, err := git_model.GetLatestCommitStatusForPairs(db.DefaultContext, []git_model.RepoSHA{{62, "774f93df12d14931ea93259ae93418da4482fcc"}})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.EqualValues(t, map[int64][]*git_model.CommitStatus{}, pairs)
|
||||
})
|
||||
|
||||
t.Run("SHA with non existant repo id", func(t *testing.T) {
|
||||
pairs, err := git_model.GetLatestCommitStatusForPairs(db.DefaultContext, []git_model.RepoSHA{{1, "774f93df12d14931ea93259ae93418da4482fcc1"}})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.EqualValues(t, map[int64][]*git_model.CommitStatus{}, pairs)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -75,26 +75,28 @@ func GetRepoAssignees(ctx context.Context, repo *Repository) (_ []*user_model.Us
|
|||
return nil, err
|
||||
}
|
||||
|
||||
additionalUserIDs := make([]int64, 0, 10)
|
||||
if err = e.Table("team_user").
|
||||
Join("INNER", "team_repo", "`team_repo`.team_id = `team_user`.team_id").
|
||||
Join("INNER", "team_unit", "`team_unit`.team_id = `team_user`.team_id").
|
||||
Where("`team_repo`.repo_id = ? AND (`team_unit`.access_mode >= ? OR (`team_unit`.access_mode = ? AND `team_unit`.`type` = ?))",
|
||||
repo.ID, perm.AccessModeWrite, perm.AccessModeRead, unit.TypePullRequests).
|
||||
Distinct("`team_user`.uid").
|
||||
Select("`team_user`.uid").
|
||||
Find(&additionalUserIDs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
uniqueUserIDs := make(container.Set[int64])
|
||||
uniqueUserIDs.AddMultiple(userIDs...)
|
||||
uniqueUserIDs.AddMultiple(additionalUserIDs...)
|
||||
|
||||
if repo.Owner.IsOrganization() {
|
||||
additionalUserIDs := make([]int64, 0, 10)
|
||||
if err = e.Table("team_user").
|
||||
Join("INNER", "team_repo", "`team_repo`.team_id = `team_user`.team_id").
|
||||
Join("INNER", "team_unit", "`team_unit`.team_id = `team_user`.team_id").
|
||||
Where("`team_repo`.repo_id = ? AND (`team_unit`.access_mode >= ? OR (`team_unit`.access_mode = ? AND `team_unit`.`type` = ?))",
|
||||
repo.ID, perm.AccessModeWrite, perm.AccessModeRead, unit.TypePullRequests).
|
||||
Distinct("`team_user`.uid").
|
||||
Select("`team_user`.uid").
|
||||
Find(&additionalUserIDs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
uniqueUserIDs.AddMultiple(additionalUserIDs...)
|
||||
}
|
||||
|
||||
// Leave a seat for owner itself to append later, but if owner is an organization
|
||||
// and just waste 1 unit is cheaper than re-allocate memory once.
|
||||
users := make([]*user_model.User, 0, len(uniqueUserIDs)+1)
|
||||
if len(userIDs) > 0 {
|
||||
if len(uniqueUserIDs) > 0 {
|
||||
if err = e.In("id", uniqueUserIDs.Values()).
|
||||
Where(builder.Eq{"`user`.is_active": true}).
|
||||
OrderBy(user_model.GetOrderByName()).
|
||||
|
|
|
@ -197,6 +197,12 @@ buttons.indent.tooltip = Sisennä yhden tason verran
|
|||
buttons.quote.tooltip = Lainaa tekstiä
|
||||
buttons.enable_monospace_font = Käytä tasalevyistä fonttia
|
||||
buttons.ref.tooltip = Viittaa ongelmaa tai vetopyyntöä
|
||||
buttons.new_table.tooltip = Lisää taulukko
|
||||
table_modal.header = Lisää taulukko
|
||||
table_modal.placeholder.header = Otsikko
|
||||
table_modal.placeholder.content = Sisältö
|
||||
table_modal.label.rows = Rivit
|
||||
table_modal.label.columns = Sarakkeet
|
||||
|
||||
[filter]
|
||||
string.asc = A - Ö
|
||||
|
@ -246,7 +252,7 @@ err_empty_db_path=SQLite3-tietokannan polku ei voi olla tyhjä.
|
|||
no_admin_and_disable_registration=Et voi kytkeä rekisteröintiä pois luomatta sitä ennen ylläpitotiliä.
|
||||
err_empty_admin_password=Ylläpitäjän salasana ei voi olla tyhjä.
|
||||
err_empty_admin_email=Ylläpitäjän sähköpostiosoite ei voi olla tyhjä.
|
||||
err_admin_name_is_reserved=Ylläpitäjän käyttäjätunnus on virheellinen: käyttäjätunnus on varattu
|
||||
err_admin_name_is_reserved=Ylläpitäjän käyttäjätunnus on virheellinen; käyttäjätunnus on varattu
|
||||
err_admin_name_is_invalid=Ylläpitäjän käyttäjätunnus on virheellinen
|
||||
|
||||
general_title=Yleiset asetukset
|
||||
|
@ -336,6 +342,7 @@ allow_dots_in_usernames = Salli pisteiden käyttö käyttäjänimissä. Ei vaiku
|
|||
enable_update_checker = Ota päivitystentarkistus käyttöön
|
||||
app_slogan = Instanssin tunnuslause
|
||||
app_slogan_helper = Syötä instanssin tunnuslause tähän. Jätä tyhjäksi poistaaksesi käytöstä.
|
||||
domain_helper = Palvelimen verkkotunnus tai isäntänimi.
|
||||
|
||||
[home]
|
||||
uname_holder=Käyttäjätunnus tai sähköpostiosoite
|
||||
|
@ -381,6 +388,7 @@ stars_few = %d tähteä
|
|||
relevant_repositories = Vain relevantit repositoriot näytetään, <a href="%s">näytä suodattamattomat tulokset</a>.
|
||||
forks_one = %d forkki
|
||||
forks_few = %d forkkia
|
||||
go_to = Siirry
|
||||
|
||||
[auth]
|
||||
create_new_account=Rekisteröi tili
|
||||
|
@ -418,7 +426,7 @@ twofa_scratch_token_incorrect=Kertakäyttökoodisi on virheellinen.
|
|||
login_userpass=Kirjaudu sisään
|
||||
tab_openid=OpenID
|
||||
oauth_signup_tab=Rekisteröi uusi tili
|
||||
oauth_signup_title=Viimeistele tili
|
||||
oauth_signup_title=Viimeistele uusi tili
|
||||
oauth_signup_submit=Viimeistele tili
|
||||
oauth_signin_tab=Linkitä olemassa olevaan tiliin
|
||||
oauth_signin_title=Kirjaudu sisään valtuuttaaksesi linkitetyn tilin
|
||||
|
@ -452,8 +460,8 @@ activate_account=Ole hyvä ja aktivoi tilisi
|
|||
|
||||
activate_email=Vahvista sähköpostiosoitteesi
|
||||
|
||||
register_notify=Tervetuloa %san
|
||||
register_notify.text_2=Voit nyt kirjautua käyttäjätunnuksella: %s.
|
||||
register_notify=Tervetuloa %s-palveluun
|
||||
register_notify.text_2=Voit nyt kirjautua tilillesi käyttäjätunnuksella: %s
|
||||
|
||||
reset_password=Palauta käyttäjätili
|
||||
reset_password.title=%s, olet pyytänyt tilisi palauttamista
|
||||
|
@ -478,6 +486,14 @@ password_change.text_1 = Tilisi salasana vaihdettiin juuri hetki sitten.
|
|||
removed_security_key.subject = Turva-avain on poistettu
|
||||
removed_security_key.text_1 = Turva-avain "%[1]s" on poistettu tililtäsi.
|
||||
team_invite.text_2 = Napsauta seuraavaa linkkiä liittyäksesi tiimiin:
|
||||
activate_account.text_1 = Hei <b>%[1]s</b>, kiitos kun rekisteröidyit palveluun %[2]s!
|
||||
activate_account.text_2 = Aktivoidaksesi tilin, napsauta alla olevaa linkkiä aikaikkunan <b>%s</b> sisällä:
|
||||
totp_disabled.subject = TOTP on poistettu käytöstä
|
||||
primary_mail_change.subject = Ensisijainen sähköpostiosoitteesi on vaihdettu
|
||||
admin.new_user.user_info = Käyttäjätiedot
|
||||
activate_email.text = Vahvista sähköpostiosoitteesi napsauttamalla linkkiä aikaikkunan <b>%s</b> sisällä:
|
||||
admin.new_user.subject = Uusi käyttäjä %s rekisteröityi juuri
|
||||
register_notify.text_3 = Jos joku muu teki tämän tilin puolestasi, <a href="%s">aseta salasana</a> ensin.
|
||||
|
||||
|
||||
|
||||
|
@ -486,13 +502,14 @@ yes=Kyllä
|
|||
no=Ei
|
||||
cancel=Peruuta
|
||||
modify=Päivitys
|
||||
confirm = Vahvista
|
||||
|
||||
[form]
|
||||
UserName=Käyttäjätunnus
|
||||
RepoName=Repon nimi
|
||||
Email=Sähköposti osoite
|
||||
Password=Salasana
|
||||
Retype=Varmista salasana
|
||||
Retype=Vahvista salasana
|
||||
SSHTitle=SSH avain nimi
|
||||
HttpsUrl=HTTPS-osoite
|
||||
TeamName=Tiimin nimi
|
||||
|
@ -762,7 +779,7 @@ orgs_none=Et ole minkään organisaation jäsen.
|
|||
|
||||
delete_account=Poista tilisi
|
||||
delete_prompt=Tämä toiminto poistaa käyttäjätilisi pysyvästi. Toimintoa <strong>EI VOI</strong> kumota.
|
||||
confirm_delete_account=Varmista poisto
|
||||
confirm_delete_account=Vahvista poisto
|
||||
delete_account_title=Poista käyttäjätili
|
||||
|
||||
email_notifications.enable=Ota käyttöön sähköposti-ilmoitukset
|
||||
|
@ -807,6 +824,7 @@ email_desc = Ensisijaista sähköpostiosoitettasi käytetään ilmoituksiin, sal
|
|||
tokens_desc = Nämä poletit mahdollistavat pääsyn tilillesi Forgejon rajapintaa vasten.
|
||||
keep_email_private_popup = Tämä piilottaa sähköpostiosoitteesi profiilistasi. Se ei ole enää oletus verkkosivukäyttöliittymän kautta tehdyissä kommiteissa, kuten tiedostojen lähetyksissä ja muokkauksissa, eikä sitä käytetä yhdistämiskommiteissa. Sen sijaan erikoisosoitetta %s voidaan käyttää kommittien liittämisessä tiliisi. Ota huomioon, ettei tämän asetuksen muuttaminen vaikuta olemassa oleviin kommitteihin.
|
||||
added_on = Lisätty %s
|
||||
additional_repo_units_hint = Ehdota repositorion lisäyksiköiden käyttöönottoa
|
||||
|
||||
[repo]
|
||||
owner=Omistaja
|
||||
|
@ -1801,6 +1819,13 @@ milestones.filter_sort.latest_due_date = Kaukaisin määräpäivä
|
|||
license_helper_desc = Lisenssi määrää, mitä muut voivat ja eivät voi tehdä koodillasi. Etkö ole varma, mikä lisenssi soveltuu projektillesi? Lue <a target="_blank" rel="noopener noreferrer" href="%s">ohje lisenssin valinnasta.</a>
|
||||
milestones.filter_sort.earliest_due_data = Lähin määräpäivä
|
||||
issues.filter_type.reviewed_by_you = Katselmoitu toimestasi
|
||||
settings.units.overview = Yleisnäkymä
|
||||
settings.remove_team_success = Tiimin pääsy repositorioon on poistettu.
|
||||
migrate.cancel_migrating_confirm = Haluatko perua tämän migraation?
|
||||
settings.units.units = Yksiköt
|
||||
settings.update_settings_no_unit = Repositorion tulisi sallia edes jonkinlainen vuorovaikutus.
|
||||
settings.units.add_more = Ota lisää käyttöön
|
||||
settings.add_team_success = Tiimillä on nyt pääsy repositorioon.
|
||||
|
||||
|
||||
|
||||
|
@ -1903,6 +1928,9 @@ code = Koodi
|
|||
teams.remove_all_repos_title = Poista kaikki tiimin repot
|
||||
form.name_reserved = Organisaation nimi "%s" on varattu.
|
||||
settings.delete_org_desc = Organisaatio poistetaan pysyvästi. Jatketaanko?
|
||||
team_access_desc = Repositorion käyttö
|
||||
teams.specific_repositories = Määritetyt repositoriot
|
||||
open_dashboard = Avaa kojelauta
|
||||
|
||||
[admin]
|
||||
dashboard=Kojelauta
|
||||
|
@ -2301,6 +2329,7 @@ error.extract_sign = Allekirjoituksen purkaminen epäonnistui
|
|||
default_key = Allekirjoitettu oletusavaimella
|
||||
|
||||
[units]
|
||||
unit = Yksikkö
|
||||
|
||||
[packages]
|
||||
title=Paketit
|
||||
|
@ -2397,6 +2426,10 @@ conda.install = Asenna paketti Condalla suorittamalla seuraava komento:
|
|||
helm.registry = Määritä tämä rekisteri komentoriviltä:
|
||||
pub.install = Asenna paketti Dartilla suorittamalla seuraava komento:
|
||||
owner.settings.cargo.title = Cargon rekisteri-indeksi
|
||||
settings.delete.description = Paketin poistaminen on peruuttamaton toimenpide, sitä ei voi perua.
|
||||
settings.link.success = Repositorion linkki päivitettiin onnistuneesti.
|
||||
settings.link.button = Päivitä repositorion linkki
|
||||
owner.settings.cleanuprules.preview.overview = %d pakettia on ajastettu poistettavaksi.
|
||||
|
||||
[secrets]
|
||||
creation.failed = Salaisuuden lisääminen epäonnistui.
|
||||
|
@ -2409,6 +2442,7 @@ deletion.failed = Salaisuuden poistaminen epäonnistui.
|
|||
secrets = Salaisuudet
|
||||
deletion.description = Salaisuuden poistaminen on pysyvä toimenpide, eikä sitä voi perua. Jatketaanko?
|
||||
deletion.success = Salaisuus on poistettu.
|
||||
description = Salaisuudet välitetään tietyille toimenpiteille, eikä niitä voi muuten lukea.
|
||||
|
||||
[actions]
|
||||
|
||||
|
@ -2496,6 +2530,8 @@ workflow.disable = Poista työnkulku käytöstä
|
|||
workflow.disable_success = Työnkulku "%s" on poistettu käytöstä.
|
||||
runs.no_job = Työnkulun tulee sisältää vähintään yksi työ
|
||||
runs.invalid_workflow_helper = Työnkulun asetustiedosto on virheellinen. Tarkista asetustiedosto: %s
|
||||
runners = Ajajat
|
||||
actions = Toimenpiteet
|
||||
|
||||
|
||||
|
||||
|
@ -2539,4 +2575,5 @@ issue_kind = Etsi ongelmia...
|
|||
milestone_kind = Etsi merkkipaaluja...
|
||||
pull_kind = Etsi pull-vetoja...
|
||||
commit_kind = Etsi kommitteja...
|
||||
fuzzy = Sumea
|
||||
fuzzy = Sumea
|
||||
runner_kind = Etsi ajajia...
|
|
@ -3195,6 +3195,11 @@ workflow.disabled = Робочий потік вимкнено.
|
|||
workflow.enable = Увімкнути робочий потік
|
||||
runs.no_workflows = Робочих потоків ще немає.
|
||||
runs.all_workflows = Усі робочі потоки
|
||||
runs.no_results = Не знайдено відповідних результатів.
|
||||
status.failure = Помилка
|
||||
status.running = Працює
|
||||
status.success = Успіх
|
||||
status.skipped = Пропущено
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1103,7 +1103,7 @@ object_format_helper=仓库的对象格式。之后无法更改。SHA1 是最兼
|
|||
readme=自述
|
||||
readme_helper=选择自述文件模板
|
||||
readme_helper_desc=这是您可以为您的项目撰写完整描述的地方。
|
||||
auto_init=初始化仓库(添加. gitignore、许可证和自述文件)
|
||||
auto_init=初始化仓库(添加 .gitignore、许可证和自述文件)
|
||||
trust_model_helper=选择签名验证的“信任模型”。可能的选项是:
|
||||
trust_model_helper_collaborator=协作者:信任协作者的签名
|
||||
trust_model_helper_committer=提交者:信任与提交者相符的签名
|
||||
|
|
1
release-notes/5988.md
Normal file
1
release-notes/5988.md
Normal file
|
@ -0,0 +1 @@
|
|||
fix: [commit](https://codeberg.org/forgejo/forgejo/commit/fc26becba4b08877a726f2e7e453992310245fe5) when a tag was removed and a release existed for that tag, it would be broken. The release is no longer broken the tag can be added again.
|
|
@ -347,12 +347,30 @@ func loadOrCreateAsymmetricKey() (any, error) {
|
|||
key, err := func() (any, error) {
|
||||
switch {
|
||||
case strings.HasPrefix(setting.OAuth2.JWTSigningAlgorithm, "RS"):
|
||||
return rsa.GenerateKey(rand.Reader, 4096)
|
||||
var bits int
|
||||
switch setting.OAuth2.JWTSigningAlgorithm {
|
||||
case "RS256":
|
||||
bits = 2048
|
||||
case "RS384":
|
||||
bits = 3072
|
||||
case "RS512":
|
||||
bits = 4096
|
||||
}
|
||||
return rsa.GenerateKey(rand.Reader, bits)
|
||||
case setting.OAuth2.JWTSigningAlgorithm == "EdDSA":
|
||||
_, pk, err := ed25519.GenerateKey(rand.Reader)
|
||||
return pk, err
|
||||
default:
|
||||
return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
var curve elliptic.Curve
|
||||
switch setting.OAuth2.JWTSigningAlgorithm {
|
||||
case "ES256":
|
||||
curve = elliptic.P256()
|
||||
case "ES384":
|
||||
curve = elliptic.P384()
|
||||
case "ES512":
|
||||
curve = elliptic.P521()
|
||||
}
|
||||
return ecdsa.GenerateKey(curve, rand.Reader)
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
|
|
116
services/auth/source/oauth2/jwtsigningkey_test.go
Normal file
116
services/auth/source/oauth2/jwtsigningkey_test.go
Normal file
|
@ -0,0 +1,116 @@
|
|||
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package oauth2
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestLoadOrCreateAsymmetricKey(t *testing.T) {
|
||||
loadKey := func(t *testing.T) any {
|
||||
t.Helper()
|
||||
loadOrCreateAsymmetricKey()
|
||||
|
||||
fileContent, err := os.ReadFile(setting.OAuth2.JWTSigningPrivateKeyFile)
|
||||
require.NoError(t, err)
|
||||
|
||||
block, _ := pem.Decode(fileContent)
|
||||
assert.NotNil(t, block)
|
||||
assert.EqualValues(t, "PRIVATE KEY", block.Type)
|
||||
|
||||
parsedKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
||||
require.NoError(t, err)
|
||||
|
||||
return parsedKey
|
||||
}
|
||||
t.Run("RSA-2048", func(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.OAuth2.JWTSigningPrivateKeyFile, filepath.Join(t.TempDir(), "jwt-rsa-2048.priv"))()
|
||||
defer test.MockVariableValue(&setting.OAuth2.JWTSigningAlgorithm, "RS256")()
|
||||
|
||||
parsedKey := loadKey(t)
|
||||
|
||||
rsaPrivateKey := parsedKey.(*rsa.PrivateKey)
|
||||
assert.EqualValues(t, 2048, rsaPrivateKey.N.BitLen())
|
||||
|
||||
t.Run("Load key with differ specified algorithm", func(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.OAuth2.JWTSigningAlgorithm, "EdDSA")()
|
||||
|
||||
parsedKey := loadKey(t)
|
||||
rsaPrivateKey := parsedKey.(*rsa.PrivateKey)
|
||||
assert.EqualValues(t, 2048, rsaPrivateKey.N.BitLen())
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("RSA-3072", func(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.OAuth2.JWTSigningPrivateKeyFile, filepath.Join(t.TempDir(), "jwt-rsa-3072.priv"))()
|
||||
defer test.MockVariableValue(&setting.OAuth2.JWTSigningAlgorithm, "RS384")()
|
||||
|
||||
parsedKey := loadKey(t)
|
||||
|
||||
rsaPrivateKey := parsedKey.(*rsa.PrivateKey)
|
||||
assert.EqualValues(t, 3072, rsaPrivateKey.N.BitLen())
|
||||
})
|
||||
|
||||
t.Run("RSA-4096", func(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.OAuth2.JWTSigningPrivateKeyFile, filepath.Join(t.TempDir(), "jwt-rsa-4096.priv"))()
|
||||
defer test.MockVariableValue(&setting.OAuth2.JWTSigningAlgorithm, "RS512")()
|
||||
|
||||
parsedKey := loadKey(t)
|
||||
|
||||
rsaPrivateKey := parsedKey.(*rsa.PrivateKey)
|
||||
assert.EqualValues(t, 4096, rsaPrivateKey.N.BitLen())
|
||||
})
|
||||
|
||||
t.Run("ECDSA-256", func(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.OAuth2.JWTSigningPrivateKeyFile, filepath.Join(t.TempDir(), "jwt-ecdsa-256.priv"))()
|
||||
defer test.MockVariableValue(&setting.OAuth2.JWTSigningAlgorithm, "ES256")()
|
||||
|
||||
parsedKey := loadKey(t)
|
||||
|
||||
ecdsaPrivateKey := parsedKey.(*ecdsa.PrivateKey)
|
||||
assert.EqualValues(t, 256, ecdsaPrivateKey.Params().BitSize)
|
||||
})
|
||||
|
||||
t.Run("ECDSA-384", func(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.OAuth2.JWTSigningPrivateKeyFile, filepath.Join(t.TempDir(), "jwt-ecdsa-384.priv"))()
|
||||
defer test.MockVariableValue(&setting.OAuth2.JWTSigningAlgorithm, "ES384")()
|
||||
|
||||
parsedKey := loadKey(t)
|
||||
|
||||
ecdsaPrivateKey := parsedKey.(*ecdsa.PrivateKey)
|
||||
assert.EqualValues(t, 384, ecdsaPrivateKey.Params().BitSize)
|
||||
})
|
||||
|
||||
t.Run("ECDSA-512", func(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.OAuth2.JWTSigningPrivateKeyFile, filepath.Join(t.TempDir(), "jwt-ecdsa-512.priv"))()
|
||||
defer test.MockVariableValue(&setting.OAuth2.JWTSigningAlgorithm, "ES512")()
|
||||
|
||||
parsedKey := loadKey(t)
|
||||
|
||||
ecdsaPrivateKey := parsedKey.(*ecdsa.PrivateKey)
|
||||
assert.EqualValues(t, 521, ecdsaPrivateKey.Params().BitSize)
|
||||
})
|
||||
|
||||
t.Run("EdDSA", func(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.OAuth2.JWTSigningPrivateKeyFile, filepath.Join(t.TempDir(), "jwt-eddsa.priv"))()
|
||||
defer test.MockVariableValue(&setting.OAuth2.JWTSigningAlgorithm, "EdDSA")()
|
||||
|
||||
parsedKey := loadKey(t)
|
||||
|
||||
assert.NotNil(t, parsedKey.(ed25519.PrivateKey))
|
||||
})
|
||||
}
|
|
@ -307,9 +307,10 @@ func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo
|
|||
}
|
||||
|
||||
releases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{
|
||||
RepoID: repo.ID,
|
||||
TagNames: tags,
|
||||
IncludeTags: true,
|
||||
RepoID: repo.ID,
|
||||
TagNames: tags,
|
||||
IncludeDrafts: true,
|
||||
IncludeTags: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("db.Find[repo_model.Release]: %w", err)
|
||||
|
@ -394,13 +395,17 @@ func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo
|
|||
}
|
||||
newReleases = append(newReleases, rel)
|
||||
} else {
|
||||
rel.Title = parts[0]
|
||||
rel.Note = note
|
||||
rel.Sha1 = commit.ID.String()
|
||||
rel.CreatedUnix = timeutil.TimeStamp(createdAt.Unix())
|
||||
rel.NumCommits = commitsCount
|
||||
if rel.IsTag && author != nil {
|
||||
rel.PublisherID = author.ID
|
||||
if rel.IsTag {
|
||||
rel.Title = parts[0]
|
||||
rel.Note = note
|
||||
if author != nil {
|
||||
rel.PublisherID = author.ID
|
||||
}
|
||||
} else {
|
||||
rel.IsDraft = false
|
||||
}
|
||||
if err = repo_model.UpdateRelease(ctx, rel); err != nil {
|
||||
return fmt.Errorf("Update: %w", err)
|
||||
|
|
|
@ -5,17 +5,20 @@
|
|||
package integration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/services/release"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
|
@ -159,3 +162,47 @@ func TestSyncRepoTags(t *testing.T) {
|
|||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestRepushTag(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||
session := loginUser(t, owner.LowerName)
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||
|
||||
httpContext := NewAPITestContext(t, owner.Name, repo.Name)
|
||||
|
||||
dstPath := t.TempDir()
|
||||
|
||||
u.Path = httpContext.GitPath()
|
||||
u.User = url.UserPassword(owner.Name, userPassword)
|
||||
|
||||
doGitClone(dstPath, u)(t)
|
||||
|
||||
// create and push a tag
|
||||
_, _, err := git.NewCommand(git.DefaultContext, "tag", "v2.0").RunStdString(&git.RunOpts{Dir: dstPath})
|
||||
require.NoError(t, err)
|
||||
_, _, err = git.NewCommand(git.DefaultContext, "push", "origin", "--tags", "v2.0").RunStdString(&git.RunOpts{Dir: dstPath})
|
||||
require.NoError(t, err)
|
||||
// create a release for the tag
|
||||
createdRelease := createNewReleaseUsingAPI(t, token, owner, repo, "v2.0", "", "Release of v2.0", "desc")
|
||||
assert.False(t, createdRelease.IsDraft)
|
||||
// delete the tag
|
||||
_, _, err = git.NewCommand(git.DefaultContext, "push", "origin", "--delete", "v2.0").RunStdString(&git.RunOpts{Dir: dstPath})
|
||||
require.NoError(t, err)
|
||||
// query the release by API and it should be a draft
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s", owner.Name, repo.Name, "v2.0"))
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
var respRelease *api.Release
|
||||
DecodeJSON(t, resp, &respRelease)
|
||||
assert.True(t, respRelease.IsDraft)
|
||||
// re-push the tag
|
||||
_, _, err = git.NewCommand(git.DefaultContext, "push", "origin", "--tags", "v2.0").RunStdString(&git.RunOpts{Dir: dstPath})
|
||||
require.NoError(t, err)
|
||||
// query the release by API and it should not be a draft
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s", owner.Name, repo.Name, "v2.0"))
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, resp, &respRelease)
|
||||
assert.False(t, respRelease.IsDraft)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -56,10 +56,21 @@ export async function renderMermaid() {
|
|||
btn.setAttribute('data-clipboard-text', source);
|
||||
mermaidBlock.append(btn);
|
||||
|
||||
const updateIframeHeight = () => {
|
||||
iframe.style.height = `${iframe.contentWindow.document.body.clientHeight}px`;
|
||||
};
|
||||
|
||||
// update height when element's visibility state changes, for example when the diagram is inside
|
||||
// a <details> + <summary> block and the <details> block becomes visible upon user interaction, it
|
||||
// would initially set a incorrect height and the correct height is set during this callback.
|
||||
(new IntersectionObserver(() => {
|
||||
updateIframeHeight();
|
||||
}, {root: document.documentElement})).observe(iframe);
|
||||
|
||||
iframe.addEventListener('load', () => {
|
||||
pre.replaceWith(mermaidBlock);
|
||||
mermaidBlock.classList.remove('tw-hidden');
|
||||
iframe.style.height = `${iframe.contentWindow.document.body.clientHeight}px`;
|
||||
updateIframeHeight();
|
||||
setTimeout(() => { // avoid flash of iframe background
|
||||
mermaidBlock.classList.remove('is-loading');
|
||||
iframe.classList.remove('tw-invisible');
|
||||
|
|
Loading…
Reference in a new issue