Merge branch 'main' into lunny/some_refactors

This commit is contained in:
Lunny Xiao
2026-01-12 12:23:52 -08:00
committed by GitHub
21 changed files with 384 additions and 130 deletions
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
- run: make generate-gitignore
timeout-minutes: 40
- name: push translations to repo
uses: appleboy/git-push-action@v1.0.0
uses: appleboy/git-push-action@v1.2.0
with:
author_email: "teabot@gitea.io"
author_name: GiteaBot
+1 -1
View File
@@ -29,7 +29,7 @@ jobs:
- name: update locales
run: ./build/update-locales.sh
- name: push translations to repo
uses: appleboy/git-push-action@v1.0.0
uses: appleboy/git-push-action@v1.2.0
with:
author_email: "teabot@gitea.io"
author_name: GiteaBot
+4 -4
View File
@@ -101,10 +101,10 @@ func GetLFSLock(ctx context.Context, repo *repo_model.Repository, path string) (
return rel, nil
}
// GetLFSLockByID returns release by given id.
func GetLFSLockByID(ctx context.Context, id int64) (*LFSLock, error) {
// GetLFSLockByIDAndRepo returns lfs lock by given id and repository id.
func GetLFSLockByIDAndRepo(ctx context.Context, id, repoID int64) (*LFSLock, error) {
lock := new(LFSLock)
has, err := db.GetEngine(ctx).ID(id).Get(lock)
has, err := db.GetEngine(ctx).ID(id).And("repo_id = ?", repoID).Get(lock)
if err != nil {
return nil, err
} else if !has {
@@ -153,7 +153,7 @@ func CountLFSLockByRepoID(ctx context.Context, repoID int64) (int64, error) {
// DeleteLFSLockByID deletes a lock by given ID.
func DeleteLFSLockByID(ctx context.Context, id int64, repo *repo_model.Repository, u *user_model.User, force bool) (*LFSLock, error) {
return db.WithTx2(ctx, func(ctx context.Context) (*LFSLock, error) {
lock, err := GetLFSLockByID(ctx, id)
lock, err := GetLFSLockByIDAndRepo(ctx, id, repo.ID)
if err != nil {
return nil, err
}
+82
View File
@@ -0,0 +1,82 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package git
import (
"fmt"
"testing"
"time"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func createTestLock(t *testing.T, repo *repo_model.Repository, owner *user_model.User) *LFSLock {
t.Helper()
path := fmt.Sprintf("%s-%d-%d", t.Name(), repo.ID, time.Now().UnixNano())
lock, err := CreateLFSLock(t.Context(), repo, &LFSLock{
OwnerID: owner.ID,
Path: path,
})
require.NoError(t, err)
return lock
}
func TestGetLFSLockByIDAndRepo(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
lockRepo1 := createTestLock(t, repo1, user2)
lockRepo3 := createTestLock(t, repo3, user4)
fetched, err := GetLFSLockByIDAndRepo(t.Context(), lockRepo1.ID, repo1.ID)
require.NoError(t, err)
assert.Equal(t, lockRepo1.ID, fetched.ID)
assert.Equal(t, repo1.ID, fetched.RepoID)
_, err = GetLFSLockByIDAndRepo(t.Context(), lockRepo1.ID, repo3.ID)
assert.Error(t, err)
assert.True(t, IsErrLFSLockNotExist(err))
_, err = GetLFSLockByIDAndRepo(t.Context(), lockRepo3.ID, repo1.ID)
assert.Error(t, err)
assert.True(t, IsErrLFSLockNotExist(err))
}
func TestDeleteLFSLockByIDRequiresRepoMatch(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
lockRepo1 := createTestLock(t, repo1, user2)
lockRepo3 := createTestLock(t, repo3, user4)
_, err := DeleteLFSLockByID(t.Context(), lockRepo3.ID, repo1, user2, true)
assert.Error(t, err)
assert.True(t, IsErrLFSLockNotExist(err))
existing, err := GetLFSLockByIDAndRepo(t.Context(), lockRepo3.ID, repo3.ID)
require.NoError(t, err)
assert.Equal(t, lockRepo3.ID, existing.ID)
deleted, err := DeleteLFSLockByID(t.Context(), lockRepo3.ID, repo3, user4, true)
require.NoError(t, err)
assert.Equal(t, lockRepo3.ID, deleted.ID)
deleted, err = DeleteLFSLockByID(t.Context(), lockRepo1.ID, repo1, user2, false)
require.NoError(t, err)
assert.Equal(t, lockRepo1.ID, deleted.ID)
}
+2 -2
View File
@@ -46,8 +46,8 @@ func parseLsTreeLine(line []byte) (*LsTreeEntry, error) {
entry.Size = optional.Some(size)
}
entry.EntryMode, err = ParseEntryMode(string(entryMode))
if err != nil || entry.EntryMode == EntryModeNoEntry {
entry.EntryMode = ParseEntryMode(string(entryMode))
if entry.EntryMode == EntryModeNoEntry {
return nil, fmt.Errorf("invalid ls-tree output (invalid mode): %q, err: %w", line, err)
}
+26 -10
View File
@@ -4,7 +4,6 @@
package git
import (
"fmt"
"strconv"
)
@@ -55,21 +54,38 @@ func (e EntryMode) IsExecutable() bool {
return e == EntryModeExec
}
func ParseEntryMode(mode string) (EntryMode, error) {
func ParseEntryMode(mode string) EntryMode {
switch mode {
case "000000":
return EntryModeNoEntry, nil
return EntryModeNoEntry
case "100644":
return EntryModeBlob, nil
return EntryModeBlob
case "100755":
return EntryModeExec, nil
return EntryModeExec
case "120000":
return EntryModeSymlink, nil
return EntryModeSymlink
case "160000":
return EntryModeCommit, nil
case "040000", "040755": // git uses 040000 for tree object, but some users may get 040755 for unknown reasons
return EntryModeTree, nil
return EntryModeCommit
case "040000":
return EntryModeTree
default:
return 0, fmt.Errorf("unparsable entry mode: %s", mode)
// git uses 040000 for tree object, but some users may get 040755 from non-standard git implementations
m, _ := strconv.ParseInt(mode, 8, 32)
modeInt := EntryMode(m)
switch modeInt & 0o770000 {
case 0o040000:
return EntryModeTree
case 0o160000:
return EntryModeCommit
case 0o120000:
return EntryModeSymlink
case 0o100000:
if modeInt&0o777 == 0o755 {
return EntryModeExec
}
return EntryModeBlob
default:
return EntryModeNoEntry
}
}
}
+27
View File
@@ -27,3 +27,30 @@ func TestEntriesCustomSort(t *testing.T) {
entries.CustomSort(strings.Compare)
assert.Equal(t, expected, entries)
}
func TestParseEntryMode(t *testing.T) {
tests := []struct {
modeStr string
expectMod EntryMode
}{
{"000000", EntryModeNoEntry},
{"000755", EntryModeNoEntry},
{"100644", EntryModeBlob},
{"100755", EntryModeExec},
{"120000", EntryModeSymlink},
{"120755", EntryModeSymlink},
{"160000", EntryModeCommit},
{"160755", EntryModeCommit},
{"040000", EntryModeTree},
{"040755", EntryModeTree},
{"777777", EntryModeNoEntry}, // invalid mode
}
for _, test := range tests {
mod := ParseEntryMode(test.modeStr)
assert.Equal(t, test.expectMod, mod, "modeStr: %s", test.modeStr)
}
}
+5 -5
View File
@@ -2542,8 +2542,8 @@
"repo.diff.too_many_files": "Some files were not shown because too many files have changed in this diff",
"repo.diff.show_more": "Show More",
"repo.diff.load": "Load Diff",
"repo.diff.generated": "generated",
"repo.diff.vendored": "vendored",
"repo.diff.generated": "Generated",
"repo.diff.vendored": "Vendored",
"repo.diff.comment.add_line_comment": "Add line comment",
"repo.diff.comment.placeholder": "Leave a comment",
"repo.diff.comment.add_single_comment": "Add single comment",
@@ -3724,8 +3724,8 @@
"projects.exit_fullscreen": "Exit Fullscreen",
"git.filemode.changed_filemode": "%[1]s → %[2]s",
"git.filemode.directory": "Directory",
"git.filemode.normal_file": "Normal file",
"git.filemode.executable_file": "Executable file",
"git.filemode.symbolic_link": "Symbolic link",
"git.filemode.normal_file": "Regular",
"git.filemode.executable_file": "Executable",
"git.filemode.symbolic_link": "Symlink",
"git.filemode.submodule": "Submodule"
}
+31 -1
View File
@@ -32,6 +32,7 @@
"password": "Pasfhocal",
"access_token": "Comhartha Rochtana",
"re_type": "Deimhnigh Pasfhocal",
"captcha": "CAPTCHA",
"twofa": "Fíordheimhniú Dhá-Fhachtóir",
"twofa_scratch": "Cód Scratch Dhá-Fhachtóra",
"passcode": "Paschód",
@@ -132,6 +133,7 @@
"confirm_delete_selected": "Deimhnigh chun gach earra roghnaithe a scriosadh?",
"name": "Ainm",
"value": "Luach",
"readme": "Léigh-mé",
"filter_title": "Scagaire",
"filter.clear": "Scagaire Soiléir",
"filter.is_archived": "Cartlannaithe",
@@ -229,6 +231,7 @@
"install.db_name": "Ainm Bunachar Sonraí",
"install.db_schema": "Scéim",
"install.db_schema_helper": "Fág bán le haghaidh réamhshocraithe bunachar sonraí (\"poiblí\").",
"install.ssl_mode": "SSL",
"install.path": "Cosán",
"install.sqlite_helper": "Conair comhad don bhunachar sonraí SQLite3. Cuir <br>isteach cosán iomlán má reáchtáil tú Gitea mar sheirbhís.",
"install.reinstall_error": "Tá tú ag iarraidh a shuiteáil i mbunachar sonraí Gitea atá ann cheana",
@@ -406,6 +409,7 @@
"auth.twofa_scratch_token_incorrect": "Tá do chód scratch mícheart.",
"auth.twofa_required": "Ní mór duit fíordheimhniú dhá fhachtóir a shocrú chun rochtain a fháil ar stórtha, nó iarracht a dhéanamh logáil isteach arís.",
"auth.login_userpass": "Sínigh isteach",
"auth.login_openid": "OpenID",
"auth.oauth_signup_tab": "Cláraigh Cuntas Nua",
"auth.oauth_signup_title": "Comhlánaigh Cuntas Nua",
"auth.oauth_signup_submit": "Cuntas Comhlánaigh",
@@ -654,6 +658,7 @@
"settings.twofa": "Fíordheimhniú Dhá Fachtóir (TOTP)",
"settings.account_link": "Cuntais Nasctha",
"settings.organization": "Eagraíochtaí",
"settings.uid": "UID",
"settings.webauthn": "Fíordheimhniú Dhá-Fachtóir (Eochracha Slándála)",
"settings.public_profile": "Próifíl Phoiblí",
"settings.biography_placeholder": "Inis dúinn beagán fút féin! (Is féidir leat Markdown a úsáid)",
@@ -991,6 +996,7 @@
"repo.multiple_licenses": "Ceadúnais Iolracha",
"repo.object_format": "Formáid Oibiacht",
"repo.object_format_helper": "Formáid oibiacht an stór. Ní féidir é a athrú níos déanaí. Is é SHA1 an comhoiriúnacht is fearr.",
"repo.readme": "LÉIGHMÉ",
"repo.readme_helper": "Roghnaigh comhad teimpléad README.",
"repo.readme_helper_desc": "Seo an áit inar féidir leat cur síos iomlán a scríobh do thionscadal.",
"repo.auto_init": "Taisce a thionscnamh (Cuireann sé .gitignore, Ceadúnas agus README)",
@@ -1055,6 +1061,7 @@
"repo.desc.template": "Teimpléad",
"repo.desc.internal": "Inmheánach",
"repo.desc.archived": "Cartlannaithe",
"repo.desc.sha256": "SHA256",
"repo.template.items": "Míreanna Teimpléad",
"repo.template.git_content": "Ábhar Git (Brainse Réamhshocraithe)",
"repo.template.git_hooks": "Crúcanna Git",
@@ -1083,6 +1090,7 @@
"repo.migrate_options_lfs_endpoint.description.local": "Tacaítear le cosán freastalaí áitiúil freisin.",
"repo.migrate_options_lfs_endpoint.placeholder": "Mura bhfágtar bán é, díorthófar an críochphointe ón URL clónála.",
"repo.migrate_items": "Míreanna Imirce",
"repo.migrate_items_wiki": "Vicí",
"repo.migrate_items_milestones": "Clocha míle",
"repo.migrate_items_labels": "Lipéid",
"repo.migrate_items_issues": "Saincheisteanna",
@@ -1728,8 +1736,11 @@
"repo.issues.reference_link": "Tagairt: %s",
"repo.compare.compare_base": "bonn",
"repo.compare.compare_head": "déan comparáid",
"repo.compare.title": "Athruithe a chur i gcomparáid",
"repo.compare.description": "Roghnaigh dhá bhrainse nó clib chun a fheiceáil cad atá athraithe nó chun iarratas tarraingthe nua a thosú.",
"repo.pulls.desc": "Cumasaigh iarratais tarraingthe agus athbhreithnithe cód.",
"repo.pulls.new": "Iarratas Tarraingthe Nua",
"repo.pulls.new.description": "Pléigh agus athbhreithnigh na hathruithe sa chomparáid seo le daoine eile.",
"repo.pulls.new.blocked_user": "Ní féidir iarratas tarraingthe a chruthú toisc go bhfuil úinéir an stórais bac ort.",
"repo.pulls.new.must_collaborator": "Caithfidh tú a bheith ina chomhoibritheoir chun iarratas tarraingthe a chruthú.",
"repo.pulls.new.already_existed": "Tá iarratas tarraingthe idir na brainsí seo ann cheana féin",
@@ -1739,7 +1750,6 @@
"repo.pulls.allow_edits_from_maintainers": "Ceadaigh eagarthóirí ó chothabhálaí",
"repo.pulls.allow_edits_from_maintainers_desc": "Is féidir le húsáideoirí a bhfuil rochtain scríofa acu ar an mbunbhrainse brú chuig an bhrainse",
"repo.pulls.allow_edits_from_maintainers_err": "Theip ar nuashonrú",
"repo.pulls.compare_changes_desc": "Roghnaigh an brainse le cumasc isteach agus an brainse le tarraingt uaidh.",
"repo.pulls.has_viewed_file": "Breathnaithe",
"repo.pulls.has_changed_since_last_review": "Athraithe ó d'athbhreithniú deire",
"repo.pulls.viewed_files_label": "Breathnaíodh ar %[1]d / %[2]d comhaid",
@@ -2313,8 +2323,19 @@
"repo.settings.slack_domain": "Fearann",
"repo.settings.slack_channel": "Cainéal",
"repo.settings.add_web_hook_desc": "Comhtháthaigh <a target=\"_blank\" rel=\"noreferrer\" href=\"%s\">%s</a> isteach i do stóras.",
"repo.settings.web_hook_name_gitea": "Gitea",
"repo.settings.web_hook_name_gogs": "Gogs",
"repo.settings.web_hook_name_slack": "Slack",
"repo.settings.web_hook_name_discord": "Discord",
"repo.settings.web_hook_name_dingtalk": "DingTalk",
"repo.settings.web_hook_name_telegram": "Teileagram",
"repo.settings.web_hook_name_matrix": "Maitrís",
"repo.settings.web_hook_name_msteams": "Microsoft Teams",
"repo.settings.web_hook_name_feishu_or_larksuite": "Feishu / Lark Suite",
"repo.settings.web_hook_name_feishu": "Feishu",
"repo.settings.web_hook_name_larksuite": "Lark Suite",
"repo.settings.web_hook_name_wechatwork": "WeCom (Wechat Work)",
"repo.settings.web_hook_name_packagist": "Packagist",
"repo.settings.packagist_username": "Ainm úsáideora Pacagist",
"repo.settings.packagist_api_token": "Comhartha API",
"repo.settings.packagist_package_url": "URL pacáiste Packagist",
@@ -2460,6 +2481,7 @@
"repo.settings.unarchive.success": "Rinneadh an stóras a dhíchartlann go rathúil.",
"repo.settings.unarchive.error": "Tharla earráid agus tú ag iarraidh an stóras a dhíchartlannú. Féach an logáil le haghaidh tuilleadh sonraí.",
"repo.settings.update_avatar_success": "Nuashonraíodh avatar an stóras.",
"repo.settings.lfs": "LFS",
"repo.settings.lfs_filelist": "Comhaid LFS a stóráiltear sa stóras seo",
"repo.settings.lfs_no_lfs_files": "Níl aon chomhaid LFS stóráilte sa stóras seo",
"repo.settings.lfs_findcommits": "Aimsigh gealltanais",
@@ -2479,6 +2501,7 @@
"repo.settings.lfs_force_unlock": "Díghlasáil Fórsa",
"repo.settings.lfs_pointers.found": "Fuarthas %d pointeoir(í) bloba — %d gaolmhar, %d neamhghaolmhar (%d ar iarraidh ón stóras)",
"repo.settings.lfs_pointers.sha": "SHA Blob",
"repo.settings.lfs_pointers.oid": "OID",
"repo.settings.lfs_pointers.inRepo": "I Stóras",
"repo.settings.lfs_pointers.exists": "Ann sa siopa",
"repo.settings.lfs_pointers.accessible": "Inrochtana don Úsáideoir",
@@ -2844,6 +2867,7 @@
"admin.dashboard.task.finished": "Tasc: Tá %[1]s tosaithe ag %[2]s críochnaithe",
"admin.dashboard.task.unknown": "Tasc anaithnid: %[1]s",
"admin.dashboard.cron.started": "Cron tosaithe: %[1]s",
"admin.dashboard.cron.process": "Cron: %[1]s",
"admin.dashboard.cron.cancelled": "Cron: %[1]s cealaithe: %[3]s",
"admin.dashboard.cron.error": "Earráid i gCron: %s: %[3]s",
"admin.dashboard.cron.finished": "Cron: %[1]s críochnaithe",
@@ -2923,6 +2947,7 @@
"admin.users.reserved": "In áirithe",
"admin.users.bot": "Bota",
"admin.users.remote": "Iargúlta",
"admin.users.2fa": "2FA",
"admin.users.repos": "Stórais",
"admin.users.created": "Cruthaithe",
"admin.users.last_login": "Sínigh Isteach Deiridh",
@@ -3044,6 +3069,7 @@
"admin.auths.attribute_mail": "Tréith ríomhphoist",
"admin.auths.attribute_ssh_public_key": "Tréith Eochair SSH Phoiblí",
"admin.auths.attribute_avatar": "Tréith Avatar",
"admin.auths.ssh_keys_are_verified": "Meastar gur fíoraithe iad eochracha SSH in LDAP",
"admin.auths.attributes_in_bind": "Faigh tréithe i gComhthéacs Bind DN",
"admin.auths.allow_deactivate_all": "Lig do thoradh cuardaigh folamh gach úsáideoir a dhíghníomhachtú",
"admin.auths.use_paged_search": "Úsáid Cuardach Leathanaigh",
@@ -3177,6 +3203,7 @@
"admin.config.db_name": "Ainm",
"admin.config.db_user": "Ainm úsáideora",
"admin.config.db_schema": "Scéim",
"admin.config.db_ssl_mode": "SSL",
"admin.config.db_path": "Cosán",
"admin.config.service_config": "Cumraíocht Seirbhíse",
"admin.config.register_email_confirm": "Deimhniú Ríomhphost a éileamh chun Clárú",
@@ -3430,6 +3457,7 @@
"packages.assets": "Sócmhainní",
"packages.versions": "Leaganacha",
"packages.versions.view_all": "Féach ar gach",
"packages.dependency.id": "ID",
"packages.dependency.version": "Leagan",
"packages.search_in_external_registry": "Cuardaigh i %s",
"packages.alpine.registry": "Socraigh an clárlann seo tríd an URL a chur i do chomhad <code>/etc/apk/repositories</code>:",
@@ -3594,6 +3622,7 @@
"actions.runners.new": "Cruthaigh reathaí nua",
"actions.runners.new_notice": "Conas reathaí a thosú",
"actions.runners.status": "Stádas",
"actions.runners.id": "ID",
"actions.runners.name": "Ainm",
"actions.runners.owner_type": "Cineál",
"actions.runners.description": "Cur síos",
@@ -3693,6 +3722,7 @@
"projects.type-3.display_name": "Tionscadal Eagrúcháin",
"projects.enter_fullscreen": "Lánscáileán",
"projects.exit_fullscreen": "Scoir Lánscáileáin",
"git.filemode.changed_filemode": "%[1]s → %[2]s",
"git.filemode.directory": "Eolaire",
"git.filemode.normal_file": "Comhad gnáth",
"git.filemode.executable_file": "Comhad infheidhmithe",
@@ -398,7 +398,6 @@ func DeleteReleaseAttachment(ctx *context.APIContext) {
ctx.APIErrorNotFound()
return
}
// FIXME Should prove the existence of the given repo, but results in unnecessary database requests
if err := repo_model.DeleteAttachment(ctx, attach, true); err != nil {
ctx.APIErrorInternal(err)
+38 -6
View File
@@ -4,11 +4,12 @@
package repo
import (
"fmt"
"net/http"
issues_model "code.gitea.io/gitea/models/issues"
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/httpcache"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
@@ -40,7 +41,7 @@ func uploadAttachment(ctx *context.Context, repoID int64, allowedTypes string) {
file, header, err := ctx.Req.FormFile("file")
if err != nil {
ctx.HTTPError(http.StatusInternalServerError, fmt.Sprintf("FormFile: %v", err))
ctx.ServerError("FormFile", err)
return
}
defer file.Close()
@@ -56,7 +57,7 @@ func uploadAttachment(ctx *context.Context, repoID int64, allowedTypes string) {
ctx.HTTPError(http.StatusBadRequest, err.Error())
return
}
ctx.HTTPError(http.StatusInternalServerError, fmt.Sprintf("NewAttachment: %v", err))
ctx.ServerError("UploadAttachmentGeneralSizeLimit", err)
return
}
@@ -74,13 +75,44 @@ func DeleteAttachment(ctx *context.Context) {
ctx.HTTPError(http.StatusBadRequest, err.Error())
return
}
if !ctx.IsSigned || (ctx.Doer.ID != attach.UploaderID) {
if !ctx.IsSigned {
ctx.HTTPError(http.StatusForbidden)
return
}
if attach.RepoID != ctx.Repo.Repository.ID {
ctx.HTTPError(http.StatusBadRequest, "attachment does not belong to this repository")
return
}
if ctx.Doer.ID != attach.UploaderID {
if attach.IssueID > 0 {
issue, err := issues_model.GetIssueByID(ctx, attach.IssueID)
if err != nil {
ctx.ServerError("GetIssueByID", err)
return
}
if !ctx.Repo.Permission.CanWriteIssuesOrPulls(issue.IsPull) {
ctx.HTTPError(http.StatusForbidden)
return
}
} else if attach.ReleaseID > 0 {
if !ctx.Repo.Permission.CanWrite(unit.TypeReleases) {
ctx.HTTPError(http.StatusForbidden)
return
}
} else {
if !ctx.Repo.Permission.IsAdmin() && !ctx.Repo.Permission.IsOwner() {
ctx.HTTPError(http.StatusForbidden)
return
}
}
}
err = repo_model.DeleteAttachment(ctx, attach, true)
if err != nil {
ctx.HTTPError(http.StatusInternalServerError, fmt.Sprintf("DeleteAttachment: %v", err))
ctx.ServerError("DeleteAttachment", err)
return
}
ctx.JSON(http.StatusOK, map[string]string{
@@ -114,7 +146,7 @@ func ServeAttachment(ctx *context.Context, uuid string) {
} else { // If we have the repository we check access
perm, err := access_model.GetUserRepoPermission(ctx, repository, ctx.Doer)
if err != nil {
ctx.HTTPError(http.StatusInternalServerError, "GetUserRepoPermission", err.Error())
ctx.ServerError("GetUserRepoPermission", err)
return
}
if !perm.CanRead(unitType) {
+2 -12
View File
@@ -166,16 +166,6 @@ func parseGitDiffTreeLine(line string) (*DiffTreeRecord, error) {
return nil, fmt.Errorf("unparsable output for diff-tree --raw: `%s`, expected 5 space delimited values got %d)", line, len(fields))
}
baseMode, err := git.ParseEntryMode(fields[0])
if err != nil {
return nil, err
}
headMode, err := git.ParseEntryMode(fields[1])
if err != nil {
return nil, err
}
baseBlobID := fields[2]
headBlobID := fields[3]
@@ -201,8 +191,8 @@ func parseGitDiffTreeLine(line string) (*DiffTreeRecord, error) {
return &DiffTreeRecord{
Status: status,
Score: score,
BaseMode: baseMode,
HeadMode: headMode,
BaseMode: git.ParseEntryMode(fields[0]),
HeadMode: git.ParseEntryMode(fields[1]),
BaseBlobID: baseBlobID,
HeadBlobID: headBlobID,
BasePath: basePath,
+46 -31
View File
@@ -399,20 +399,20 @@ type DiffFile struct {
isAmbiguous bool
// basic fields (parsed from diff result)
Name string
NameHash string
OldName string
Addition int
Deletion int
Type DiffFileType
Mode string
OldMode string
IsCreated bool
IsDeleted bool
IsBin bool
IsLFSFile bool
IsRenamed bool
IsSubmodule bool
Name string
NameHash string
OldName string
Addition int
Deletion int
Type DiffFileType
EntryMode string
OldEntryMode string
IsCreated bool
IsDeleted bool
IsBin bool
IsLFSFile bool
IsRenamed bool
IsSubmodule bool
// basic fields but for render purpose only
Sections []*DiffSection
IsIncomplete bool
@@ -501,21 +501,36 @@ func (diffFile *DiffFile) ShouldBeHidden() bool {
return diffFile.IsGenerated || diffFile.IsViewed
}
func (diffFile *DiffFile) ModeTranslationKey(mode string) string {
switch mode {
case "040000":
return "git.filemode.directory"
case "100644":
return "git.filemode.normal_file"
case "100755":
return "git.filemode.executable_file"
case "120000":
return "git.filemode.symbolic_link"
case "160000":
return "git.filemode.submodule"
default:
return mode
func (diffFile *DiffFile) TranslateDiffEntryMode(locale translation.Locale) string {
entryModeTr := func(mode string) string {
entryMode := git.ParseEntryMode(mode)
switch {
case entryMode.IsDir():
return locale.TrString("git.filemode.directory")
case entryMode.IsRegular():
return locale.TrString("git.filemode.normal_file")
case entryMode.IsExecutable():
return locale.TrString("git.filemode.executable_file")
case entryMode.IsLink():
return locale.TrString("git.filemode.symbolic_link")
case entryMode.IsSubModule():
return locale.TrString("git.filemode.submodule")
default:
return mode
}
}
if diffFile.EntryMode != "" && diffFile.OldEntryMode != "" {
oldMode := entryModeTr(diffFile.OldEntryMode)
newMode := entryModeTr(diffFile.EntryMode)
return locale.TrString("git.filemode.changed_filemode", oldMode, newMode)
}
if diffFile.EntryMode != "" {
if entryMode := git.ParseEntryMode(diffFile.EntryMode); !entryMode.IsRegular() {
return entryModeTr(diffFile.EntryMode)
}
}
return ""
}
type limitByteWriter struct {
@@ -695,10 +710,10 @@ parsingLoop:
strings.HasPrefix(line, "new mode "):
if strings.HasPrefix(line, "old mode ") {
curFile.OldMode = prepareValue(line, "old mode ")
curFile.OldEntryMode = prepareValue(line, "old mode ")
}
if strings.HasPrefix(line, "new mode ") {
curFile.Mode = prepareValue(line, "new mode ")
curFile.EntryMode = prepareValue(line, "new mode ")
}
if strings.HasSuffix(line, " 160000\n") {
curFile.IsSubmodule, curFile.SubmoduleDiffInfo = true, &SubmoduleDiffInfo{}
@@ -733,7 +748,7 @@ parsingLoop:
curFile.Type = DiffFileAdd
curFile.IsCreated = true
if strings.HasPrefix(line, "new file mode ") {
curFile.Mode = prepareValue(line, "new file mode ")
curFile.EntryMode = prepareValue(line, "new file mode ")
}
if strings.HasSuffix(line, " 160000\n") {
curFile.IsSubmodule, curFile.SubmoduleDiffInfo = true, &SubmoduleDiffInfo{}
+1 -1
View File
@@ -90,7 +90,7 @@ func GetListLockHandler(ctx *context.Context) {
})
return
}
lock, err := git_model.GetLFSLockByID(ctx, v)
lock, err := git_model.GetLFSLockByIDAndRepo(ctx, v, repository.ID)
if err != nil && !git_model.IsErrLFSLockNotExist(err) {
log.Error("Unable to get lock with ID[%s]: Error: %v", v, err)
}
+34 -35
View File
@@ -82,43 +82,42 @@
{{$isExpandable := or (gt $file.Addition 0) (gt $file.Deletion 0) $file.IsBin}}
{{$isReviewFile := and $.IsSigned $.PageIsPullFiles (not $.Repository.IsArchived) $.IsShowingAllCommits}}
<div class="diff-file-box file-content {{TabSizeClass $.Editorconfig $file.Name}} tw-mt-0" id="diff-{{$file.NameHash}}" data-old-filename="{{$file.OldName}}" data-new-filename="{{$file.Name}}" {{if or ($file.ShouldBeHidden) (not $isExpandable)}}data-folded="true"{{end}}>
<h4 class="diff-file-header sticky-2nd-row ui top attached header">
<div class="diff-file-header sticky-2nd-row ui top attached header">
<div class="diff-file-name tw-flex tw-flex-1 tw-items-center tw-gap-1 tw-flex-wrap">
<button class="fold-file btn interact-bg tw-p-1{{if not $isExpandable}} tw-invisible{{end}}">
{{if $file.ShouldBeHidden}}
{{svg "octicon-chevron-right" 18}}
{{else}}
{{svg "octicon-chevron-down" 18}}
{{end}}
</button>
<div class="tw-font-semibold tw-flex tw-items-center tw-font-mono">
{{if $file.IsBin}}
<span class="tw-ml-0.5 tw-mr-2">
{{ctx.Locale.Tr "repo.diff.bin"}}
</span>
{{else}}
{{template "repo/diff/stats" dict "file" . "root" $}}
{{end}}
<div class="flex-text-block">
<button class="fold-file btn interact-bg tw-flex-shrink-0 tw-p-1{{if not $isExpandable}} tw-invisible{{end}}">
{{if $file.ShouldBeHidden}}
{{svg "octicon-chevron-right" 18}}
{{else}}
{{svg "octicon-chevron-down" 18}}
{{end}}
</button>
{{$entryModeText := $file.TranslateDiffEntryMode ctx.Locale}}
<a class="muted file-link tw-font-mono" title="{{if $file.IsRenamed}}{{$file.OldName}}{{end}}{{$file.Name}}" href="#diff-{{$file.NameHash}}">
{{if $file.IsRenamed}}{{$file.OldName}}{{end}}{{$file.Name}}
</a>
</div>
<span class="file tw-flex tw-items-center tw-font-mono tw-flex-1"><a class="muted file-link" title="{{if $file.IsRenamed}}{{$file.OldName}}{{end}}{{$file.Name}}" href="#diff-{{$file.NameHash}}">{{if $file.IsRenamed}}{{$file.OldName}}{{end}}{{$file.Name}}</a>
<button class="btn interact-fg tw-p-2" data-clipboard-text="{{$file.Name}}" data-tooltip-content="{{ctx.Locale.Tr "copy_path"}}">{{svg "octicon-copy" 14}}</button>
{{if .IsLFSFile}}<span class="ui label">LFS</span>{{end}}
{{if $file.IsGenerated}}
<span class="ui label">{{ctx.Locale.Tr "repo.diff.generated"}}</span>
{{end}}
{{if $file.IsVendored}}
<span class="ui label">{{ctx.Locale.Tr "repo.diff.vendored"}}</span>
{{end}}
{{if and $file.Mode $file.OldMode}}
{{$old := ctx.Locale.Tr ($file.ModeTranslationKey $file.OldMode)}}
{{$new := ctx.Locale.Tr ($file.ModeTranslationKey $file.Mode)}}
<span class="tw-mx-2 tw-font-mono tw-whitespace-nowrap">{{ctx.Locale.Tr "git.filemode.changed_filemode" $old $new}}</span>
{{else if $file.Mode}}
<span class="tw-mx-2 tw-font-mono tw-whitespace-nowrap">{{ctx.Locale.Tr ($file.ModeTranslationKey $file.Mode)}}</span>
{{end}}
</span>
<button class="btn interact-fg tw-p-2 tw-shrink-0" data-clipboard-text="{{$file.Name}}" data-tooltip-content="{{ctx.Locale.Tr "copy_path"}}">{{svg "octicon-copy" 14}}</button>
{{if $file.IsLFSFile}}
<span class="ui label">LFS</span>
{{end}}
{{if $file.IsGenerated}}
<span class="ui label">{{ctx.Locale.Tr "repo.diff.generated"}}</span>
{{end}}
{{if $file.IsVendored}}
<span class="ui label">{{ctx.Locale.Tr "repo.diff.vendored"}}</span>
{{end}}
{{if $entryModeText}}
<span class="ui label">{{$entryModeText}}</span>
{{end}}
</div>
<div class="diff-file-header-actions tw-flex tw-items-center tw-gap-1 tw-flex-wrap">
<div class="diff-file-header-actions flex-text-block tw-justify-end tw-flex-wrap">
{{if $file.IsBin}}
{{ctx.Locale.Tr "repo.diff.bin"}}
{{else}}
{{template "repo/diff/stats" dict "Addition" .Addition "Deletion" .Deletion}}
{{end}}
{{if $showFileViewToggle}}
<div class="ui compact icon buttons">
<button class="ui tiny basic button file-view-toggle" data-toggle-selector="#diff-source-{{$file.NameHash}}" data-tooltip-content="{{ctx.Locale.Tr "repo.file_view_source"}}">{{svg "octicon-code"}}</button>
@@ -157,7 +156,7 @@
</div>
{{end}}
</div>
</h4>
</div>
<div class="diff-file-body ui attached unstackable table segment" {{if and $file.IsViewed $.IsShowingAllCommits}}data-folded="true"{{end}}>
<div id="diff-source-{{$file.NameHash}}" class="file-body file-code unicode-escaped code-diff{{if $.IsSplitStyle}} code-diff-split{{else}} code-diff-unified{{end}}{{if $showFileViewToggle}} tw-hidden{{end}}">
{{if or $file.IsIncomplete $file.IsBin}}
+17 -5
View File
@@ -1,5 +1,17 @@
{{Eval .file.Addition "+" .file.Deletion}}
<span class="diff-stats-bar tw-mx-2" data-tooltip-content="{{ctx.Locale.Tr "repo.diff.stats_desc_file" (Eval .file.Addition "+" .file.Deletion) .file.Addition .file.Deletion}}">
{{/* if the denominator is zero, then the float result is "width: NaNpx", as before, it just works */}}
<div class="diff-stats-add-bar" style="width: {{Eval 100 "*" .file.Addition "/" "(" .file.Addition "+" .file.Deletion "+" 0.0 ")"}}%"></div>
</span>
{{/* Template Attributes:
* Addition: Number of additions
* Deletion: Number of deletions
* Classes: Additional classes for the root element
*/}}
{{if or .Addition .Deletion}}
<div class="flex-text-block tw-flex-shrink-0 tw-text-[13px] {{if .Classes}}{{.Classes}}{{end}}">
<span>
{{if .Addition}}<span class="tw-text-diff-prompt-add-fg">+{{.Addition}}</span>{{end}}
{{if .Deletion}}<span class="tw-text-diff-prompt-del-fg">-{{.Deletion}}</span>{{end}}
</span>
<span class="diff-stats-bar" data-tooltip-content="{{ctx.Locale.Tr "repo.diff.stats_desc_file" (Eval .Addition "+" .Deletion) .Addition .Deletion}}">
{{/* if the denominator is zero, then the float result is "width: NaNpx", as before, it just works */}}
<div class="diff-stats-add-bar" style="width: {{Eval 100 "*" .Addition "/" "(" .Addition "+" .Deletion "+" 0.0 ")"}}%"></div>
</span>
</div>
{{end}}
+1 -6
View File
@@ -16,12 +16,7 @@
<span class="ui small label">{{if .NumFiles}}{{.NumFiles}}{{else}}-{{end}}</span>
</a>
{{if or .DiffShortStat.TotalAddition .DiffShortStat.TotalDeletion}}
<span class="tw-ml-auto tw-pl-3 tw-whitespace-nowrap tw-pr-0 tw-font-bold tw-flex tw-items-center tw-gap-2">
<span><span class="text green">{{if .DiffShortStat.TotalAddition}}+{{.DiffShortStat.TotalAddition}}{{end}}</span> <span class="text red">{{if .DiffShortStat.TotalDeletion}}-{{.DiffShortStat.TotalDeletion}}{{end}}</span></span>
<span class="diff-stats-bar">
<div class="diff-stats-add-bar" style="width: {{Eval 100 "*" .DiffShortStat.TotalAddition "/" "(" .DiffShortStat.TotalAddition "+" .DiffShortStat.TotalDeletion "+" 0.0 ")"}}%"></div>
</span>
</span>
{{template "repo/diff/stats" dict "Addition" .DiffShortStat.TotalAddition "Deletion" .DiffShortStat.TotalDeletion "Classes" "tw-ml-auto tw-pl-3 tw-font-semibold"}}
{{end}}
</div>
<div class="ui tabs divider"></div>
+45 -1
View File
@@ -34,6 +34,14 @@ func testGeneratePngBytes() []byte {
}
func testCreateIssueAttachment(t *testing.T, session *TestSession, repoURL, filename string, content []byte, expectedStatus int) string {
return testCreateAttachment(t, session, repoURL, "issues", filename, content, expectedStatus)
}
func testCreateReleaseAttachment(t *testing.T, session *TestSession, repoURL, filename string, content []byte, expectedStatus int) string {
return testCreateAttachment(t, session, repoURL, "releases", filename, content, expectedStatus)
}
func testCreateAttachment(t *testing.T, session *TestSession, repoURL, issueOrRelease, filename string, content []byte, expectedStatus int) string {
body := &bytes.Buffer{}
// Setup multi-part
@@ -45,7 +53,7 @@ func testCreateIssueAttachment(t *testing.T, session *TestSession, repoURL, file
err = writer.Close()
assert.NoError(t, err)
req := NewRequestWithBody(t, "POST", repoURL+"/issues/attachments", body)
req := NewRequestWithBody(t, "POST", repoURL+"/"+issueOrRelease+"/attachments", body)
req.Header.Add("Content-Type", writer.FormDataContentType())
resp := session.MakeRequest(t, req, expectedStatus)
@@ -57,12 +65,23 @@ func testCreateIssueAttachment(t *testing.T, session *TestSession, repoURL, file
return obj["uuid"]
}
func testDeleteIssueAttachment(t *testing.T, session *TestSession, repoURL, uuid string, expectedStatus int) {
req := NewRequestWithValues(t, "POST", repoURL+"/issues/attachments/remove", map[string]string{"file": uuid})
session.MakeRequest(t, req, expectedStatus)
}
func testDeleteReleaseAttachment(t *testing.T, session *TestSession, repoURL, uuid string, expectedStatus int) {
req := NewRequestWithValues(t, "POST", repoURL+"/releases/attachments/remove", map[string]string{"file": uuid})
session.MakeRequest(t, req, expectedStatus)
}
func TestAttachments(t *testing.T) {
defer tests.PrepareTestEnv(t)()
t.Run("CreateAnonymousAttachment", testCreateAnonymousAttachment)
t.Run("CreateUser2IssueAttachment", testCreateUser2IssueAttachment)
t.Run("UploadAttachmentDeleteTemp", testUploadAttachmentDeleteTemp)
t.Run("GetAttachment", testGetAttachment)
t.Run("DeleteAttachmentPermissions", testDeleteAttachmentPermissions)
}
func testUploadAttachmentDeleteTemp(t *testing.T) {
@@ -157,3 +176,28 @@ func testGetAttachment(t *testing.T) {
})
}
}
func testDeleteAttachmentPermissions(t *testing.T) {
const repoURL = "user2/repo1"
ownerSession := loginUser(t, "user2")
readonlySession := loginUser(t, "user5")
issueFromOwner := testCreateIssueAttachment(t, ownerSession, repoURL, "owner-issue.png", testGeneratePngBytes(), http.StatusOK)
testDeleteIssueAttachment(t, readonlySession, repoURL, issueFromOwner, http.StatusForbidden)
issueFromReader := testCreateIssueAttachment(t, readonlySession, repoURL, "reader-issue.png", testGeneratePngBytes(), http.StatusOK)
testDeleteIssueAttachment(t, ownerSession, repoURL, issueFromReader, http.StatusOK)
testCreateReleaseAttachment(t, readonlySession, repoURL, "reader-release.png", testGeneratePngBytes(), http.StatusNotFound)
crossRepoUUID := testCreateIssueAttachment(t, ownerSession, repoURL, "cross-repo.png", testGeneratePngBytes(), http.StatusOK)
testDeleteIssueAttachment(t, ownerSession, "user2/repo2", crossRepoUUID, http.StatusBadRequest)
testDeleteIssueAttachment(t, ownerSession, repoURL, crossRepoUUID, http.StatusOK)
releaseUUID := testCreateReleaseAttachment(t, ownerSession, repoURL, "reader-release.png", testGeneratePngBytes(), http.StatusOK)
testDeleteReleaseAttachment(t, ownerSession, repoURL, releaseUUID, http.StatusOK)
// test deleting release attachment from another repo
testDeleteReleaseAttachment(t, ownerSession, "user2/repo2", crossRepoUUID, http.StatusBadRequest)
}
+17 -8
View File
@@ -1293,8 +1293,7 @@ td .commit-summary {
filter: drop-shadow(-4px 0 0 var(--color-primary-alpha-30)) !important;
}
.code-comment:target,
.diff-file-box:target {
.code-comment:target {
border-color: var(--color-primary) !important;
border-radius: var(--border-radius) !important;
box-shadow: 0 0 0 3px var(--color-primary-alpha-30) !important;
@@ -1681,18 +1680,27 @@ tbody.commit-list {
margin-top: 1em;
}
.diff-file-box:target {
border-color: var(--color-primary) !important;
border-radius: var(--border-radius) !important;
box-shadow: 0 0 0 3px var(--color-primary-alpha-30) !important;
}
.diff-file-header {
padding: 5px 8px !important;
box-shadow: 0 -1px 0 1px var(--color-body); /* prevent borders being visible behind top corners when sticky and scrolled */
font-weight: var(--font-weight-normal);
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 0.5em;
/* prevent borders from being visible behind top corners when sticky and scrolled,
this "shadow" is used to use body's color to cover the scrolled-up left and right borders at corners */
box-shadow: 0 -1px 0 1px var(--color-body);
}
.diff-file-header .file {
min-width: 0;
.diff-file-box:target .diff-file-header {
box-shadow: unset; /* when targeted, still use the parent's box-shadow, remove the patched above */
}
.diff-file-header .file-link {
@@ -1715,6 +1723,7 @@ tbody.commit-list {
.diff-file-header {
flex-direction: column;
align-items: stretch;
gap: 0;
}
}
@@ -1743,13 +1752,13 @@ tbody.commit-list {
.diff-stats-bar {
display: inline-block;
background-color: var(--color-red);
background-color: var(--color-diff-prompt-del-fg); /* the background is used as "text foreground color" */
height: 12px;
width: 44px;
}
.diff-stats-bar .diff-stats-add-bar {
background-color: var(--color-green);
background-color: var(--color-diff-prompt-add-fg);
height: 100%;
}
+2
View File
@@ -158,6 +158,8 @@ gitea-theme-meta-info {
--color-diff-removed-row-bg: #301e1e;
--color-diff-removed-row-border: #634343;
--color-diff-removed-word-bg: #6f3333;
--color-diff-prompt-add-fg: #87ab63;
--color-diff-prompt-del-fg: #cc4848;
--color-diff-inactive: #22282d;
--color-error-border: #a04141;
--color-error-bg: #522;
+2
View File
@@ -158,6 +158,8 @@ gitea-theme-meta-info {
--color-diff-removed-row-bg: #ffeef0;
--color-diff-removed-row-border: #f1c0c0;
--color-diff-removed-word-bg: #fdb8c0;
--color-diff-prompt-add-fg: #21ba45;
--color-diff-prompt-del-fg: #db2828;
--color-diff-inactive: #f0f2f4;
--color-error-border: #e0b4b4;
--color-error-bg: #fff6f6;