forked from hinterland/hearth
feat: vendor gitea 1.16.2
This commit is contained in:
@@ -23,7 +23,6 @@ import (
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
notify_service "code.gitea.io/gitea/services/notify"
|
||||
)
|
||||
|
||||
@@ -75,7 +74,7 @@ func AdoptRepository(ctx context.Context, doer, owner *user_model.User, opts Cre
|
||||
// WARNING: Don't override all later err with local variables
|
||||
defer func() {
|
||||
if err != nil {
|
||||
// we can not use the ctx because it maybe canceled or timeout
|
||||
// we can not use `ctx` because it may be canceled or timed out
|
||||
if errDel := deleteFailedAdoptRepository(repo.ID); errDel != nil {
|
||||
log.Error("Failed to delete repository %s that could not be adopted: %v", repo.FullName(), errDel)
|
||||
}
|
||||
@@ -148,11 +147,11 @@ func adoptRepository(ctx context.Context, repo *repo_model.Repository, defaultBr
|
||||
}
|
||||
defer gitRepo.Close()
|
||||
|
||||
if _, err = repo_module.SyncRepoBranchesWithRepo(ctx, repo, gitRepo, 0); err != nil {
|
||||
if _, _, err = repo_module.SyncRepoBranchesWithRepo(ctx, repo, gitRepo, 0); err != nil {
|
||||
return fmt.Errorf("SyncRepoBranchesWithRepo: %w", err)
|
||||
}
|
||||
|
||||
if err = repo_module.SyncReleasesWithTags(ctx, repo, gitRepo); err != nil {
|
||||
if _, err = repo_module.SyncReleasesWithTags(ctx, repo, gitRepo); err != nil {
|
||||
return fmt.Errorf("SyncReleasesWithTags: %w", err)
|
||||
}
|
||||
|
||||
@@ -214,13 +213,13 @@ func DeleteUnadoptedRepository(ctx context.Context, doer, u *user_model.User, re
|
||||
return err
|
||||
}
|
||||
|
||||
repoPath := repo_model.RepoPath(u.Name, repoName)
|
||||
isExist, err := util.IsExist(repoPath)
|
||||
relativePath := repo_model.RelativePath(u.Name, repoName)
|
||||
exist, err := gitrepo.IsRepositoryExist(ctx, repo_model.StorageRepo(relativePath))
|
||||
if err != nil {
|
||||
log.Error("Unable to check if %s exists. Error: %v", repoPath, err)
|
||||
log.Error("Unable to check if %s exists. Error: %v", relativePath, err)
|
||||
return err
|
||||
}
|
||||
if !isExist {
|
||||
if !exist {
|
||||
return repo_model.ErrRepoNotExist{
|
||||
OwnerName: u.Name,
|
||||
Name: repoName,
|
||||
@@ -236,21 +235,20 @@ func DeleteUnadoptedRepository(ctx context.Context, doer, u *user_model.User, re
|
||||
}
|
||||
}
|
||||
|
||||
return util.RemoveAll(repoPath)
|
||||
return gitrepo.DeleteRepository(ctx, repo_model.StorageRepo(relativePath))
|
||||
}
|
||||
|
||||
type unadoptedRepositories struct {
|
||||
repositories []string
|
||||
index int
|
||||
start int
|
||||
end int
|
||||
count int64
|
||||
start, end int64
|
||||
}
|
||||
|
||||
func (unadopted *unadoptedRepositories) add(repository string) {
|
||||
if unadopted.index >= unadopted.start && unadopted.index < unadopted.end {
|
||||
if unadopted.count >= unadopted.start && unadopted.count < unadopted.end {
|
||||
unadopted.repositories = append(unadopted.repositories, repository)
|
||||
}
|
||||
unadopted.index++
|
||||
unadopted.count++
|
||||
}
|
||||
|
||||
func checkUnadoptedRepositories(ctx context.Context, userName string, repoNamesToCheck []string, unadopted *unadoptedRepositories) error {
|
||||
@@ -292,7 +290,8 @@ func checkUnadoptedRepositories(ctx context.Context, userName string, repoNamesT
|
||||
}
|
||||
|
||||
// ListUnadoptedRepositories lists all the unadopted repositories that match the provided query
|
||||
func ListUnadoptedRepositories(ctx context.Context, query string, opts *db.ListOptions) ([]string, int, error) {
|
||||
func ListUnadoptedRepositories(ctx context.Context, query string, opts *db.ListOptions) ([]string, int64, error) {
|
||||
opts.SetDefaultValues()
|
||||
globUser, _ := glob.Compile("*")
|
||||
globRepo, _ := glob.Compile("*")
|
||||
|
||||
@@ -312,12 +311,12 @@ func ListUnadoptedRepositories(ctx context.Context, query string, opts *db.ListO
|
||||
}
|
||||
var repoNamesToCheck []string
|
||||
|
||||
start := (opts.Page - 1) * opts.PageSize
|
||||
start := int64((opts.Page - 1) * opts.PageSize)
|
||||
unadopted := &unadoptedRepositories{
|
||||
repositories: make([]string, 0, opts.PageSize),
|
||||
start: start,
|
||||
end: start + opts.PageSize,
|
||||
index: 0,
|
||||
end: start + int64(opts.PageSize),
|
||||
count: 0,
|
||||
}
|
||||
|
||||
var userName string
|
||||
@@ -373,5 +372,5 @@ func ListUnadoptedRepositories(ctx context.Context, query string, opts *db.ListO
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return unadopted.repositories, unadopted.index, nil
|
||||
return unadopted.repositories, unadopted.count, nil
|
||||
}
|
||||
|
||||
@@ -20,20 +20,20 @@ import (
|
||||
)
|
||||
|
||||
func TestCheckUnadoptedRepositories_Add(t *testing.T) {
|
||||
start := 10
|
||||
end := 20
|
||||
const start = 10
|
||||
const end = 20
|
||||
unadopted := &unadoptedRepositories{
|
||||
start: start,
|
||||
end: end,
|
||||
index: 0,
|
||||
count: 0,
|
||||
}
|
||||
|
||||
total := 30
|
||||
const total = 30
|
||||
for range total {
|
||||
unadopted.add("something")
|
||||
}
|
||||
|
||||
assert.Equal(t, total, unadopted.index)
|
||||
assert.EqualValues(t, total, unadopted.count)
|
||||
assert.Len(t, unadopted.repositories, end-start)
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ func TestCheckUnadoptedRepositories(t *testing.T) {
|
||||
err = checkUnadoptedRepositories(t.Context(), userName, []string{repoName}, unadopted)
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, unadopted.repositories)
|
||||
assert.Equal(t, 0, unadopted.index)
|
||||
assert.Zero(t, unadopted.count)
|
||||
}
|
||||
|
||||
func TestListUnadoptedRepositories_ListOptions(t *testing.T) {
|
||||
@@ -78,13 +78,13 @@ func TestListUnadoptedRepositories_ListOptions(t *testing.T) {
|
||||
opts := db.ListOptions{Page: 1, PageSize: 1}
|
||||
repoNames, count, err := ListUnadoptedRepositories(t.Context(), "", &opts)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 2, count)
|
||||
assert.EqualValues(t, 2, count)
|
||||
assert.Equal(t, unadoptedList[0], repoNames[0])
|
||||
|
||||
opts = db.ListOptions{Page: 2, PageSize: 1}
|
||||
repoNames, count, err = ListUnadoptedRepositories(t.Context(), "", &opts)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 2, count)
|
||||
assert.EqualValues(t, 2, count)
|
||||
assert.Equal(t, unadoptedList[1], repoNames[0])
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -16,6 +15,7 @@ import (
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/git/gitcmd"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/graceful"
|
||||
"code.gitea.io/gitea/modules/httplib"
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/queue"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
gitea_context "code.gitea.io/gitea/services/context"
|
||||
)
|
||||
|
||||
@@ -33,61 +34,34 @@ import (
|
||||
// This is entirely opaque to external entities, though, and mostly used as a
|
||||
// handle elsewhere.
|
||||
type ArchiveRequest struct {
|
||||
RepoID int64
|
||||
Type git.ArchiveType
|
||||
Repo *repo_model.Repository
|
||||
Type repo_model.ArchiveType
|
||||
CommitID string
|
||||
Paths []string
|
||||
|
||||
archiveRefShortName string // the ref short name to download the archive, for example: "master", "v1.0.0", "commit id"
|
||||
}
|
||||
|
||||
// ErrUnknownArchiveFormat request archive format is not supported
|
||||
type ErrUnknownArchiveFormat struct {
|
||||
RequestNameType string
|
||||
}
|
||||
|
||||
// Error implements error
|
||||
func (err ErrUnknownArchiveFormat) Error() string {
|
||||
return "unknown format: " + err.RequestNameType
|
||||
}
|
||||
|
||||
// Is implements error
|
||||
func (ErrUnknownArchiveFormat) Is(err error) bool {
|
||||
_, ok := err.(ErrUnknownArchiveFormat)
|
||||
return ok
|
||||
}
|
||||
|
||||
// RepoRefNotFoundError is returned when a requested reference (commit, tag) was not found.
|
||||
type RepoRefNotFoundError struct {
|
||||
RefShortName string
|
||||
}
|
||||
|
||||
// Error implements error.
|
||||
func (e RepoRefNotFoundError) Error() string {
|
||||
return "unrecognized repository reference: " + e.RefShortName
|
||||
}
|
||||
|
||||
func (e RepoRefNotFoundError) Is(err error) bool {
|
||||
_, ok := err.(RepoRefNotFoundError)
|
||||
return ok
|
||||
}
|
||||
|
||||
// NewRequest creates an archival request, based on the URI. The
|
||||
// resulting ArchiveRequest is suitable for being passed to Await()
|
||||
// if it's determined that the request still needs to be satisfied.
|
||||
func NewRequest(repoID int64, repo *git.Repository, archiveRefExt string) (*ArchiveRequest, error) {
|
||||
func NewRequest(repo *repo_model.Repository, gitRepo *git.Repository, archiveRefExt string, paths []string) (*ArchiveRequest, error) {
|
||||
// here the archiveRefShortName is not a clear ref, it could be a tag, branch or commit id
|
||||
archiveRefShortName, archiveType := git.SplitArchiveNameType(archiveRefExt)
|
||||
if archiveType == git.ArchiveUnknown {
|
||||
return nil, ErrUnknownArchiveFormat{archiveRefExt}
|
||||
archiveRefShortName, archiveType := repo_model.SplitArchiveNameType(archiveRefExt)
|
||||
if archiveType == repo_model.ArchiveUnknown {
|
||||
return nil, util.NewInvalidArgumentErrorf("unknown format: %s", archiveRefExt)
|
||||
}
|
||||
if archiveType == repo_model.ArchiveBundle && len(paths) != 0 {
|
||||
return nil, util.NewInvalidArgumentErrorf("cannot specify paths when requesting a bundle")
|
||||
}
|
||||
|
||||
// Get corresponding commit.
|
||||
commitID, err := repo.ConvertToGitID(archiveRefShortName)
|
||||
commitID, err := gitRepo.ConvertToGitID(archiveRefShortName)
|
||||
if err != nil {
|
||||
return nil, RepoRefNotFoundError{RefShortName: archiveRefShortName}
|
||||
return nil, util.NewNotExistErrorf("unrecognized repository reference: %s", archiveRefShortName)
|
||||
}
|
||||
|
||||
r := &ArchiveRequest{RepoID: repoID, archiveRefShortName: archiveRefShortName, Type: archiveType}
|
||||
r := &ArchiveRequest{Repo: repo, archiveRefShortName: archiveRefShortName, Type: archiveType, Paths: paths}
|
||||
r.CommitID = commitID.String()
|
||||
return r, nil
|
||||
}
|
||||
@@ -105,7 +79,7 @@ func (aReq *ArchiveRequest) GetArchiveName() string {
|
||||
// context is cancelled/times out a started archiver will still continue to run
|
||||
// in the background.
|
||||
func (aReq *ArchiveRequest) Await(ctx context.Context) (*repo_model.RepoArchiver, error) {
|
||||
archiver, err := repo_model.GetRepoArchiver(ctx, aReq.RepoID, aReq.Type, aReq.CommitID)
|
||||
archiver, err := repo_model.GetRepoArchiver(ctx, aReq.Repo.ID, aReq.Type, aReq.CommitID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("models.GetRepoArchiver: %w", err)
|
||||
}
|
||||
@@ -130,7 +104,7 @@ func (aReq *ArchiveRequest) Await(ctx context.Context) (*repo_model.RepoArchiver
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case <-poll.C:
|
||||
archiver, err = repo_model.GetRepoArchiver(ctx, aReq.RepoID, aReq.Type, aReq.CommitID)
|
||||
archiver, err = repo_model.GetRepoArchiver(ctx, aReq.Repo.ID, aReq.Type, aReq.CommitID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("repo_model.GetRepoArchiver: %w", err)
|
||||
}
|
||||
@@ -143,20 +117,23 @@ func (aReq *ArchiveRequest) Await(ctx context.Context) (*repo_model.RepoArchiver
|
||||
|
||||
// Stream satisfies the ArchiveRequest being passed in. Processing
|
||||
// will occur directly in this routine.
|
||||
func (aReq *ArchiveRequest) Stream(ctx context.Context, gitRepo *git.Repository, w io.Writer) error {
|
||||
if aReq.Type == git.ArchiveBundle {
|
||||
return gitRepo.CreateBundle(
|
||||
func (aReq *ArchiveRequest) Stream(ctx context.Context, w io.Writer) error {
|
||||
if aReq.Type == repo_model.ArchiveBundle {
|
||||
return gitrepo.CreateBundle(
|
||||
ctx,
|
||||
aReq.Repo,
|
||||
aReq.CommitID,
|
||||
w,
|
||||
)
|
||||
}
|
||||
return gitRepo.CreateArchive(
|
||||
return gitrepo.CreateArchive(
|
||||
ctx,
|
||||
aReq.Type,
|
||||
aReq.Repo,
|
||||
aReq.Type.String(),
|
||||
w,
|
||||
setting.Repository.PrefixArchiveFiles,
|
||||
aReq.CommitID,
|
||||
aReq.Paths,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -167,10 +144,10 @@ func (aReq *ArchiveRequest) Stream(ctx context.Context, gitRepo *git.Repository,
|
||||
// being returned for completion, as it may be different than the one they passed
|
||||
// in.
|
||||
func doArchive(ctx context.Context, r *ArchiveRequest) (*repo_model.RepoArchiver, error) {
|
||||
ctx, _, finished := process.GetManager().AddContext(ctx, fmt.Sprintf("ArchiveRequest[%d]: %s", r.RepoID, r.GetArchiveName()))
|
||||
ctx, _, finished := process.GetManager().AddContext(ctx, fmt.Sprintf("ArchiveRequest[%s]: %s", r.Repo.FullName(), r.GetArchiveName()))
|
||||
defer finished()
|
||||
|
||||
archiver, err := repo_model.GetRepoArchiver(ctx, r.RepoID, r.Type, r.CommitID)
|
||||
archiver, err := repo_model.GetRepoArchiver(ctx, r.Repo.ID, r.Type, r.CommitID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -179,11 +156,11 @@ func doArchive(ctx context.Context, r *ArchiveRequest) (*repo_model.RepoArchiver
|
||||
// FIXME: If another process are generating it, we think it's not ready and just return
|
||||
// Or we should wait until the archive generated.
|
||||
if archiver.Status == repo_model.ArchiverGenerating {
|
||||
return nil, nil
|
||||
return nil, nil //nolint:nilnil // return nil because the archive is still being generated
|
||||
}
|
||||
} else {
|
||||
archiver = &repo_model.RepoArchiver{
|
||||
RepoID: r.RepoID,
|
||||
RepoID: r.Repo.ID,
|
||||
Type: r.Type,
|
||||
CommitID: r.CommitID,
|
||||
Status: repo_model.ArchiverGenerating,
|
||||
@@ -215,28 +192,18 @@ func doArchive(ctx context.Context, r *ArchiveRequest) (*repo_model.RepoArchiver
|
||||
_ = rd.Close()
|
||||
}()
|
||||
done := make(chan error, 1) // Ensure that there is some capacity which will ensure that the goroutine below can always finish
|
||||
repo, err := repo_model.GetRepositoryByID(ctx, archiver.RepoID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("archiver.LoadRepo failed: %w", err)
|
||||
}
|
||||
|
||||
gitRepo, err := gitrepo.OpenRepository(ctx, repo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer gitRepo.Close()
|
||||
|
||||
go func(done chan error, w *io.PipeWriter, archiveReq *ArchiveRequest, gitRepo *git.Repository) {
|
||||
go func(done chan error, w *io.PipeWriter, archiveReq *ArchiveRequest) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
done <- fmt.Errorf("%v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
err := archiveReq.Stream(ctx, gitRepo, w)
|
||||
err := archiveReq.Stream(ctx, w)
|
||||
_ = w.CloseWithError(err)
|
||||
done <- err
|
||||
}(done, w, r, gitRepo)
|
||||
}(done, w, r)
|
||||
|
||||
// TODO: add lfs data to zip
|
||||
// TODO: add submodule data to zip
|
||||
@@ -347,53 +314,54 @@ func DeleteRepositoryArchives(ctx context.Context) error {
|
||||
return storage.Clean(storage.RepoArchives)
|
||||
}
|
||||
|
||||
func ServeRepoArchive(ctx *gitea_context.Base, repo *repo_model.Repository, gitRepo *git.Repository, archiveReq *ArchiveRequest) {
|
||||
func ServeRepoArchive(ctx *gitea_context.Base, archiveReq *ArchiveRequest) error {
|
||||
// Add nix format link header so tarballs lock correctly:
|
||||
// https://github.com/nixos/nix/blob/56763ff918eb308db23080e560ed2ea3e00c80a7/doc/manual/src/protocols/tarball-fetcher.md
|
||||
ctx.Resp.Header().Add("Link", fmt.Sprintf(`<%s/archive/%s.%s?rev=%s>; rel="immutable"`,
|
||||
repo.APIURL(),
|
||||
archiveReq.Repo.APIURL(),
|
||||
archiveReq.CommitID,
|
||||
archiveReq.Type.String(),
|
||||
archiveReq.CommitID,
|
||||
))
|
||||
downloadName := repo.Name + "-" + archiveReq.GetArchiveName()
|
||||
downloadName := archiveReq.Repo.Name + "-" + archiveReq.GetArchiveName()
|
||||
|
||||
if setting.Repository.StreamArchives {
|
||||
httplib.ServeSetHeaders(ctx.Resp, &httplib.ServeHeaderOptions{Filename: downloadName})
|
||||
if err := archiveReq.Stream(ctx, gitRepo, ctx.Resp); err != nil && !ctx.Written() {
|
||||
log.Error("Archive %v streaming failed: %v", archiveReq, err)
|
||||
ctx.HTTPError(http.StatusInternalServerError)
|
||||
if setting.Repository.StreamArchives || len(archiveReq.Paths) > 0 {
|
||||
// the header must be set before starting streaming even an error would occur,
|
||||
// because errors may happen in git command and such cases aren't in our control.
|
||||
httplib.ServeSetHeaders(ctx.Resp, httplib.ServeHeaderOptions{Filename: downloadName})
|
||||
if err := archiveReq.Stream(ctx, ctx.Resp); err != nil && !ctx.Written() {
|
||||
if gitcmd.StderrHasPrefix(err, "fatal: pathspec") {
|
||||
return util.NewInvalidArgumentErrorf("path doesn't exist or is invalid")
|
||||
}
|
||||
return fmt.Errorf("archive repo %s: failed to stream: %w", archiveReq.Repo.FullName(), err)
|
||||
}
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
archiver, err := archiveReq.Await(ctx)
|
||||
if err != nil {
|
||||
log.Error("Archive %v await failed: %v", archiveReq, err)
|
||||
ctx.HTTPError(http.StatusInternalServerError)
|
||||
return
|
||||
return fmt.Errorf("archive repo %s: failed to await: %w", archiveReq.Repo.FullName(), err)
|
||||
}
|
||||
|
||||
rPath := archiver.RelativePath()
|
||||
if setting.RepoArchive.Storage.ServeDirect() {
|
||||
// If we have a signed url (S3, object storage), redirect to this directly.
|
||||
u, err := storage.RepoArchives.URL(rPath, downloadName, ctx.Req.Method, nil)
|
||||
u, err := storage.RepoArchives.ServeDirectURL(rPath, downloadName, ctx.Req.Method, nil)
|
||||
if u != nil && err == nil {
|
||||
ctx.Redirect(u.String())
|
||||
return
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
fr, err := storage.RepoArchives.Open(rPath)
|
||||
if err != nil {
|
||||
log.Error("Archive %v open file failed: %v", archiveReq, err)
|
||||
ctx.HTTPError(http.StatusInternalServerError)
|
||||
return
|
||||
return fmt.Errorf("archive repo %s: failed to open archive file: %w", archiveReq.Repo.FullName(), err)
|
||||
}
|
||||
defer fr.Close()
|
||||
|
||||
ctx.ServeContent(fr, &gitea_context.ServeHeaderOptions{
|
||||
ctx.ServeContent(fr, gitea_context.ServeHeaderOptions{
|
||||
Filename: downloadName,
|
||||
LastModified: archiver.CreatedUnix.AsLocalTime(),
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -8,11 +8,13 @@ import (
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/services/contexttest"
|
||||
|
||||
_ "code.gitea.io/gitea/models/actions"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
@@ -29,47 +31,47 @@ func TestArchive_Basic(t *testing.T) {
|
||||
contexttest.LoadGitRepo(t, ctx)
|
||||
defer ctx.Repo.GitRepo.Close()
|
||||
|
||||
bogusReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip")
|
||||
bogusReq, err := NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, firstCommit+".zip", nil)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, bogusReq)
|
||||
assert.Equal(t, firstCommit+".zip", bogusReq.GetArchiveName())
|
||||
|
||||
// Check a series of bogus requests.
|
||||
// Step 1, valid commit with a bad extension.
|
||||
bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".unknown")
|
||||
bogusReq, err = NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, firstCommit+".unknown", nil)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, bogusReq)
|
||||
|
||||
// Step 2, missing commit.
|
||||
bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "dbffff.zip")
|
||||
bogusReq, err = NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, "dbffff.zip", nil)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, bogusReq)
|
||||
|
||||
// Step 3, doesn't look like branch/tag/commit.
|
||||
bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "db.zip")
|
||||
bogusReq, err = NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, "db.zip", nil)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, bogusReq)
|
||||
|
||||
bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "master.zip")
|
||||
bogusReq, err = NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, "master.zip", nil)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, bogusReq)
|
||||
assert.Equal(t, "master.zip", bogusReq.GetArchiveName())
|
||||
|
||||
bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "test/archive.zip")
|
||||
bogusReq, err = NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, "test/archive.zip", nil)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, bogusReq)
|
||||
assert.Equal(t, "test-archive.zip", bogusReq.GetArchiveName())
|
||||
|
||||
// Now two valid requests, firstCommit with valid extensions.
|
||||
zipReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip")
|
||||
zipReq, err := NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, firstCommit+".zip", nil)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, zipReq)
|
||||
|
||||
tgzReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".tar.gz")
|
||||
tgzReq, err := NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, firstCommit+".tar.gz", nil)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, tgzReq)
|
||||
|
||||
secondReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, secondCommit+".bundle")
|
||||
secondReq, err := NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, secondCommit+".bundle", nil)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, secondReq)
|
||||
|
||||
@@ -89,7 +91,7 @@ func TestArchive_Basic(t *testing.T) {
|
||||
// Sleep two seconds to make sure the queue doesn't change.
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
zipReq2, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip")
|
||||
zipReq2, err := NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, firstCommit+".zip", nil)
|
||||
assert.NoError(t, err)
|
||||
// This zipReq should match what's sitting in the queue, as we haven't
|
||||
// let it release yet. From the consumer's point of view, this looks like
|
||||
@@ -104,12 +106,12 @@ func TestArchive_Basic(t *testing.T) {
|
||||
// Now we'll submit a request and TimedWaitForCompletion twice, before and
|
||||
// after we release it. We should trigger both the timeout and non-timeout
|
||||
// cases.
|
||||
timedReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, secondCommit+".tar.gz")
|
||||
timedReq, err := NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, secondCommit+".tar.gz", nil)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, timedReq)
|
||||
doArchive(t.Context(), timedReq)
|
||||
|
||||
zipReq2, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip")
|
||||
zipReq2, err = NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, firstCommit+".zip", nil)
|
||||
assert.NoError(t, err)
|
||||
// Now, we're guaranteed to have released the original zipReq from the queue.
|
||||
// Ensure that we don't get handed back the released entry somehow, but they
|
||||
@@ -124,9 +126,13 @@ func TestArchive_Basic(t *testing.T) {
|
||||
// Ideally, the extension would match what we originally requested.
|
||||
assert.NotEqual(t, zipReq.GetArchiveName(), tgzReq.GetArchiveName())
|
||||
assert.NotEqual(t, zipReq.GetArchiveName(), secondReq.GetArchiveName())
|
||||
}
|
||||
|
||||
func TestErrUnknownArchiveFormat(t *testing.T) {
|
||||
err := ErrUnknownArchiveFormat{RequestNameType: "xxx"}
|
||||
assert.ErrorIs(t, err, ErrUnknownArchiveFormat{})
|
||||
t.Run("BadPath", func(t *testing.T) {
|
||||
badRequest, err := NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, firstCommit+".tar.gz", []string{"not-a-path"})
|
||||
require.NoError(t, err)
|
||||
err = ServeRepoArchive(ctx.Base, badRequest)
|
||||
require.Error(t, err)
|
||||
assert.ErrorIs(t, err, util.ErrInvalidArgument)
|
||||
assert.ErrorContains(t, err, "path doesn't exist or is invalid")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
func UploadAvatar(ctx context.Context, repo *repo_model.Repository, data []byte) error {
|
||||
avatarData, err := avatar.ProcessAvatarImage(data)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("UploadAvatar: failed to process repo avatar image: %w", err)
|
||||
}
|
||||
|
||||
newAvatar := avatar.HashAvatar(repo.ID, data)
|
||||
@@ -36,19 +36,19 @@ func UploadAvatar(ctx context.Context, repo *repo_model.Repository, data []byte)
|
||||
// Then repo will be removed - only it avatar file will be removed
|
||||
repo.Avatar = newAvatar
|
||||
if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "avatar"); err != nil {
|
||||
return fmt.Errorf("UploadAvatar: Update repository avatar: %w", err)
|
||||
return fmt.Errorf("UploadAvatar: failed to update repository avatar: %w", err)
|
||||
}
|
||||
|
||||
if err := storage.SaveFrom(storage.RepoAvatars, repo.CustomAvatarRelativePath(), func(w io.Writer) error {
|
||||
_, err := w.Write(avatarData)
|
||||
return err
|
||||
}); err != nil {
|
||||
return fmt.Errorf("UploadAvatar %s failed: Failed to remove old repo avatar %s: %w", repo.RepoPath(), newAvatar, err)
|
||||
return fmt.Errorf("UploadAvatar: failed to save repo avatar %s: %w", newAvatar, err)
|
||||
}
|
||||
|
||||
if len(oldAvatarPath) > 0 {
|
||||
if err := storage.RepoAvatars.Delete(oldAvatarPath); err != nil {
|
||||
return fmt.Errorf("UploadAvatar: Failed to remove old repo avatar %s: %w", oldAvatarPath, err)
|
||||
return fmt.Errorf("UploadAvatar: failed to remove old repo avatar %s: %w", oldAvatarPath, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -33,19 +33,18 @@ import (
|
||||
actions_service "code.gitea.io/gitea/services/actions"
|
||||
notify_service "code.gitea.io/gitea/services/notify"
|
||||
release_service "code.gitea.io/gitea/services/release"
|
||||
files_service "code.gitea.io/gitea/services/repository/files"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// CreateNewBranch creates a new repository branch
|
||||
func CreateNewBranch(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, oldBranchName, branchName string) (err error) {
|
||||
func CreateNewBranch(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, oldBranchName, branchName string) (err error) {
|
||||
branch, err := git_model.GetBranch(ctx, repo.ID, oldBranchName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return CreateNewBranchFromCommit(ctx, doer, repo, gitRepo, branch.CommitID, branchName)
|
||||
return CreateNewBranchFromCommit(ctx, doer, repo, branch.CommitID, branchName)
|
||||
}
|
||||
|
||||
// Branch contains the branch information
|
||||
@@ -123,9 +122,9 @@ func getDivergenceCacheKey(repoID int64, branchName string) string {
|
||||
}
|
||||
|
||||
// getDivergenceFromCache gets the divergence from cache
|
||||
func getDivergenceFromCache(repoID int64, branchName string) (*git.DivergeObject, bool) {
|
||||
func getDivergenceFromCache(repoID int64, branchName string) (*gitrepo.DivergeObject, bool) {
|
||||
data, ok := cache.GetCache().Get(getDivergenceCacheKey(repoID, branchName))
|
||||
res := git.DivergeObject{
|
||||
res := gitrepo.DivergeObject{
|
||||
Ahead: -1,
|
||||
Behind: -1,
|
||||
}
|
||||
@@ -139,7 +138,7 @@ func getDivergenceFromCache(repoID int64, branchName string) (*git.DivergeObject
|
||||
return &res, true
|
||||
}
|
||||
|
||||
func putDivergenceFromCache(repoID int64, branchName string, divergence *git.DivergeObject) error {
|
||||
func putDivergenceFromCache(repoID int64, branchName string, divergence *gitrepo.DivergeObject) error {
|
||||
bs, err := json.Marshal(divergence)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -178,7 +177,7 @@ func loadOneBranch(ctx context.Context, repo *repo_model.Repository, dbBranch *g
|
||||
p := protectedBranches.GetFirstMatched(branchName)
|
||||
isProtected := p != nil
|
||||
|
||||
var divergence *git.DivergeObject
|
||||
var divergence *gitrepo.DivergeObject
|
||||
|
||||
// it's not default branch
|
||||
if repo.DefaultBranch != dbBranch.Name && !dbBranch.IsDeleted {
|
||||
@@ -186,9 +185,9 @@ func loadOneBranch(ctx context.Context, repo *repo_model.Repository, dbBranch *g
|
||||
divergence, cached = getDivergenceFromCache(repo.ID, dbBranch.Name)
|
||||
if !cached {
|
||||
var err error
|
||||
divergence, err = files_service.CountDivergingCommits(ctx, repo, git.BranchPrefix+branchName)
|
||||
divergence, err = gitrepo.GetDivergingCommits(ctx, repo, repo.DefaultBranch, git.BranchPrefix+branchName)
|
||||
if err != nil {
|
||||
log.Error("CountDivergingCommits: %v", err)
|
||||
log.Error("GetDivergingCommits: %v", err)
|
||||
} else {
|
||||
if err = putDivergenceFromCache(repo.ID, dbBranch.Name, divergence); err != nil {
|
||||
log.Error("putDivergenceFromCache: %v", err)
|
||||
@@ -199,7 +198,7 @@ func loadOneBranch(ctx context.Context, repo *repo_model.Repository, dbBranch *g
|
||||
|
||||
if divergence == nil {
|
||||
// tolerate the error that we cannot get divergence
|
||||
divergence = &git.DivergeObject{Ahead: -1, Behind: -1}
|
||||
divergence = &gitrepo.DivergeObject{Ahead: -1, Behind: -1}
|
||||
}
|
||||
|
||||
pr, err := issues_model.GetLatestPullRequestByHeadInfo(ctx, repo.ID, branchName)
|
||||
@@ -265,12 +264,12 @@ func checkBranchName(ctx context.Context, repo *repo_model.Repository, name stri
|
||||
return git_model.ErrBranchAlreadyExists{
|
||||
BranchName: name,
|
||||
}
|
||||
// If branchRefName like a/b but we want to create a branch named a then we have a conflict
|
||||
// If branchRefName like "a/b" but we want to create a branch named a then we have a conflict
|
||||
case strings.HasPrefix(branchRefName, name+"/"):
|
||||
return git_model.ErrBranchNameConflict{
|
||||
BranchName: branchRefName,
|
||||
}
|
||||
// Conversely if branchRefName like a but we want to create a branch named a/b then we also have a conflict
|
||||
// Conversely if branchRefName like "a" but we want to create a branch named "a/b" then we also have a conflict
|
||||
case strings.HasPrefix(name, branchRefName+"/"):
|
||||
return git_model.ErrBranchNameConflict{
|
||||
BranchName: branchRefName,
|
||||
@@ -282,7 +281,6 @@ func checkBranchName(ctx context.Context, repo *repo_model.Repository, name stri
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -375,7 +373,7 @@ func SyncBranchesToDB(ctx context.Context, repoID, pusherID int64, branchNames,
|
||||
}
|
||||
|
||||
// CreateNewBranchFromCommit creates a new repository branch
|
||||
func CreateNewBranchFromCommit(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, commitID, branchName string) (err error) {
|
||||
func CreateNewBranchFromCommit(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, commitID, branchName string) (err error) {
|
||||
err = repo.MustNotBeArchived()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -386,8 +384,7 @@ func CreateNewBranchFromCommit(ctx context.Context, doer *user_model.User, repo
|
||||
return err
|
||||
}
|
||||
|
||||
if err := git.Push(ctx, repo.RepoPath(), git.PushOptions{
|
||||
Remote: repo.RepoPath(),
|
||||
if err := gitrepo.Push(ctx, repo, repo, git.PushOptions{
|
||||
Branch: fmt.Sprintf("%s:%s%s", commitID, git.BranchPrefix, branchName),
|
||||
Env: repo_module.PushingEnvironment(doer, repo),
|
||||
}); err != nil {
|
||||
@@ -400,7 +397,7 @@ func CreateNewBranchFromCommit(ctx context.Context, doer *user_model.User, repo
|
||||
}
|
||||
|
||||
// RenameBranch rename a branch
|
||||
func RenameBranch(ctx context.Context, repo *repo_model.Repository, doer *user_model.User, gitRepo *git.Repository, from, to string) (string, error) {
|
||||
func RenameBranch(ctx context.Context, repo *repo_model.Repository, doer *user_model.User, from, to string) (string, error) {
|
||||
err := repo.MustNotBeArchived()
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -410,15 +407,19 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, doer *user_m
|
||||
return "target_exist", nil
|
||||
}
|
||||
|
||||
if gitrepo.IsBranchExist(ctx, repo, to) {
|
||||
if exist, _ := git_model.IsBranchExist(ctx, repo.ID, to); exist {
|
||||
return "target_exist", nil
|
||||
}
|
||||
|
||||
if !gitrepo.IsBranchExist(ctx, repo, from) {
|
||||
return "from_not_exist", nil
|
||||
fromBranch, err := git_model.GetBranch(ctx, repo.ID, from)
|
||||
if err != nil {
|
||||
if git_model.IsErrBranchNotExist(err) {
|
||||
return "from_not_exist", nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
perm, err := access_model.GetUserRepoPermission(ctx, repo, doer)
|
||||
perm, err := access_model.GetDoerRepoPermission(ctx, repo, doer)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -441,8 +442,17 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, doer *user_m
|
||||
}
|
||||
}
|
||||
|
||||
// We also need to check if "to" matches with a protected branch rule.
|
||||
rule, err := git_model.GetFirstMatchProtectedBranchRule(ctx, repo.ID, to)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if rule != nil && !rule.CanUserPush(ctx, doer) {
|
||||
return "", git_model.ErrBranchIsProtected
|
||||
}
|
||||
|
||||
if err := git_model.RenameBranch(ctx, repo, from, to, func(ctx context.Context, isDefault bool) error {
|
||||
err2 := gitRepo.RenameBranch(from, to)
|
||||
err2 := gitrepo.RenameBranch(ctx, repo, from, to)
|
||||
if err2 != nil {
|
||||
return err2
|
||||
}
|
||||
@@ -473,26 +483,79 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, doer *user_m
|
||||
}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
refNameTo := git.RefNameFromBranch(to)
|
||||
refID, err := gitRepo.GetRefCommitID(refNameTo.String())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
notify_service.DeleteRef(ctx, doer, repo, git.RefNameFromBranch(from))
|
||||
notify_service.CreateRef(ctx, doer, repo, refNameTo, refID)
|
||||
notify_service.CreateRef(ctx, doer, repo, git.RefNameFromBranch(to), fromBranch.CommitID)
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
var ErrBranchIsDefault = util.ErrorWrap(util.ErrPermissionDenied, "branch is default")
|
||||
// UpdateBranch moves a branch reference to the provided commit. permission check should be done before calling this function.
|
||||
func UpdateBranch(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, doer *user_model.User, branchName, newCommitID, expectedOldCommitID string, force bool) error {
|
||||
branch, err := git_model.GetBranch(ctx, repo.ID, branchName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if branch.IsDeleted {
|
||||
return git_model.ErrBranchNotExist{
|
||||
BranchName: branchName,
|
||||
}
|
||||
}
|
||||
|
||||
if expectedOldCommitID != "" {
|
||||
expectedID, err := gitRepo.ConvertToGitID(expectedOldCommitID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ConvertToGitID(old): %w", err)
|
||||
}
|
||||
if expectedID.String() != branch.CommitID {
|
||||
return util.NewInvalidArgumentErrorf("branch commit does not match [expected: %s, given: %s]", expectedID.String(), branch.CommitID)
|
||||
}
|
||||
}
|
||||
|
||||
newID, err := gitRepo.ConvertToGitID(newCommitID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ConvertToGitID(new): %w", err)
|
||||
}
|
||||
newCommit, err := gitRepo.GetCommit(newID.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if newCommit.ID.String() == branch.CommitID {
|
||||
return nil
|
||||
}
|
||||
|
||||
isForcePush, err := newCommit.IsForcePush(branch.CommitID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isForcePush && !force {
|
||||
return util.NewInvalidArgumentErrorf("Force push %s need a confirm force parameter", branchName)
|
||||
}
|
||||
|
||||
pushOpts := git.PushOptions{
|
||||
Branch: fmt.Sprintf("%s:%s%s", newCommit.ID.String(), git.BranchPrefix, branchName),
|
||||
Env: repo_module.PushingEnvironment(doer, repo),
|
||||
Force: isForcePush || force,
|
||||
}
|
||||
|
||||
if expectedOldCommitID != "" {
|
||||
pushOpts.ForceWithLease = fmt.Sprintf("%s:%s", git.BranchPrefix+branchName, branch.CommitID)
|
||||
}
|
||||
|
||||
// branch protection will be checked in the pre received hook, so that we don't need any check here
|
||||
return gitrepo.Push(ctx, repo, repo, pushOpts)
|
||||
}
|
||||
|
||||
var ErrBranchIsDefault = util.ErrorWrap(util.ErrPermissionDenied, "branch is default or pull request target")
|
||||
|
||||
func CanDeleteBranch(ctx context.Context, repo *repo_model.Repository, branchName string, doer *user_model.User) error {
|
||||
if branchName == repo.DefaultBranch {
|
||||
unitPRConfig := repo.MustGetUnit(ctx, unit.TypePullRequests).PullRequestsConfig()
|
||||
if branchName == repo.DefaultBranch || branchName == unitPRConfig.DefaultTargetBranch {
|
||||
return ErrBranchIsDefault
|
||||
}
|
||||
|
||||
perm, err := access_model.GetUserRepoPermission(ctx, repo, doer)
|
||||
perm, err := access_model.GetDoerRepoPermission(ctx, repo, doer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -510,8 +573,38 @@ func CanDeleteBranch(ctx context.Context, repo *repo_model.Repository, branchNam
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteBranchInternal(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, branchName string, branchCommit *git.Commit) (branchExisted bool, err error) {
|
||||
activeInDB, err := git_model.IsBranchExist(ctx, repo.ID, branchName)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("IsBranchExist: %w", err)
|
||||
}
|
||||
|
||||
// process the branch in db
|
||||
if activeInDB {
|
||||
if err := git_model.MarkBranchAsDeleted(ctx, repo.ID, branchName, doer.ID); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
// process the branch in git
|
||||
if branchCommit != nil {
|
||||
err := gitrepo.DeleteBranch(ctx, repo, branchName, true)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("DeleteBranch: %w", err)
|
||||
}
|
||||
// since the branch existed in git, return branchExisted=true
|
||||
branchExisted = true
|
||||
} else {
|
||||
// the branch didn't exist in git, return activeInDB to indicate whether the branch was active in DB,
|
||||
// for consistency with that the user had seen on the web ui or in the branch list API response.
|
||||
branchExisted = activeInDB
|
||||
}
|
||||
|
||||
return branchExisted, nil
|
||||
}
|
||||
|
||||
// DeleteBranch delete branch
|
||||
func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, branchName string, pr *issues_model.PullRequest) error {
|
||||
func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, branchName string) error {
|
||||
err := repo.MustNotBeArchived()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -521,48 +614,31 @@ func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.R
|
||||
return err
|
||||
}
|
||||
|
||||
rawBranch, err := git_model.GetBranch(ctx, repo.ID, branchName)
|
||||
if err != nil && !git_model.IsErrBranchNotExist(err) {
|
||||
return fmt.Errorf("GetBranch: %vc", err)
|
||||
}
|
||||
|
||||
// database branch record not exist or it's a deleted branch
|
||||
notExist := git_model.IsErrBranchNotExist(err) || rawBranch.IsDeleted
|
||||
|
||||
branchCommit, err := gitRepo.GetBranchCommit(branchName)
|
||||
// branchCommit can be nil if the branch doesn't exist in git
|
||||
if err != nil && !errors.Is(err, util.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||
if !notExist {
|
||||
if err := git_model.AddDeletedBranch(ctx, repo.ID, branchName, doer.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if pr != nil {
|
||||
if err := issues_model.AddDeletePRBranchComment(ctx, doer, pr.BaseRepo, pr.Issue.ID, pr.HeadBranch); err != nil {
|
||||
return fmt.Errorf("DeleteBranch: %v", err)
|
||||
}
|
||||
}
|
||||
if branchCommit == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gitRepo.DeleteBranch(branchName, git.DeleteBranchOptions{
|
||||
Force: true,
|
||||
})
|
||||
}); err != nil {
|
||||
branchExisted, err := db.WithTx2(ctx, func(ctx context.Context) (bool, error) {
|
||||
return deleteBranchInternal(ctx, doer, repo, branchName, branchCommit)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if branchCommit == nil {
|
||||
return nil
|
||||
if !branchExisted {
|
||||
return git.ErrBranchNotExist{Name: branchName}
|
||||
}
|
||||
|
||||
// Don't return error below this
|
||||
// Don't return error below this since the deletion has succeeded
|
||||
if branchCommit != nil {
|
||||
deleteBranchSuccessPostProcess(doer, repo, branchName, branchCommit)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteBranchSuccessPostProcess(doer *user_model.User, repo *repo_model.Repository, branchName string, branchCommit *git.Commit) {
|
||||
objectFormat := git.ObjectFormatFromName(repo.ObjectFormatName)
|
||||
if err := PushUpdate(
|
||||
&repo_module.PushUpdateOptions{
|
||||
@@ -576,8 +652,6 @@ func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.R
|
||||
}); err != nil {
|
||||
log.Error("PushUpdateOptions: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type BranchSyncOptions struct {
|
||||
@@ -627,7 +701,7 @@ func SetRepoDefaultBranch(ctx context.Context, repo *repo_model.Repository, newB
|
||||
return nil
|
||||
}
|
||||
|
||||
if !gitrepo.IsBranchExist(ctx, repo, newBranchName) {
|
||||
if exist, _ := git_model.IsBranchExist(ctx, repo.ID, newBranchName); !exist {
|
||||
return git_model.ErrBranchNotExist{
|
||||
BranchName: newBranchName,
|
||||
}
|
||||
@@ -717,7 +791,7 @@ func GetBranchDivergingInfo(ctx reqctx.RequestContext, baseRepo *repo_model.Repo
|
||||
// if the fork repo has new commits, this call will fail because they are not in the base repo
|
||||
// exit status 128 - fatal: Invalid symmetric difference expression aaaaaaaaaaaa...bbbbbbbbbbbb
|
||||
// so at the moment, we first check the update time, then check whether the fork branch has base's head
|
||||
diff, err := git.GetDivergingCommits(ctx, baseRepo.RepoPath(), baseGitBranch.CommitID, headGitBranch.CommitID)
|
||||
diff, err := gitrepo.GetDivergingCommits(ctx, baseRepo, baseGitBranch.CommitID, headGitBranch.CommitID)
|
||||
if err != nil {
|
||||
info.BaseHasNewCommits = baseGitBranch.UpdatedUnix > headGitBranch.UpdatedUnix
|
||||
if headRepo.IsFork && info.BaseHasNewCommits {
|
||||
@@ -825,9 +899,17 @@ func DeleteBranchAfterMerge(ctx context.Context, doer *user_model.User, prID int
|
||||
return util.ErrorWrapTranslatable(util.ErrUnprocessableContent, "repo.branch.delete_branch_has_new_commits", fullBranchName)
|
||||
}
|
||||
|
||||
err = DeleteBranch(ctx, doer, pr.HeadRepo, gitHeadRepo, pr.HeadBranch, pr)
|
||||
err = DeleteBranch(ctx, doer, pr.HeadRepo, gitHeadRepo, pr.HeadBranch)
|
||||
if errors.Is(err, util.ErrPermissionDenied) || errors.Is(err, util.ErrNotExist) {
|
||||
return errFailedToDelete(err)
|
||||
}
|
||||
return err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// intentionally ignore the following error, since the branch has already been deleted successfully
|
||||
if err := issues_model.AddDeletePRBranchComment(ctx, doer, pr.BaseRepo, pr.Issue.ID, pr.HeadBranch); err != nil {
|
||||
log.Error("AddDeletePRBranchComment: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/cache"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
)
|
||||
|
||||
// CacheRef cachhe last commit information of the branch or the tag
|
||||
@@ -19,7 +20,9 @@ func CacheRef(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Rep
|
||||
}
|
||||
|
||||
if gitRepo.LastCommitCache == nil {
|
||||
commitsCount, err := cache.GetInt64(repo.GetCommitsCountCacheKey(fullRefName.ShortName(), true), commit.CommitsCount)
|
||||
commitsCount, err := cache.GetInt64(repo.GetCommitsCountCacheKey(fullRefName.ShortName(), true), func() (int64, error) {
|
||||
return gitrepo.CommitsCountOfCommit(ctx, repo, commit.ID.String())
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
@@ -89,24 +88,24 @@ func GitGcRepo(ctx context.Context, repo *repo_model.Repository, timeout time.Du
|
||||
command := gitcmd.NewCommand("gc").AddArguments(args...)
|
||||
var stdout string
|
||||
var err error
|
||||
stdout, _, err = command.RunStdString(ctx, &gitcmd.RunOpts{Timeout: timeout, Dir: repo.RepoPath()})
|
||||
stdout, _, err = gitrepo.RunCmdString(ctx, repo, command)
|
||||
if err != nil {
|
||||
log.Error("Repository garbage collection failed for %-v. Stdout: %s\nError: %v", repo, stdout, err)
|
||||
desc := fmt.Sprintf("Repository garbage collection failed for %s. Stdout: %s\nError: %v", repo.RepoPath(), stdout, err)
|
||||
desc := fmt.Sprintf("Repository garbage collection failed (%s). Stdout: %s\nError: %v", repo.FullName(), stdout, err)
|
||||
if err := system_model.CreateRepositoryNotice(desc); err != nil {
|
||||
log.Error("CreateRepositoryNotice: %v", err)
|
||||
}
|
||||
return fmt.Errorf("Repository garbage collection failed in repo: %s: Error: %w", repo.FullName(), err)
|
||||
return fmt.Errorf("Repository garbage collection failed in repo: %s: Error: %w", repo.RelativePath(), err)
|
||||
}
|
||||
|
||||
// Now update the size of the repository
|
||||
if err := repo_module.UpdateRepoSize(ctx, repo); err != nil {
|
||||
log.Error("Updating size as part of garbage collection failed for %-v. Stdout: %s\nError: %v", repo, stdout, err)
|
||||
desc := fmt.Sprintf("Updating size as part of garbage collection failed for %s. Stdout: %s\nError: %v", repo.RepoPath(), stdout, err)
|
||||
desc := fmt.Sprintf("Updating size as part of garbage collection failed (%s). Stdout: %s\nError: %v", repo.FullName(), stdout, err)
|
||||
if err := system_model.CreateRepositoryNotice(desc); err != nil {
|
||||
log.Error("CreateRepositoryNotice: %v", err)
|
||||
}
|
||||
return fmt.Errorf("Updating size as part of garbage collection failed in repo: %s: Error: %w", repo.FullName(), err)
|
||||
return fmt.Errorf("Updating size as part of garbage collection failed in repo: %s: Error: %w", repo.RelativePath(), err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -123,11 +122,11 @@ func gatherMissingRepoRecords(ctx context.Context) (repo_model.RepositoryList, e
|
||||
return db.ErrCancelledf("during gathering missing repo records before checking %s", repo.FullName())
|
||||
default:
|
||||
}
|
||||
isDir, err := util.IsDir(repo.RepoPath())
|
||||
exist, err := gitrepo.IsRepositoryExist(ctx, repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to check dir for %s. %w", repo.FullName(), err)
|
||||
}
|
||||
if !isDir {
|
||||
if !exist {
|
||||
repos = append(repos, repo)
|
||||
}
|
||||
return nil
|
||||
@@ -164,7 +163,7 @@ func DeleteMissingRepositories(ctx context.Context, doer *user_model.User) error
|
||||
log.Trace("Deleting %d/%d...", repo.OwnerID, repo.ID)
|
||||
if err := DeleteRepositoryDirectly(ctx, repo.ID); err != nil {
|
||||
log.Error("Failed to DeleteRepository %-v: Error: %v", repo, err)
|
||||
if err2 := system_model.CreateRepositoryNotice("Failed to DeleteRepository %s [%d]: Error: %v", repo.FullName(), repo.ID, err); err2 != nil {
|
||||
if err2 := system_model.CreateRepositoryNotice("Failed to DeleteRepository (%s) [%d]: Error: %v", repo.FullName(), repo.ID, err); err2 != nil {
|
||||
log.Error("CreateRepositoryNotice: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -191,8 +190,8 @@ func ReinitMissingRepositories(ctx context.Context) error {
|
||||
}
|
||||
log.Trace("Initializing %d/%d...", repo.OwnerID, repo.ID)
|
||||
if err := gitrepo.InitRepository(ctx, repo, repo.ObjectFormatName); err != nil {
|
||||
log.Error("Unable (re)initialize repository %d at %s. Error: %v", repo.ID, repo.RepoPath(), err)
|
||||
if err2 := system_model.CreateRepositoryNotice("InitRepository [%d]: %v", repo.ID, err); err2 != nil {
|
||||
log.Error("Unable (re)initialize repository %d at %s. Error: %v", repo.ID, repo.RelativePath(), err)
|
||||
if err2 := system_model.CreateRepositoryNotice("InitRepository (%s) [%d]: %v", repo.FullName(), repo.ID, err); err2 != nil {
|
||||
log.Error("CreateRepositoryNotice: %v", err2)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,12 +69,10 @@ func deleteCommitStatusCache(repoID int64, branchName string) error {
|
||||
// NOTE: All text-values will be trimmed from whitespaces.
|
||||
// Requires: Repo, Creator, SHA
|
||||
func CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, creator *user_model.User, sha string, status *git_model.CommitStatus) error {
|
||||
repoPath := repo.RepoPath()
|
||||
|
||||
// confirm that commit is exist
|
||||
gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("OpenRepository[%s]: %w", repoPath, err)
|
||||
return fmt.Errorf("OpenRepository[%s]: %w", repo.RelativePath(), err)
|
||||
}
|
||||
defer closer.Close()
|
||||
|
||||
@@ -120,8 +118,8 @@ func CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, creato
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindReposLastestCommitStatuses loading repository default branch latest combined commit status with cache
|
||||
func FindReposLastestCommitStatuses(ctx context.Context, repos []*repo_model.Repository) ([]*git_model.CommitStatus, error) {
|
||||
// FindReposLatestCommitStatuses loading repository default branch latest combined commit status with cache
|
||||
func FindReposLatestCommitStatuses(ctx context.Context, repos []*repo_model.Repository) ([]*git_model.CommitStatus, error) {
|
||||
results := make([]*git_model.CommitStatus, len(repos))
|
||||
allCached := true
|
||||
for i, repo := range repos {
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -85,7 +84,7 @@ func GetContributorStats(ctx context.Context, cache cache.StringCache, repo *rep
|
||||
if !cache.IsExist(cacheKey) {
|
||||
genReady := make(chan struct{})
|
||||
|
||||
// dont start multiple async generations
|
||||
// don't start multiple async generations
|
||||
_, run := generateLock.Load(cacheKey)
|
||||
if run {
|
||||
return nil, ErrAwaitGeneration
|
||||
@@ -117,27 +116,17 @@ func getExtendedCommitStats(repo *git.Repository, revision string /*, limit int
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stdoutReader, stdoutWriter, err := os.Pipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
_ = stdoutReader.Close()
|
||||
_ = stdoutWriter.Close()
|
||||
}()
|
||||
|
||||
gitCmd := gitcmd.NewCommand("log", "--shortstat", "--no-merges", "--pretty=format:---%n%aN%n%aE%n%as", "--reverse")
|
||||
// AddOptionFormat("--max-count=%d", limit)
|
||||
gitCmd.AddDynamicArguments(baseCommit.ID.String())
|
||||
|
||||
stdoutReader, stdoutReaderClose := gitCmd.MakeStdoutPipe()
|
||||
defer stdoutReaderClose()
|
||||
|
||||
var extendedCommitStats []*ExtendedCommitStats
|
||||
stderr := new(strings.Builder)
|
||||
err = gitCmd.Run(repo.Ctx, &gitcmd.RunOpts{
|
||||
Dir: repo.Path,
|
||||
Stdout: stdoutWriter,
|
||||
Stderr: stderr,
|
||||
PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error {
|
||||
_ = stdoutWriter.Close()
|
||||
err = gitCmd.WithDir(repo.Path).
|
||||
WithPipelineFunc(func(ctx gitcmd.Context) error {
|
||||
scanner := bufio.NewScanner(stdoutReader)
|
||||
|
||||
for scanner.Scan() {
|
||||
@@ -189,12 +178,11 @@ func getExtendedCommitStats(repo *git.Repository, revision string /*, limit int
|
||||
}
|
||||
extendedCommitStats = append(extendedCommitStats, res)
|
||||
}
|
||||
_ = stdoutReader.Close()
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}).
|
||||
RunWithStderr(repo.Ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to get ContributorsCommitStats for repository.\nError: %w\nStderr: %s", err, stderr)
|
||||
return nil, fmt.Errorf("ContributorsCommitStats: %w", err)
|
||||
}
|
||||
|
||||
return extendedCommitStats, nil
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/templates/vars"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
// CreateRepoOptions contains the create repository options
|
||||
@@ -69,9 +70,10 @@ func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir
|
||||
)
|
||||
|
||||
// Clone to temporary path and do the init commit.
|
||||
if stdout, _, err := gitcmd.NewCommand("clone").AddDynamicArguments(repo.RepoPath(), tmpDir).
|
||||
RunStdString(ctx, &gitcmd.RunOpts{Dir: "", Env: env}); err != nil {
|
||||
log.Error("Failed to clone from %v into %s: stdout: %s\nError: %v", repo, tmpDir, stdout, err)
|
||||
if err := gitrepo.CloneRepoToLocal(ctx, repo, tmpDir, git.CloneRepoOptions{
|
||||
Env: env,
|
||||
}); err != nil {
|
||||
log.Error("Failed to clone from %v into %s\nError: %v", repo, tmpDir, err)
|
||||
return fmt.Errorf("git clone: %w", err)
|
||||
}
|
||||
|
||||
@@ -84,7 +86,7 @@ func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir
|
||||
cloneLink := repo.CloneLink(ctx, nil /* no doer so do not generate user-related SSH link */)
|
||||
match := map[string]string{
|
||||
"Name": repo.Name,
|
||||
"Description": repo.Description,
|
||||
"Description": util.NormalizeStringEOL(repo.Description),
|
||||
"CloneURL.SSH": cloneLink.SSH,
|
||||
"CloneURL.HTTPS": cloneLink.HTTPS,
|
||||
"OwnerName": repo.OwnerName,
|
||||
@@ -229,6 +231,9 @@ func CreateRepositoryDirectly(ctx context.Context, doer, owner *user_model.User,
|
||||
if opts.ObjectFormatName == "" {
|
||||
opts.ObjectFormatName = git.Sha1ObjectFormat.Name()
|
||||
}
|
||||
if opts.ObjectFormatName != git.Sha1ObjectFormat.Name() && opts.ObjectFormatName != git.Sha256ObjectFormat.Name() {
|
||||
return nil, fmt.Errorf("unsupported object format: %s", opts.ObjectFormatName)
|
||||
}
|
||||
|
||||
repo := &repo_model.Repository{
|
||||
OwnerID: owner.ID,
|
||||
@@ -264,8 +269,8 @@ func CreateRepositoryDirectly(ctx context.Context, doer, owner *user_model.User,
|
||||
// WARNING: Don't override all later err with local variables
|
||||
defer func() {
|
||||
if err != nil {
|
||||
// we can not use the ctx because it maybe canceled or timeout
|
||||
cleanupRepository(repo.ID)
|
||||
// we can not use `ctx` because it may be canceled or timed out
|
||||
cleanupRepository(repo)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -314,7 +319,7 @@ func CreateRepositoryDirectly(ctx context.Context, doer, owner *user_model.User,
|
||||
licenses = append(licenses, opts.License)
|
||||
|
||||
var stdout string
|
||||
stdout, _, err = gitcmd.NewCommand("rev-parse", "HEAD").RunStdString(ctx, &gitcmd.RunOpts{Dir: repo.RepoPath()})
|
||||
stdout, _, err = gitrepo.RunCmdString(ctx, repo, gitcmd.NewCommand("rev-parse", "HEAD"))
|
||||
if err != nil {
|
||||
log.Error("CreateRepository(git rev-parse HEAD) in %v: Stdout: %s\nError: %v", repo, stdout, err)
|
||||
return nil, fmt.Errorf("CreateRepository(git rev-parse HEAD): %w", err)
|
||||
@@ -382,15 +387,7 @@ func createRepositoryInDB(ctx context.Context, doer, u *user_model.User, repo *r
|
||||
},
|
||||
})
|
||||
case unit.TypePullRequests:
|
||||
units = append(units, repo_model.RepoUnit{
|
||||
RepoID: repo.ID,
|
||||
Type: tp,
|
||||
Config: &repo_model.PullRequestsConfig{
|
||||
AllowMerge: true, AllowRebase: true, AllowRebaseMerge: true, AllowSquash: true, AllowFastForwardOnly: true,
|
||||
DefaultMergeStyle: repo_model.MergeStyle(setting.Repository.PullRequest.DefaultMergeStyle),
|
||||
AllowRebaseUpdate: true,
|
||||
},
|
||||
})
|
||||
units = append(units, repo_model.DefaultPullRequestsUnit(repo.ID))
|
||||
case unit.TypeProjects:
|
||||
units = append(units, repo_model.RepoUnit{
|
||||
RepoID: repo.ID,
|
||||
@@ -460,11 +457,11 @@ func createRepositoryInDB(ctx context.Context, doer, u *user_model.User, repo *r
|
||||
return nil
|
||||
}
|
||||
|
||||
func cleanupRepository(repoID int64) {
|
||||
if errDelete := DeleteRepositoryDirectly(graceful.GetManager().ShutdownContext(), repoID); errDelete != nil {
|
||||
func cleanupRepository(repo *repo_model.Repository) {
|
||||
ctx := graceful.GetManager().ShutdownContext()
|
||||
if errDelete := DeleteRepositoryDirectly(ctx, repo.ID); errDelete != nil {
|
||||
log.Error("cleanupRepository failed: %v", errDelete)
|
||||
// add system notice
|
||||
if err := system_model.CreateRepositoryNotice("DeleteRepositoryDirectly failed when cleanup repository: %v", errDelete); err != nil {
|
||||
if err := system_model.CreateRepositoryNotice("DeleteRepositoryDirectly failed when cleanup repository (%s)", repo.FullName(), errDelete); err != nil {
|
||||
log.Error("CreateRepositoryNotice: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -475,8 +472,8 @@ func updateGitRepoAfterCreate(ctx context.Context, repo *repo_model.Repository)
|
||||
return fmt.Errorf("checkDaemonExportOK: %w", err)
|
||||
}
|
||||
|
||||
if stdout, _, err := gitcmd.NewCommand("update-server-info").
|
||||
RunStdString(ctx, &gitcmd.RunOpts{Dir: repo.RepoPath()}); err != nil {
|
||||
if stdout, _, err := gitrepo.RunCmdString(ctx, repo,
|
||||
gitcmd.NewCommand("update-server-info")); err != nil {
|
||||
log.Error("CreateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err)
|
||||
return fmt.Errorf("CreateRepository(git update-server-info): %w", err)
|
||||
}
|
||||
|
||||
@@ -309,7 +309,7 @@ func DeleteRepositoryDirectly(ctx context.Context, repoID int64, ignoreOrgTeams
|
||||
|
||||
// Remove repository files.
|
||||
if err := gitrepo.DeleteRepository(ctx, repo); err != nil {
|
||||
desc := fmt.Sprintf("Delete repository files [%s]: %v", repo.FullName(), err)
|
||||
desc := fmt.Sprintf("Delete repository files (%s): %v", repo.FullName(), err)
|
||||
if err = system_model.CreateNotice(graceful.GetManager().ShutdownContext(), system_model.NoticeRepository, desc); err != nil {
|
||||
log.Error("CreateRepositoryNotice: %v", err)
|
||||
}
|
||||
@@ -317,7 +317,7 @@ func DeleteRepositoryDirectly(ctx context.Context, repoID int64, ignoreOrgTeams
|
||||
|
||||
// Remove wiki files if it exists.
|
||||
if err := gitrepo.DeleteRepository(ctx, repo.WikiStorageRepo()); err != nil {
|
||||
desc := fmt.Sprintf("Delete wiki repository files [%s]: %v", repo.FullName(), err)
|
||||
desc := fmt.Sprintf("Delete wiki repository files (%s): %v", repo.FullName(), err)
|
||||
// Note we use the db.DefaultContext here rather than passing in a context as the context may be cancelled
|
||||
if err = system_model.CreateNotice(graceful.GetManager().ShutdownContext(), system_model.NoticeRepository, desc); err != nil {
|
||||
log.Error("CreateRepositoryNotice: %v", err)
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/services/pull"
|
||||
@@ -35,7 +36,13 @@ func (err ErrCommitIDDoesNotMatch) Error() string {
|
||||
|
||||
// CherryPick cherry-picks or reverts a commit to the given repository
|
||||
func CherryPick(ctx context.Context, repo *repo_model.Repository, doer *user_model.User, revert bool, opts *ApplyDiffPatchOptions) (*structs.FileResponse, error) {
|
||||
if err := opts.Validate(ctx, repo, doer); err != nil {
|
||||
gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, repo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer closer.Close()
|
||||
|
||||
if err := opts.Validate(ctx, repo, gitRepo, doer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
message := strings.TrimSpace(opts.Message)
|
||||
@@ -131,7 +138,7 @@ func CherryPick(ctx context.Context, repo *repo_model.Repository, doer *user_mod
|
||||
}
|
||||
|
||||
// Then push this tree to NewBranch
|
||||
if err := t.Push(ctx, doer, commitHash, opts.NewBranch); err != nil {
|
||||
if err := t.Push(ctx, doer, commitHash, opts.NewBranch, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
@@ -6,21 +6,11 @@ package files
|
||||
import (
|
||||
"context"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
||||
)
|
||||
|
||||
// CountDivergingCommits determines how many commits a branch is ahead or behind the repository's base branch
|
||||
func CountDivergingCommits(ctx context.Context, repo *repo_model.Repository, branch string) (*git.DivergeObject, error) {
|
||||
divergence, err := git.GetDivergingCommits(ctx, repo.RepoPath(), repo.DefaultBranch, branch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &divergence, nil
|
||||
}
|
||||
|
||||
// GetPayloadCommitVerification returns the verification information of a commit
|
||||
func GetPayloadCommitVerification(ctx context.Context, commit *git.Commit) *structs.PayloadCommitVerification {
|
||||
verification := &structs.PayloadCommitVerification{}
|
||||
|
||||
@@ -11,7 +11,9 @@ import (
|
||||
"strings"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/cache"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/lfs"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
@@ -125,7 +127,20 @@ func GetFileContents(ctx context.Context, repo *repo_model.Repository, gitRepo *
|
||||
return getFileContentsByEntryInternal(ctx, repo, gitRepo, refCommit, entry, opts)
|
||||
}
|
||||
|
||||
func getFileContentsByEntryInternal(_ context.Context, repo *repo_model.Repository, gitRepo *git.Repository, refCommit *utils.RefCommit, entry *git.TreeEntry, opts GetContentsOrListOptions) (*api.ContentsResponse, error) {
|
||||
func addLastCommitCache(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, cacheKey, fullName, sha string) error {
|
||||
if gitRepo.LastCommitCache == nil {
|
||||
commitsCount, err := cache.GetInt64(cacheKey, func() (int64, error) {
|
||||
return gitrepo.CommitsCountOfCommit(ctx, repo, sha)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gitRepo.LastCommitCache = git.NewLastCommitCache(commitsCount, fullName, gitRepo, cache.GetCache())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getFileContentsByEntryInternal(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, refCommit *utils.RefCommit, entry *git.TreeEntry, opts GetContentsOrListOptions) (*api.ContentsResponse, error) {
|
||||
refType := refCommit.RefName.RefType()
|
||||
commit := refCommit.Commit
|
||||
selfURL, err := url.Parse(repo.APIURL() + "/contents/" + util.PathEscapeSegments(opts.TreePath) + "?ref=" + url.QueryEscape(refCommit.InputRef))
|
||||
@@ -147,7 +162,7 @@ func getFileContentsByEntryInternal(_ context.Context, repo *repo_model.Reposito
|
||||
}
|
||||
|
||||
if opts.IncludeCommitMetadata || opts.IncludeCommitMessage {
|
||||
err = gitRepo.AddLastCommitCache(repo.GetCommitsCountCacheKey(refCommit.InputRef, refType != git.RefTypeCommit), repo.FullName(), refCommit.CommitID)
|
||||
err = addLastCommitCache(ctx, repo, gitRepo, repo.GetCommitsCountCacheKey(refCommit.InputRef, refType != git.RefTypeCommit), repo.FullName(), refCommit.CommitID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -158,18 +173,18 @@ func getFileContentsByEntryInternal(_ context.Context, repo *repo_model.Reposito
|
||||
}
|
||||
|
||||
if opts.IncludeCommitMetadata {
|
||||
contentsResponse.LastCommitSHA = util.ToPointer(lastCommit.ID.String())
|
||||
contentsResponse.LastCommitSHA = new(lastCommit.ID.String())
|
||||
// GitHub doesn't have these fields in the response, but we could follow other similar APIs to name them
|
||||
// https://docs.github.com/en/rest/commits/commits?apiVersion=2022-11-28#list-commits
|
||||
if lastCommit.Committer != nil {
|
||||
contentsResponse.LastCommitterDate = util.ToPointer(lastCommit.Committer.When)
|
||||
contentsResponse.LastCommitterDate = new(lastCommit.Committer.When)
|
||||
}
|
||||
if lastCommit.Author != nil {
|
||||
contentsResponse.LastAuthorDate = util.ToPointer(lastCommit.Author.When)
|
||||
contentsResponse.LastAuthorDate = new(lastCommit.Author.When)
|
||||
}
|
||||
}
|
||||
if opts.IncludeCommitMessage {
|
||||
contentsResponse.LastCommitMessage = util.ToPointer(lastCommit.Message())
|
||||
contentsResponse.LastCommitMessage = new(lastCommit.Message())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,7 +281,7 @@ func GetBlobBySHA(repo *repo_model.Repository, gitRepo *git.Repository, sha stri
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret.Encoding, ret.Content = util.ToPointer("base64"), &content
|
||||
ret.Encoding, ret.Content = new("base64"), &content
|
||||
if originContent != nil {
|
||||
ret.LfsOid, ret.LfsSize = parsePossibleLfsPointerBuffer(strings.NewReader(originContent.String()))
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/services/contexttest"
|
||||
|
||||
_ "code.gitea.io/gitea/models/actions"
|
||||
@@ -37,8 +36,8 @@ func TestGetContents(t *testing.T) {
|
||||
ctx.SetPathParam("sha", sha)
|
||||
gbr, err := GetBlobBySHA(ctx.Repo.Repository, ctx.Repo.GitRepo, ctx.PathParam("sha"))
|
||||
expectedGBR := &api.GitBlobResponse{
|
||||
Content: util.ToPointer("dHJlZSAyYTJmMWQ0NjcwNzI4YTJlMTAwNDllMzQ1YmQ3YTI3NjQ2OGJlYWI2CmF1dGhvciB1c2VyMSA8YWRkcmVzczFAZXhhbXBsZS5jb20+IDE0ODk5NTY0NzkgLTA0MDAKY29tbWl0dGVyIEV0aGFuIEtvZW5pZyA8ZXRoYW50a29lbmlnQGdtYWlsLmNvbT4gMTQ4OTk1NjQ3OSAtMDQwMAoKSW5pdGlhbCBjb21taXQK"),
|
||||
Encoding: util.ToPointer("base64"),
|
||||
Content: new("dHJlZSAyYTJmMWQ0NjcwNzI4YTJlMTAwNDllMzQ1YmQ3YTI3NjQ2OGJlYWI2CmF1dGhvciB1c2VyMSA8YWRkcmVzczFAZXhhbXBsZS5jb20+IDE0ODk5NTY0NzkgLTA0MDAKY29tbWl0dGVyIEV0aGFuIEtvZW5pZyA8ZXRoYW50a29lbmlnQGdtYWlsLmNvbT4gMTQ4OTk1NjQ3OSAtMDQwMAoKSW5pdGlhbCBjb21taXQK"),
|
||||
Encoding: new("base64"),
|
||||
URL: "https://try.gitea.io/api/v1/repos/user2/repo1/git/blobs/65f1bf27bc3bf70f64657658635e66094edbcb4d",
|
||||
SHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d",
|
||||
Size: 180,
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
// GetDiffPreview produces and returns diff result of a file which is not yet committed.
|
||||
func GetDiffPreview(ctx context.Context, repo *repo_model.Repository, branch, treePath, content string) (*gitdiff.Diff, error) {
|
||||
func GetDiffPreview(ctx context.Context, repo *repo_model.Repository, branch, treePath, oldContent, newContent string) (*gitdiff.Diff, error) {
|
||||
if branch == "" {
|
||||
branch = repo.DefaultBranch
|
||||
}
|
||||
@@ -29,7 +29,7 @@ func GetDiffPreview(ctx context.Context, repo *repo_model.Repository, branch, tr
|
||||
}
|
||||
|
||||
// Add the object to the database
|
||||
objectHash, err := t.HashObjectAndWrite(ctx, strings.NewReader(content))
|
||||
objectHash, err := t.HashObjectAndWrite(ctx, strings.NewReader(newContent))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -38,5 +38,5 @@ func GetDiffPreview(ctx context.Context, repo *repo_model.Repository, branch, tr
|
||||
if err := t.AddObjectToIndex(ctx, "100644", objectHash, treePath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return t.DiffIndex(ctx)
|
||||
return t.DiffIndex(ctx, oldContent, newContent)
|
||||
}
|
||||
|
||||
@@ -27,8 +27,30 @@ func TestGetDiffPreview(t *testing.T) {
|
||||
|
||||
branch := ctx.Repo.Repository.DefaultBranch
|
||||
treePath := "README.md"
|
||||
oldContent := "# repo1\n\nDescription for repo1"
|
||||
content := "# repo1\n\nDescription for repo1\nthis is a new line"
|
||||
|
||||
t.Run("Errors", func(t *testing.T) {
|
||||
t.Run("empty repo", func(t *testing.T) {
|
||||
diff, err := GetDiffPreview(ctx, &repo_model.Repository{}, branch, treePath, oldContent, content)
|
||||
assert.Nil(t, diff)
|
||||
assert.EqualError(t, err, "repository does not exist [id: 0, uid: 0, owner_name: , name: ]")
|
||||
})
|
||||
|
||||
t.Run("bad branch", func(t *testing.T) {
|
||||
badBranch := "bad_branch"
|
||||
diff, err := GetDiffPreview(ctx, ctx.Repo.Repository, badBranch, treePath, oldContent, content)
|
||||
assert.Nil(t, diff)
|
||||
assert.EqualError(t, err, "branch does not exist [name: "+badBranch+"]")
|
||||
})
|
||||
|
||||
t.Run("empty treePath", func(t *testing.T) {
|
||||
diff, err := GetDiffPreview(ctx, ctx.Repo.Repository, branch, "", oldContent, content)
|
||||
assert.Nil(t, diff)
|
||||
assert.EqualError(t, err, "path is invalid [path: ]")
|
||||
})
|
||||
})
|
||||
|
||||
expectedDiff := &gitdiff.Diff{
|
||||
Files: []*gitdiff.DiffFile{
|
||||
{
|
||||
@@ -112,56 +134,22 @@ func TestGetDiffPreview(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("with given branch", func(t *testing.T) {
|
||||
diff, err := GetDiffPreview(ctx, ctx.Repo.Repository, branch, treePath, content)
|
||||
diff, err := GetDiffPreview(ctx, ctx.Repo.Repository, branch, treePath, oldContent, content)
|
||||
assert.NoError(t, err)
|
||||
expectedBs, err := json.Marshal(expectedDiff)
|
||||
assert.NoError(t, err)
|
||||
bs, err := json.Marshal(diff)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, string(expectedBs), string(bs))
|
||||
assert.JSONEq(t, string(expectedBs), string(bs))
|
||||
})
|
||||
|
||||
t.Run("empty branch, same results", func(t *testing.T) {
|
||||
diff, err := GetDiffPreview(ctx, ctx.Repo.Repository, "", treePath, content)
|
||||
diff, err := GetDiffPreview(ctx, ctx.Repo.Repository, "", treePath, oldContent, content)
|
||||
assert.NoError(t, err)
|
||||
expectedBs, err := json.Marshal(expectedDiff)
|
||||
assert.NoError(t, err)
|
||||
bs, err := json.Marshal(diff)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expectedBs, bs)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetDiffPreviewErrors(t *testing.T) {
|
||||
unittest.PrepareTestEnv(t)
|
||||
ctx, _ := contexttest.MockContext(t, "user2/repo1")
|
||||
ctx.SetPathParam("id", "1")
|
||||
contexttest.LoadRepo(t, ctx, 1)
|
||||
contexttest.LoadRepoCommit(t, ctx)
|
||||
contexttest.LoadUser(t, ctx, 2)
|
||||
contexttest.LoadGitRepo(t, ctx)
|
||||
defer ctx.Repo.GitRepo.Close()
|
||||
|
||||
branch := ctx.Repo.Repository.DefaultBranch
|
||||
treePath := "README.md"
|
||||
content := "# repo1\n\nDescription for repo1\nthis is a new line"
|
||||
|
||||
t.Run("empty repo", func(t *testing.T) {
|
||||
diff, err := GetDiffPreview(ctx, &repo_model.Repository{}, branch, treePath, content)
|
||||
assert.Nil(t, diff)
|
||||
assert.EqualError(t, err, "repository does not exist [id: 0, uid: 0, owner_name: , name: ]")
|
||||
})
|
||||
|
||||
t.Run("bad branch", func(t *testing.T) {
|
||||
badBranch := "bad_branch"
|
||||
diff, err := GetDiffPreview(ctx, ctx.Repo.Repository, badBranch, treePath, content)
|
||||
assert.Nil(t, diff)
|
||||
assert.EqualError(t, err, "branch does not exist [name: "+badBranch+"]")
|
||||
})
|
||||
|
||||
t.Run("empty treePath", func(t *testing.T) {
|
||||
diff, err := GetDiffPreview(ctx, ctx.Repo.Repository, branch, "", content)
|
||||
assert.Nil(t, diff)
|
||||
assert.EqualError(t, err, "path is invalid [path: ]")
|
||||
assert.JSONEq(t, string(expectedBs), string(bs))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/git/gitcmd"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
@@ -52,7 +53,7 @@ type ApplyDiffPatchOptions struct {
|
||||
}
|
||||
|
||||
// Validate validates the provided options
|
||||
func (opts *ApplyDiffPatchOptions) Validate(ctx context.Context, repo *repo_model.Repository, doer *user_model.User) error {
|
||||
func (opts *ApplyDiffPatchOptions) Validate(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, doer *user_model.User) error {
|
||||
// If no branch name is set, assume master
|
||||
if opts.OldBranch == "" {
|
||||
opts.OldBranch = repo.DefaultBranch
|
||||
@@ -95,7 +96,7 @@ func (opts *ApplyDiffPatchOptions) Validate(ctx context.Context, repo *repo_mode
|
||||
}
|
||||
}
|
||||
if protectedBranch != nil && protectedBranch.RequireSignedCommits {
|
||||
_, _, _, err := asymkey_service.SignCRUDAction(ctx, repo.RepoPath(), doer, repo.RepoPath(), opts.OldBranch)
|
||||
_, _, _, err := asymkey_service.SignCRUDAction(ctx, doer, gitRepo, opts.OldBranch)
|
||||
if err != nil {
|
||||
if !asymkey_service.IsErrWontSign(err) {
|
||||
return err
|
||||
@@ -116,7 +117,13 @@ func ApplyDiffPatch(ctx context.Context, repo *repo_model.Repository, doer *user
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := opts.Validate(ctx, repo, doer); err != nil {
|
||||
gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, repo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer closer.Close()
|
||||
|
||||
if err := opts.Validate(ctx, repo, gitRepo, doer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -157,21 +164,15 @@ func ApplyDiffPatch(ctx context.Context, repo *repo_model.Repository, doer *user
|
||||
}
|
||||
}
|
||||
|
||||
stdout := &strings.Builder{}
|
||||
stderr := &strings.Builder{}
|
||||
|
||||
cmdApply := gitcmd.NewCommand("apply", "--index", "--recount", "--cached", "--ignore-whitespace", "--whitespace=fix", "--binary")
|
||||
if git.DefaultFeatures().CheckVersionAtLeast("2.32") {
|
||||
cmdApply.AddArguments("-3")
|
||||
}
|
||||
|
||||
if err := cmdApply.Run(ctx, &gitcmd.RunOpts{
|
||||
Dir: t.basePath,
|
||||
Stdout: stdout,
|
||||
Stderr: stderr,
|
||||
Stdin: strings.NewReader(opts.Content),
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("Error: Stdout: %s\nStderr: %s\nErr: %w", stdout.String(), stderr.String(), err)
|
||||
if err := cmdApply.WithDir(t.basePath).
|
||||
WithStdinBytes([]byte(opts.Content)).
|
||||
RunWithStderr(ctx); err != nil {
|
||||
return nil, fmt.Errorf("git apply error: %w", err)
|
||||
}
|
||||
|
||||
// Now write the tree
|
||||
@@ -201,7 +202,7 @@ func ApplyDiffPatch(ctx context.Context, repo *repo_model.Repository, doer *user
|
||||
}
|
||||
|
||||
// Then push this tree to NewBranch
|
||||
if err := t.Push(ctx, doer, commitHash, opts.NewBranch); err != nil {
|
||||
if err := t.Push(ctx, doer, commitHash, opts.NewBranch, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/git/gitcmd"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
@@ -54,12 +55,11 @@ func (t *TemporaryUploadRepository) Close() {
|
||||
|
||||
// Clone the base repository to our path and set branch as the HEAD
|
||||
func (t *TemporaryUploadRepository) Clone(ctx context.Context, branch string, bare bool) error {
|
||||
cmd := gitcmd.NewCommand("clone", "-s", "-b").AddDynamicArguments(branch, t.repo.RepoPath(), t.basePath)
|
||||
if bare {
|
||||
cmd.AddArguments("--bare")
|
||||
}
|
||||
|
||||
if _, _, err := cmd.RunStdString(ctx, nil); err != nil {
|
||||
if err := gitrepo.CloneRepoToLocal(ctx, t.repo, t.basePath, git.CloneRepoOptions{
|
||||
Bare: bare,
|
||||
Branch: branch,
|
||||
Shared: true,
|
||||
}); err != nil {
|
||||
stderr := err.Error()
|
||||
if matched, _ := regexp.MatchString(".*Remote branch .* not found in upstream origin.*", stderr); matched {
|
||||
return git.ErrBranchNotExist{
|
||||
@@ -98,7 +98,7 @@ func (t *TemporaryUploadRepository) Init(ctx context.Context, objectFormatName s
|
||||
|
||||
// SetDefaultIndex sets the git index to our HEAD
|
||||
func (t *TemporaryUploadRepository) SetDefaultIndex(ctx context.Context) error {
|
||||
if _, _, err := gitcmd.NewCommand("read-tree", "HEAD").RunStdString(ctx, &gitcmd.RunOpts{Dir: t.basePath}); err != nil {
|
||||
if err := gitcmd.NewCommand("read-tree", "HEAD").WithDir(t.basePath).RunWithStderr(ctx); err != nil {
|
||||
return fmt.Errorf("SetDefaultIndex: %w", err)
|
||||
}
|
||||
return nil
|
||||
@@ -106,7 +106,7 @@ func (t *TemporaryUploadRepository) SetDefaultIndex(ctx context.Context) error {
|
||||
|
||||
// RefreshIndex looks at the current index and checks to see if merges or updates are needed by checking stat() information.
|
||||
func (t *TemporaryUploadRepository) RefreshIndex(ctx context.Context) error {
|
||||
if _, _, err := gitcmd.NewCommand("update-index", "--refresh").RunStdString(ctx, &gitcmd.RunOpts{Dir: t.basePath}); err != nil {
|
||||
if err := gitcmd.NewCommand("update-index", "--refresh").WithDir(t.basePath).RunWithStderr(ctx); err != nil {
|
||||
return fmt.Errorf("RefreshIndex: %w", err)
|
||||
}
|
||||
return nil
|
||||
@@ -115,17 +115,11 @@ func (t *TemporaryUploadRepository) RefreshIndex(ctx context.Context) error {
|
||||
// LsFiles checks if the given filename arguments are in the index
|
||||
func (t *TemporaryUploadRepository) LsFiles(ctx context.Context, filenames ...string) ([]string, error) {
|
||||
stdOut := new(bytes.Buffer)
|
||||
stdErr := new(bytes.Buffer)
|
||||
|
||||
if err := gitcmd.NewCommand("ls-files", "-z").AddDashesAndList(filenames...).
|
||||
Run(ctx, &gitcmd.RunOpts{
|
||||
Dir: t.basePath,
|
||||
Stdout: stdOut,
|
||||
Stderr: stdErr,
|
||||
}); err != nil {
|
||||
log.Error("Unable to run git ls-files for temporary repo: %s (%s) Error: %v\nstdout: %s\nstderr: %s", t.repo.FullName(), t.basePath, err, stdOut.String(), stdErr.String())
|
||||
err = fmt.Errorf("Unable to run git ls-files for temporary repo of: %s Error: %w\nstdout: %s\nstderr: %s", t.repo.FullName(), err, stdOut.String(), stdErr.String())
|
||||
return nil, err
|
||||
WithDir(t.basePath).
|
||||
WithStdoutBuffer(stdOut).
|
||||
RunWithStderr(ctx); err != nil {
|
||||
return nil, fmt.Errorf("unable to run git ls-files for temporary repo of: %s, error: %w", t.repo.FullName(), err)
|
||||
}
|
||||
|
||||
fileList := make([]string, 0, len(filenames))
|
||||
@@ -136,14 +130,20 @@ func (t *TemporaryUploadRepository) LsFiles(ctx context.Context, filenames ...st
|
||||
return fileList, nil
|
||||
}
|
||||
|
||||
func (t *TemporaryUploadRepository) RemoveRecursivelyFromIndex(ctx context.Context, path string) error {
|
||||
_, _, err := gitcmd.NewCommand("rm", "--cached", "-r").
|
||||
AddDynamicArguments(path).
|
||||
WithDir(t.basePath).
|
||||
RunStdBytes(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// RemoveFilesFromIndex removes the given files from the index
|
||||
func (t *TemporaryUploadRepository) RemoveFilesFromIndex(ctx context.Context, filenames ...string) error {
|
||||
objFmt, err := t.gitRepo.GetObjectFormat()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get object format for temporary repo: %q, error: %w", t.repo.FullName(), err)
|
||||
}
|
||||
stdOut := new(bytes.Buffer)
|
||||
stdErr := new(bytes.Buffer)
|
||||
stdIn := new(bytes.Buffer)
|
||||
for _, file := range filenames {
|
||||
if file != "" {
|
||||
@@ -154,13 +154,10 @@ func (t *TemporaryUploadRepository) RemoveFilesFromIndex(ctx context.Context, fi
|
||||
}
|
||||
|
||||
if err := gitcmd.NewCommand("update-index", "--remove", "-z", "--index-info").
|
||||
Run(ctx, &gitcmd.RunOpts{
|
||||
Dir: t.basePath,
|
||||
Stdin: stdIn,
|
||||
Stdout: stdOut,
|
||||
Stderr: stdErr,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("unable to update-index for temporary repo: %q, error: %w\nstdout: %s\nstderr: %s", t.repo.FullName(), err, stdOut.String(), stdErr.String())
|
||||
WithDir(t.basePath).
|
||||
WithStdinBytes(stdIn.Bytes()).
|
||||
RunWithStderr(ctx); err != nil {
|
||||
return fmt.Errorf("unable to update-index for temporary repo: %q, error: %w", t.repo.FullName(), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -168,17 +165,12 @@ func (t *TemporaryUploadRepository) RemoveFilesFromIndex(ctx context.Context, fi
|
||||
// HashObjectAndWrite writes the provided content to the object db and returns its hash
|
||||
func (t *TemporaryUploadRepository) HashObjectAndWrite(ctx context.Context, content io.Reader) (string, error) {
|
||||
stdOut := new(bytes.Buffer)
|
||||
stdErr := new(bytes.Buffer)
|
||||
|
||||
if err := gitcmd.NewCommand("hash-object", "-w", "--stdin").
|
||||
Run(ctx, &gitcmd.RunOpts{
|
||||
Dir: t.basePath,
|
||||
Stdin: content,
|
||||
Stdout: stdOut,
|
||||
Stderr: stdErr,
|
||||
}); err != nil {
|
||||
log.Error("Unable to hash-object to temporary repo: %s (%s) Error: %v\nstdout: %s\nstderr: %s", t.repo.FullName(), t.basePath, err, stdOut.String(), stdErr.String())
|
||||
return "", fmt.Errorf("Unable to hash-object to temporary repo: %s Error: %w\nstdout: %s\nstderr: %s", t.repo.FullName(), err, stdOut.String(), stdErr.String())
|
||||
WithDir(t.basePath).
|
||||
WithStdoutBuffer(stdOut).
|
||||
WithStdinCopy(content).
|
||||
RunWithStderr(ctx); err != nil {
|
||||
return "", fmt.Errorf("unable to hash-object to temporary repo: %s, error: %w", t.repo.FullName(), err)
|
||||
}
|
||||
|
||||
return strings.TrimSpace(stdOut.String()), nil
|
||||
@@ -186,23 +178,23 @@ func (t *TemporaryUploadRepository) HashObjectAndWrite(ctx context.Context, cont
|
||||
|
||||
// AddObjectToIndex adds the provided object hash to the index with the provided mode and path
|
||||
func (t *TemporaryUploadRepository) AddObjectToIndex(ctx context.Context, mode, objectHash, objectPath string) error {
|
||||
if _, _, err := gitcmd.NewCommand("update-index", "--add", "--replace", "--cacheinfo").AddDynamicArguments(mode, objectHash, objectPath).RunStdString(ctx, &gitcmd.RunOpts{Dir: t.basePath}); err != nil {
|
||||
stderr := err.Error()
|
||||
if matched, _ := regexp.MatchString(".*Invalid path '.*", stderr); matched {
|
||||
cmd := gitcmd.NewCommand("update-index", "--add", "--replace", "--cacheinfo").
|
||||
AddDynamicArguments(mode + "," + objectHash + "," + objectPath).WithDir(t.basePath)
|
||||
if err := cmd.RunWithStderr(ctx); err != nil {
|
||||
if matched, _ := regexp.MatchString(".*Invalid path '.*", err.Stderr()); matched {
|
||||
return ErrFilePathInvalid{
|
||||
Message: objectPath,
|
||||
Path: objectPath,
|
||||
}
|
||||
}
|
||||
log.Error("Unable to add object to index: %s %s %s in temporary repo %s(%s) Error: %v", mode, objectHash, objectPath, t.repo.FullName(), t.basePath, err)
|
||||
return fmt.Errorf("Unable to add object to index at %s in temporary repo %s Error: %w", objectPath, t.repo.FullName(), err)
|
||||
return fmt.Errorf("unable to add object to index at %s in temporary repo %s, error: %w", objectPath, t.repo.FullName(), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteTree writes the current index as a tree to the object db and returns its hash
|
||||
func (t *TemporaryUploadRepository) WriteTree(ctx context.Context) (string, error) {
|
||||
stdout, _, err := gitcmd.NewCommand("write-tree").RunStdString(ctx, &gitcmd.RunOpts{Dir: t.basePath})
|
||||
stdout, _, err := gitcmd.NewCommand("write-tree").WithDir(t.basePath).RunStdString(ctx)
|
||||
if err != nil {
|
||||
log.Error("Unable to write tree in temporary repo: %s(%s): Error: %v", t.repo.FullName(), t.basePath, err)
|
||||
return "", fmt.Errorf("Unable to write-tree in temporary repo for: %s Error: %w", t.repo.FullName(), err)
|
||||
@@ -220,7 +212,7 @@ func (t *TemporaryUploadRepository) GetLastCommitByRef(ctx context.Context, ref
|
||||
if ref == "" {
|
||||
ref = "HEAD"
|
||||
}
|
||||
stdout, _, err := gitcmd.NewCommand("rev-parse").AddDynamicArguments(ref).RunStdString(ctx, &gitcmd.RunOpts{Dir: t.basePath})
|
||||
stdout, _, err := gitcmd.NewCommand("rev-parse").AddDynamicArguments(ref).WithDir(t.basePath).RunStdString(ctx)
|
||||
if err != nil {
|
||||
log.Error("Unable to get last ref for %s in temporary repo: %s(%s): Error: %v", ref, t.repo.FullName(), t.basePath, err)
|
||||
return "", fmt.Errorf("Unable to rev-parse %s in temporary repo for: %s Error: %w", ref, t.repo.FullName(), err)
|
||||
@@ -268,7 +260,7 @@ func (t *TemporaryUploadRepository) CommitTree(ctx context.Context, opts *Commit
|
||||
authorDate := opts.AuthorTime
|
||||
committerDate := opts.CommitterTime
|
||||
if authorDate == nil && committerDate == nil {
|
||||
authorDate = util.ToPointer(time.Now())
|
||||
authorDate = new(time.Now())
|
||||
committerDate = authorDate
|
||||
} else if authorDate == nil {
|
||||
authorDate = committerDate
|
||||
@@ -297,9 +289,9 @@ func (t *TemporaryUploadRepository) CommitTree(ctx context.Context, opts *Commit
|
||||
var key *git.SigningKey
|
||||
var signer *git.Signature
|
||||
if opts.ParentCommitID != "" {
|
||||
sign, key, signer, _ = asymkey_service.SignCRUDAction(ctx, t.repo.RepoPath(), opts.DoerUser, t.basePath, opts.ParentCommitID)
|
||||
sign, key, signer, _ = asymkey_service.SignCRUDAction(ctx, opts.DoerUser, t.gitRepo, opts.ParentCommitID)
|
||||
} else {
|
||||
sign, key, signer, _ = asymkey_service.SignInitialCommit(ctx, t.repo.RepoPath(), opts.DoerUser)
|
||||
sign, key, signer, _ = asymkey_service.SignInitialCommit(ctx, opts.DoerUser)
|
||||
}
|
||||
if sign {
|
||||
if key.Format != "" {
|
||||
@@ -336,38 +328,29 @@ func (t *TemporaryUploadRepository) CommitTree(ctx context.Context, opts *Commit
|
||||
)
|
||||
|
||||
stdout := new(bytes.Buffer)
|
||||
stderr := new(bytes.Buffer)
|
||||
if err := cmdCommitTree.
|
||||
Run(ctx, &gitcmd.RunOpts{
|
||||
Env: env,
|
||||
Dir: t.basePath,
|
||||
Stdin: messageBytes,
|
||||
Stdout: stdout,
|
||||
Stderr: stderr,
|
||||
}); err != nil {
|
||||
log.Error("Unable to commit-tree in temporary repo: %s (%s) Error: %v\nStdout: %s\nStderr: %s",
|
||||
t.repo.FullName(), t.basePath, err, stdout, stderr)
|
||||
return "", fmt.Errorf("Unable to commit-tree in temporary repo: %s Error: %w\nStdout: %s\nStderr: %s",
|
||||
t.repo.FullName(), err, stdout, stderr)
|
||||
WithEnv(env).
|
||||
WithDir(t.basePath).
|
||||
WithStdoutBuffer(stdout).
|
||||
WithStdinBytes(messageBytes.Bytes()).
|
||||
RunWithStderr(ctx); err != nil {
|
||||
return "", fmt.Errorf("unable to commit-tree in temporary repo: %s Error: %w", t.repo.FullName(), err)
|
||||
}
|
||||
return strings.TrimSpace(stdout.String()), nil
|
||||
}
|
||||
|
||||
// Push the provided commitHash to the repository branch by the provided user
|
||||
func (t *TemporaryUploadRepository) Push(ctx context.Context, doer *user_model.User, commitHash, branch string) error {
|
||||
func (t *TemporaryUploadRepository) Push(ctx context.Context, doer *user_model.User, commitHash, branch string, force bool) error {
|
||||
// Because calls hooks we need to pass in the environment
|
||||
env := repo_module.PushingEnvironment(doer, t.repo)
|
||||
if err := git.Push(ctx, t.basePath, git.PushOptions{
|
||||
Remote: t.repo.RepoPath(),
|
||||
if err := gitrepo.PushFromLocal(ctx, t.basePath, t.repo, git.PushOptions{
|
||||
Branch: strings.TrimSpace(commitHash) + ":" + git.BranchPrefix + strings.TrimSpace(branch),
|
||||
Env: env,
|
||||
Force: force,
|
||||
}); err != nil {
|
||||
if git.IsErrPushOutOfDate(err) {
|
||||
return err
|
||||
} else if git.IsErrPushRejected(err) {
|
||||
rejectErr := err.(*git.ErrPushRejected)
|
||||
log.Info("Unable to push back to repo from temporary repo due to rejection: %s (%s)\nStdout: %s\nStderr: %s\nError: %v",
|
||||
t.repo.FullName(), t.basePath, rejectErr.StdOut, rejectErr.StdErr, rejectErr.Err)
|
||||
return err
|
||||
}
|
||||
log.Error("Unable to push back to repo from temporary repo: %s (%s)\nError: %v",
|
||||
@@ -379,41 +362,31 @@ func (t *TemporaryUploadRepository) Push(ctx context.Context, doer *user_model.U
|
||||
}
|
||||
|
||||
// DiffIndex returns a Diff of the current index to the head
|
||||
func (t *TemporaryUploadRepository) DiffIndex(ctx context.Context) (*gitdiff.Diff, error) {
|
||||
stdoutReader, stdoutWriter, err := os.Pipe()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to open stdout pipe: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = stdoutReader.Close()
|
||||
_ = stdoutWriter.Close()
|
||||
}()
|
||||
stderr := new(bytes.Buffer)
|
||||
func (t *TemporaryUploadRepository) DiffIndex(ctx context.Context, oldContent, newContent string) (*gitdiff.Diff, error) {
|
||||
var diff *gitdiff.Diff
|
||||
err = gitcmd.NewCommand("diff-index", "--src-prefix=\\a/", "--dst-prefix=\\b/", "--cached", "-p", "HEAD").
|
||||
Run(ctx, &gitcmd.RunOpts{
|
||||
Timeout: 30 * time.Second,
|
||||
Dir: t.basePath,
|
||||
Stdout: stdoutWriter,
|
||||
Stderr: stderr,
|
||||
PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error {
|
||||
_ = stdoutWriter.Close()
|
||||
defer cancel()
|
||||
var diffErr error
|
||||
diff, diffErr = gitdiff.ParsePatch(ctx, setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, stdoutReader, "")
|
||||
_ = stdoutReader.Close()
|
||||
if diffErr != nil {
|
||||
// if the diffErr is not nil, it will be returned as the error of "Run()"
|
||||
return fmt.Errorf("ParsePatch: %w", diffErr)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
})
|
||||
if err != nil && !git.IsErrCanceledOrKilled(err) {
|
||||
log.Error("Unable to diff-index in temporary repo %s (%s). Error: %v\nStderr: %s", t.repo.FullName(), t.basePath, err, stderr)
|
||||
cmd := gitcmd.NewCommand("diff-index", "--src-prefix=\\a/", "--dst-prefix=\\b/", "--cached", "-p", "HEAD")
|
||||
stdoutReader, stdoutReaderClose := cmd.MakeStdoutPipe()
|
||||
defer stdoutReaderClose()
|
||||
|
||||
err := cmd.WithTimeout(30 * time.Second).
|
||||
WithDir(t.basePath).
|
||||
WithPipelineFunc(func(ctx gitcmd.Context) error {
|
||||
var diffErr error
|
||||
diff, diffErr = gitdiff.ParsePatch(ctx, setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, stdoutReader, "")
|
||||
if diffErr != nil {
|
||||
// if the diffErr is not nil, it will be returned as the error of "Run()"
|
||||
return fmt.Errorf("ParsePatch: %w", diffErr)
|
||||
}
|
||||
return nil
|
||||
}).
|
||||
RunWithStderr(ctx)
|
||||
if err != nil && !gitcmd.IsErrorCanceledOrKilled(err) {
|
||||
return nil, fmt.Errorf("unable to run diff-index pipeline in temporary repo: %w", err)
|
||||
}
|
||||
|
||||
if len(diff.Files) > 0 {
|
||||
gitdiff.FillDiffFileHighlightLinesByContent(diff.Files[0], util.UnsafeStringToBytes(oldContent), util.UnsafeStringToBytes(newContent))
|
||||
}
|
||||
return diff, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package files
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestTemporaryUploadRepository(t *testing.T) {
|
||||
mockedRepo := &repo_model.Repository{Name: "mocked-repo-name", OwnerName: "mocked-owner-name"}
|
||||
|
||||
doTest := func(t *testing.T, objectFormatName string) {
|
||||
tmpGitRepo, err := NewTemporaryUploadRepository(mockedRepo)
|
||||
require.NoError(t, err)
|
||||
defer tmpGitRepo.Close()
|
||||
|
||||
require.NoError(t, tmpGitRepo.Init(t.Context(), objectFormatName))
|
||||
|
||||
require.NoError(t, tmpGitRepo.RemoveFilesFromIndex(t.Context(), "any-file-name"))
|
||||
require.NoError(t, tmpGitRepo.RemoveFilesFromIndex(t.Context(), "--any-file-name"))
|
||||
|
||||
objID, err := tmpGitRepo.HashObjectAndWrite(t.Context(), bytes.NewReader(nil))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, tmpGitRepo.AddObjectToIndex(t.Context(), "100644", objID, "any-file-name"))
|
||||
require.NoError(t, tmpGitRepo.AddObjectToIndex(t.Context(), "100644", objID, "--any-file-name"))
|
||||
}
|
||||
|
||||
t.Run("sha1", func(t *testing.T) {
|
||||
doTest(t, git.Sha1ObjectFormat.Name())
|
||||
})
|
||||
|
||||
t.Run("sha256", func(t *testing.T) {
|
||||
if !git.DefaultFeatures().SupportHashSha256 {
|
||||
t.Skip("sha256 is not supported")
|
||||
}
|
||||
doTest(t, git.Sha256ObjectFormat.Name())
|
||||
})
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"strings"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/fileicon"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
@@ -61,33 +62,18 @@ func GetTreeBySHA(ctx context.Context, repo *repo_model.Repository, gitRepo *git
|
||||
return nil, err
|
||||
}
|
||||
apiURL := repo.APIURL()
|
||||
apiURLLen := len(apiURL)
|
||||
objectFormat := git.ObjectFormatFromName(repo.ObjectFormatName)
|
||||
hashLen := objectFormat.FullLength()
|
||||
|
||||
const gitBlobsPath = "/git/blobs/"
|
||||
blobURL := make([]byte, apiURLLen+hashLen+len(gitBlobsPath))
|
||||
copy(blobURL, apiURL)
|
||||
copy(blobURL[apiURLLen:], []byte(gitBlobsPath))
|
||||
|
||||
const gitTreePath = "/git/trees/"
|
||||
treeURL := make([]byte, apiURLLen+hashLen+len(gitTreePath))
|
||||
copy(treeURL, apiURL)
|
||||
copy(treeURL[apiURLLen:], []byte(gitTreePath))
|
||||
|
||||
// copyPos is at the start of the hash
|
||||
copyPos := len(treeURL) - hashLen
|
||||
blobURLBase := apiURL + "/git/blobs/"
|
||||
treeURLBase := apiURL + "/git/trees/"
|
||||
|
||||
if perPage <= 0 || perPage > setting.API.DefaultGitTreesPerPage {
|
||||
perPage = setting.API.DefaultGitTreesPerPage
|
||||
}
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
page = max(page, 1)
|
||||
|
||||
tree.Page = page
|
||||
tree.TotalCount = len(entries)
|
||||
rangeStart := perPage * (page - 1)
|
||||
if rangeStart >= len(entries) {
|
||||
rangeStart := perPage * (page - 1) // int might overflow
|
||||
if rangeStart < 0 || rangeStart >= len(entries) {
|
||||
return tree, nil
|
||||
}
|
||||
rangeEnd := min(rangeStart+perPage, len(entries))
|
||||
@@ -103,16 +89,14 @@ func GetTreeBySHA(ctx context.Context, repo *repo_model.Repository, gitRepo *git
|
||||
tree.Entries[i].SHA = entries[e].ID.String()
|
||||
|
||||
if entries[e].IsDir() {
|
||||
copy(treeURL[copyPos:], entries[e].ID.String())
|
||||
tree.Entries[i].URL = string(treeURL)
|
||||
tree.Entries[i].URL = treeURLBase + entries[e].ID.String()
|
||||
} else if entries[e].IsSubModule() {
|
||||
// In Github Rest API Version=2022-11-28, if a tree entry is a submodule,
|
||||
// In GitHub Rest API Version=2022-11-28, if a tree entry is a submodule,
|
||||
// its url will be returned as an empty string.
|
||||
// So the URL will be set to "" here.
|
||||
tree.Entries[i].URL = ""
|
||||
} else {
|
||||
copy(blobURL[copyPos:], entries[e].ID.String())
|
||||
tree.Entries[i].URL = string(blobURL)
|
||||
tree.Entries[i].URL = blobURLBase + entries[e].ID.String()
|
||||
}
|
||||
}
|
||||
return tree, nil
|
||||
@@ -187,7 +171,7 @@ func sortTreeViewNodes(nodes []*TreeViewNode) {
|
||||
if a != b {
|
||||
return a < b
|
||||
}
|
||||
return nodes[i].EntryName < nodes[j].EntryName
|
||||
return base.NaturalSortCompare(nodes[i].EntryName, nodes[j].EntryName) < 0
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ func TestGetTreeBySHA(t *testing.T) {
|
||||
|
||||
func TestGetTreeViewNodes(t *testing.T) {
|
||||
unittest.PrepareTestEnv(t)
|
||||
|
||||
ctx, _ := contexttest.MockContext(t, "user2/repo1")
|
||||
ctx.Repo.RefFullName = git.RefNameFromBranch("sub-home-md-img-check")
|
||||
contexttest.LoadRepo(t, ctx, 1)
|
||||
@@ -69,11 +70,13 @@ func TestGetTreeViewNodes(t *testing.T) {
|
||||
mockIconForFile := func(id string) template.HTML {
|
||||
return template.HTML(`<svg class="svg git-entry-icon octicon-file" width="16" height="16" aria-hidden="true"><use href="#` + id + `"></use></svg>`)
|
||||
}
|
||||
mockIconForFolder := func(id string) template.HTML {
|
||||
return template.HTML(`<svg class="svg git-entry-icon octicon-file-directory-fill" width="16" height="16" aria-hidden="true"><use href="#` + id + `"></use></svg>`)
|
||||
mockIconForFolder := func() template.HTML {
|
||||
// With basic theme (default for folders), we get octicon icons without IDs
|
||||
return template.HTML(`<span>octicon-file-directory-fill(16/)</span>`)
|
||||
}
|
||||
mockOpenIconForFolder := func(id string) template.HTML {
|
||||
return template.HTML(`<svg class="svg git-entry-icon octicon-file-directory-open-fill" width="16" height="16" aria-hidden="true"><use href="#` + id + `"></use></svg>`)
|
||||
mockOpenIconForFolder := func() template.HTML {
|
||||
// With basic theme (default for folders), we get octicon icons without IDs
|
||||
return template.HTML(`<span>octicon-file-directory-open-fill(16/)</span>`)
|
||||
}
|
||||
treeNodes, err := GetTreeViewNodes(ctx, curRepoLink, renderedIconPool, ctx.Repo.Commit, "", "")
|
||||
assert.NoError(t, err)
|
||||
@@ -82,8 +85,8 @@ func TestGetTreeViewNodes(t *testing.T) {
|
||||
EntryName: "docs",
|
||||
EntryMode: "tree",
|
||||
FullPath: "docs",
|
||||
EntryIcon: mockIconForFolder(`svg-mfi-folder-docs`),
|
||||
EntryIconOpen: mockOpenIconForFolder(`svg-mfi-folder-docs`),
|
||||
EntryIcon: mockIconForFolder(),
|
||||
EntryIconOpen: mockOpenIconForFolder(),
|
||||
},
|
||||
}, treeNodes)
|
||||
|
||||
@@ -94,8 +97,8 @@ func TestGetTreeViewNodes(t *testing.T) {
|
||||
EntryName: "docs",
|
||||
EntryMode: "tree",
|
||||
FullPath: "docs",
|
||||
EntryIcon: mockIconForFolder(`svg-mfi-folder-docs`),
|
||||
EntryIconOpen: mockOpenIconForFolder(`svg-mfi-folder-docs`),
|
||||
EntryIcon: mockIconForFolder(),
|
||||
EntryIconOpen: mockOpenIconForFolder(),
|
||||
Children: []*TreeViewNode{
|
||||
{
|
||||
EntryName: "README.md",
|
||||
|
||||
@@ -46,7 +46,10 @@ type ChangeRepoFile struct {
|
||||
FromTreePath string
|
||||
ContentReader io.ReadSeeker
|
||||
SHA string
|
||||
Options *RepoFileOptions
|
||||
|
||||
DeleteRecursively bool // when deleting, work as `git rm -r ...`
|
||||
|
||||
Options *RepoFileOptions // FIXME: need to refactor, internal usage only
|
||||
}
|
||||
|
||||
// ChangeRepoFilesOptions holds the repository files update options
|
||||
@@ -60,6 +63,7 @@ type ChangeRepoFilesOptions struct {
|
||||
Committer *IdentityOptions
|
||||
Dates *CommitDateOptions
|
||||
Signoff bool
|
||||
ForcePush bool
|
||||
}
|
||||
|
||||
type RepoFileOptions struct {
|
||||
@@ -68,26 +72,6 @@ type RepoFileOptions struct {
|
||||
executable bool
|
||||
}
|
||||
|
||||
// ErrRepoFileDoesNotExist represents a "RepoFileDoesNotExist" kind of error.
|
||||
type ErrRepoFileDoesNotExist struct {
|
||||
Path string
|
||||
Name string
|
||||
}
|
||||
|
||||
// IsErrRepoFileDoesNotExist checks if an error is a ErrRepoDoesNotExist.
|
||||
func IsErrRepoFileDoesNotExist(err error) bool {
|
||||
_, ok := err.(ErrRepoFileDoesNotExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrRepoFileDoesNotExist) Error() string {
|
||||
return fmt.Sprintf("repository file does not exist [path: %s]", err.Path)
|
||||
}
|
||||
|
||||
func (err ErrRepoFileDoesNotExist) Unwrap() error {
|
||||
return util.ErrNotExist
|
||||
}
|
||||
|
||||
type LazyReadSeeker interface {
|
||||
io.ReadSeeker
|
||||
io.Closer
|
||||
@@ -176,11 +160,14 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
|
||||
return nil, err
|
||||
}
|
||||
if exist {
|
||||
return nil, git_model.ErrBranchAlreadyExists{
|
||||
BranchName: opts.NewBranch,
|
||||
if !opts.ForcePush {
|
||||
// branch exists but force option not set
|
||||
return nil, git_model.ErrBranchAlreadyExists{
|
||||
BranchName: opts.NewBranch,
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if err := VerifyBranchProtection(ctx, repo, doer, opts.OldBranch, treePaths); err != nil {
|
||||
} else if err := VerifyBranchProtection(ctx, repo, gitRepo, doer, opts.OldBranch, treePaths); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -213,24 +200,6 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
|
||||
}
|
||||
}
|
||||
|
||||
for _, file := range opts.Files {
|
||||
if file.Operation == "delete" {
|
||||
// Get the files in the index
|
||||
filesInIndex, err := t.LsFiles(ctx, file.TreePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("DeleteRepoFile: %w", err)
|
||||
}
|
||||
|
||||
// Find the file we want to delete in the index
|
||||
inFilelist := slices.Contains(filesInIndex, file.TreePath)
|
||||
if !inFilelist {
|
||||
return nil, ErrRepoFileDoesNotExist{
|
||||
Path: file.TreePath,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if hasOldBranch {
|
||||
// Get the commit of the original branch
|
||||
commit, err := t.GetBranchCommit(opts.OldBranch)
|
||||
@@ -268,8 +237,14 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
|
||||
addedLfsPointers = append(addedLfsPointers, *addedLfsPointer)
|
||||
}
|
||||
case "delete":
|
||||
if err = t.RemoveFilesFromIndex(ctx, file.TreePath); err != nil {
|
||||
return nil, err
|
||||
if file.DeleteRecursively {
|
||||
if err = t.RemoveRecursivelyFromIndex(ctx, file.TreePath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if err = t.RemoveFilesFromIndex(ctx, file.TreePath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid file operation: %s %s, supported operations are create, update, delete", file.Operation, file.Options.treePath)
|
||||
@@ -303,8 +278,7 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
|
||||
}
|
||||
|
||||
// Then push this tree to NewBranch
|
||||
if err := t.Push(ctx, doer, commitHash, opts.NewBranch); err != nil {
|
||||
log.Error("%T %v", err, err)
|
||||
if err := t.Push(ctx, doer, commitHash, opts.NewBranch, opts.ForcePush); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -536,7 +510,7 @@ func modifyFile(ctx context.Context, t *TemporaryUploadRepository, file *ChangeR
|
||||
}
|
||||
|
||||
if writeObjectRet.LfsContent == nil {
|
||||
return nil, nil // No LFS pointer, so nothing to do
|
||||
return nil, nil //nolint:nilnil // No LFS pointer, so nothing to do
|
||||
}
|
||||
defer writeObjectRet.LfsContent.Close()
|
||||
|
||||
@@ -685,7 +659,7 @@ func writeRepoObjectForRename(ctx context.Context, t *TemporaryUploadRepository,
|
||||
}
|
||||
|
||||
// VerifyBranchProtection verify the branch protection for modifying the given treePath on the given branch
|
||||
func VerifyBranchProtection(ctx context.Context, repo *repo_model.Repository, doer *user_model.User, branchName string, treePaths []string) error {
|
||||
func VerifyBranchProtection(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, doer *user_model.User, branchName string, treePaths []string) error {
|
||||
protectedBranch, err := git_model.GetFirstMatchProtectedBranchRule(ctx, repo.ID, branchName)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -712,7 +686,7 @@ func VerifyBranchProtection(ctx context.Context, repo *repo_model.Repository, do
|
||||
}
|
||||
}
|
||||
if protectedBranch.RequireSignedCommits {
|
||||
_, _, _, err := asymkey_service.SignCRUDAction(ctx, repo.RepoPath(), doer, repo.RepoPath(), branchName)
|
||||
_, _, _, err := asymkey_service.SignCRUDAction(ctx, doer, gitRepo, branchName)
|
||||
if err != nil {
|
||||
if !asymkey_service.IsErrWontSign(err) {
|
||||
return err
|
||||
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/git/gitcmd"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
@@ -124,8 +123,8 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork
|
||||
// WARNING: Don't override all later err with local variables
|
||||
defer func() {
|
||||
if err != nil {
|
||||
// we can not use the ctx because it maybe canceled or timeout
|
||||
cleanupRepository(repo.ID)
|
||||
// we can not use `ctx` because it may be canceled or timed out
|
||||
cleanupRepository(repo)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -147,14 +146,16 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork
|
||||
}
|
||||
|
||||
// 3 - Clone the repository
|
||||
cloneCmd := gitcmd.NewCommand("clone", "--bare")
|
||||
if opts.SingleBranch != "" {
|
||||
cloneCmd.AddArguments("--single-branch", "--branch").AddDynamicArguments(opts.SingleBranch)
|
||||
cloneOpts := git.CloneRepoOptions{
|
||||
Bare: true,
|
||||
Timeout: 10 * time.Minute,
|
||||
}
|
||||
var stdout []byte
|
||||
if stdout, _, err = cloneCmd.AddDynamicArguments(opts.BaseRepo.RepoPath(), repo.RepoPath()).
|
||||
RunStdBytes(ctx, &gitcmd.RunOpts{Timeout: 10 * time.Minute}); err != nil {
|
||||
log.Error("Fork Repository (git clone) Failed for %v (from %v):\nStdout: %s\nError: %v", repo, opts.BaseRepo, stdout, err)
|
||||
if opts.SingleBranch != "" {
|
||||
cloneOpts.SingleBranch = true
|
||||
cloneOpts.Branch = opts.SingleBranch
|
||||
}
|
||||
if err = gitrepo.Clone(ctx, opts.BaseRepo, repo, cloneOpts); err != nil {
|
||||
log.Error("Fork Repository (git clone) Failed for %v (from %v):\nError: %v", repo, opts.BaseRepo, err)
|
||||
return nil, fmt.Errorf("git clone: %w", err)
|
||||
}
|
||||
|
||||
@@ -176,10 +177,10 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork
|
||||
}
|
||||
defer gitRepo.Close()
|
||||
|
||||
if _, err = repo_module.SyncRepoBranchesWithRepo(ctx, repo, gitRepo, doer.ID); err != nil {
|
||||
if _, _, err = repo_module.SyncRepoBranchesWithRepo(ctx, repo, gitRepo, doer.ID); err != nil {
|
||||
return nil, fmt.Errorf("SyncRepoBranchesWithRepo: %w", err)
|
||||
}
|
||||
if err = repo_module.SyncReleasesWithTags(ctx, repo, gitRepo); err != nil {
|
||||
if _, err = repo_module.SyncReleasesWithTags(ctx, repo, gitRepo); err != nil {
|
||||
return nil, fmt.Errorf("Sync releases from git tags failed: %v", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/git/gitcmd"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/glob"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
@@ -104,12 +103,12 @@ func generateExpansion(ctx context.Context, src string, templateRepo, generateRe
|
||||
|
||||
// giteaTemplateFileMatcher holds information about a .gitea/template file
|
||||
type giteaTemplateFileMatcher struct {
|
||||
LocalFullPath string
|
||||
globs []glob.Glob
|
||||
relPath string
|
||||
globs []glob.Glob
|
||||
}
|
||||
|
||||
func newGiteaTemplateFileMatcher(fullPath string, content []byte) *giteaTemplateFileMatcher {
|
||||
gt := &giteaTemplateFileMatcher{LocalFullPath: fullPath}
|
||||
func newGiteaTemplateFileMatcher(relPath string, content []byte) *giteaTemplateFileMatcher {
|
||||
gt := &giteaTemplateFileMatcher{relPath: relPath}
|
||||
gt.globs = make([]glob.Glob, 0)
|
||||
scanner := bufio.NewScanner(bytes.NewReader(content))
|
||||
for scanner.Scan() {
|
||||
@@ -140,64 +139,44 @@ func (gt *giteaTemplateFileMatcher) Match(s string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func readLocalTmpRepoFileContent(localPath string, limit int) ([]byte, error) {
|
||||
ok, err := util.IsRegularFile(localPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !ok {
|
||||
return nil, fs.ErrNotExist
|
||||
}
|
||||
|
||||
f, err := os.Open(localPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return util.ReadWithLimit(f, limit)
|
||||
}
|
||||
|
||||
func readGiteaTemplateFile(tmpDir string) (*giteaTemplateFileMatcher, error) {
|
||||
localPath := filepath.Join(tmpDir, ".gitea", "template")
|
||||
content, err := readLocalTmpRepoFileContent(localPath, 1024*1024)
|
||||
templateRelPath := filepath.Join(".gitea", "template")
|
||||
content, err := util.ReadRegularPathFile(tmpDir, templateRelPath, 1024*1024)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, util.Iif(errors.Is(err, util.ErrNotRegularPathFile), os.ErrNotExist, err)
|
||||
}
|
||||
return newGiteaTemplateFileMatcher(localPath, content), nil
|
||||
return newGiteaTemplateFileMatcher(templateRelPath, content), nil
|
||||
}
|
||||
|
||||
func substGiteaTemplateFile(ctx context.Context, tmpDir, tmpDirSubPath string, templateRepo, generateRepo *repo_model.Repository) error {
|
||||
tmpFullPath := filepath.Join(tmpDir, tmpDirSubPath)
|
||||
content, err := readLocalTmpRepoFileContent(tmpFullPath, 1024*1024)
|
||||
content, err := util.ReadRegularPathFile(tmpDir, tmpDirSubPath, 1024*1024)
|
||||
if err != nil {
|
||||
return util.Iif(errors.Is(err, fs.ErrNotExist), nil, err)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if err := util.Remove(tmpFullPath); err != nil {
|
||||
if err := os.Remove(util.FilePathJoinAbs(tmpDir, tmpDirSubPath)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
generatedContent := generateExpansion(ctx, string(content), templateRepo, generateRepo)
|
||||
substSubPath := filePathSanitize(generateExpansion(ctx, tmpDirSubPath, templateRepo, generateRepo))
|
||||
newLocalPath := filepath.Join(tmpDir, substSubPath)
|
||||
regular, err := util.IsRegularFile(newLocalPath)
|
||||
if canWrite := regular || errors.Is(err, fs.ErrNotExist); !canWrite {
|
||||
return nil
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(newLocalPath), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(newLocalPath, []byte(generatedContent), 0o644)
|
||||
return util.WriteRegularPathFile(tmpDir, substSubPath, []byte(generatedContent), 0o755, 0o644)
|
||||
}
|
||||
|
||||
func processGiteaTemplateFile(ctx context.Context, tmpDir string, templateRepo, generateRepo *repo_model.Repository, fileMatcher *giteaTemplateFileMatcher) error {
|
||||
if err := util.Remove(fileMatcher.LocalFullPath); err != nil {
|
||||
return fmt.Errorf("unable to remove .gitea/template: %w", err)
|
||||
// processGiteaTemplateFile processes and removes the .gitea/template file, does variable expansion for template files
|
||||
// and save the processed files to the filesystem. It returns a list of skipped files that are not regular paths.
|
||||
func processGiteaTemplateFile(ctx context.Context, tmpDir string, templateRepo, generateRepo *repo_model.Repository, fileMatcher *giteaTemplateFileMatcher) (skippedFiles []string, _ error) {
|
||||
// Why not use "os.Root" here: symlink is unsafe even in the same root but "os.Root" can't help, it's more difficult to use "os.Root" to do the WalkDir.
|
||||
if err := os.Remove(util.FilePathJoinAbs(tmpDir, fileMatcher.relPath)); err != nil {
|
||||
return nil, fmt.Errorf("unable to remove .gitea/template: %w", err)
|
||||
}
|
||||
if !fileMatcher.HasRules() {
|
||||
return nil // Avoid walking tree if there are no globs
|
||||
return skippedFiles, nil // Avoid walking tree if there are no globs
|
||||
}
|
||||
|
||||
return filepath.WalkDir(tmpDir, func(fullPath string, d os.DirEntry, walkErr error) error {
|
||||
err := filepath.WalkDir(tmpDir, func(fullPath string, d os.DirEntry, walkErr error) error {
|
||||
if walkErr != nil {
|
||||
return walkErr
|
||||
}
|
||||
@@ -209,29 +188,30 @@ func processGiteaTemplateFile(ctx context.Context, tmpDir string, templateRepo,
|
||||
return err
|
||||
}
|
||||
if fileMatcher.Match(filepath.ToSlash(tmpDirSubPath)) {
|
||||
return substGiteaTemplateFile(ctx, tmpDir, tmpDirSubPath, templateRepo, generateRepo)
|
||||
err := substGiteaTemplateFile(ctx, tmpDir, tmpDirSubPath, templateRepo, generateRepo)
|
||||
if errors.Is(err, util.ErrNotRegularPathFile) {
|
||||
skippedFiles = append(skippedFiles, tmpDirSubPath)
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}) // end: WalkDir
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = util.RemoveAll(util.FilePathJoinAbs(tmpDir, ".git")); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return skippedFiles, nil
|
||||
}
|
||||
|
||||
func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *repo_model.Repository, tmpDir string) error {
|
||||
commitTimeStr := time.Now().Format(time.RFC3339)
|
||||
authorSig := repo.Owner.NewGitSig()
|
||||
|
||||
// Because this may call hooks we should pass in the environment
|
||||
env := append(os.Environ(),
|
||||
"GIT_AUTHOR_NAME="+authorSig.Name,
|
||||
"GIT_AUTHOR_EMAIL="+authorSig.Email,
|
||||
"GIT_AUTHOR_DATE="+commitTimeStr,
|
||||
"GIT_COMMITTER_NAME="+authorSig.Name,
|
||||
"GIT_COMMITTER_EMAIL="+authorSig.Email,
|
||||
"GIT_COMMITTER_DATE="+commitTimeStr,
|
||||
)
|
||||
// set default branch based on whether it's specified in the newly generated repo or not
|
||||
repo.DefaultBranch = util.IfZero(repo.DefaultBranch, templateRepo.DefaultBranch)
|
||||
|
||||
// Clone to temporary path and do the init commit.
|
||||
templateRepoPath := templateRepo.RepoPath()
|
||||
if err := git.Clone(ctx, templateRepoPath, tmpDir, git.CloneRepoOptions{
|
||||
if err := gitrepo.CloneRepoToLocal(ctx, templateRepo, tmpDir, git.CloneRepoOptions{
|
||||
Depth: 1,
|
||||
Branch: templateRepo.DefaultBranch,
|
||||
}); err != nil {
|
||||
@@ -251,7 +231,7 @@ func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *r
|
||||
// Variable expansion
|
||||
fileMatcher, err := readGiteaTemplateFile(tmpDir)
|
||||
if err == nil {
|
||||
err = processGiteaTemplateFile(ctx, tmpDir, templateRepo, generateRepo, fileMatcher)
|
||||
_, err = processGiteaTemplateFile(ctx, tmpDir, templateRepo, generateRepo, fileMatcher)
|
||||
if err != nil {
|
||||
return fmt.Errorf("processGiteaTemplateFile: %w", err)
|
||||
}
|
||||
@@ -265,23 +245,11 @@ func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *r
|
||||
return err
|
||||
}
|
||||
|
||||
if stdout, _, err := gitcmd.NewCommand("remote", "add", "origin").AddDynamicArguments(repo.RepoPath()).
|
||||
RunStdString(ctx, &gitcmd.RunOpts{Dir: tmpDir, Env: env}); err != nil {
|
||||
log.Error("Unable to add %v as remote origin to temporary repo to %s: stdout %s\nError: %v", repo, tmpDir, stdout, err)
|
||||
return fmt.Errorf("git remote add: %w", err)
|
||||
}
|
||||
|
||||
if err = git.AddTemplateSubmoduleIndexes(ctx, tmpDir, submodules); err != nil {
|
||||
return fmt.Errorf("failed to add submodules: %v", err)
|
||||
}
|
||||
|
||||
// set default branch based on whether it's specified in the newly generated repo or not
|
||||
defaultBranch := repo.DefaultBranch
|
||||
if strings.TrimSpace(defaultBranch) == "" {
|
||||
defaultBranch = templateRepo.DefaultBranch
|
||||
}
|
||||
|
||||
return initRepoCommit(ctx, tmpDir, repo, repo.Owner, defaultBranch)
|
||||
return initRepoCommit(ctx, tmpDir, repo, repo.Owner, repo.DefaultBranch)
|
||||
}
|
||||
|
||||
// GenerateGitContent generates git content from a template repository
|
||||
@@ -295,17 +263,6 @@ func GenerateGitContent(ctx context.Context, templateRepo, generateRepo *repo_mo
|
||||
if err = generateRepoCommit(ctx, generateRepo, templateRepo, generateRepo, tmpDir); err != nil {
|
||||
return fmt.Errorf("generateRepoCommit: %w", err)
|
||||
}
|
||||
|
||||
// re-fetch repo
|
||||
if generateRepo, err = repo_model.GetRepositoryByID(ctx, generateRepo.ID); err != nil {
|
||||
return fmt.Errorf("getRepositoryByID: %w", err)
|
||||
}
|
||||
|
||||
// if there was no default branch supplied when generating the repo, use the default one from the template
|
||||
if strings.TrimSpace(generateRepo.DefaultBranch) == "" {
|
||||
generateRepo.DefaultBranch = templateRepo.DefaultBranch
|
||||
}
|
||||
|
||||
if err = gitrepo.SetDefaultBranch(ctx, generateRepo, generateRepo.DefaultBranch); err != nil {
|
||||
return fmt.Errorf("setDefaultBranch: %w", err)
|
||||
}
|
||||
@@ -320,6 +277,9 @@ func GenerateGitContent(ctx context.Context, templateRepo, generateRepo *repo_mo
|
||||
if err := git_model.CopyLFS(ctx, generateRepo, templateRepo); err != nil {
|
||||
return fmt.Errorf("failed to copy LFS: %w", err)
|
||||
}
|
||||
if _, err := repo_module.SyncRepoBranches(ctx, generateRepo.ID, 0); err != nil {
|
||||
return fmt.Errorf("SyncRepoBranches: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ func TestFilePathSanitize(t *testing.T) {
|
||||
assert.Equal(t, ".", filePathSanitize("/"))
|
||||
}
|
||||
|
||||
func TestProcessGiteaTemplateFile(t *testing.T) {
|
||||
func TestProcessGiteaTemplateFileGenerate(t *testing.T) {
|
||||
tmpDir := filepath.Join(t.TempDir(), "gitea-template-test")
|
||||
|
||||
assertFileContent := func(path, expected string) {
|
||||
@@ -97,6 +97,8 @@ func TestProcessGiteaTemplateFile(t *testing.T) {
|
||||
assert.Equal(t, expected, link, "symlink target mismatch for %s", path)
|
||||
}
|
||||
|
||||
require.NoError(t, os.MkdirAll(tmpDir+"/.git", 0o755))
|
||||
require.NoError(t, os.WriteFile(tmpDir+"/.git/config", []byte("git-config-dummy"), 0o644))
|
||||
require.NoError(t, os.MkdirAll(tmpDir+"/.gitea", 0o755))
|
||||
require.NoError(t, os.WriteFile(tmpDir+"/.gitea/template", []byte("*\ninclude/**"), 0o644))
|
||||
require.NoError(t, os.MkdirAll(tmpDir+"/sub", 0o755))
|
||||
@@ -127,10 +129,20 @@ func TestProcessGiteaTemplateFile(t *testing.T) {
|
||||
assertFileContent("subst-${TEMPLATE_NAME}-to-link", toLinkContent)
|
||||
assertFileContent("subst-${TEMPLATE_NAME}-from-link", fromLinkContent)
|
||||
}
|
||||
|
||||
// case-5
|
||||
{
|
||||
require.NoError(t, os.MkdirAll(tmpDir+"/real-dir", 0o755))
|
||||
require.NoError(t, os.WriteFile(tmpDir+"/real-dir/real-file", []byte("origin content"), 0o644))
|
||||
require.NoError(t, os.MkdirAll(tmpDir+"/include/subst-${TEMPLATE_NAME}-link-dir", 0o755))
|
||||
require.NoError(t, os.WriteFile(tmpDir+"/include/subst-${TEMPLATE_NAME}-link-dir/real-file", []byte("template content"), 0o644))
|
||||
require.NoError(t, os.Symlink(tmpDir+"/real-dir", tmpDir+"/include/subst-TemplateRepoName-link-dir"))
|
||||
}
|
||||
|
||||
{
|
||||
// will succeed
|
||||
require.NoError(t, os.WriteFile(tmpDir+"/subst-${TEMPLATE_NAME}-normal", []byte("dummy subst template name normal"), 0o644))
|
||||
// will skil if the path subst result is a link
|
||||
// will be skipped if the path subst result is a link
|
||||
require.NoError(t, os.WriteFile(tmpDir+"/subst-${TEMPLATE_NAME}-to-link", []byte("dummy subst template name to link"), 0o644))
|
||||
require.NoError(t, os.Symlink(tmpDir+"/sub/link-target", tmpDir+"/subst-TemplateRepoName-to-link"))
|
||||
// will be skipped since the source is a symlink
|
||||
@@ -143,9 +155,20 @@ func TestProcessGiteaTemplateFile(t *testing.T) {
|
||||
{
|
||||
templateRepo := &repo_model.Repository{Name: "TemplateRepoName"}
|
||||
generatedRepo := &repo_model.Repository{Name: "/../.gIt/name"}
|
||||
assertFileContent(".git/config", "git-config-dummy")
|
||||
fileMatcher, _ := readGiteaTemplateFile(tmpDir)
|
||||
err := processGiteaTemplateFile(t.Context(), tmpDir, templateRepo, generatedRepo, fileMatcher)
|
||||
skippedFiles, err := processGiteaTemplateFile(t.Context(), tmpDir, templateRepo, generatedRepo, fileMatcher)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []string{
|
||||
"include/subst-${TEMPLATE_NAME}-link-dir/real-file",
|
||||
"include/subst-TemplateRepoName-link-dir",
|
||||
"link",
|
||||
"subst-${TEMPLATE_NAME}-from-link",
|
||||
"subst-${TEMPLATE_NAME}-to-link",
|
||||
"subst-TemplateRepoName-to-link",
|
||||
}, skippedFiles)
|
||||
assertFileContent(".git/config", "")
|
||||
assertFileContent(".gitea/template", "")
|
||||
assertFileContent("include/foo/bar/test.txt", "include subdir TemplateRepoName")
|
||||
}
|
||||
|
||||
@@ -182,32 +205,38 @@ func TestProcessGiteaTemplateFile(t *testing.T) {
|
||||
assertSymLink("subst-${TEMPLATE_NAME}-from-link", tmpDir+"/sub/link-target")
|
||||
}
|
||||
|
||||
// case-5
|
||||
{
|
||||
templateFilePath := tmpDir + "/.gitea/template"
|
||||
|
||||
_ = os.Remove(templateFilePath)
|
||||
_, err := os.Lstat(templateFilePath)
|
||||
require.ErrorIs(t, err, fs.ErrNotExist)
|
||||
_, err = readGiteaTemplateFile(tmpDir) // no template file
|
||||
require.ErrorIs(t, err, fs.ErrNotExist)
|
||||
|
||||
_ = os.WriteFile(templateFilePath+".target", []byte("test-data-target"), 0o644)
|
||||
_ = os.Symlink(templateFilePath+".target", templateFilePath)
|
||||
content, _ := os.ReadFile(templateFilePath)
|
||||
require.Equal(t, "test-data-target", string(content))
|
||||
_, err = readGiteaTemplateFile(tmpDir) // symlinked template file
|
||||
require.ErrorIs(t, err, fs.ErrNotExist)
|
||||
|
||||
_ = os.Remove(templateFilePath)
|
||||
_ = os.WriteFile(templateFilePath, []byte("test-data-regular"), 0o644)
|
||||
content, _ = os.ReadFile(templateFilePath)
|
||||
require.Equal(t, "test-data-regular", string(content))
|
||||
fm, err := readGiteaTemplateFile(tmpDir) // regular template file
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, fm.globs, 1)
|
||||
assertFileContent("real-dir/real-file", "origin content")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessGiteaTemplateFileRead(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
_ = os.Mkdir(tmpDir+"/.gitea", 0o755)
|
||||
templateFilePath := tmpDir + "/.gitea/template"
|
||||
_ = os.Remove(templateFilePath)
|
||||
_, err := os.Lstat(templateFilePath)
|
||||
require.ErrorIs(t, err, fs.ErrNotExist)
|
||||
_, err = readGiteaTemplateFile(tmpDir) // no template file
|
||||
require.ErrorIs(t, err, fs.ErrNotExist)
|
||||
|
||||
_ = os.WriteFile(templateFilePath+".target", []byte("test-data-target"), 0o644)
|
||||
_ = os.Symlink(templateFilePath+".target", templateFilePath)
|
||||
content, _ := os.ReadFile(templateFilePath)
|
||||
require.Equal(t, "test-data-target", string(content))
|
||||
_, err = readGiteaTemplateFile(tmpDir) // symlinked template file
|
||||
require.ErrorIs(t, err, fs.ErrNotExist)
|
||||
|
||||
_ = os.Remove(templateFilePath)
|
||||
_ = os.WriteFile(templateFilePath, []byte("test-data-regular"), 0o644)
|
||||
content, _ = os.ReadFile(templateFilePath)
|
||||
require.Equal(t, "test-data-regular", string(content))
|
||||
fm, err := readGiteaTemplateFile(tmpDir) // regular template file
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, fm.globs, 1)
|
||||
}
|
||||
|
||||
func TestTransformers(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
|
||||
@@ -6,9 +6,6 @@ package gitgraph
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/git/gitcmd"
|
||||
@@ -45,22 +42,14 @@ func GetCommitGraph(r *git.Repository, page, maxAllowedColors int, hidePRRefs bo
|
||||
}
|
||||
graph := NewGraph()
|
||||
|
||||
stderr := new(strings.Builder)
|
||||
stdoutReader, stdoutWriter, err := os.Pipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
commitsToSkip := setting.UI.GraphMaxCommitNum * (page - 1)
|
||||
|
||||
scanner := bufio.NewScanner(stdoutReader)
|
||||
|
||||
if err := graphCmd.Run(r.Ctx, &gitcmd.RunOpts{
|
||||
Dir: r.Path,
|
||||
Stdout: stdoutWriter,
|
||||
Stderr: stderr,
|
||||
PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error {
|
||||
_ = stdoutWriter.Close()
|
||||
defer stdoutReader.Close()
|
||||
stdoutReader, stdoutReaderClose := graphCmd.MakeStdoutPipe()
|
||||
defer stdoutReaderClose()
|
||||
if err := graphCmd.
|
||||
WithDir(r.Path).
|
||||
WithPipelineFunc(func(ctx gitcmd.Context) error {
|
||||
scanner := bufio.NewScanner(stdoutReader)
|
||||
parser := &Parser{}
|
||||
parser.firstInUse = -1
|
||||
parser.maxAllowedColors = maxAllowedColors
|
||||
@@ -92,8 +81,7 @@ func GetCommitGraph(r *git.Repository, page, maxAllowedColors int, hidePRRefs bo
|
||||
line := scanner.Bytes()
|
||||
if bytes.IndexByte(line, '*') >= 0 {
|
||||
if err := parser.AddLineToGraph(graph, row, line); err != nil {
|
||||
cancel()
|
||||
return err
|
||||
return ctx.CancelPipeline(err)
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -104,13 +92,12 @@ func GetCommitGraph(r *git.Repository, page, maxAllowedColors int, hidePRRefs bo
|
||||
row++
|
||||
line := scanner.Bytes()
|
||||
if err := parser.AddLineToGraph(graph, row, line); err != nil {
|
||||
cancel()
|
||||
return err
|
||||
return ctx.CancelPipeline(err)
|
||||
}
|
||||
}
|
||||
return scanner.Err()
|
||||
},
|
||||
}); err != nil {
|
||||
}).
|
||||
RunWithStderr(r.Ctx); err != nil {
|
||||
return graph, err
|
||||
}
|
||||
return graph, nil
|
||||
|
||||
@@ -238,8 +238,8 @@ func TestCommitStringParsing(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.testName, func(t *testing.T) {
|
||||
testString := fmt.Sprintf("%s%s", dataFirstPart, test.commitMessage)
|
||||
idx := strings.Index(testString, "DATA:")
|
||||
commit, err := NewCommit(0, 0, []byte(testString[idx+5:]))
|
||||
_, after, _ := strings.Cut(testString, "DATA:")
|
||||
commit, err := NewCommit(0, 0, []byte(after))
|
||||
if err != nil && test.shouldPass {
|
||||
t.Errorf("Could not parse %s", testString)
|
||||
return
|
||||
|
||||
@@ -44,11 +44,11 @@ func (parser *Parser) Reset() {
|
||||
|
||||
// AddLineToGraph adds the line as a row to the graph
|
||||
func (parser *Parser) AddLineToGraph(graph *Graph, row int, line []byte) error {
|
||||
idx := bytes.Index(line, []byte("DATA:"))
|
||||
if idx < 0 {
|
||||
before, after, ok := bytes.Cut(line, []byte("DATA:"))
|
||||
if !ok {
|
||||
parser.ParseGlyphs(line)
|
||||
} else {
|
||||
parser.ParseGlyphs(line[:idx])
|
||||
parser.ParseGlyphs(before)
|
||||
}
|
||||
|
||||
var err error
|
||||
@@ -72,7 +72,7 @@ func (parser *Parser) AddLineToGraph(graph *Graph, row int, line []byte) error {
|
||||
}
|
||||
}
|
||||
commitDone = true
|
||||
if idx < 0 {
|
||||
if !ok {
|
||||
if err != nil {
|
||||
err = fmt.Errorf("missing data section on line %d with commit: %s. %w", row, string(line), err)
|
||||
} else {
|
||||
@@ -80,7 +80,7 @@ func (parser *Parser) AddLineToGraph(graph *Graph, row int, line []byte) error {
|
||||
}
|
||||
continue
|
||||
}
|
||||
err2 := graph.AddCommit(row, column, flowID, line[idx+5:])
|
||||
err2 := graph.AddCommit(row, column, flowID, after)
|
||||
if err != nil && err2 != nil {
|
||||
err = fmt.Errorf("%v %w", err2, err)
|
||||
continue
|
||||
|
||||
@@ -11,7 +11,9 @@ import (
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/git/gitcmd"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
@@ -33,8 +35,7 @@ func initRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Reposi
|
||||
committerName := sig.Name
|
||||
committerEmail := sig.Email
|
||||
|
||||
if stdout, _, err := gitcmd.NewCommand("add", "--all").
|
||||
RunStdString(ctx, &gitcmd.RunOpts{Dir: tmpPath}); err != nil {
|
||||
if stdout, _, err := gitcmd.NewCommand("add", "--all").WithDir(tmpPath).RunStdString(ctx); err != nil {
|
||||
log.Error("git add --all failed: Stdout: %s\nError: %v", stdout, err)
|
||||
return fmt.Errorf("git add --all: %w", err)
|
||||
}
|
||||
@@ -42,7 +43,7 @@ func initRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Reposi
|
||||
cmd := gitcmd.NewCommand("commit", "--message=Initial commit").
|
||||
AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email)
|
||||
|
||||
sign, key, signer, _ := asymkey_service.SignInitialCommit(ctx, tmpPath, u)
|
||||
sign, key, signer, _ := asymkey_service.SignInitialCommit(ctx, u)
|
||||
if sign {
|
||||
if key.Format != "" {
|
||||
cmd.AddConfig("gpg.format", key.Format)
|
||||
@@ -63,8 +64,7 @@ func initRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Reposi
|
||||
"GIT_COMMITTER_EMAIL="+committerEmail,
|
||||
)
|
||||
|
||||
if stdout, _, err := cmd.
|
||||
RunStdString(ctx, &gitcmd.RunOpts{Dir: tmpPath, Env: env}); err != nil {
|
||||
if stdout, _, err := cmd.WithDir(tmpPath).WithEnv(env).RunStdString(ctx); err != nil {
|
||||
log.Error("Failed to commit: %v: Stdout: %s\nError: %v", cmd.LogString(), stdout, err)
|
||||
return fmt.Errorf("git commit: %w", err)
|
||||
}
|
||||
@@ -73,9 +73,12 @@ func initRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Reposi
|
||||
defaultBranch = setting.Repository.DefaultBranch
|
||||
}
|
||||
|
||||
if stdout, _, err := gitcmd.NewCommand("push", "origin").AddDynamicArguments("HEAD:"+defaultBranch).
|
||||
RunStdString(ctx, &gitcmd.RunOpts{Dir: tmpPath, Env: repo_module.InternalPushingEnvironment(u, repo)}); err != nil {
|
||||
log.Error("Failed to push back to HEAD: Stdout: %s\nError: %v", stdout, err)
|
||||
if err := gitrepo.PushFromLocal(ctx, tmpPath, repo, git.PushOptions{
|
||||
LocalRefName: "HEAD",
|
||||
Branch: defaultBranch,
|
||||
Env: repo_module.InternalPushingEnvironment(u, repo),
|
||||
}); err != nil {
|
||||
log.Error("Failed to push back to HEAD Error: %v", err)
|
||||
return fmt.Errorf("git push: %w", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -123,10 +123,8 @@ func GarbageCollectLFSMetaObjectsForRepo(ctx context.Context, repo *repo_model.R
|
||||
//
|
||||
// It is likely that a week is potentially excessive but it should definitely be enough that any
|
||||
// unassociated LFS object is genuinely unassociated.
|
||||
OlderThan: timeutil.TimeStamp(opts.OlderThan.Unix()),
|
||||
UpdatedLessRecentlyThan: timeutil.TimeStamp(opts.UpdatedLessRecentlyThan.Unix()),
|
||||
OrderByUpdated: true,
|
||||
LoopFunctionAlwaysUpdates: true,
|
||||
OlderThan: timeutil.TimeStamp(opts.OlderThan.Unix()),
|
||||
UpdatedLessRecentlyThan: timeutil.TimeStamp(opts.UpdatedLessRecentlyThan.Unix()),
|
||||
})
|
||||
|
||||
if err == errStop {
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/lfs"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -22,7 +23,8 @@ import (
|
||||
func TestGarbageCollectLFSMetaObjects(t *testing.T) {
|
||||
unittest.PrepareTestEnv(t)
|
||||
|
||||
setting.LFS.StartServer = true
|
||||
defer test.MockVariableValue(&setting.LFS.StartServer, true)()
|
||||
|
||||
err := storage.Init()
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -46,6 +48,32 @@ func TestGarbageCollectLFSMetaObjects(t *testing.T) {
|
||||
assert.ErrorIs(t, err, git_model.ErrLFSObjectNotExist)
|
||||
}
|
||||
|
||||
func TestGarbageCollectLFSMetaObjectsForRepoAutoFix(t *testing.T) {
|
||||
unittest.PrepareTestEnv(t)
|
||||
|
||||
defer test.MockVariableValue(&setting.LFS.StartServer, true)()
|
||||
|
||||
err := storage.Init()
|
||||
assert.NoError(t, err)
|
||||
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
|
||||
// add lfs object
|
||||
lfsContent := []byte("gitea2")
|
||||
lfsOid := storeObjectInRepo(t, repo.ID, &lfsContent)
|
||||
|
||||
err = repo_service.GarbageCollectLFSMetaObjectsForRepo(t.Context(), repo, repo_service.GarbageCollectLFSMetaObjectsOptions{
|
||||
LogDetail: func(string, ...any) {},
|
||||
AutoFix: true,
|
||||
OlderThan: time.Now().Add(24 * time.Hour * 7),
|
||||
UpdatedLessRecentlyThan: time.Now().Add(24 * time.Hour * 3),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = git_model.GetLFSMetaObjectByOid(t.Context(), repo.ID, lfsOid)
|
||||
assert.ErrorIs(t, err, git_model.ErrLFSObjectNotExist)
|
||||
}
|
||||
|
||||
func storeObjectInRepo(t *testing.T, repositoryID int64, content *[]byte) string {
|
||||
pointer, err := lfs.GeneratePointer(bytes.NewReader(*content))
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/reqctx"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
@@ -33,8 +34,7 @@ func MergeUpstream(ctx reqctx.RequestContext, doer *user_model.User, repo *repo_
|
||||
return "up-to-date", nil
|
||||
}
|
||||
|
||||
err = git.Push(ctx, repo.BaseRepo.RepoPath(), git.PushOptions{
|
||||
Remote: repo.RepoPath(),
|
||||
err = gitrepo.Push(ctx, repo.BaseRepo, repo, git.PushOptions{
|
||||
Branch: fmt.Sprintf("%s:%s", divergingInfo.BaseBranchName, branch),
|
||||
Env: repo_module.PushingEnvironment(doer, repo),
|
||||
})
|
||||
|
||||
@@ -27,23 +27,24 @@ import (
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
func cloneWiki(ctx context.Context, u *user_model.User, opts migration.MigrateOptions, migrateTimeout time.Duration) (string, error) {
|
||||
wikiPath := repo_model.WikiPath(u.Name, opts.RepoName)
|
||||
wikiRemotePath := repo_module.WikiRemoteURL(ctx, opts.CloneAddr)
|
||||
if wikiRemotePath == "" {
|
||||
func cloneWiki(ctx context.Context, repo *repo_model.Repository, opts migration.MigrateOptions, migrateTimeout time.Duration) (string, error) {
|
||||
wikiRemoteURL := repo_module.WikiRemoteURL(ctx, opts.CloneAddr)
|
||||
if wikiRemoteURL == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if err := util.RemoveAll(wikiPath); err != nil {
|
||||
return "", fmt.Errorf("failed to remove existing wiki dir %q, err: %w", wikiPath, err)
|
||||
storageRepo := repo.WikiStorageRepo()
|
||||
|
||||
if err := gitrepo.DeleteRepository(ctx, storageRepo); err != nil {
|
||||
return "", fmt.Errorf("failed to remove existing wiki dir %q, err: %w", storageRepo.RelativePath(), err)
|
||||
}
|
||||
|
||||
cleanIncompleteWikiPath := func() {
|
||||
if err := util.RemoveAll(wikiPath); err != nil {
|
||||
log.Error("Failed to remove incomplete wiki dir %q, err: %v", wikiPath, err)
|
||||
if err := gitrepo.DeleteRepository(ctx, storageRepo); err != nil {
|
||||
log.Error("Failed to remove incomplete wiki dir %q, err: %v", storageRepo.RelativePath(), err)
|
||||
}
|
||||
}
|
||||
if err := git.Clone(ctx, wikiRemotePath, wikiPath, git.CloneRepoOptions{
|
||||
if err := gitrepo.CloneExternalRepo(ctx, wikiRemoteURL, storageRepo, git.CloneRepoOptions{
|
||||
Mirror: true,
|
||||
Quiet: true,
|
||||
Timeout: migrateTimeout,
|
||||
@@ -54,15 +55,15 @@ func cloneWiki(ctx context.Context, u *user_model.User, opts migration.MigrateOp
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := git.WriteCommitGraph(ctx, wikiPath); err != nil {
|
||||
if err := gitrepo.WriteCommitGraph(ctx, storageRepo); err != nil {
|
||||
cleanIncompleteWikiPath()
|
||||
return "", err
|
||||
}
|
||||
|
||||
defaultBranch, err := git.GetDefaultBranch(ctx, wikiPath)
|
||||
defaultBranch, err := gitrepo.GetDefaultBranch(ctx, storageRepo)
|
||||
if err != nil {
|
||||
cleanIncompleteWikiPath()
|
||||
return "", fmt.Errorf("failed to get wiki repo default branch for %q, err: %w", wikiPath, err)
|
||||
return "", fmt.Errorf("failed to get wiki repo default branch for %q, err: %w", storageRepo.RelativePath(), err)
|
||||
}
|
||||
|
||||
return defaultBranch, nil
|
||||
@@ -73,8 +74,6 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User,
|
||||
repo *repo_model.Repository, opts migration.MigrateOptions,
|
||||
httpTransport *http.Transport,
|
||||
) (*repo_model.Repository, error) {
|
||||
repoPath := repo_model.RepoPath(u.Name, opts.RepoName)
|
||||
|
||||
if u.IsOrganization() {
|
||||
t, err := organization.OrgFromUser(u).GetOwnerTeam(ctx)
|
||||
if err != nil {
|
||||
@@ -87,11 +86,11 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User,
|
||||
|
||||
migrateTimeout := time.Duration(setting.Git.Timeout.Migrate) * time.Second
|
||||
|
||||
if err := util.RemoveAll(repoPath); err != nil {
|
||||
return repo, fmt.Errorf("failed to remove existing repo dir %q, err: %w", repoPath, err)
|
||||
if err := gitrepo.DeleteRepository(ctx, repo); err != nil {
|
||||
return repo, fmt.Errorf("failed to remove existing repo dir %q, err: %w", repo.FullName(), err)
|
||||
}
|
||||
|
||||
if err := git.Clone(ctx, opts.CloneAddr, repoPath, git.CloneRepoOptions{
|
||||
if err := gitrepo.CloneExternalRepo(ctx, opts.CloneAddr, repo, git.CloneRepoOptions{
|
||||
Mirror: true,
|
||||
Quiet: true,
|
||||
Timeout: migrateTimeout,
|
||||
@@ -103,12 +102,12 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User,
|
||||
return repo, fmt.Errorf("clone error: %w", err)
|
||||
}
|
||||
|
||||
if err := git.WriteCommitGraph(ctx, repoPath); err != nil {
|
||||
if err := gitrepo.WriteCommitGraph(ctx, repo); err != nil {
|
||||
return repo, err
|
||||
}
|
||||
|
||||
if opts.Wiki {
|
||||
defaultWikiBranch, err := cloneWiki(ctx, u, opts, migrateTimeout)
|
||||
defaultWikiBranch, err := cloneWiki(ctx, repo, opts, migrateTimeout)
|
||||
if err != nil {
|
||||
return repo, fmt.Errorf("clone wiki error: %w", err)
|
||||
}
|
||||
@@ -123,7 +122,7 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User,
|
||||
return nil, fmt.Errorf("updateGitRepoAfterCreate: %w", err)
|
||||
}
|
||||
|
||||
gitRepo, err := git.OpenRepository(ctx, repoPath)
|
||||
gitRepo, err := gitrepo.OpenRepository(ctx, repo)
|
||||
if err != nil {
|
||||
return repo, fmt.Errorf("OpenRepository: %w", err)
|
||||
}
|
||||
@@ -137,7 +136,7 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User,
|
||||
if !repo.IsEmpty {
|
||||
if len(repo.DefaultBranch) == 0 {
|
||||
// Try to get HEAD branch and set it as default branch.
|
||||
headBranchName, err := git.GetDefaultBranch(ctx, repoPath)
|
||||
headBranchName, err := gitrepo.GetDefaultBranch(ctx, repo)
|
||||
if err != nil {
|
||||
return repo, fmt.Errorf("GetHEADBranch: %w", err)
|
||||
}
|
||||
@@ -146,7 +145,7 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User,
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := repo_module.SyncRepoBranchesWithRepo(ctx, repo, gitRepo, u.ID); err != nil {
|
||||
if _, _, err := repo_module.SyncRepoBranchesWithRepo(ctx, repo, gitRepo, u.ID); err != nil {
|
||||
return repo, fmt.Errorf("SyncRepoBranchesWithRepo: %v", err)
|
||||
}
|
||||
|
||||
@@ -154,7 +153,7 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User,
|
||||
// otherwise, the releases sync will be done out of this function
|
||||
if !opts.Releases {
|
||||
repo.IsMirror = opts.Mirror
|
||||
if err = repo_module.SyncReleasesWithTags(ctx, repo, gitRepo); err != nil {
|
||||
if _, err = repo_module.SyncReleasesWithTags(ctx, repo, gitRepo); err != nil {
|
||||
log.Error("Failed to synchronize tags to releases for repository: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -226,9 +225,9 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User,
|
||||
|
||||
// this is necessary for sync local tags from remote
|
||||
configName := fmt.Sprintf("remote.%s.fetch", mirrorModel.GetRemoteName())
|
||||
if stdout, _, err := gitcmd.NewCommand("config").
|
||||
AddOptionValues("--add", configName, `+refs/tags/*:refs/tags/*`).
|
||||
RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath}); err != nil {
|
||||
if stdout, _, err := gitrepo.RunCmdString(ctx, repo,
|
||||
gitcmd.NewCommand("config").
|
||||
AddOptionValues("--add", configName, `+refs/tags/*:refs/tags/*`)); err != nil {
|
||||
log.Error("MigrateRepositoryGitData(git config --add <remote> +refs/tags/*:refs/tags/*) in %v: Stdout: %s\nError: %v", repo, stdout, err)
|
||||
return repo, fmt.Errorf("error in MigrateRepositoryGitData(git config --add <remote> +refs/tags/*:refs/tags/*): %w", err)
|
||||
}
|
||||
|
||||
@@ -402,7 +402,7 @@ func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo
|
||||
}
|
||||
|
||||
rel, has := relMap[lowerTag]
|
||||
title, note := git.SplitCommitTitleBody(tag.Message, 255)
|
||||
title, note := git.SplitCommitTitleBody(strings.ToValidUTF8(tag.Message, "?"), 255)
|
||||
if !has {
|
||||
rel = &repo_model.Release{
|
||||
RepoID: repo.ID,
|
||||
|
||||
@@ -108,7 +108,7 @@ func removeAllRepositoriesFromTeam(ctx context.Context, t *organization.Team) (e
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove watches from all users and now unaccessible repos
|
||||
// Remove watches from all users and now inaccessible repos
|
||||
for _, user := range t.Members {
|
||||
has, err := access_model.HasAnyUnitAccess(ctx, user.ID, repo)
|
||||
if err != nil {
|
||||
|
||||
@@ -7,8 +7,6 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
activities_model "code.gitea.io/gitea/models/activities"
|
||||
@@ -28,7 +26,6 @@ import (
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
notify_service "code.gitea.io/gitea/services/notify"
|
||||
pull_service "code.gitea.io/gitea/services/pull"
|
||||
)
|
||||
@@ -125,9 +122,9 @@ func UpdateRepository(ctx context.Context, repo *repo_model.Repository, visibili
|
||||
})
|
||||
}
|
||||
|
||||
func MakeRepoPublic(ctx context.Context, repo *repo_model.Repository) (err error) {
|
||||
func MakeRepoPrivate(ctx context.Context, repo *repo_model.Repository, private bool) (err error) {
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
repo.IsPrivate = false
|
||||
repo.IsPrivate = private
|
||||
if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "is_private"); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -147,15 +144,33 @@ func MakeRepoPublic(ctx context.Context, repo *repo_model.Repository) (err error
|
||||
return err
|
||||
}
|
||||
|
||||
forkRepos, err := repo_model.GetRepositoriesByForkID(ctx, repo.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getRepositoriesByForkID: %w", err)
|
||||
// If repo has become private, we need to set its actions to private, and clear stars and watches.
|
||||
if private {
|
||||
_, err = db.GetEngine(ctx).
|
||||
Where("repo_id = ?", repo.ID).Cols("is_private").Update(&activities_model.Action{IsPrivate: true})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = repo_model.ClearRepoStars(ctx, repo.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = repo_model.ClearRepoWatches(ctx, repo.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if repo.Owner.Visibility != structs.VisibleTypePrivate {
|
||||
for i := range forkRepos {
|
||||
if err = MakeRepoPublic(ctx, forkRepos[i]); err != nil {
|
||||
return fmt.Errorf("MakeRepoPublic[%d]: %w", forkRepos[i].ID, err)
|
||||
shouldUpdateForks := private
|
||||
if !private && repo.Owner.Visibility != structs.VisibleTypePrivate {
|
||||
shouldUpdateForks = true
|
||||
}
|
||||
if shouldUpdateForks {
|
||||
forkRepos, err := repo_model.GetRepositoriesByForkID(ctx, repo.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getRepositoriesByForkID: %w", err)
|
||||
}
|
||||
for _, forkRepo := range forkRepos {
|
||||
if err = MakeRepoPrivate(ctx, forkRepo, private); err != nil {
|
||||
return fmt.Errorf("MakeRepoPrivate[%d]: %w", forkRepo.ID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -163,63 +178,6 @@ func MakeRepoPublic(ctx context.Context, repo *repo_model.Repository) (err error
|
||||
// If visibility is changed, we need to update the issue indexer.
|
||||
// Since the data in the issue indexer have field to indicate if the repo is public or not.
|
||||
issue_indexer.UpdateRepoIndexer(ctx, repo.ID)
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func MakeRepoPrivate(ctx context.Context, repo *repo_model.Repository) (err error) {
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
repo.IsPrivate = true
|
||||
if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "is_private"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = repo.LoadOwner(ctx); err != nil {
|
||||
return fmt.Errorf("LoadOwner: %w", err)
|
||||
}
|
||||
if repo.Owner.IsOrganization() {
|
||||
// Organization repository need to recalculate access table when visibility is changed.
|
||||
if err = access_model.RecalculateTeamAccesses(ctx, repo, 0); err != nil {
|
||||
return fmt.Errorf("recalculateTeamAccesses: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// If repo has become private, we need to set its actions to private.
|
||||
_, err = db.GetEngine(ctx).Where("repo_id = ?", repo.ID).Cols("is_private").Update(&activities_model.Action{
|
||||
IsPrivate: true,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = repo_model.ClearRepoStars(ctx, repo.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = repo_model.ClearRepoWatches(ctx, repo.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create/Remove git-daemon-export-ok for git-daemon...
|
||||
if err := CheckDaemonExportOK(ctx, repo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
forkRepos, err := repo_model.GetRepositoriesByForkID(ctx, repo.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getRepositoriesByForkID: %w", err)
|
||||
}
|
||||
for i := range forkRepos {
|
||||
if err = MakeRepoPrivate(ctx, forkRepos[i]); err != nil {
|
||||
return fmt.Errorf("MakeRepoPrivate[%d]: %w", forkRepos[i].ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
// If visibility is changed, we need to update the issue indexer.
|
||||
// Since the data in the issue indexer have field to indicate if the repo is public or not.
|
||||
issue_indexer.UpdateRepoIndexer(ctx, repo.ID)
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -255,9 +213,8 @@ func CheckDaemonExportOK(ctx context.Context, repo *repo_model.Repository) error
|
||||
}
|
||||
|
||||
// Create/Remove git-daemon-export-ok for git-daemon...
|
||||
daemonExportFile := filepath.Join(repo.RepoPath(), `git-daemon-export-ok`)
|
||||
|
||||
isExist, err := util.IsExist(daemonExportFile)
|
||||
daemonExportFile := `git-daemon-export-ok`
|
||||
isExist, err := gitrepo.IsRepoFileExist(ctx, repo, daemonExportFile)
|
||||
if err != nil {
|
||||
log.Error("Unable to check if %s exists. Error: %v", daemonExportFile, err)
|
||||
return err
|
||||
@@ -265,11 +222,11 @@ func CheckDaemonExportOK(ctx context.Context, repo *repo_model.Repository) error
|
||||
|
||||
isPublic := !repo.IsPrivate && repo.Owner.Visibility == structs.VisibleTypePublic
|
||||
if !isPublic && isExist {
|
||||
if err = util.Remove(daemonExportFile); err != nil {
|
||||
if err = gitrepo.RemoveRepoFileOrDir(ctx, repo, daemonExportFile); err != nil {
|
||||
log.Error("Failed to remove %s: %v", daemonExportFile, err)
|
||||
}
|
||||
} else if isPublic && !isExist {
|
||||
if f, err := os.Create(daemonExportFile); err != nil {
|
||||
if f, err := gitrepo.CreateRepoFile(ctx, repo, daemonExportFile); err != nil {
|
||||
log.Error("Failed to create %s: %v", daemonExportFile, err)
|
||||
} else {
|
||||
f.Close()
|
||||
@@ -349,3 +306,31 @@ func HasWiki(ctx context.Context, repo *repo_model.Repository) bool {
|
||||
}
|
||||
return hasWiki && err == nil
|
||||
}
|
||||
|
||||
// CheckCreateRepository check if doer could create a repository in new owner
|
||||
func CheckCreateRepository(ctx context.Context, doer, owner *user_model.User, name string, overwriteOrAdopt bool) error {
|
||||
if !doer.CanCreateRepoIn(owner) {
|
||||
return repo_model.ErrReachLimitOfRepo{Limit: owner.MaxRepoCreation}
|
||||
}
|
||||
|
||||
if err := repo_model.IsUsableRepoName(name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
has, err := repo_model.IsRepositoryModelExist(ctx, owner, name)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if has {
|
||||
return repo_model.ErrRepoAlreadyExist{Uname: owner.Name, Name: name}
|
||||
}
|
||||
repo := repo_model.StorageRepo(repo_model.RelativePath(owner.Name, name))
|
||||
isExist, err := gitrepo.IsRepositoryExist(ctx, repo)
|
||||
if err != nil {
|
||||
log.Error("Unable to check if %s exists. Error: %v", repo.RelativePath(), err)
|
||||
return err
|
||||
}
|
||||
if !overwriteOrAdopt && isExist {
|
||||
return repo_model.ErrRepoFilesAlreadyExist{Uname: owner.Name, Name: name}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -74,13 +74,13 @@ func TestMakeRepoPrivateClearsWatches(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
repo.IsPrivate = false
|
||||
assert.False(t, repo.IsPrivate)
|
||||
|
||||
watchers, err := repo_model.GetRepoWatchersIDs(t.Context(), repo.ID)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, watchers)
|
||||
|
||||
assert.NoError(t, MakeRepoPrivate(t.Context(), repo))
|
||||
assert.NoError(t, MakeRepoPrivate(t.Context(), repo, true))
|
||||
|
||||
watchers, err = repo_model.GetRepoWatchersIDs(t.Context(), repo.ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -33,11 +33,12 @@ func GenerateIssueLabels(ctx context.Context, templateRepo, generateRepo *repo_m
|
||||
newLabels := make([]*issues_model.Label, 0, len(templateLabels))
|
||||
for _, templateLabel := range templateLabels {
|
||||
newLabels = append(newLabels, &issues_model.Label{
|
||||
RepoID: generateRepo.ID,
|
||||
Name: templateLabel.Name,
|
||||
Exclusive: templateLabel.Exclusive,
|
||||
Description: templateLabel.Description,
|
||||
Color: templateLabel.Color,
|
||||
RepoID: generateRepo.ID,
|
||||
Name: templateLabel.Name,
|
||||
Exclusive: templateLabel.Exclusive,
|
||||
ExclusiveOrder: templateLabel.ExclusiveOrder,
|
||||
Description: templateLabel.Description,
|
||||
Color: templateLabel.Color,
|
||||
})
|
||||
}
|
||||
return db.Insert(ctx, newLabels)
|
||||
@@ -100,8 +101,8 @@ func GenerateRepository(ctx context.Context, doer, owner *user_model.User, templ
|
||||
// last - clean up the repository if something goes wrong
|
||||
defer func() {
|
||||
if err != nil {
|
||||
// we can not use the ctx because it maybe canceled or timeout
|
||||
cleanupRepository(generateRepo.ID)
|
||||
// we can not use `ctx` because it may be canceled or timed out
|
||||
cleanupRepository(generateRepo)
|
||||
}
|
||||
}()
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@ package repository
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
@@ -90,6 +90,17 @@ func AcceptTransferOwnership(ctx context.Context, repo *repo_model.Repository, d
|
||||
return nil
|
||||
}
|
||||
|
||||
// isRepositoryModelOrDirExist returns true if the repository with given name under user has already existed.
|
||||
func isRepositoryModelOrDirExist(ctx context.Context, u *user_model.User, repoName string) (bool, error) {
|
||||
has, err := repo_model.IsRepositoryModelExist(ctx, u, repoName)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
repo := repo_model.StorageRepo(repo_model.RelativePath(u.Name, repoName))
|
||||
isExist, err := gitrepo.IsRepositoryExist(ctx, repo)
|
||||
return has || isExist, err
|
||||
}
|
||||
|
||||
// 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, teams []*organization.Team) (err error) {
|
||||
repoRenamed := false
|
||||
@@ -107,16 +118,18 @@ func transferOwnership(ctx context.Context, doer *user_model.User, newOwnerName
|
||||
}
|
||||
|
||||
if repoRenamed {
|
||||
if err := util.Rename(repo_model.RepoPath(newOwnerName, repo.Name), repo_model.RepoPath(oldOwnerName, repo.Name)); err != nil {
|
||||
oldRelativePath, newRelativePath := repo_model.RelativePath(newOwnerName, repo.Name), repo_model.RelativePath(oldOwnerName, repo.Name)
|
||||
if err := gitrepo.RenameRepository(ctx, repo_model.StorageRepo(oldRelativePath), repo_model.StorageRepo(newRelativePath)); 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)
|
||||
oldRelativePath, newRelativePath, err)
|
||||
}
|
||||
}
|
||||
|
||||
if wikiRenamed {
|
||||
if err := util.Rename(repo_model.WikiPath(newOwnerName, repo.Name), repo_model.WikiPath(oldOwnerName, repo.Name)); err != nil {
|
||||
oldRelativePath, newRelativePath := repo_model.RelativeWikiPath(newOwnerName, repo.Name), repo_model.RelativeWikiPath(oldOwnerName, repo.Name)
|
||||
if err := gitrepo.RenameRepository(ctx, repo_model.StorageRepo(oldRelativePath), repo_model.StorageRepo(newRelativePath)); 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)
|
||||
oldRelativePath, newRelativePath, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +154,7 @@ func transferOwnership(ctx context.Context, doer *user_model.User, newOwnerName
|
||||
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 {
|
||||
if has, err := isRepositoryModelOrDirExist(ctx, newOwner, repo.Name); err != nil {
|
||||
return fmt.Errorf("IsRepositoryExist: %w", err)
|
||||
} else if has {
|
||||
return repo_model.ErrRepoAlreadyExist{
|
||||
@@ -234,6 +247,19 @@ func transferOwnership(ctx context.Context, doer *user_model.User, newOwnerName
|
||||
return fmt.Errorf("recalculateAccesses: %w", err)
|
||||
}
|
||||
|
||||
// Remove repository from old owner's Actions AllowedCrossRepoIDs if present
|
||||
if oldActionsCfg, err := actions_model.GetOwnerActionsConfig(ctx, oldOwner.ID); err == nil {
|
||||
newAllowedCrossRepoIDs := util.SliceRemoveAll(oldActionsCfg.AllowedCrossRepoIDs, repo.ID)
|
||||
if len(newAllowedCrossRepoIDs) != len(oldActionsCfg.AllowedCrossRepoIDs) {
|
||||
oldActionsCfg.AllowedCrossRepoIDs = newAllowedCrossRepoIDs
|
||||
if err := actions_model.SetOwnerActionsConfig(ctx, oldOwner.ID, oldActionsCfg); err != nil {
|
||||
return fmt.Errorf("SetOwnerActionsConfig: %w", err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("GetOwnerActionsConfig: %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)
|
||||
@@ -278,23 +304,19 @@ func transferOwnership(ctx context.Context, doer *user_model.User, newOwnerName
|
||||
}
|
||||
|
||||
// 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 {
|
||||
oldRelativePath, newRelativePath := repo_model.RelativePath(oldOwner.Name, repo.Name), repo_model.RelativePath(newOwner.Name, repo.Name)
|
||||
if err := gitrepo.RenameRepository(ctx, repo_model.StorageRepo(oldRelativePath), repo_model.StorageRepo(newRelativePath)); 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)
|
||||
wikiStorageRepo := repo_model.StorageRepo(repo_model.RelativeWikiPath(oldOwner.Name, repo.Name))
|
||||
if isExist, err := gitrepo.IsRepositoryExist(ctx, wikiStorageRepo); err != nil {
|
||||
log.Error("Unable to check if %s exists. Error: %v", wikiStorageRepo.RelativePath(), err)
|
||||
return err
|
||||
} else if isExist {
|
||||
if err := util.Rename(wikiPath, repo_model.WikiPath(newOwner.Name, repo.Name)); err != nil {
|
||||
if err := gitrepo.RenameRepository(ctx, wikiStorageRepo, repo_model.StorageRepo(repo_model.RelativeWikiPath(newOwner.Name, repo.Name))); err != nil {
|
||||
return fmt.Errorf("rename repository wiki: %w", err)
|
||||
}
|
||||
wikiRenamed = true
|
||||
@@ -343,7 +365,7 @@ func changeRepositoryName(ctx context.Context, repo *repo_model.Repository, newR
|
||||
return err
|
||||
}
|
||||
|
||||
has, err := repo_model.IsRepositoryModelOrDirExist(ctx, repo.Owner, newRepoName)
|
||||
has, err := isRepositoryModelOrDirExist(ctx, repo.Owner, newRepoName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("IsRepositoryExist: %w", err)
|
||||
} else if has {
|
||||
@@ -517,9 +539,9 @@ func canUserCancelTransfer(ctx context.Context, r *repo_model.RepoTransfer, u *u
|
||||
return r.Repo.OwnerID == u.ID
|
||||
}
|
||||
|
||||
perm, err := access_model.GetUserRepoPermission(ctx, r.Repo, u)
|
||||
perm, err := access_model.GetIndividualUserRepoPermission(ctx, r.Repo, u)
|
||||
if err != nil {
|
||||
log.Error("GetUserRepoPermission: %v", err)
|
||||
log.Error("GetIndividualUserRepoPermission: %v", err)
|
||||
return false
|
||||
}
|
||||
return perm.IsOwner()
|
||||
|
||||
Reference in New Issue
Block a user