From 2da233ad8be107de29190720f1c30199410fe0cd Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Mon, 5 Feb 2024 08:52:56 +0300 Subject: [PATCH 1/7] Propagate install_if and provider_priority to APKINDEX (#28899) Resolves https://github.com/go-gitea/gitea/issues/28704 Example of an entry in the generated `APKINDEX` file: ``` C:Q1xCO3H9LTTEbhKt9G1alSC87I56c= P:hello V:2.12-r1 A:x86_64 T:The GNU Hello program produces a familiar, friendly greeting U:https://www.gnu.org/software/hello/ L:GPL-3.0-or-later S:15403 I:36864 o:hello m: t:1705934118 D:so:libc.musl-x86_64.so.1 p:cmd:hello=2.12-r1 i:foobar=1.0 !baz k:42 ``` the `i:` and `k:` entries are new. --------- Co-authored-by: KN4CK3R --- modules/packages/alpine/metadata.go | 26 ++++++++++++++++---------- services/packages/alpine/repository.go | 6 ++++++ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/modules/packages/alpine/metadata.go b/modules/packages/alpine/metadata.go index c2d0caffa1259..582c42610d207 100644 --- a/modules/packages/alpine/metadata.go +++ b/modules/packages/alpine/metadata.go @@ -55,16 +55,17 @@ type VersionMetadata struct { } type FileMetadata struct { - Checksum string `json:"checksum"` - Packager string `json:"packager,omitempty"` - BuildDate int64 `json:"build_date,omitempty"` - Size int64 `json:"size,omitempty"` - Architecture string `json:"architecture,omitempty"` - Origin string `json:"origin,omitempty"` - CommitHash string `json:"commit_hash,omitempty"` - InstallIf string `json:"install_if,omitempty"` - Provides []string `json:"provides,omitempty"` - Dependencies []string `json:"dependencies,omitempty"` + Checksum string `json:"checksum"` + Packager string `json:"packager,omitempty"` + BuildDate int64 `json:"build_date,omitempty"` + Size int64 `json:"size,omitempty"` + Architecture string `json:"architecture,omitempty"` + Origin string `json:"origin,omitempty"` + CommitHash string `json:"commit_hash,omitempty"` + InstallIf string `json:"install_if,omitempty"` + Provides []string `json:"provides,omitempty"` + Dependencies []string `json:"dependencies,omitempty"` + ProviderPriority int64 `json:"provider_priority,omitempty"` } // ParsePackage parses the Alpine package file @@ -188,6 +189,11 @@ func ParsePackageInfo(r io.Reader) (*Package, error) { if value != "" { p.FileMetadata.Dependencies = append(p.FileMetadata.Dependencies, value) } + case "provider_priority": + n, err := strconv.ParseInt(value, 10, 64) + if err == nil { + p.FileMetadata.ProviderPriority = n + } } } if err := scanner.Err(); err != nil { diff --git a/services/packages/alpine/repository.go b/services/packages/alpine/repository.go index 30b7a06eb36d9..104548b421555 100644 --- a/services/packages/alpine/repository.go +++ b/services/packages/alpine/repository.go @@ -230,6 +230,12 @@ func buildPackagesIndex(ctx context.Context, ownerID int64, repoVersion *package if len(pd.FileMetadata.Provides) > 0 { fmt.Fprintf(&buf, "p:%s\n", strings.Join(pd.FileMetadata.Provides, " ")) } + if pd.FileMetadata.InstallIf != "" { + fmt.Fprintf(&buf, "i:%s\n", pd.FileMetadata.InstallIf) + } + if pd.FileMetadata.ProviderPriority > 0 { + fmt.Fprintf(&buf, "k:%d\n", pd.FileMetadata.ProviderPriority) + } fmt.Fprint(&buf, "\n") } From 9bb1adf8ea5db928e0b21d6ee89876d15d2ec6a1 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 5 Feb 2024 14:17:23 +0800 Subject: [PATCH 2/7] Move some repository transfer functions to service layer (#28855) --- models/repo/update.go | 50 ----- models/repo_transfer.go | 247 +--------------------- models/repo_transfer_test.go | 57 ----- routers/api/v1/repo/transfer.go | 2 +- routers/web/repo/repo.go | 2 +- routers/web/repo/setting/setting.go | 2 +- services/repository/transfer.go | 302 ++++++++++++++++++++++++++- services/repository/transfer_test.go | 43 ++++ 8 files changed, 347 insertions(+), 358 deletions(-) delete mode 100644 models/repo_transfer_test.go diff --git a/models/repo/update.go b/models/repo/update.go index 6ddf1a8905eb8..e7ca2240282f2 100644 --- a/models/repo/update.go +++ b/models/repo/update.go @@ -6,7 +6,6 @@ package repo import ( "context" "fmt" - "strings" "time" "code.gitea.io/gitea/models/db" @@ -135,55 +134,6 @@ func CheckCreateRepository(ctx context.Context, doer, u *user_model.User, name s return nil } -// ChangeRepositoryName changes all corresponding setting from old repository name to new one. -func ChangeRepositoryName(ctx context.Context, doer *user_model.User, repo *Repository, newRepoName string) (err error) { - oldRepoName := repo.Name - newRepoName = strings.ToLower(newRepoName) - if err = IsUsableRepoName(newRepoName); err != nil { - return err - } - - if err := repo.LoadOwner(ctx); err != nil { - return err - } - - has, err := IsRepositoryModelOrDirExist(ctx, repo.Owner, newRepoName) - if err != nil { - return fmt.Errorf("IsRepositoryExist: %w", err) - } else if has { - return ErrRepoAlreadyExist{repo.Owner.Name, newRepoName} - } - - newRepoPath := RepoPath(repo.Owner.Name, newRepoName) - if err = util.Rename(repo.RepoPath(), newRepoPath); err != nil { - return fmt.Errorf("rename repository directory: %w", err) - } - - wikiPath := repo.WikiPath() - isExist, err := util.IsExist(wikiPath) - if err != nil { - log.Error("Unable to check if %s exists. Error: %v", wikiPath, err) - return err - } - if isExist { - if err = util.Rename(wikiPath, WikiPath(repo.Owner.Name, newRepoName)); err != nil { - return fmt.Errorf("rename repository wiki: %w", err) - } - } - - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - if err := NewRedirect(ctx, repo.Owner.ID, repo.ID, oldRepoName, newRepoName); err != nil { - return err - } - - return committer.Commit() -} - // UpdateRepoSize updates the repository size, calculating it using getDirectorySize func UpdateRepoSize(ctx context.Context, repoID, gitSize, lfsSize int64) error { _, err := db.GetEngine(ctx).ID(repoID).Cols("size", "git_size", "lfs_size").NoAutoTime().Update(&Repository{ diff --git a/models/repo_transfer.go b/models/repo_transfer.go index 630c243c8e958..676e2dbb63382 100644 --- a/models/repo_transfer.go +++ b/models/repo_transfer.go @@ -6,17 +6,13 @@ package models import ( "context" "fmt" - "os" "code.gitea.io/gitea/models/db" - issues_model "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/models/organization" - access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/timeutil" - "code.gitea.io/gitea/modules/util" ) // RepoTransfer is used to manage repository transfers @@ -115,32 +111,11 @@ func GetPendingRepositoryTransfer(ctx context.Context, repo *repo_model.Reposito return transfer, nil } -func deleteRepositoryTransfer(ctx context.Context, repoID int64) error { +func DeleteRepositoryTransfer(ctx context.Context, repoID int64) error { _, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).Delete(&RepoTransfer{}) return err } -// CancelRepositoryTransfer marks the repository as ready and remove pending transfer entry, -// thus cancel the transfer process. -func CancelRepositoryTransfer(ctx context.Context, repo *repo_model.Repository) error { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - repo.Status = repo_model.RepositoryReady - if err := repo_model.UpdateRepositoryCols(ctx, repo, "status"); err != nil { - return err - } - - if err := deleteRepositoryTransfer(ctx, repo.ID); err != nil { - return err - } - - return committer.Commit() -} - // TestRepositoryReadyForTransfer make sure repo is ready to transfer func TestRepositoryReadyForTransfer(status repo_model.RepositoryStatus) error { switch status { @@ -197,223 +172,3 @@ func CreatePendingRepositoryTransfer(ctx context.Context, doer, newOwner *user_m return db.Insert(ctx, transfer) }) } - -// TransferOwnership transfers all corresponding repository items from old user to new one. -func TransferOwnership(ctx context.Context, doer *user_model.User, newOwnerName string, repo *repo_model.Repository) (err error) { - repoRenamed := false - wikiRenamed := false - oldOwnerName := doer.Name - - defer func() { - if !repoRenamed && !wikiRenamed { - return - } - - recoverErr := recover() - if err == nil && recoverErr == nil { - return - } - - if repoRenamed { - if err := util.Rename(repo_model.RepoPath(newOwnerName, repo.Name), repo_model.RepoPath(oldOwnerName, repo.Name)); err != nil { - log.Critical("Unable to move repository %s/%s directory from %s back to correct place %s: %v", oldOwnerName, repo.Name, - repo_model.RepoPath(newOwnerName, repo.Name), repo_model.RepoPath(oldOwnerName, repo.Name), err) - } - } - - if wikiRenamed { - if err := util.Rename(repo_model.WikiPath(newOwnerName, repo.Name), repo_model.WikiPath(oldOwnerName, repo.Name)); err != nil { - log.Critical("Unable to move wiki for repository %s/%s directory from %s back to correct place %s: %v", oldOwnerName, repo.Name, - repo_model.WikiPath(newOwnerName, repo.Name), repo_model.WikiPath(oldOwnerName, repo.Name), err) - } - } - - if recoverErr != nil { - log.Error("Panic within TransferOwnership: %v\n%s", recoverErr, log.Stack(2)) - panic(recoverErr) - } - }() - - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - sess := db.GetEngine(ctx) - - newOwner, err := user_model.GetUserByName(ctx, newOwnerName) - if err != nil { - return fmt.Errorf("get new owner '%s': %w", newOwnerName, err) - } - newOwnerName = newOwner.Name // ensure capitalisation matches - - // Check if new owner has repository with same name. - if has, err := repo_model.IsRepositoryModelOrDirExist(ctx, newOwner, repo.Name); err != nil { - return fmt.Errorf("IsRepositoryExist: %w", err) - } else if has { - return repo_model.ErrRepoAlreadyExist{ - Uname: newOwnerName, - Name: repo.Name, - } - } - - oldOwner := repo.Owner - oldOwnerName = oldOwner.Name - - // Note: we have to set value here to make sure recalculate accesses is based on - // new owner. - repo.OwnerID = newOwner.ID - repo.Owner = newOwner - repo.OwnerName = newOwner.Name - - // Update repository. - if _, err := sess.ID(repo.ID).Update(repo); err != nil { - return fmt.Errorf("update owner: %w", err) - } - - // Remove redundant collaborators. - collaborators, err := repo_model.GetCollaborators(ctx, repo.ID, db.ListOptions{}) - if err != nil { - return fmt.Errorf("getCollaborators: %w", err) - } - - // Dummy object. - collaboration := &repo_model.Collaboration{RepoID: repo.ID} - for _, c := range collaborators { - if c.IsGhost() { - collaboration.ID = c.Collaboration.ID - if _, err := sess.Delete(collaboration); err != nil { - return fmt.Errorf("remove collaborator '%d': %w", c.ID, err) - } - collaboration.ID = 0 - } - - if c.ID != newOwner.ID { - isMember, err := organization.IsOrganizationMember(ctx, newOwner.ID, c.ID) - if err != nil { - return fmt.Errorf("IsOrgMember: %w", err) - } else if !isMember { - continue - } - } - collaboration.UserID = c.ID - if _, err := sess.Delete(collaboration); err != nil { - return fmt.Errorf("remove collaborator '%d': %w", c.ID, err) - } - collaboration.UserID = 0 - } - - // Remove old team-repository relations. - if oldOwner.IsOrganization() { - if err := organization.RemoveOrgRepo(ctx, oldOwner.ID, repo.ID); err != nil { - return fmt.Errorf("removeOrgRepo: %w", err) - } - } - - if newOwner.IsOrganization() { - teams, err := organization.FindOrgTeams(ctx, newOwner.ID) - if err != nil { - return fmt.Errorf("LoadTeams: %w", err) - } - for _, t := range teams { - if t.IncludesAllRepositories { - if err := AddRepository(ctx, t, repo); err != nil { - return fmt.Errorf("AddRepository: %w", err) - } - } - } - } else if err := access_model.RecalculateAccesses(ctx, repo); err != nil { - // Organization called this in addRepository method. - return fmt.Errorf("recalculateAccesses: %w", err) - } - - // Update repository count. - if _, err := sess.Exec("UPDATE `user` SET num_repos=num_repos+1 WHERE id=?", newOwner.ID); err != nil { - return fmt.Errorf("increase new owner repository count: %w", err) - } else if _, err := sess.Exec("UPDATE `user` SET num_repos=num_repos-1 WHERE id=?", oldOwner.ID); err != nil { - return fmt.Errorf("decrease old owner repository count: %w", err) - } - - if err := repo_model.WatchRepo(ctx, doer.ID, repo.ID, true); err != nil { - return fmt.Errorf("watchRepo: %w", err) - } - - // Remove watch for organization. - if oldOwner.IsOrganization() { - if err := repo_model.WatchRepo(ctx, oldOwner.ID, repo.ID, false); err != nil { - return fmt.Errorf("watchRepo [false]: %w", err) - } - } - - // Delete labels that belong to the old organization and comments that added these labels - if oldOwner.IsOrganization() { - if _, err := sess.Exec(`DELETE FROM issue_label WHERE issue_label.id IN ( - SELECT il_too.id FROM ( - SELECT il_too_too.id - FROM issue_label AS il_too_too - INNER JOIN label ON il_too_too.label_id = label.id - INNER JOIN issue on issue.id = il_too_too.issue_id - WHERE - issue.repo_id = ? AND ((label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != ?)) - ) AS il_too )`, repo.ID, newOwner.ID); err != nil { - return fmt.Errorf("Unable to remove old org labels: %w", err) - } - - if _, err := sess.Exec(`DELETE FROM comment WHERE comment.id IN ( - SELECT il_too.id FROM ( - SELECT com.id - FROM comment AS com - INNER JOIN label ON com.label_id = label.id - INNER JOIN issue ON issue.id = com.issue_id - WHERE - com.type = ? AND issue.repo_id = ? AND ((label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != ?)) - ) AS il_too)`, issues_model.CommentTypeLabel, repo.ID, newOwner.ID); err != nil { - return fmt.Errorf("Unable to remove old org label comments: %w", err) - } - } - - // Rename remote repository to new path and delete local copy. - dir := user_model.UserPath(newOwner.Name) - - if err := os.MkdirAll(dir, os.ModePerm); err != nil { - return fmt.Errorf("Failed to create dir %s: %w", dir, err) - } - - if err := util.Rename(repo_model.RepoPath(oldOwner.Name, repo.Name), repo_model.RepoPath(newOwner.Name, repo.Name)); err != nil { - return fmt.Errorf("rename repository directory: %w", err) - } - repoRenamed = true - - // Rename remote wiki repository to new path and delete local copy. - wikiPath := repo_model.WikiPath(oldOwner.Name, repo.Name) - - if isExist, err := util.IsExist(wikiPath); err != nil { - log.Error("Unable to check if %s exists. Error: %v", wikiPath, err) - return err - } else if isExist { - if err := util.Rename(wikiPath, repo_model.WikiPath(newOwner.Name, repo.Name)); err != nil { - return fmt.Errorf("rename repository wiki: %w", err) - } - wikiRenamed = true - } - - if err := deleteRepositoryTransfer(ctx, repo.ID); err != nil { - return fmt.Errorf("deleteRepositoryTransfer: %w", err) - } - repo.Status = repo_model.RepositoryReady - if err := repo_model.UpdateRepositoryCols(ctx, repo, "status"); err != nil { - return err - } - - // If there was previously a redirect at this location, remove it. - if err := repo_model.DeleteRedirect(ctx, newOwner.ID, repo.Name); err != nil { - return fmt.Errorf("delete repo redirect: %w", err) - } - - if err := repo_model.NewRedirect(ctx, oldOwner.ID, repo.ID, repo.Name, repo.Name); err != nil { - return fmt.Errorf("repo_model.NewRedirect: %w", err) - } - - return committer.Commit() -} diff --git a/models/repo_transfer_test.go b/models/repo_transfer_test.go deleted file mode 100644 index b55cef9473860..0000000000000 --- a/models/repo_transfer_test.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2021 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package models - -import ( - "testing" - - "code.gitea.io/gitea/models/db" - 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" -) - -func TestRepositoryTransfer(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) - - transfer, err := GetPendingRepositoryTransfer(db.DefaultContext, repo) - assert.NoError(t, err) - assert.NotNil(t, transfer) - - // Cancel transfer - assert.NoError(t, CancelRepositoryTransfer(db.DefaultContext, repo)) - - transfer, err = GetPendingRepositoryTransfer(db.DefaultContext, repo) - assert.Error(t, err) - assert.Nil(t, transfer) - assert.True(t, IsErrNoPendingTransfer(err)) - - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - - assert.NoError(t, CreatePendingRepositoryTransfer(db.DefaultContext, doer, user2, repo.ID, nil)) - - transfer, err = GetPendingRepositoryTransfer(db.DefaultContext, repo) - assert.Nil(t, err) - assert.NoError(t, transfer.LoadAttributes(db.DefaultContext)) - assert.Equal(t, "user2", transfer.Recipient.Name) - - org6 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - - // Only transfer can be started at any given time - err = CreatePendingRepositoryTransfer(db.DefaultContext, doer, org6, repo.ID, nil) - assert.Error(t, err) - assert.True(t, IsErrRepoTransferInProgress(err)) - - // Unknown user - err = CreatePendingRepositoryTransfer(db.DefaultContext, doer, &user_model.User{ID: 1000, LowerName: "user1000"}, repo.ID, nil) - assert.Error(t, err) - - // Cancel transfer - assert.NoError(t, CancelRepositoryTransfer(db.DefaultContext, repo)) -} diff --git a/routers/api/v1/repo/transfer.go b/routers/api/v1/repo/transfer.go index b3120f4be0898..c0a40ce0620f5 100644 --- a/routers/api/v1/repo/transfer.go +++ b/routers/api/v1/repo/transfer.go @@ -230,5 +230,5 @@ func acceptOrRejectRepoTransfer(ctx *context.APIContext, accept bool) error { return repo_service.TransferOwnership(ctx, repoTransfer.Doer, repoTransfer.Recipient, ctx.Repo.Repository, repoTransfer.Teams) } - return models.CancelRepositoryTransfer(ctx, ctx.Repo.Repository) + return repo_service.CancelRepositoryTransfer(ctx, ctx.Repo.Repository) } diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index b64db914060f4..6880d646149dc 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -362,7 +362,7 @@ func acceptOrRejectRepoTransfer(ctx *context.Context, accept bool) error { } ctx.Flash.Success(ctx.Tr("repo.settings.transfer.success")) } else { - if err := models.CancelRepositoryTransfer(ctx, ctx.Repo.Repository); err != nil { + if err := repo_service.CancelRepositoryTransfer(ctx, ctx.Repo.Repository); err != nil { return err } ctx.Flash.Success(ctx.Tr("repo.settings.transfer.rejected")) diff --git a/routers/web/repo/setting/setting.go b/routers/web/repo/setting/setting.go index fc1403b3cc0b0..8c1daf52bcf73 100644 --- a/routers/web/repo/setting/setting.go +++ b/routers/web/repo/setting/setting.go @@ -813,7 +813,7 @@ func SettingsPost(ctx *context.Context) { return } - if err := models.CancelRepositoryTransfer(ctx, ctx.Repo.Repository); err != nil { + if err := repo_service.CancelRepositoryTransfer(ctx, ctx.Repo.Repository); err != nil { ctx.ServerError("CancelRepositoryTransfer", err) return } diff --git a/services/repository/transfer.go b/services/repository/transfer.go index 574b6c6a56342..59a4eb260e290 100644 --- a/services/repository/transfer.go +++ b/services/repository/transfer.go @@ -6,8 +6,12 @@ package repository import ( "context" "fmt" + "os" + "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" + issues_model "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" access_model "code.gitea.io/gitea/models/perm/access" @@ -16,6 +20,7 @@ import ( "code.gitea.io/gitea/modules/log" repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/sync" + "code.gitea.io/gitea/modules/util" notify_service "code.gitea.io/gitea/services/notify" ) @@ -37,7 +42,7 @@ func TransferOwnership(ctx context.Context, doer, newOwner *user_model.User, rep oldOwner := repo.Owner repoWorkingPool.CheckIn(fmt.Sprint(repo.ID)) - if err := models.TransferOwnership(ctx, doer, newOwner.Name, repo); err != nil { + if err := transferOwnership(ctx, doer, newOwner.Name, repo); err != nil { repoWorkingPool.CheckOut(fmt.Sprint(repo.ID)) return err } @@ -59,6 +64,278 @@ func TransferOwnership(ctx context.Context, doer, newOwner *user_model.User, rep return nil } +// transferOwnership transfers all corresponding repository items from old user to new one. +func transferOwnership(ctx context.Context, doer *user_model.User, newOwnerName string, repo *repo_model.Repository) (err error) { + repoRenamed := false + wikiRenamed := false + oldOwnerName := doer.Name + + defer func() { + if !repoRenamed && !wikiRenamed { + return + } + + recoverErr := recover() + if err == nil && recoverErr == nil { + return + } + + if repoRenamed { + if err := util.Rename(repo_model.RepoPath(newOwnerName, repo.Name), repo_model.RepoPath(oldOwnerName, repo.Name)); err != nil { + log.Critical("Unable to move repository %s/%s directory from %s back to correct place %s: %v", oldOwnerName, repo.Name, + repo_model.RepoPath(newOwnerName, repo.Name), repo_model.RepoPath(oldOwnerName, repo.Name), err) + } + } + + if wikiRenamed { + if err := util.Rename(repo_model.WikiPath(newOwnerName, repo.Name), repo_model.WikiPath(oldOwnerName, repo.Name)); err != nil { + log.Critical("Unable to move wiki for repository %s/%s directory from %s back to correct place %s: %v", oldOwnerName, repo.Name, + repo_model.WikiPath(newOwnerName, repo.Name), repo_model.WikiPath(oldOwnerName, repo.Name), err) + } + } + + if recoverErr != nil { + log.Error("Panic within TransferOwnership: %v\n%s", recoverErr, log.Stack(2)) + panic(recoverErr) + } + }() + + ctx, committer, err := db.TxContext(ctx) + if err != nil { + return err + } + defer committer.Close() + + sess := db.GetEngine(ctx) + + newOwner, err := user_model.GetUserByName(ctx, newOwnerName) + if err != nil { + return fmt.Errorf("get new owner '%s': %w", newOwnerName, err) + } + newOwnerName = newOwner.Name // ensure capitalisation matches + + // Check if new owner has repository with same name. + if has, err := repo_model.IsRepositoryModelOrDirExist(ctx, newOwner, repo.Name); err != nil { + return fmt.Errorf("IsRepositoryExist: %w", err) + } else if has { + return repo_model.ErrRepoAlreadyExist{ + Uname: newOwnerName, + Name: repo.Name, + } + } + + oldOwner := repo.Owner + oldOwnerName = oldOwner.Name + + // Note: we have to set value here to make sure recalculate accesses is based on + // new owner. + repo.OwnerID = newOwner.ID + repo.Owner = newOwner + repo.OwnerName = newOwner.Name + + // Update repository. + if _, err := sess.ID(repo.ID).Update(repo); err != nil { + return fmt.Errorf("update owner: %w", err) + } + + // Remove redundant collaborators. + collaborators, err := repo_model.GetCollaborators(ctx, repo.ID, db.ListOptions{}) + if err != nil { + return fmt.Errorf("getCollaborators: %w", err) + } + + // Dummy object. + collaboration := &repo_model.Collaboration{RepoID: repo.ID} + for _, c := range collaborators { + if c.IsGhost() { + collaboration.ID = c.Collaboration.ID + if _, err := sess.Delete(collaboration); err != nil { + return fmt.Errorf("remove collaborator '%d': %w", c.ID, err) + } + collaboration.ID = 0 + } + + if c.ID != newOwner.ID { + isMember, err := organization.IsOrganizationMember(ctx, newOwner.ID, c.ID) + if err != nil { + return fmt.Errorf("IsOrgMember: %w", err) + } else if !isMember { + continue + } + } + collaboration.UserID = c.ID + if _, err := sess.Delete(collaboration); err != nil { + return fmt.Errorf("remove collaborator '%d': %w", c.ID, err) + } + collaboration.UserID = 0 + } + + // Remove old team-repository relations. + if oldOwner.IsOrganization() { + if err := organization.RemoveOrgRepo(ctx, oldOwner.ID, repo.ID); err != nil { + return fmt.Errorf("removeOrgRepo: %w", err) + } + } + + if newOwner.IsOrganization() { + teams, err := organization.FindOrgTeams(ctx, newOwner.ID) + if err != nil { + return fmt.Errorf("LoadTeams: %w", err) + } + for _, t := range teams { + if t.IncludesAllRepositories { + if err := models.AddRepository(ctx, t, repo); err != nil { + return fmt.Errorf("AddRepository: %w", err) + } + } + } + } else if err := access_model.RecalculateAccesses(ctx, repo); err != nil { + // Organization called this in addRepository method. + return fmt.Errorf("recalculateAccesses: %w", err) + } + + // Update repository count. + if _, err := sess.Exec("UPDATE `user` SET num_repos=num_repos+1 WHERE id=?", newOwner.ID); err != nil { + return fmt.Errorf("increase new owner repository count: %w", err) + } else if _, err := sess.Exec("UPDATE `user` SET num_repos=num_repos-1 WHERE id=?", oldOwner.ID); err != nil { + return fmt.Errorf("decrease old owner repository count: %w", err) + } + + if err := repo_model.WatchRepo(ctx, doer.ID, repo.ID, true); err != nil { + return fmt.Errorf("watchRepo: %w", err) + } + + // Remove watch for organization. + if oldOwner.IsOrganization() { + if err := repo_model.WatchRepo(ctx, oldOwner.ID, repo.ID, false); err != nil { + return fmt.Errorf("watchRepo [false]: %w", err) + } + } + + // Delete labels that belong to the old organization and comments that added these labels + if oldOwner.IsOrganization() { + if _, err := sess.Exec(`DELETE FROM issue_label WHERE issue_label.id IN ( + SELECT il_too.id FROM ( + SELECT il_too_too.id + FROM issue_label AS il_too_too + INNER JOIN label ON il_too_too.label_id = label.id + INNER JOIN issue on issue.id = il_too_too.issue_id + WHERE + issue.repo_id = ? AND ((label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != ?)) + ) AS il_too )`, repo.ID, newOwner.ID); err != nil { + return fmt.Errorf("Unable to remove old org labels: %w", err) + } + + if _, err := sess.Exec(`DELETE FROM comment WHERE comment.id IN ( + SELECT il_too.id FROM ( + SELECT com.id + FROM comment AS com + INNER JOIN label ON com.label_id = label.id + INNER JOIN issue ON issue.id = com.issue_id + WHERE + com.type = ? AND issue.repo_id = ? AND ((label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != ?)) + ) AS il_too)`, issues_model.CommentTypeLabel, repo.ID, newOwner.ID); err != nil { + return fmt.Errorf("Unable to remove old org label comments: %w", err) + } + } + + // Rename remote repository to new path and delete local copy. + dir := user_model.UserPath(newOwner.Name) + + if err := os.MkdirAll(dir, os.ModePerm); err != nil { + return fmt.Errorf("Failed to create dir %s: %w", dir, err) + } + + if err := util.Rename(repo_model.RepoPath(oldOwner.Name, repo.Name), repo_model.RepoPath(newOwner.Name, repo.Name)); err != nil { + return fmt.Errorf("rename repository directory: %w", err) + } + repoRenamed = true + + // Rename remote wiki repository to new path and delete local copy. + wikiPath := repo_model.WikiPath(oldOwner.Name, repo.Name) + + if isExist, err := util.IsExist(wikiPath); err != nil { + log.Error("Unable to check if %s exists. Error: %v", wikiPath, err) + return err + } else if isExist { + if err := util.Rename(wikiPath, repo_model.WikiPath(newOwner.Name, repo.Name)); err != nil { + return fmt.Errorf("rename repository wiki: %w", err) + } + wikiRenamed = true + } + + if err := models.DeleteRepositoryTransfer(ctx, repo.ID); err != nil { + return fmt.Errorf("deleteRepositoryTransfer: %w", err) + } + repo.Status = repo_model.RepositoryReady + if err := repo_model.UpdateRepositoryCols(ctx, repo, "status"); err != nil { + return err + } + + // If there was previously a redirect at this location, remove it. + if err := repo_model.DeleteRedirect(ctx, newOwner.ID, repo.Name); err != nil { + return fmt.Errorf("delete repo redirect: %w", err) + } + + if err := repo_model.NewRedirect(ctx, oldOwner.ID, repo.ID, repo.Name, repo.Name); err != nil { + return fmt.Errorf("repo_model.NewRedirect: %w", err) + } + + return committer.Commit() +} + +// changeRepositoryName changes all corresponding setting from old repository name to new one. +func changeRepositoryName(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, newRepoName string) (err error) { + oldRepoName := repo.Name + newRepoName = strings.ToLower(newRepoName) + if err = repo_model.IsUsableRepoName(newRepoName); err != nil { + return err + } + + if err := repo.LoadOwner(ctx); err != nil { + return err + } + + has, err := repo_model.IsRepositoryModelOrDirExist(ctx, repo.Owner, newRepoName) + if err != nil { + return fmt.Errorf("IsRepositoryExist: %w", err) + } else if has { + return repo_model.ErrRepoAlreadyExist{ + Uname: repo.Owner.Name, + Name: newRepoName, + } + } + + newRepoPath := repo_model.RepoPath(repo.Owner.Name, newRepoName) + if err = util.Rename(repo.RepoPath(), newRepoPath); err != nil { + return fmt.Errorf("rename repository directory: %w", err) + } + + wikiPath := repo.WikiPath() + isExist, err := util.IsExist(wikiPath) + if err != nil { + log.Error("Unable to check if %s exists. Error: %v", wikiPath, err) + return err + } + if isExist { + if err = util.Rename(wikiPath, repo_model.WikiPath(repo.Owner.Name, newRepoName)); err != nil { + return fmt.Errorf("rename repository wiki: %w", err) + } + } + + ctx, committer, err := db.TxContext(ctx) + if err != nil { + return err + } + defer committer.Close() + + if err := repo_model.NewRedirect(ctx, repo.Owner.ID, repo.ID, oldRepoName, newRepoName); err != nil { + return err + } + + return committer.Commit() +} + // ChangeRepositoryName changes all corresponding setting from old repository name to new one. func ChangeRepositoryName(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, newRepoName string) error { log.Trace("ChangeRepositoryName: %s/%s -> %s", doer.Name, repo.Name, newRepoName) @@ -70,7 +347,7 @@ func ChangeRepositoryName(ctx context.Context, doer *user_model.User, repo *repo // local copy's origin accordingly. repoWorkingPool.CheckIn(fmt.Sprint(repo.ID)) - if err := repo_model.ChangeRepositoryName(ctx, doer, repo, newRepoName); err != nil { + if err := changeRepositoryName(ctx, doer, repo, newRepoName); err != nil { repoWorkingPool.CheckOut(fmt.Sprint(repo.ID)) return err } @@ -130,3 +407,24 @@ func StartRepositoryTransfer(ctx context.Context, doer, newOwner *user_model.Use return nil } + +// CancelRepositoryTransfer marks the repository as ready and remove pending transfer entry, +// thus cancel the transfer process. +func CancelRepositoryTransfer(ctx context.Context, repo *repo_model.Repository) error { + ctx, committer, err := db.TxContext(ctx) + if err != nil { + return err + } + defer committer.Close() + + repo.Status = repo_model.RepositoryReady + if err := repo_model.UpdateRepositoryCols(ctx, repo, "status"); err != nil { + return err + } + + if err := models.DeleteRepositoryTransfer(ctx, repo.ID); err != nil { + return err + } + + return committer.Commit() +} diff --git a/services/repository/transfer_test.go b/services/repository/transfer_test.go index d55c76ea47fc8..c3f03d6638f60 100644 --- a/services/repository/transfer_test.go +++ b/services/repository/transfer_test.go @@ -7,6 +7,7 @@ import ( "sync" "testing" + "code.gitea.io/gitea/models" activities_model "code.gitea.io/gitea/models/activities" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" @@ -78,3 +79,45 @@ func TestStartRepositoryTransferSetPermission(t *testing.T) { unittest.CheckConsistencyFor(t, &repo_model.Repository{}, &user_model.User{}, &organization.Team{}) } + +func TestRepositoryTransfer(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) + + transfer, err := models.GetPendingRepositoryTransfer(db.DefaultContext, repo) + assert.NoError(t, err) + assert.NotNil(t, transfer) + + // Cancel transfer + assert.NoError(t, CancelRepositoryTransfer(db.DefaultContext, repo)) + + transfer, err = models.GetPendingRepositoryTransfer(db.DefaultContext, repo) + assert.Error(t, err) + assert.Nil(t, transfer) + assert.True(t, models.IsErrNoPendingTransfer(err)) + + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + assert.NoError(t, models.CreatePendingRepositoryTransfer(db.DefaultContext, doer, user2, repo.ID, nil)) + + transfer, err = models.GetPendingRepositoryTransfer(db.DefaultContext, repo) + assert.Nil(t, err) + assert.NoError(t, transfer.LoadAttributes(db.DefaultContext)) + assert.Equal(t, "user2", transfer.Recipient.Name) + + org6 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + // Only transfer can be started at any given time + err = models.CreatePendingRepositoryTransfer(db.DefaultContext, doer, org6, repo.ID, nil) + assert.Error(t, err) + assert.True(t, models.IsErrRepoTransferInProgress(err)) + + // Unknown user + err = models.CreatePendingRepositoryTransfer(db.DefaultContext, doer, &user_model.User{ID: 1000, LowerName: "user1000"}, repo.ID, nil) + assert.Error(t, err) + + // Cancel transfer + assert.NoError(t, CancelRepositoryTransfer(db.DefaultContext, repo)) +} From 4bb1fcd2e727c3514658d720abed145ca751049f Mon Sep 17 00:00:00 2001 From: Wang Date: Mon, 5 Feb 2024 16:19:05 +0800 Subject: [PATCH 3/7] Fix typos in the documentation (#29048) Corrected two typos. --- docs/content/usage/actions/comparison.zh-cn.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/usage/actions/comparison.zh-cn.md b/docs/content/usage/actions/comparison.zh-cn.md index 006fc8de3f537..dbe9ca007d235 100644 --- a/docs/content/usage/actions/comparison.zh-cn.md +++ b/docs/content/usage/actions/comparison.zh-cn.md @@ -132,7 +132,7 @@ Gitea Actions目前不支持此功能。 如果你使用 `uses: actions/checkout@v4`,Gitea将会从 https://github.com/actions/checkout.git 下载这个 actions 项目。 如果你想要从另外一个 Git服务下载actions,你只需要使用绝对URL `uses: https://gitea.com/actions/checkout@v4` 来下载。 -如果你的 Gitea 实例是部署在一个互联网限制的网络中,有可以使用绝对地址来下载 actions。你也可以讲配置项修改为 `[actions].DEFAULT_ACTIONS_URL = self`。这样所有的相对路径的actions引用,将不再会从 github.com 去下载,而会从这个 Gitea 实例自己的仓库中去下载。例如: `uses: actions/checkout@v4` 将会从 `[server].ROOT_URL`/actions/checkout.git 这个地址去下载 actions。 +如果你的 Gitea 实例是部署在一个互联网限制的网络中,也可以使用绝对地址来下载 actions。你也可以将配置项修改为 `[actions].DEFAULT_ACTIONS_URL = self`。这样所有的相对路径的actions引用,将不再会从 github.com 去下载,而会从这个 Gitea 实例自己的仓库中去下载。例如: `uses: actions/checkout@v4` 将会从 `[server].ROOT_URL`/actions/checkout.git 这个地址去下载 actions。 设置`[actions].DEFAULT_ACTIONS_URL`进行配置。请参阅[配置备忘单](administration/config-cheat-sheet.md#actions-actions)。 From 0d136df3f0b3920d34ce850499e1346fbff29e30 Mon Sep 17 00:00:00 2001 From: Jason Song Date: Mon, 5 Feb 2024 17:23:08 +0800 Subject: [PATCH 4/7] Remove useless template file (#29053) Removed `templates/repo/settings/nav.tmpl`. I don't think it's still used. On main branch: image On commit 755eec745fa324fdd13078f0147638f8731652ba (the commit that created this file): image --- templates/repo/settings/nav.tmpl | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 templates/repo/settings/nav.tmpl diff --git a/templates/repo/settings/nav.tmpl b/templates/repo/settings/nav.tmpl deleted file mode 100644 index 93ecb3877b9d3..0000000000000 --- a/templates/repo/settings/nav.tmpl +++ /dev/null @@ -1,19 +0,0 @@ - From 6992ef98fc227a60cf06e0a06b9ae2492b3d61be Mon Sep 17 00:00:00 2001 From: Yarden Shoham Date: Mon, 5 Feb 2024 11:56:20 +0200 Subject: [PATCH 5/7] Don't do a full page load when clicking `Watch` or `Star` (#29001) - The watch/unwatch button and star/unstar get their own template - The backend returns HTML instead of redirect --------- Signed-off-by: Yarden Shoham Co-authored-by: John Olheiser --- routers/web/repo/repo.go | 31 ++++++++++++++++++++++++++++++ templates/repo/header.tmpl | 32 ++----------------------------- templates/repo/star_unstar.tmpl | 14 ++++++++++++++ templates/repo/watch_unwatch.tmpl | 14 ++++++++++++++ 4 files changed, 61 insertions(+), 30 deletions(-) create mode 100644 templates/repo/star_unstar.tmpl create mode 100644 templates/repo/watch_unwatch.tmpl diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index 6880d646149dc..bede21be1763e 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -302,6 +302,11 @@ func CreatePost(ctx *context.Context) { handleCreateError(ctx, ctxUser, err, "CreatePost", tplCreate, &form) } +const ( + tplWatchUnwatch base.TplName = "repo/watch_unwatch" + tplStarUnstar base.TplName = "repo/star_unstar" +) + // Action response for actions to a repository func Action(ctx *context.Context) { var err error @@ -334,6 +339,32 @@ func Action(ctx *context.Context) { return } + switch ctx.Params(":action") { + case "watch", "unwatch": + ctx.Data["IsWatchingRepo"] = repo_model.IsWatching(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID) + case "star", "unstar": + ctx.Data["IsStaringRepo"] = repo_model.IsStaring(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID) + } + + switch ctx.Params(":action") { + case "watch", "unwatch", "star", "unstar": + // we have to reload the repository because NumStars or NumWatching (used in the templates) has just changed + ctx.Data["Repository"], err = repo_model.GetRepositoryByName(ctx, ctx.Repo.Repository.OwnerID, ctx.Repo.Repository.Name) + if err != nil { + ctx.ServerError(fmt.Sprintf("Action (%s)", ctx.Params(":action")), err) + return + } + } + + switch ctx.Params(":action") { + case "watch", "unwatch": + ctx.HTML(http.StatusOK, tplWatchUnwatch) + return + case "star", "unstar": + ctx.HTML(http.StatusOK, tplStarUnstar) + return + } + ctx.RedirectToFirst(ctx.FormString("redirect_to"), ctx.Repo.RepoLink) } diff --git a/templates/repo/header.tmpl b/templates/repo/header.tmpl index dac30af600c3a..93102467ccb3f 100644 --- a/templates/repo/header.tmpl +++ b/templates/repo/header.tmpl @@ -58,37 +58,9 @@ {{svg "octicon-rss" 16}} {{end}} -
- {{$.CsrfTokenHtml}} -
- - - {{CountFmt .NumWatches}} - -
-
+ {{template "repo/watch_unwatch" $}} {{if not $.DisableStars}} -
- {{$.CsrfTokenHtml}} -
- - - {{CountFmt .NumStars}} - -
-
+ {{template "repo/star_unstar" $}} {{end}} {{if and (not .IsEmpty) ($.Permission.CanRead $.UnitTypeCode)}}
+
+ + + {{CountFmt .Repository.NumStars}} + +
+ diff --git a/templates/repo/watch_unwatch.tmpl b/templates/repo/watch_unwatch.tmpl new file mode 100644 index 0000000000000..c42bc5a9e77d9 --- /dev/null +++ b/templates/repo/watch_unwatch.tmpl @@ -0,0 +1,14 @@ +
+
+ + + {{CountFmt .Repository.NumWatches}} + +
+
From f69914dd0b5607861f36f89c362f4b2cc3f60403 Mon Sep 17 00:00:00 2001 From: GiteaBot Date: Tue, 6 Feb 2024 00:23:28 +0000 Subject: [PATCH 6/7] [skip ci] Updated translations via Crowdin --- options/locale/locale_zh-CN.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index faa1215d5bcd7..6d22468c9df9e 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -1005,10 +1005,10 @@ reactions_more=再加载 %d unit_disabled=站点管理员已禁用此仓库单元。 language_other=其它 adopt_search=输入用户名以搜索未被收录的仓库... (留空以查找全部) -adopt_preexisting_label=收录文件 -adopt_preexisting=收录已存在的文件 +adopt_preexisting_label=收录仓库 +adopt_preexisting=收录已存在的仓库 adopt_preexisting_content=从 %s 创建仓库 -adopt_preexisting_success=从 %s 收录文件并创建仓库成功 +adopt_preexisting_success=从 %s 收录仓库成功 delete_preexisting_label=刪除 delete_preexisting=删除已存在的文件 delete_preexisting_content=删除 %s 中的文件 From f9072dbf3c558ba5d4365b551d95936a52e4c94d Mon Sep 17 00:00:00 2001 From: Jason Song Date: Tue, 6 Feb 2024 19:57:25 +0800 Subject: [PATCH 7/7] Hide code links on release page if user cannot read code (#29064) On the release list page, if the user doesn't have the permission to read code, the code links will lead to 404 pages or api errors: image After this PR: image And this PR also removed some dead code. After #23465, the tag list page has an independent template, and all `IsTag` in the release list template are always false. --- templates/repo/release/list.tmpl | 29 +++++------------------------ 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/templates/repo/release/list.tmpl b/templates/repo/release/list.tmpl index ccea14c4e2686..fb2fce2950dc8 100644 --- a/templates/repo/release/list.tmpl +++ b/templates/repo/release/list.tmpl @@ -8,8 +8,8 @@ {{range $idx, $release := .Releases}}
  • - {{svg "octicon-tag" 16 "gt-mr-2"}}{{.TagName}} - {{if .Sha1}} + {{svg "octicon-tag" 16 "gt-mr-2"}}{{.TagName}} + {{if and .Sha1 ($.Permission.CanRead $.UnitTypeCode)}} {{svg "octicon-git-commit" 16 "gt-mr-2"}}{{ShortSha .Sha1}} {{template "repo/branch_dropdown" dict "root" $ "release" .}} {{end}} @@ -22,36 +22,18 @@ {{ctx.Locale.Tr "repo.release.draft"}} {{else if .IsPrerelease}} {{ctx.Locale.Tr "repo.release.prerelease"}} - {{else if not .IsTag}} + {{else}} {{ctx.Locale.Tr "repo.release.stable"}} {{end}}
    - {{if and $.CanCreateRelease (not .IsTag)}} + {{if $.CanCreateRelease}} {{svg "octicon-pencil"}} {{end}}
    - {{if .IsTag}} -

    - {{if gt .Publisher.ID 0}} - - {{ctx.AvatarUtils.Avatar .Publisher 20 "gt-mr-2"}} - {{.Publisher.Name}} - - - {{ctx.Locale.Tr "repo.tagged_this"}} - - {{if .CreatedUnix}} - {{TimeSinceUnix .CreatedUnix ctx.Locale}} - {{end}} - | - {{end}} - {{ctx.Locale.Tr "repo.release.ahead.commits" .NumCommitsBehind | Str2html}} {{ctx.Locale.Tr "repo.tag.ahead.target" .TargetBehind}} -

    - {{else}}

    {{if .OriginalAuthor}} @@ -69,11 +51,10 @@ {{if .CreatedUnix}} {{TimeSinceUnix .CreatedUnix ctx.Locale}} {{end}} - {{if not .IsDraft}} + {{if and (not .IsDraft) ($.Permission.CanRead $.UnitTypeCode)}} | {{ctx.Locale.Tr "repo.release.ahead.commits" .NumCommitsBehind | Str2html}} {{ctx.Locale.Tr "repo.release.ahead.target" .TargetBehind}} {{end}}

    - {{end}}
    {{Str2html .Note}}