Allow adding new files to an empty repo (#24164)
![image](https://user-images.githubusercontent.com/2114189/232561612-2bfcfd0a-fc04-47ba-965f-5d0bcea46c54.png)
This commit is contained in:
parent
01214c8ada
commit
e422342eeb
31 changed files with 314 additions and 138 deletions
|
@ -19,13 +19,16 @@ func TestIterate(t *testing.T) {
|
||||||
xe := unittest.GetXORMEngine()
|
xe := unittest.GetXORMEngine()
|
||||||
assert.NoError(t, xe.Sync(&repo_model.RepoUnit{}))
|
assert.NoError(t, xe.Sync(&repo_model.RepoUnit{}))
|
||||||
|
|
||||||
var repoCnt int
|
cnt, err := db.GetEngine(db.DefaultContext).Count(&repo_model.RepoUnit{})
|
||||||
err := db.Iterate(db.DefaultContext, nil, func(ctx context.Context, repo *repo_model.RepoUnit) error {
|
assert.NoError(t, err)
|
||||||
repoCnt++
|
|
||||||
|
var repoUnitCnt int
|
||||||
|
err = db.Iterate(db.DefaultContext, nil, func(ctx context.Context, repo *repo_model.RepoUnit) error {
|
||||||
|
repoUnitCnt++
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.EqualValues(t, 89, repoCnt)
|
assert.EqualValues(t, cnt, repoUnitCnt)
|
||||||
|
|
||||||
err = db.Iterate(db.DefaultContext, nil, func(ctx context.Context, repoUnit *repo_model.RepoUnit) error {
|
err = db.Iterate(db.DefaultContext, nil, func(ctx context.Context, repoUnit *repo_model.RepoUnit) error {
|
||||||
reopUnit2 := repo_model.RepoUnit{ID: repoUnit.ID}
|
reopUnit2 := repo_model.RepoUnit{ID: repoUnit.ID}
|
||||||
|
|
|
@ -31,15 +31,20 @@ func TestFind(t *testing.T) {
|
||||||
xe := unittest.GetXORMEngine()
|
xe := unittest.GetXORMEngine()
|
||||||
assert.NoError(t, xe.Sync(&repo_model.RepoUnit{}))
|
assert.NoError(t, xe.Sync(&repo_model.RepoUnit{}))
|
||||||
|
|
||||||
|
var repoUnitCount int
|
||||||
|
_, err := db.GetEngine(db.DefaultContext).SQL("SELECT COUNT(*) FROM repo_unit").Get(&repoUnitCount)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, repoUnitCount)
|
||||||
|
|
||||||
opts := mockListOptions{}
|
opts := mockListOptions{}
|
||||||
var repoUnits []repo_model.RepoUnit
|
var repoUnits []repo_model.RepoUnit
|
||||||
err := db.Find(db.DefaultContext, &opts, &repoUnits)
|
err = db.Find(db.DefaultContext, &opts, &repoUnits)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.EqualValues(t, 89, len(repoUnits))
|
assert.EqualValues(t, repoUnitCount, len(repoUnits))
|
||||||
|
|
||||||
cnt, err := db.Count(db.DefaultContext, &opts, new(repo_model.RepoUnit))
|
cnt, err := db.Count(db.DefaultContext, &opts, new(repo_model.RepoUnit))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.EqualValues(t, 89, cnt)
|
assert.EqualValues(t, repoUnitCount, cnt)
|
||||||
|
|
||||||
repoUnits = make([]repo_model.RepoUnit, 0, 10)
|
repoUnits = make([]repo_model.RepoUnit, 0, 10)
|
||||||
newCnt, err := db.FindAndCount(db.DefaultContext, &opts, &repoUnits)
|
newCnt, err := db.FindAndCount(db.DefaultContext, &opts, &repoUnits)
|
||||||
|
|
|
@ -12,8 +12,6 @@ import (
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func changeDefaultFileBlockSize(n int64) (restore func()) {
|
func changeDefaultFileBlockSize(n int64) (restore func()) {
|
||||||
|
|
|
@ -601,3 +601,9 @@
|
||||||
repo_id: 57
|
repo_id: 57
|
||||||
type: 5
|
type: 5
|
||||||
created_unix: 946684810
|
created_unix: 946684810
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 90
|
||||||
|
repo_id: 52
|
||||||
|
type: 1
|
||||||
|
created_unix: 946684810
|
||||||
|
|
|
@ -1560,6 +1560,7 @@
|
||||||
owner_name: user30
|
owner_name: user30
|
||||||
lower_name: empty
|
lower_name: empty
|
||||||
name: empty
|
name: empty
|
||||||
|
default_branch: master
|
||||||
num_watches: 0
|
num_watches: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
num_forks: 0
|
num_forks: 0
|
||||||
|
|
|
@ -1091,7 +1091,7 @@
|
||||||
max_repo_creation: -1
|
max_repo_creation: -1
|
||||||
is_active: true
|
is_active: true
|
||||||
is_admin: false
|
is_admin: false
|
||||||
is_restricted: true
|
is_restricted: false
|
||||||
allow_git_hook: false
|
allow_git_hook: false
|
||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
|
|
|
@ -225,6 +225,12 @@ func (repo *Repository) IsBroken() bool {
|
||||||
return repo.Status == RepositoryBroken
|
return repo.Status == RepositoryBroken
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarkAsBrokenEmpty marks the repo as broken and empty
|
||||||
|
func (repo *Repository) MarkAsBrokenEmpty() {
|
||||||
|
repo.Status = RepositoryBroken
|
||||||
|
repo.IsEmpty = true
|
||||||
|
}
|
||||||
|
|
||||||
// AfterLoad is invoked from XORM after setting the values of all fields of this object.
|
// AfterLoad is invoked from XORM after setting the values of all fields of this object.
|
||||||
func (repo *Repository) AfterLoad() {
|
func (repo *Repository) AfterLoad() {
|
||||||
repo.NumOpenIssues = repo.NumIssues - repo.NumClosedIssues
|
repo.NumOpenIssues = repo.NumIssues - repo.NumClosedIssues
|
||||||
|
@ -729,7 +735,7 @@ func IsRepositoryExist(ctx context.Context, u *user_model.User, repoName string)
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
isDir, err := util.IsDir(RepoPath(u.Name, repoName))
|
isDir, err := util.IsDir(RepoPath(u.Name, repoName))
|
||||||
return has && isDir, err
|
return has || isDir, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTemplateRepo populates repo.TemplateRepo for a generated repository and
|
// GetTemplateRepo populates repo.TemplateRepo for a generated repository and
|
||||||
|
|
|
@ -17,7 +17,7 @@ import (
|
||||||
"xorm.io/xorm/schemas"
|
"xorm.io/xorm/schemas"
|
||||||
)
|
)
|
||||||
|
|
||||||
var fixtures *testfixtures.Loader
|
var fixturesLoader *testfixtures.Loader
|
||||||
|
|
||||||
// GetXORMEngine gets the XORM engine
|
// GetXORMEngine gets the XORM engine
|
||||||
func GetXORMEngine(engine ...*xorm.Engine) (x *xorm.Engine) {
|
func GetXORMEngine(engine ...*xorm.Engine) (x *xorm.Engine) {
|
||||||
|
@ -30,11 +30,11 @@ func GetXORMEngine(engine ...*xorm.Engine) (x *xorm.Engine) {
|
||||||
// InitFixtures initialize test fixtures for a test database
|
// InitFixtures initialize test fixtures for a test database
|
||||||
func InitFixtures(opts FixturesOptions, engine ...*xorm.Engine) (err error) {
|
func InitFixtures(opts FixturesOptions, engine ...*xorm.Engine) (err error) {
|
||||||
e := GetXORMEngine(engine...)
|
e := GetXORMEngine(engine...)
|
||||||
var testfiles func(*testfixtures.Loader) error
|
var fixtureOptionFiles func(*testfixtures.Loader) error
|
||||||
if opts.Dir != "" {
|
if opts.Dir != "" {
|
||||||
testfiles = testfixtures.Directory(opts.Dir)
|
fixtureOptionFiles = testfixtures.Directory(opts.Dir)
|
||||||
} else {
|
} else {
|
||||||
testfiles = testfixtures.Files(opts.Files...)
|
fixtureOptionFiles = testfixtures.Files(opts.Files...)
|
||||||
}
|
}
|
||||||
dialect := "unknown"
|
dialect := "unknown"
|
||||||
switch e.Dialect().URI().DBType {
|
switch e.Dialect().URI().DBType {
|
||||||
|
@ -54,14 +54,14 @@ func InitFixtures(opts FixturesOptions, engine ...*xorm.Engine) (err error) {
|
||||||
testfixtures.Database(e.DB().DB),
|
testfixtures.Database(e.DB().DB),
|
||||||
testfixtures.Dialect(dialect),
|
testfixtures.Dialect(dialect),
|
||||||
testfixtures.DangerousSkipTestDatabaseCheck(),
|
testfixtures.DangerousSkipTestDatabaseCheck(),
|
||||||
testfiles,
|
fixtureOptionFiles,
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.Dialect().URI().DBType == schemas.POSTGRES {
|
if e.Dialect().URI().DBType == schemas.POSTGRES {
|
||||||
loaderOptions = append(loaderOptions, testfixtures.SkipResetSequences())
|
loaderOptions = append(loaderOptions, testfixtures.SkipResetSequences())
|
||||||
}
|
}
|
||||||
|
|
||||||
fixtures, err = testfixtures.New(loaderOptions...)
|
fixturesLoader, err = testfixtures.New(loaderOptions...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -78,11 +78,9 @@ func InitFixtures(opts FixturesOptions, engine ...*xorm.Engine) (err error) {
|
||||||
func LoadFixtures(engine ...*xorm.Engine) error {
|
func LoadFixtures(engine ...*xorm.Engine) error {
|
||||||
e := GetXORMEngine(engine...)
|
e := GetXORMEngine(engine...)
|
||||||
var err error
|
var err error
|
||||||
// Database transaction conflicts could occur and result in ROLLBACK
|
// (doubt) database transaction conflicts could occur and result in ROLLBACK? just try for a few times.
|
||||||
// As a simple workaround, we just retry 20 times.
|
for i := 0; i < 5; i++ {
|
||||||
for i := 0; i < 20; i++ {
|
if err = fixturesLoader.Load(); err == nil {
|
||||||
err = fixtures.Load()
|
|
||||||
if err == nil {
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
time.Sleep(200 * time.Millisecond)
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
|
@ -5,6 +5,7 @@ package user_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -64,9 +65,10 @@ func TestSearchUsers(t *testing.T) {
|
||||||
testSuccess := func(opts *user_model.SearchUserOptions, expectedUserOrOrgIDs []int64) {
|
testSuccess := func(opts *user_model.SearchUserOptions, expectedUserOrOrgIDs []int64) {
|
||||||
users, _, err := user_model.SearchUsers(opts)
|
users, _, err := user_model.SearchUsers(opts)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
if assert.Len(t, users, len(expectedUserOrOrgIDs), opts) {
|
cassText := fmt.Sprintf("ids: %v, opts: %v", expectedUserOrOrgIDs, opts)
|
||||||
|
if assert.Len(t, users, len(expectedUserOrOrgIDs), "case: %s", cassText) {
|
||||||
for i, expectedID := range expectedUserOrOrgIDs {
|
for i, expectedID := range expectedUserOrOrgIDs {
|
||||||
assert.EqualValues(t, expectedID, users[i].ID)
|
assert.EqualValues(t, expectedID, users[i].ID, "case: %s", cassText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -118,7 +120,7 @@ func TestSearchUsers(t *testing.T) {
|
||||||
[]int64{1})
|
[]int64{1})
|
||||||
|
|
||||||
testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsRestricted: util.OptionalBoolTrue},
|
testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsRestricted: util.OptionalBoolTrue},
|
||||||
[]int64{29, 30})
|
[]int64{29})
|
||||||
|
|
||||||
testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsProhibitLogin: util.OptionalBoolTrue},
|
testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsProhibitLogin: util.OptionalBoolTrue},
|
||||||
[]int64{30})
|
[]int64{30})
|
||||||
|
|
|
@ -301,7 +301,7 @@ func (ctx *Context) serverErrorInternal(logMsg string, logErr error) {
|
||||||
|
|
||||||
// it's safe to show internal error to admin users, and it helps
|
// it's safe to show internal error to admin users, and it helps
|
||||||
if !setting.IsProd || (ctx.Doer != nil && ctx.Doer.IsAdmin) {
|
if !setting.IsProd || (ctx.Doer != nil && ctx.Doer.IsAdmin) {
|
||||||
ctx.Data["ErrorMsg"] = logErr
|
ctx.Data["ErrorMsg"] = fmt.Sprintf("%s, %s", logMsg, logErr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -184,6 +184,9 @@ func (r *Repository) CanCreateIssueDependencies(user *user_model.User, isPull bo
|
||||||
|
|
||||||
// GetCommitsCount returns cached commit count for current view
|
// GetCommitsCount returns cached commit count for current view
|
||||||
func (r *Repository) GetCommitsCount() (int64, error) {
|
func (r *Repository) GetCommitsCount() (int64, error) {
|
||||||
|
if r.Commit == nil {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
var contextName string
|
var contextName string
|
||||||
if r.IsViewBranch {
|
if r.IsViewBranch {
|
||||||
contextName = r.BranchName
|
contextName = r.BranchName
|
||||||
|
@ -642,8 +645,7 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "repository does not exist") || strings.Contains(err.Error(), "no such file or directory") {
|
if strings.Contains(err.Error(), "repository does not exist") || strings.Contains(err.Error(), "no such file or directory") {
|
||||||
log.Error("Repository %-v has a broken repository on the file system: %s Error: %v", ctx.Repo.Repository, ctx.Repo.Repository.RepoPath(), err)
|
log.Error("Repository %-v has a broken repository on the file system: %s Error: %v", ctx.Repo.Repository, ctx.Repo.Repository.RepoPath(), err)
|
||||||
ctx.Repo.Repository.Status = repo_model.RepositoryBroken
|
ctx.Repo.Repository.MarkAsBrokenEmpty()
|
||||||
ctx.Repo.Repository.IsEmpty = true
|
|
||||||
ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
|
ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
|
||||||
// Only allow access to base of repo or settings
|
// Only allow access to base of repo or settings
|
||||||
if !isHomeOrSettings {
|
if !isHomeOrSettings {
|
||||||
|
@ -689,7 +691,7 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) {
|
||||||
ctx.Data["BranchesCount"] = len(brs)
|
ctx.Data["BranchesCount"] = len(brs)
|
||||||
|
|
||||||
// If not branch selected, try default one.
|
// If not branch selected, try default one.
|
||||||
// If default branch doesn't exists, fall back to some other branch.
|
// If default branch doesn't exist, fall back to some other branch.
|
||||||
if len(ctx.Repo.BranchName) == 0 {
|
if len(ctx.Repo.BranchName) == 0 {
|
||||||
if len(ctx.Repo.Repository.DefaultBranch) > 0 && gitRepo.IsBranchExist(ctx.Repo.Repository.DefaultBranch) {
|
if len(ctx.Repo.Repository.DefaultBranch) > 0 && gitRepo.IsBranchExist(ctx.Repo.Repository.DefaultBranch) {
|
||||||
ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch
|
ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch
|
||||||
|
@ -878,6 +880,10 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
|
||||||
return func(ctx *Context) (cancel context.CancelFunc) {
|
return func(ctx *Context) (cancel context.CancelFunc) {
|
||||||
// Empty repository does not have reference information.
|
// Empty repository does not have reference information.
|
||||||
if ctx.Repo.Repository.IsEmpty {
|
if ctx.Repo.Repository.IsEmpty {
|
||||||
|
// assume the user is viewing the (non-existent) default branch
|
||||||
|
ctx.Repo.IsViewBranch = true
|
||||||
|
ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch
|
||||||
|
ctx.Data["TreePath"] = ""
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -907,27 +913,30 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
|
||||||
refName = ctx.Repo.Repository.DefaultBranch
|
refName = ctx.Repo.Repository.DefaultBranch
|
||||||
if !ctx.Repo.GitRepo.IsBranchExist(refName) {
|
if !ctx.Repo.GitRepo.IsBranchExist(refName) {
|
||||||
brs, _, err := ctx.Repo.GitRepo.GetBranchNames(0, 0)
|
brs, _, err := ctx.Repo.GitRepo.GetBranchNames(0, 0)
|
||||||
if err != nil {
|
if err == nil && len(brs) != 0 {
|
||||||
ctx.ServerError("GetBranches", err)
|
|
||||||
return
|
|
||||||
} else if len(brs) == 0 {
|
|
||||||
err = fmt.Errorf("No branches in non-empty repository %s",
|
|
||||||
ctx.Repo.GitRepo.Path)
|
|
||||||
ctx.ServerError("GetBranches", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
refName = brs[0]
|
refName = brs[0]
|
||||||
|
} else if len(brs) == 0 {
|
||||||
|
log.Error("No branches in non-empty repository %s", ctx.Repo.GitRepo.Path)
|
||||||
|
ctx.Repo.Repository.MarkAsBrokenEmpty()
|
||||||
|
} else {
|
||||||
|
log.Error("GetBranches error: %v", err)
|
||||||
|
ctx.Repo.Repository.MarkAsBrokenEmpty()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ctx.Repo.RefName = refName
|
ctx.Repo.RefName = refName
|
||||||
ctx.Repo.BranchName = refName
|
ctx.Repo.BranchName = refName
|
||||||
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
|
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
|
||||||
if err != nil {
|
if err == nil {
|
||||||
|
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
|
||||||
|
} else if strings.Contains(err.Error(), "fatal: not a git repository") || strings.Contains(err.Error(), "object does not exist") {
|
||||||
|
// if the repository is broken, we can continue to the handler code, to show "Settings -> Delete Repository" for end users
|
||||||
|
log.Error("GetBranchCommit: %v", err)
|
||||||
|
ctx.Repo.Repository.MarkAsBrokenEmpty()
|
||||||
|
} else {
|
||||||
ctx.ServerError("GetBranchCommit", err)
|
ctx.ServerError("GetBranchCommit", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
|
|
||||||
ctx.Repo.IsViewBranch = true
|
ctx.Repo.IsViewBranch = true
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
refName = getRefName(ctx, refType)
|
refName = getRefName(ctx, refType)
|
||||||
ctx.Repo.RefName = refName
|
ctx.Repo.RefName = refName
|
||||||
|
|
|
@ -211,7 +211,15 @@ type RunOpts struct {
|
||||||
Env []string
|
Env []string
|
||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
UseContextTimeout bool
|
UseContextTimeout bool
|
||||||
|
|
||||||
|
// Dir is the working dir for the git command, however:
|
||||||
|
// FIXME: this could be incorrect in many cases, for example:
|
||||||
|
// * /some/path/.git
|
||||||
|
// * /some/path/.git/gitea-data/data/repositories/user/repo.git
|
||||||
|
// If "user/repo.git" is invalid/broken, then running git command in it will use "/some/path/.git", and produce unexpected results
|
||||||
|
// The correct approach is to use `--git-dir" global argument
|
||||||
Dir string
|
Dir string
|
||||||
|
|
||||||
Stdout, Stderr io.Writer
|
Stdout, Stderr io.Writer
|
||||||
Stdin io.Reader
|
Stdin io.Reader
|
||||||
PipelineFunc func(context.Context, context.CancelFunc) error
|
PipelineFunc func(context.Context, context.CancelFunc) error
|
||||||
|
|
|
@ -80,7 +80,7 @@ func InitRepository(ctx context.Context, repoPath string, bare bool) error {
|
||||||
// IsEmpty Check if repository is empty.
|
// IsEmpty Check if repository is empty.
|
||||||
func (repo *Repository) IsEmpty() (bool, error) {
|
func (repo *Repository) IsEmpty() (bool, error) {
|
||||||
var errbuf, output strings.Builder
|
var errbuf, output strings.Builder
|
||||||
if err := NewCommand(repo.Ctx, "show-ref", "--head", "^HEAD$").
|
if err := NewCommand(repo.Ctx).AddOptionFormat("--git-dir=%s", repo.Path).AddArguments("show-ref", "--head", "^HEAD$").
|
||||||
Run(&RunOpts{
|
Run(&RunOpts{
|
||||||
Dir: repo.Path,
|
Dir: repo.Path,
|
||||||
Stdout: &output,
|
Stdout: &output,
|
||||||
|
|
|
@ -61,7 +61,7 @@ func OpenRepository(ctx context.Context, repoPath string) (*Repository, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
repo.batchWriter, repo.batchReader, repo.batchCancel = CatFileBatch(ctx, repoPath)
|
repo.batchWriter, repo.batchReader, repo.batchCancel = CatFileBatch(ctx, repoPath)
|
||||||
repo.checkWriter, repo.checkReader, repo.checkCancel = CatFileBatchCheck(ctx, repo.Path)
|
repo.checkWriter, repo.checkReader, repo.checkCancel = CatFileBatchCheck(ctx, repoPath)
|
||||||
|
|
||||||
return repo, nil
|
return repo, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -154,7 +154,9 @@ func Init() {
|
||||||
log.Trace("IndexerData Process Repo: %d", indexerData.RepoID)
|
log.Trace("IndexerData Process Repo: %d", indexerData.RepoID)
|
||||||
|
|
||||||
if err := index(ctx, indexer, indexerData.RepoID); err != nil {
|
if err := index(ctx, indexer, indexerData.RepoID); err != nil {
|
||||||
log.Error("index: %v", err)
|
if !setting.IsInTesting {
|
||||||
|
log.Error("indexer index error for repo %v: %v", indexerData.RepoID, err)
|
||||||
|
}
|
||||||
if indexer.Ping() {
|
if indexer.Ping() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/graceful"
|
"code.gitea.io/gitea/modules/graceful"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/process"
|
"code.gitea.io/gitea/modules/process"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DBIndexer implements Indexer interface to use database's like search
|
// DBIndexer implements Indexer interface to use database's like search
|
||||||
|
@ -46,7 +47,7 @@ func (db *DBIndexer) Index(id int64) error {
|
||||||
// Get latest commit for default branch
|
// Get latest commit for default branch
|
||||||
commitID, err := gitRepo.GetBranchCommitID(repo.DefaultBranch)
|
commitID, err := gitRepo.GetBranchCommitID(repo.DefaultBranch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if git.IsErrBranchNotExist(err) || git.IsErrNotExist(err) {
|
if git.IsErrBranchNotExist(err) || git.IsErrNotExist(err) || setting.IsInTesting {
|
||||||
log.Debug("Unable to get commit ID for default branch %s in %s ... skipping this repository", repo.DefaultBranch, repo.RepoPath())
|
log.Debug("Unable to get commit ID for default branch %s in %s ... skipping this repository", repo.DefaultBranch, repo.RepoPath())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -62,7 +63,9 @@ func (db *DBIndexer) Index(id int64) error {
|
||||||
// Calculate and save language statistics to database
|
// Calculate and save language statistics to database
|
||||||
stats, err := gitRepo.GetLanguageStats(commitID)
|
stats, err := gitRepo.GetLanguageStats(commitID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if !setting.IsInTesting {
|
||||||
log.Error("Unable to get language stats for ID %s for default branch %s in %s. Error: %v", commitID, repo.DefaultBranch, repo.RepoPath(), err)
|
log.Error("Unable to get language stats for ID %s for default branch %s in %s. Error: %v", commitID, repo.DefaultBranch, repo.RepoPath(), err)
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = repo_model.UpdateLanguageStats(repo, commitID, stats)
|
err = repo_model.UpdateLanguageStats(repo, commitID, stats)
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/graceful"
|
"code.gitea.io/gitea/modules/graceful"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/queue"
|
"code.gitea.io/gitea/modules/queue"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
// statsQueue represents a queue to handle repository stats updates
|
// statsQueue represents a queue to handle repository stats updates
|
||||||
|
@ -20,9 +21,11 @@ func handle(data ...queue.Data) []queue.Data {
|
||||||
for _, datum := range data {
|
for _, datum := range data {
|
||||||
opts := datum.(int64)
|
opts := datum.(int64)
|
||||||
if err := indexer.Index(opts); err != nil {
|
if err := indexer.Index(opts); err != nil {
|
||||||
|
if !setting.IsInTesting {
|
||||||
log.Error("stats queue indexer.Index(%d) failed: %v", opts, err)
|
log.Error("stats queue indexer.Index(%d) failed: %v", opts, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ import (
|
||||||
var (
|
var (
|
||||||
// AppVer is the version of the current build of Gitea. It is set in main.go from main.Version.
|
// AppVer is the version of the current build of Gitea. It is set in main.go from main.Version.
|
||||||
AppVer string
|
AppVer string
|
||||||
// AppBuiltWith represents a human readable version go runtime build version and build tags. (See main.go formatBuiltWith().)
|
// AppBuiltWith represents a human-readable version go runtime build version and build tags. (See main.go formatBuiltWith().)
|
||||||
AppBuiltWith string
|
AppBuiltWith string
|
||||||
// AppStartTime store time gitea has started
|
// AppStartTime store time gitea has started
|
||||||
AppStartTime time.Time
|
AppStartTime time.Time
|
||||||
|
@ -40,7 +40,8 @@ var (
|
||||||
// AppWorkPath is used as the base path for several other paths.
|
// AppWorkPath is used as the base path for several other paths.
|
||||||
AppWorkPath string
|
AppWorkPath string
|
||||||
|
|
||||||
// Global setting objects
|
// Other global setting objects
|
||||||
|
|
||||||
CfgProvider ConfigProvider
|
CfgProvider ConfigProvider
|
||||||
CustomPath string // Custom directory path
|
CustomPath string // Custom directory path
|
||||||
CustomConf string
|
CustomConf string
|
||||||
|
@ -48,6 +49,10 @@ var (
|
||||||
RunUser string
|
RunUser string
|
||||||
IsProd bool
|
IsProd bool
|
||||||
IsWindows bool
|
IsWindows bool
|
||||||
|
|
||||||
|
// IsInTesting indicates whether the testing is running. A lot of unreliable code causes a lot of nonsense error logs during testing
|
||||||
|
// TODO: this is only a temporary solution, we should make the test code more reliable
|
||||||
|
IsInTesting = false
|
||||||
)
|
)
|
||||||
|
|
||||||
func getAppPath() (string, error) {
|
func getAppPath() (string, error) {
|
||||||
|
@ -108,8 +113,12 @@ func getWorkPath(appPath string) string {
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
IsWindows = runtime.GOOS == "windows"
|
IsWindows = runtime.GOOS == "windows"
|
||||||
|
if AppVer == "" {
|
||||||
|
AppVer = "dev"
|
||||||
|
}
|
||||||
|
|
||||||
// We can rely on log.CanColorStdout being set properly because modules/log/console_windows.go comes before modules/setting/setting.go lexicographically
|
// We can rely on log.CanColorStdout being set properly because modules/log/console_windows.go comes before modules/setting/setting.go lexicographically
|
||||||
// By default set this logger at Info - we'll change it later but we need to start with something.
|
// By default set this logger at Info - we'll change it later, but we need to start with something.
|
||||||
log.NewLogger(0, "console", "console", fmt.Sprintf(`{"level": "info", "colorize": %t, "stacktraceLevel": "none"}`, log.CanColorStdout))
|
log.NewLogger(0, "console", "console", fmt.Sprintf(`{"level": "info", "colorize": %t, "stacktraceLevel": "none"}`, log.CanColorStdout))
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|
|
@ -108,6 +108,7 @@ func GlobalInitInstalled(ctx context.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
mustInitCtx(ctx, git.InitFull)
|
mustInitCtx(ctx, git.InitFull)
|
||||||
|
log.Info("Gitea Version: %s%s", setting.AppVer, setting.AppBuiltWith)
|
||||||
log.Info("Git Version: %s (home: %s)", git.VersionInfo(), git.HomeDir())
|
log.Info("Git Version: %s (home: %s)", git.VersionInfo(), git.HomeDir())
|
||||||
log.Info("AppPath: %s", setting.AppPath)
|
log.Info("AppPath: %s", setting.AppPath)
|
||||||
log.Info("AppWorkPath: %s", setting.AppWorkPath)
|
log.Info("AppWorkPath: %s", setting.AppWorkPath)
|
||||||
|
@ -115,7 +116,6 @@ func GlobalInitInstalled(ctx context.Context) {
|
||||||
log.Info("Log path: %s", setting.Log.RootPath)
|
log.Info("Log path: %s", setting.Log.RootPath)
|
||||||
log.Info("Configuration file: %s", setting.CustomConf)
|
log.Info("Configuration file: %s", setting.CustomConf)
|
||||||
log.Info("Run Mode: %s", util.ToTitleCase(setting.RunMode))
|
log.Info("Run Mode: %s", util.ToTitleCase(setting.RunMode))
|
||||||
log.Info("Gitea v%s%s", setting.AppVer, setting.AppBuiltWith)
|
|
||||||
|
|
||||||
// Setup i18n
|
// Setup i18n
|
||||||
translation.InitLocales(ctx)
|
translation.InitLocales(ctx)
|
||||||
|
|
|
@ -82,7 +82,7 @@ func editFile(ctx *context.Context, isNewFile bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the filename (and additional path) is specified in the querystring
|
// Check if the filename (and additional path) is specified in the querystring
|
||||||
// (filename is a misnomer, but kept for compatibility with Github)
|
// (filename is a misnomer, but kept for compatibility with GitHub)
|
||||||
filePath, fileName := path.Split(ctx.Req.URL.Query().Get("filename"))
|
filePath, fileName := path.Split(ctx.Req.URL.Query().Get("filename"))
|
||||||
filePath = strings.Trim(filePath, "/")
|
filePath = strings.Trim(filePath, "/")
|
||||||
treeNames, treePaths := getParentTreeFields(path.Join(ctx.Repo.TreePath, filePath))
|
treeNames, treePaths := getParentTreeFields(path.Join(ctx.Repo.TreePath, filePath))
|
||||||
|
@ -327,6 +327,10 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ctx.Repo.Repository.IsEmpty {
|
||||||
|
_ = repo_model.UpdateRepositoryCols(ctx, &repo_model.Repository{ID: ctx.Repo.Repository.ID, IsEmpty: false}, "is_empty")
|
||||||
|
}
|
||||||
|
|
||||||
if form.CommitChoice == frmCommitChoiceNewBranch && ctx.Repo.Repository.UnitEnabled(ctx, unit.TypePullRequests) {
|
if form.CommitChoice == frmCommitChoiceNewBranch && ctx.Repo.Repository.UnitEnabled(ctx, unit.TypePullRequests) {
|
||||||
ctx.Redirect(ctx.Repo.RepoLink + "/compare/" + util.PathEscapeSegments(ctx.Repo.BranchName) + "..." + util.PathEscapeSegments(form.NewBranchName))
|
ctx.Redirect(ctx.Repo.RepoLink + "/compare/" + util.PathEscapeSegments(ctx.Repo.BranchName) + "..." + util.PathEscapeSegments(form.NewBranchName))
|
||||||
} else {
|
} else {
|
||||||
|
@ -617,27 +621,27 @@ func UploadFilePost(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !ctx.Repo.Repository.IsEmpty {
|
||||||
var newTreePath string
|
var newTreePath string
|
||||||
for _, part := range treeNames {
|
for _, part := range treeNames {
|
||||||
newTreePath = path.Join(newTreePath, part)
|
newTreePath = path.Join(newTreePath, part)
|
||||||
entry, err := ctx.Repo.Commit.GetTreeEntryByPath(newTreePath)
|
entry, err := ctx.Repo.Commit.GetTreeEntryByPath(newTreePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if git.IsErrNotExist(err) {
|
if git.IsErrNotExist(err) {
|
||||||
// Means there is no item with that name, so we're good
|
break // Means there is no item with that name, so we're good
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.ServerError("Repo.Commit.GetTreeEntryByPath", err)
|
ctx.ServerError("Repo.Commit.GetTreeEntryByPath", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// User can only upload files to a directory.
|
// User can only upload files to a directory, the directory name shouldn't be an existing file.
|
||||||
if !entry.IsDir() {
|
if !entry.IsDir() {
|
||||||
ctx.Data["Err_TreePath"] = true
|
ctx.Data["Err_TreePath"] = true
|
||||||
ctx.RenderWithErr(ctx.Tr("repo.editor.directory_is_a_file", part), tplUploadFile, &form)
|
ctx.RenderWithErr(ctx.Tr("repo.editor.directory_is_a_file", part), tplUploadFile, &form)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
message := strings.TrimSpace(form.CommitSummary)
|
message := strings.TrimSpace(form.CommitSummary)
|
||||||
if len(message) == 0 {
|
if len(message) == 0 {
|
||||||
|
@ -714,6 +718,10 @@ func UploadFilePost(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ctx.Repo.Repository.IsEmpty {
|
||||||
|
_ = repo_model.UpdateRepositoryCols(ctx, &repo_model.Repository{ID: ctx.Repo.Repository.ID, IsEmpty: false}, "is_empty")
|
||||||
|
}
|
||||||
|
|
||||||
if form.CommitChoice == frmCommitChoiceNewBranch && ctx.Repo.Repository.UnitEnabled(ctx, unit.TypePullRequests) {
|
if form.CommitChoice == frmCommitChoiceNewBranch && ctx.Repo.Repository.UnitEnabled(ctx, unit.TypePullRequests) {
|
||||||
ctx.Redirect(ctx.Repo.RepoLink + "/compare/" + util.PathEscapeSegments(ctx.Repo.BranchName) + "..." + util.PathEscapeSegments(form.NewBranchName))
|
ctx.Redirect(ctx.Repo.RepoLink + "/compare/" + util.PathEscapeSegments(ctx.Repo.BranchName) + "..." + util.PathEscapeSegments(form.NewBranchName))
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -154,16 +154,6 @@ func renderDirectory(ctx *context.Context, treeLink string) {
|
||||||
ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+path.Base(ctx.Repo.TreePath), ctx.Repo.RefName)
|
ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+path.Base(ctx.Repo.TreePath), ctx.Repo.RefName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check permission to add or upload new file.
|
|
||||||
if ctx.Repo.CanWrite(unit_model.TypeCode) && ctx.Repo.IsViewBranch {
|
|
||||||
ctx.Data["CanAddFile"] = !ctx.Repo.Repository.IsArchived
|
|
||||||
ctx.Data["CanUploadFile"] = setting.Repository.Upload.Enabled && !ctx.Repo.Repository.IsArchived
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.Written() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
subfolder, readmeFile, err := findReadmeFileInEntries(ctx, entries, true)
|
subfolder, readmeFile, err := findReadmeFileInEntries(ctx, entries, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("findReadmeFileInEntries", err)
|
ctx.ServerError("findReadmeFileInEntries", err)
|
||||||
|
@ -868,21 +858,25 @@ func renderRepoTopics(ctx *context.Context) {
|
||||||
|
|
||||||
func renderCode(ctx *context.Context) {
|
func renderCode(ctx *context.Context) {
|
||||||
ctx.Data["PageIsViewCode"] = true
|
ctx.Data["PageIsViewCode"] = true
|
||||||
|
ctx.Data["RepositoryUploadEnabled"] = setting.Repository.Upload.Enabled
|
||||||
|
|
||||||
if ctx.Repo.Repository.IsEmpty {
|
if ctx.Repo.Commit == nil || ctx.Repo.Repository.IsEmpty || ctx.Repo.Repository.IsBroken() {
|
||||||
reallyEmpty := true
|
showEmpty := true
|
||||||
var err error
|
var err error
|
||||||
if ctx.Repo.GitRepo != nil {
|
if ctx.Repo.GitRepo != nil {
|
||||||
reallyEmpty, err = ctx.Repo.GitRepo.IsEmpty()
|
showEmpty, err = ctx.Repo.GitRepo.IsEmpty()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("GitRepo.IsEmpty", err)
|
log.Error("GitRepo.IsEmpty: %v", err)
|
||||||
return
|
ctx.Repo.Repository.Status = repo_model.RepositoryBroken
|
||||||
|
showEmpty = true
|
||||||
|
ctx.Flash.Error(ctx.Tr("error.occurred"), true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if reallyEmpty {
|
if showEmpty {
|
||||||
ctx.HTML(http.StatusOK, tplRepoEMPTY)
|
ctx.HTML(http.StatusOK, tplRepoEMPTY)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// the repo is not really empty, so we should update the modal in database
|
// the repo is not really empty, so we should update the modal in database
|
||||||
// such problem may be caused by:
|
// such problem may be caused by:
|
||||||
// 1) an error occurs during pushing/receiving. 2) the user replaces an empty git repo manually
|
// 1) an error occurs during pushing/receiving. 2) the user replaces an empty git repo manually
|
||||||
|
@ -898,6 +892,14 @@ func renderCode(ctx *context.Context) {
|
||||||
ctx.ServerError("UpdateRepoSize", err)
|
ctx.ServerError("UpdateRepoSize", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the repo's IsEmpty has been updated, redirect to this page to make sure middlewares can get the correct values
|
||||||
|
link := ctx.Link
|
||||||
|
if ctx.Req.URL.RawQuery != "" {
|
||||||
|
link += "?" + ctx.Req.URL.RawQuery
|
||||||
|
}
|
||||||
|
ctx.Redirect(link)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
title := ctx.Repo.Repository.Owner.Name + "/" + ctx.Repo.Repository.Name
|
title := ctx.Repo.Repository.Owner.Name + "/" + ctx.Repo.Repository.Name
|
||||||
|
@ -927,12 +929,10 @@ func renderCode(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ctx.Repo.Repository.IsEmpty {
|
|
||||||
checkCitationFile(ctx, entry)
|
checkCitationFile(ctx, entry)
|
||||||
if ctx.Written() {
|
if ctx.Written() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
renderLanguageStats(ctx)
|
renderLanguageStats(ctx)
|
||||||
if ctx.Written() {
|
if ctx.Written() {
|
||||||
|
|
|
@ -1184,7 +1184,7 @@ func RegisterRoutes(m *web.Route) {
|
||||||
m.Post("/upload-file", repo.UploadFileToServer)
|
m.Post("/upload-file", repo.UploadFileToServer)
|
||||||
m.Post("/upload-remove", web.Bind(forms.RemoveUploadFileForm{}), repo.RemoveUploadFileFromServer)
|
m.Post("/upload-remove", web.Bind(forms.RemoveUploadFileForm{}), repo.RemoveUploadFileFromServer)
|
||||||
}, repo.MustBeEditable, repo.MustBeAbleToUpload)
|
}, repo.MustBeEditable, repo.MustBeAbleToUpload)
|
||||||
}, context.RepoRef(), canEnableEditor, context.RepoMustNotBeArchived(), repo.MustBeNotEmpty)
|
}, context.RepoRef(), canEnableEditor, context.RepoMustNotBeArchived())
|
||||||
|
|
||||||
m.Group("/branches", func() {
|
m.Group("/branches", func() {
|
||||||
m.Group("/_new", func() {
|
m.Group("/_new", func() {
|
||||||
|
|
|
@ -32,7 +32,7 @@ func Authenticate(user *user_model.User, login, password string) (*user_model.Us
|
||||||
}
|
}
|
||||||
|
|
||||||
// WARN: DON'T check user.IsActive, that will be checked on reqSign so that
|
// WARN: DON'T check user.IsActive, that will be checked on reqSign so that
|
||||||
// user could be hint to resend confirm email.
|
// user could be hinted to resend confirm email.
|
||||||
if user.ProhibitLogin {
|
if user.ProhibitLogin {
|
||||||
return nil, user_model.ErrUserProhibitLogin{
|
return nil, user_model.ErrUserProhibitLogin{
|
||||||
UID: user.ID,
|
UID: user.ID,
|
||||||
|
|
|
@ -86,12 +86,23 @@ func UploadRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer t.Close()
|
defer t.Close()
|
||||||
if err := t.Clone(opts.OldBranch); err != nil {
|
|
||||||
|
hasOldBranch := true
|
||||||
|
if err = t.Clone(opts.OldBranch); err != nil {
|
||||||
|
if !git.IsErrBranchNotExist(err) || !repo.IsEmpty {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := t.SetDefaultIndex(); err != nil {
|
if err = t.Init(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
hasOldBranch = false
|
||||||
|
opts.LastCommitID = ""
|
||||||
|
}
|
||||||
|
if hasOldBranch {
|
||||||
|
if err = t.SetDefaultIndex(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var filename2attribute2info map[string]map[string]string
|
var filename2attribute2info map[string]map[string]string
|
||||||
if setting.LFS.StartServer {
|
if setting.LFS.StartServer {
|
||||||
|
|
|
@ -39,6 +39,7 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{{if not .Repository.IsEmpty}}
|
||||||
<div class="field">
|
<div class="field">
|
||||||
{{$pullRequestEnabled := .Repository.UnitEnabled $.Context $.UnitTypePullRequests}}
|
{{$pullRequestEnabled := .Repository.UnitEnabled $.Context $.UnitTypePullRequests}}
|
||||||
{{$prUnit := .Repository.MustGetUnit $.Context $.UnitTypePullRequests}}
|
{{$prUnit := .Repository.MustGetUnit $.Context $.UnitTypePullRequests}}
|
||||||
|
@ -65,6 +66,7 @@
|
||||||
<span class="text-muted js-quick-pull-normalization-info"></span>
|
<span class="text-muted js-quick-pull-normalization-info"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button id="commit-button" type="submit" class="ui green button">
|
<button id="commit-button" type="submit" class="ui green button">
|
||||||
|
|
|
@ -21,10 +21,23 @@
|
||||||
<div class="ui attached guide table segment empty-repo-guide">
|
<div class="ui attached guide table segment empty-repo-guide">
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<h3>{{.locale.Tr "repo.clone_this_repo"}} <small>{{.locale.Tr "repo.clone_helper" "http://git-scm.com/book/en/Git-Basics-Getting-a-Git-Repository" | Str2html}}</small></h3>
|
<h3>{{.locale.Tr "repo.clone_this_repo"}} <small>{{.locale.Tr "repo.clone_helper" "http://git-scm.com/book/en/Git-Basics-Getting-a-Git-Repository" | Str2html}}</small></h3>
|
||||||
<div class="ui action small input">
|
|
||||||
|
<div class="gt-df">
|
||||||
|
{{if and .CanWriteCode (not .Repository.IsArchived)}}
|
||||||
|
<a class="ui small button" href="{{.RepoLink}}/_new/{{.BranchName | PathEscapeSegments}}/">
|
||||||
|
{{.locale.Tr "repo.editor.new_file"}}
|
||||||
|
</a>
|
||||||
|
{{if .RepositoryUploadEnabled}}
|
||||||
|
<a class="ui small button" href="{{.RepoLink}}/_upload/{{.BranchName | PathEscapeSegments}}/">
|
||||||
|
{{.locale.Tr "repo.editor.upload_file"}}
|
||||||
|
</a>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
<div class="ui action small input gt-df gt-f1">
|
||||||
{{template "repo/clone_buttons" .}}
|
{{template "repo/clone_buttons" .}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{{if not .Repository.IsArchived}}
|
{{if not .Repository.IsArchived}}
|
||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>
|
||||||
|
|
|
@ -71,29 +71,27 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
<a href="{{.Repository.Link}}/find/{{.BranchNameSubURL}}" class="ui compact basic button">{{.locale.Tr "repo.find_file.go_to_file"}}</a>
|
<a href="{{.Repository.Link}}/find/{{.BranchNameSubURL}}" class="ui compact basic button">{{.locale.Tr "repo.find_file.go_to_file"}}</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{if or .CanAddFile .CanUploadFile}}
|
|
||||||
|
{{if and .CanWriteCode .IsViewBranch (not .Repository.IsArchived)}}
|
||||||
<button class="ui basic compact dropdown jump icon button gt-mr-2"{{if not .Repository.CanEnableEditor}} disabled{{end}}>
|
<button class="ui basic compact dropdown jump icon button gt-mr-2"{{if not .Repository.CanEnableEditor}} disabled{{end}}>
|
||||||
<span class="text">{{.locale.Tr "repo.editor.add_file"}}</span>
|
<span class="text">{{.locale.Tr "repo.editor.add_file"}}</span>
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
{{if .CanAddFile}}
|
|
||||||
<a class="item" href="{{.RepoLink}}/_new/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}">
|
<a class="item" href="{{.RepoLink}}/_new/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}">
|
||||||
{{.locale.Tr "repo.editor.new_file"}}
|
{{.locale.Tr "repo.editor.new_file"}}
|
||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{if .RepositoryUploadEnabled}}
|
||||||
{{if .CanUploadFile}}
|
|
||||||
<a class="item" href="{{.RepoLink}}/_upload/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}">
|
<a class="item" href="{{.RepoLink}}/_upload/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}">
|
||||||
{{.locale.Tr "repo.editor.upload_file"}}
|
{{.locale.Tr "repo.editor.upload_file"}}
|
||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{if .CanAddFile}}
|
|
||||||
<a class="item" href="{{.RepoLink}}/_diffpatch/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}">
|
<a class="item" href="{{.RepoLink}}/_diffpatch/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}">
|
||||||
{{.locale.Tr "repo.editor.patch"}}
|
{{.locale.Tr "repo.editor.patch"}}
|
||||||
</a>
|
</a>
|
||||||
{{end}}
|
|
||||||
</div>
|
</div>
|
||||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||||
</button>
|
</button>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{if and (eq $n 0) (.Repository.IsTemplate)}}
|
{{if and (eq $n 0) (.Repository.IsTemplate)}}
|
||||||
<a role="button" class="ui primary compact button" href="{{AppSubUrl}}/repo/create?template_id={{.Repository.ID}}">
|
<a role="button" class="ui primary compact button" href="{{AppSubUrl}}/repo/create?template_id={{.Repository.ID}}">
|
||||||
{{.locale.Tr "repo.use_template"}}
|
{{.locale.Tr "repo.use_template"}}
|
||||||
|
|
|
@ -4,12 +4,19 @@
|
||||||
package integration
|
package integration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/json"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/test"
|
||||||
"code.gitea.io/gitea/tests"
|
"code.gitea.io/gitea/tests"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -17,7 +24,7 @@ import (
|
||||||
|
|
||||||
func TestEmptyRepo(t *testing.T) {
|
func TestEmptyRepo(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrepareTestEnv(t)()
|
||||||
subpaths := []string{
|
subPaths := []string{
|
||||||
"commits/master",
|
"commits/master",
|
||||||
"raw/foo",
|
"raw/foo",
|
||||||
"commit/1ae57b34ccf7e18373",
|
"commit/1ae57b34ccf7e18373",
|
||||||
|
@ -26,8 +33,75 @@ func TestEmptyRepo(t *testing.T) {
|
||||||
emptyRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 5})
|
emptyRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 5})
|
||||||
assert.True(t, emptyRepo.IsEmpty)
|
assert.True(t, emptyRepo.IsEmpty)
|
||||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: emptyRepo.OwnerID})
|
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: emptyRepo.OwnerID})
|
||||||
for _, subpath := range subpaths {
|
for _, subPath := range subPaths {
|
||||||
req := NewRequestf(t, "GET", "/%s/%s/%s", owner.Name, emptyRepo.Name, subpath)
|
req := NewRequestf(t, "GET", "/%s/%s/%s", owner.Name, emptyRepo.Name, subPath)
|
||||||
MakeRequest(t, req, http.StatusNotFound)
|
MakeRequest(t, req, http.StatusNotFound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEmptyRepoAddFile(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
err := user_model.UpdateUserCols(db.DefaultContext, &user_model.User{ID: 30, ProhibitLogin: false}, "prohibit_login")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
session := loginUser(t, "user30")
|
||||||
|
req := NewRequest(t, "GET", "/user30/empty/_new/"+setting.Repository.DefaultBranch)
|
||||||
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
doc := NewHTMLParser(t, resp.Body).Find(`input[name="commit_choice"]`)
|
||||||
|
assert.Equal(t, "", doc.AttrOr("checked", "_no_"))
|
||||||
|
req = NewRequestWithValues(t, "POST", "/user30/empty/_new/"+setting.Repository.DefaultBranch, map[string]string{
|
||||||
|
"_csrf": GetCSRF(t, session, "/user/settings"),
|
||||||
|
"commit_choice": "direct",
|
||||||
|
"tree_path": "test-file.md",
|
||||||
|
"content": "newly-added-test-file",
|
||||||
|
})
|
||||||
|
|
||||||
|
resp = session.MakeRequest(t, req, http.StatusSeeOther)
|
||||||
|
redirect := test.RedirectURL(resp)
|
||||||
|
assert.Equal(t, "/user30/empty/src/branch/"+setting.Repository.DefaultBranch+"/test-file.md", redirect)
|
||||||
|
|
||||||
|
req = NewRequest(t, "GET", redirect)
|
||||||
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
assert.Contains(t, resp.Body.String(), "newly-added-test-file")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEmptyRepoUploadFile(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
err := user_model.UpdateUserCols(db.DefaultContext, &user_model.User{ID: 30, ProhibitLogin: false}, "prohibit_login")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
session := loginUser(t, "user30")
|
||||||
|
req := NewRequest(t, "GET", "/user30/empty/_new/"+setting.Repository.DefaultBranch)
|
||||||
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
doc := NewHTMLParser(t, resp.Body).Find(`input[name="commit_choice"]`)
|
||||||
|
assert.Equal(t, "", doc.AttrOr("checked", "_no_"))
|
||||||
|
|
||||||
|
body := &bytes.Buffer{}
|
||||||
|
mpForm := multipart.NewWriter(body)
|
||||||
|
_ = mpForm.WriteField("_csrf", GetCSRF(t, session, "/user/settings"))
|
||||||
|
file, _ := mpForm.CreateFormFile("file", "uploaded-file.txt")
|
||||||
|
_, _ = io.Copy(file, bytes.NewBufferString("newly-uploaded-test-file"))
|
||||||
|
_ = mpForm.Close()
|
||||||
|
|
||||||
|
req = NewRequestWithBody(t, "POST", "/user30/empty/upload-file", body)
|
||||||
|
req.Header.Add("Content-Type", mpForm.FormDataContentType())
|
||||||
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
respMap := map[string]string{}
|
||||||
|
assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), &respMap))
|
||||||
|
|
||||||
|
req = NewRequestWithValues(t, "POST", "/user30/empty/_upload/"+setting.Repository.DefaultBranch, map[string]string{
|
||||||
|
"_csrf": GetCSRF(t, session, "/user/settings"),
|
||||||
|
"commit_choice": "direct",
|
||||||
|
"files": respMap["uuid"],
|
||||||
|
"tree_path": "",
|
||||||
|
})
|
||||||
|
resp = session.MakeRequest(t, req, http.StatusSeeOther)
|
||||||
|
redirect := test.RedirectURL(resp)
|
||||||
|
assert.Equal(t, "/user30/empty/src/branch/"+setting.Repository.DefaultBranch+"/", redirect)
|
||||||
|
|
||||||
|
req = NewRequest(t, "GET", redirect)
|
||||||
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
assert.Contains(t, resp.Body.String(), "uploaded-file.txt")
|
||||||
|
}
|
||||||
|
|
|
@ -124,6 +124,9 @@ func TestMain(m *testing.M) {
|
||||||
fmt.Printf("Error initializing test database: %v\n", err)
|
fmt.Printf("Error initializing test database: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: the console logger is deleted by mistake, so if there is any `log.Fatal`, developers won't see any error message.
|
||||||
|
// Instead, "No tests were found", last nonsense log is "According to the configuration, subsequent logs will not be printed to the console"
|
||||||
exitCode := m.Run()
|
exitCode := m.Run()
|
||||||
|
|
||||||
tests.WriterCloser.Reset()
|
tests.WriterCloser.Reset()
|
||||||
|
@ -366,10 +369,12 @@ const NoExpectedStatus = -1
|
||||||
func MakeRequest(t testing.TB, req *http.Request, expectedStatus int) *httptest.ResponseRecorder {
|
func MakeRequest(t testing.TB, req *http.Request, expectedStatus int) *httptest.ResponseRecorder {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
recorder := httptest.NewRecorder()
|
recorder := httptest.NewRecorder()
|
||||||
|
if req.RemoteAddr == "" {
|
||||||
|
req.RemoteAddr = "test-mock:12345"
|
||||||
|
}
|
||||||
c.ServeHTTP(recorder, req)
|
c.ServeHTTP(recorder, req)
|
||||||
if expectedStatus != NoExpectedStatus {
|
if expectedStatus != NoExpectedStatus {
|
||||||
if !assert.EqualValues(t, expectedStatus, recorder.Code,
|
if !assert.EqualValues(t, expectedStatus, recorder.Code, "Request: %s %s", req.Method, req.URL.String()) {
|
||||||
"Request: %s %s", req.Method, req.URL.String()) {
|
|
||||||
logUnexpectedResponse(t, recorder)
|
logUnexpectedResponse(t, recorder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -412,6 +417,8 @@ func logUnexpectedResponse(t testing.TB, recorder *httptest.ResponseRecorder) {
|
||||||
// if body is short, just log the whole thing
|
// if body is short, just log the whole thing
|
||||||
t.Log("Response: ", string(respBytes))
|
t.Log("Response: ", string(respBytes))
|
||||||
return
|
return
|
||||||
|
} else {
|
||||||
|
t.Log("Response length: ", len(respBytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
// log the "flash" error message, if one exists
|
// log the "flash" error message, if one exists
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
@ -30,29 +29,44 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func exitf(format string, args ...interface{}) {
|
||||||
|
fmt.Printf(format+"\n", args...)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
func InitTest(requireGitea bool) {
|
func InitTest(requireGitea bool) {
|
||||||
giteaRoot := base.SetupGiteaRoot()
|
giteaRoot := base.SetupGiteaRoot()
|
||||||
if giteaRoot == "" {
|
if giteaRoot == "" {
|
||||||
fmt.Println("Environment variable $GITEA_ROOT not set")
|
exitf("Environment variable $GITEA_ROOT not set")
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
setting.AppWorkPath = giteaRoot
|
||||||
if requireGitea {
|
if requireGitea {
|
||||||
giteaBinary := "gitea"
|
giteaBinary := "gitea"
|
||||||
if runtime.GOOS == "windows" {
|
if setting.IsWindows {
|
||||||
giteaBinary += ".exe"
|
giteaBinary += ".exe"
|
||||||
}
|
}
|
||||||
setting.AppPath = path.Join(giteaRoot, giteaBinary)
|
setting.AppPath = path.Join(giteaRoot, giteaBinary)
|
||||||
if _, err := os.Stat(setting.AppPath); err != nil {
|
if _, err := os.Stat(setting.AppPath); err != nil {
|
||||||
fmt.Printf("Could not find gitea binary at %s\n", setting.AppPath)
|
exitf("Could not find gitea binary at %s", setting.AppPath)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
giteaConf := os.Getenv("GITEA_CONF")
|
giteaConf := os.Getenv("GITEA_CONF")
|
||||||
if giteaConf == "" {
|
if giteaConf == "" {
|
||||||
fmt.Println("Environment variable $GITEA_CONF not set")
|
// By default, use sqlite.ini for testing, then IDE like GoLand can start the test process with debugger.
|
||||||
os.Exit(1)
|
// It's easier for developers to debug bugs step by step with a debugger.
|
||||||
} else if !path.IsAbs(giteaConf) {
|
// Notice: when doing "ssh push", Gitea executes sub processes, debugger won't work for the sub processes.
|
||||||
|
giteaConf = "tests/sqlite.ini"
|
||||||
|
_ = os.Setenv("GITEA_CONF", giteaConf)
|
||||||
|
fmt.Printf("Environment variable $GITEA_CONF not set, use default: %s\n", giteaConf)
|
||||||
|
if !setting.EnableSQLite3 {
|
||||||
|
exitf(`Need to enable SQLite3 for sqlite.ini testing, please set: -tags "sqlite,sqlite_unlock_notify"`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setting.IsInTesting = true
|
||||||
|
|
||||||
|
if !path.IsAbs(giteaConf) {
|
||||||
setting.CustomConf = path.Join(giteaRoot, giteaConf)
|
setting.CustomConf = path.Join(giteaRoot, giteaConf)
|
||||||
} else {
|
} else {
|
||||||
setting.CustomConf = giteaConf
|
setting.CustomConf = giteaConf
|
||||||
|
@ -69,8 +83,7 @@ func InitTest(requireGitea bool) {
|
||||||
|
|
||||||
setting.LoadDBSetting()
|
setting.LoadDBSetting()
|
||||||
if err := storage.Init(); err != nil {
|
if err := storage.Init(); err != nil {
|
||||||
fmt.Printf("Init storage failed: %v", err)
|
exitf("Init storage failed: %v", err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
@ -221,7 +234,7 @@ func PrepareTestEnv(t testing.TB, skip ...int) func() {
|
||||||
return deferFn
|
return deferFn
|
||||||
}
|
}
|
||||||
|
|
||||||
// resetFixtures flushes queues, reloads fixtures and resets test repositories within a single test.
|
// ResetFixtures flushes queues, reloads fixtures and resets test repositories within a single test.
|
||||||
// Most tests should call defer tests.PrepareTestEnv(t)() (or have onGiteaRun do that for them) but sometimes
|
// Most tests should call defer tests.PrepareTestEnv(t)() (or have onGiteaRun do that for them) but sometimes
|
||||||
// within a single test this is required
|
// within a single test this is required
|
||||||
func ResetFixtures(t *testing.T) {
|
func ResetFixtures(t *testing.T) {
|
||||||
|
|
|
@ -1911,15 +1911,12 @@
|
||||||
border-radius: var(--border-radius) 0 0 var(--border-radius);
|
border-radius: var(--border-radius) 0 0 var(--border-radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
.repository.quickstart .guide .ui.action.small.input {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.repository.quickstart .guide #repo-clone-url {
|
.repository.quickstart .guide #repo-clone-url {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
|
flex: 1
|
||||||
}
|
}
|
||||||
|
|
||||||
.repository.release #release-list {
|
.repository.release #release-list {
|
||||||
|
|
Loading…
Reference in a new issue