![image](https://user-images.githubusercontent.com/2114189/232561612-2bfcfd0a-fc04-47ba-965f-5d0bcea46c54.png)tags/v1.20.0-rc0
@@ -19,13 +19,16 @@ func TestIterate(t *testing.T) { | |||
xe := unittest.GetXORMEngine() | |||
assert.NoError(t, xe.Sync(&repo_model.RepoUnit{})) | |||
var repoCnt int | |||
err := db.Iterate(db.DefaultContext, nil, func(ctx context.Context, repo *repo_model.RepoUnit) error { | |||
repoCnt++ | |||
cnt, err := db.GetEngine(db.DefaultContext).Count(&repo_model.RepoUnit{}) | |||
assert.NoError(t, err) | |||
var repoUnitCnt int | |||
err = db.Iterate(db.DefaultContext, nil, func(ctx context.Context, repo *repo_model.RepoUnit) error { | |||
repoUnitCnt++ | |||
return nil | |||
}) | |||
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 { | |||
reopUnit2 := repo_model.RepoUnit{ID: repoUnit.ID} |
@@ -31,15 +31,20 @@ func TestFind(t *testing.T) { | |||
xe := unittest.GetXORMEngine() | |||
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{} | |||
var repoUnits []repo_model.RepoUnit | |||
err := db.Find(db.DefaultContext, &opts, &repoUnits) | |||
err = db.Find(db.DefaultContext, &opts, &repoUnits) | |||
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)) | |||
assert.NoError(t, err) | |||
assert.EqualValues(t, 89, cnt) | |||
assert.EqualValues(t, repoUnitCount, cnt) | |||
repoUnits = make([]repo_model.RepoUnit, 0, 10) | |||
newCnt, err := db.FindAndCount(db.DefaultContext, &opts, &repoUnits) |
@@ -12,8 +12,6 @@ import ( | |||
"code.gitea.io/gitea/models/db" | |||
"github.com/stretchr/testify/assert" | |||
_ "github.com/mattn/go-sqlite3" | |||
) | |||
func changeDefaultFileBlockSize(n int64) (restore func()) { |
@@ -601,3 +601,9 @@ | |||
repo_id: 57 | |||
type: 5 | |||
created_unix: 946684810 | |||
- | |||
id: 90 | |||
repo_id: 52 | |||
type: 1 | |||
created_unix: 946684810 |
@@ -1560,6 +1560,7 @@ | |||
owner_name: user30 | |||
lower_name: empty | |||
name: empty | |||
default_branch: master | |||
num_watches: 0 | |||
num_stars: 0 | |||
num_forks: 0 |
@@ -1091,7 +1091,7 @@ | |||
max_repo_creation: -1 | |||
is_active: true | |||
is_admin: false | |||
is_restricted: true | |||
is_restricted: false | |||
allow_git_hook: false | |||
allow_import_local: false | |||
allow_create_organization: true |
@@ -225,6 +225,12 @@ func (repo *Repository) IsBroken() bool { | |||
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. | |||
func (repo *Repository) AfterLoad() { | |||
repo.NumOpenIssues = repo.NumIssues - repo.NumClosedIssues | |||
@@ -729,7 +735,7 @@ func IsRepositoryExist(ctx context.Context, u *user_model.User, repoName string) | |||
return false, err | |||
} | |||
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 |
@@ -17,7 +17,7 @@ import ( | |||
"xorm.io/xorm/schemas" | |||
) | |||
var fixtures *testfixtures.Loader | |||
var fixturesLoader *testfixtures.Loader | |||
// GetXORMEngine gets the 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 | |||
func InitFixtures(opts FixturesOptions, engine ...*xorm.Engine) (err error) { | |||
e := GetXORMEngine(engine...) | |||
var testfiles func(*testfixtures.Loader) error | |||
var fixtureOptionFiles func(*testfixtures.Loader) error | |||
if opts.Dir != "" { | |||
testfiles = testfixtures.Directory(opts.Dir) | |||
fixtureOptionFiles = testfixtures.Directory(opts.Dir) | |||
} else { | |||
testfiles = testfixtures.Files(opts.Files...) | |||
fixtureOptionFiles = testfixtures.Files(opts.Files...) | |||
} | |||
dialect := "unknown" | |||
switch e.Dialect().URI().DBType { | |||
@@ -54,14 +54,14 @@ func InitFixtures(opts FixturesOptions, engine ...*xorm.Engine) (err error) { | |||
testfixtures.Database(e.DB().DB), | |||
testfixtures.Dialect(dialect), | |||
testfixtures.DangerousSkipTestDatabaseCheck(), | |||
testfiles, | |||
fixtureOptionFiles, | |||
} | |||
if e.Dialect().URI().DBType == schemas.POSTGRES { | |||
loaderOptions = append(loaderOptions, testfixtures.SkipResetSequences()) | |||
} | |||
fixtures, err = testfixtures.New(loaderOptions...) | |||
fixturesLoader, err = testfixtures.New(loaderOptions...) | |||
if err != nil { | |||
return err | |||
} | |||
@@ -78,11 +78,9 @@ func InitFixtures(opts FixturesOptions, engine ...*xorm.Engine) (err error) { | |||
func LoadFixtures(engine ...*xorm.Engine) error { | |||
e := GetXORMEngine(engine...) | |||
var err error | |||
// Database transaction conflicts could occur and result in ROLLBACK | |||
// As a simple workaround, we just retry 20 times. | |||
for i := 0; i < 20; i++ { | |||
err = fixtures.Load() | |||
if err == nil { | |||
// (doubt) database transaction conflicts could occur and result in ROLLBACK? just try for a few times. | |||
for i := 0; i < 5; i++ { | |||
if err = fixturesLoader.Load(); err == nil { | |||
break | |||
} | |||
time.Sleep(200 * time.Millisecond) |
@@ -5,6 +5,7 @@ package user_test | |||
import ( | |||
"context" | |||
"fmt" | |||
"math/rand" | |||
"strings" | |||
"testing" | |||
@@ -64,9 +65,10 @@ func TestSearchUsers(t *testing.T) { | |||
testSuccess := func(opts *user_model.SearchUserOptions, expectedUserOrOrgIDs []int64) { | |||
users, _, err := user_model.SearchUsers(opts) | |||
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 { | |||
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}) | |||
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}, | |||
[]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 | |||
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 | |||
func (r *Repository) GetCommitsCount() (int64, error) { | |||
if r.Commit == nil { | |||
return 0, nil | |||
} | |||
var contextName string | |||
if r.IsViewBranch { | |||
contextName = r.BranchName | |||
@@ -642,8 +645,7 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) { | |||
if err != nil { | |||
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) | |||
ctx.Repo.Repository.Status = repo_model.RepositoryBroken | |||
ctx.Repo.Repository.IsEmpty = true | |||
ctx.Repo.Repository.MarkAsBrokenEmpty() | |||
ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch | |||
// Only allow access to base of repo or settings | |||
if !isHomeOrSettings { | |||
@@ -689,7 +691,7 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) { | |||
ctx.Data["BranchesCount"] = len(brs) | |||
// 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.Repository.DefaultBranch) > 0 && gitRepo.IsBranchExist(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) { | |||
// Empty repository does not have reference information. | |||
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 | |||
} | |||
@@ -907,27 +913,30 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context | |||
refName = ctx.Repo.Repository.DefaultBranch | |||
if !ctx.Repo.GitRepo.IsBranchExist(refName) { | |||
brs, _, err := ctx.Repo.GitRepo.GetBranchNames(0, 0) | |||
if err != nil { | |||
ctx.ServerError("GetBranches", err) | |||
return | |||
if err == nil && len(brs) != 0 { | |||
refName = brs[0] | |||
} else if len(brs) == 0 { | |||
err = fmt.Errorf("No branches in non-empty repository %s", | |||
ctx.Repo.GitRepo.Path) | |||
ctx.ServerError("GetBranches", err) | |||
return | |||
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() | |||
} | |||
refName = brs[0] | |||
} | |||
ctx.Repo.RefName = refName | |||
ctx.Repo.BranchName = 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) | |||
return | |||
} | |||
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String() | |||
ctx.Repo.IsViewBranch = true | |||
} else { | |||
refName = getRefName(ctx, refType) | |||
ctx.Repo.RefName = refName |
@@ -211,10 +211,18 @@ type RunOpts struct { | |||
Env []string | |||
Timeout time.Duration | |||
UseContextTimeout bool | |||
Dir string | |||
Stdout, Stderr io.Writer | |||
Stdin io.Reader | |||
PipelineFunc func(context.Context, context.CancelFunc) error | |||
// 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 | |||
Stdout, Stderr io.Writer | |||
Stdin io.Reader | |||
PipelineFunc func(context.Context, context.CancelFunc) error | |||
} | |||
func commonBaseEnvs() []string { |
@@ -80,7 +80,7 @@ func InitRepository(ctx context.Context, repoPath string, bare bool) error { | |||
// IsEmpty Check if repository is empty. | |||
func (repo *Repository) IsEmpty() (bool, error) { | |||
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{ | |||
Dir: repo.Path, | |||
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.checkWriter, repo.checkReader, repo.checkCancel = CatFileBatchCheck(ctx, repo.Path) | |||
repo.checkWriter, repo.checkReader, repo.checkCancel = CatFileBatchCheck(ctx, repoPath) | |||
return repo, nil | |||
} |
@@ -154,7 +154,9 @@ func Init() { | |||
log.Trace("IndexerData Process Repo: %d", indexerData.RepoID) | |||
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() { | |||
continue | |||
} |
@@ -11,6 +11,7 @@ import ( | |||
"code.gitea.io/gitea/modules/graceful" | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/process" | |||
"code.gitea.io/gitea/modules/setting" | |||
) | |||
// 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 | |||
commitID, err := gitRepo.GetBranchCommitID(repo.DefaultBranch) | |||
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()) | |||
return nil | |||
} | |||
@@ -62,7 +63,9 @@ func (db *DBIndexer) Index(id int64) error { | |||
// Calculate and save language statistics to database | |||
stats, err := gitRepo.GetLanguageStats(commitID) | |||
if err != nil { | |||
log.Error("Unable to get language stats for ID %s for default branch %s in %s. Error: %v", commitID, repo.DefaultBranch, repo.RepoPath(), err) | |||
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) | |||
} | |||
return err | |||
} | |||
err = repo_model.UpdateLanguageStats(repo, commitID, stats) |
@@ -10,6 +10,7 @@ import ( | |||
"code.gitea.io/gitea/modules/graceful" | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/queue" | |||
"code.gitea.io/gitea/modules/setting" | |||
) | |||
// statsQueue represents a queue to handle repository stats updates | |||
@@ -20,7 +21,9 @@ func handle(data ...queue.Data) []queue.Data { | |||
for _, datum := range data { | |||
opts := datum.(int64) | |||
if err := indexer.Index(opts); err != nil { | |||
log.Error("stats queue indexer.Index(%d) failed: %v", opts, err) | |||
if !setting.IsInTesting { | |||
log.Error("stats queue indexer.Index(%d) failed: %v", opts, err) | |||
} | |||
} | |||
} | |||
return nil |
@@ -27,7 +27,7 @@ import ( | |||
var ( | |||
// AppVer is the version of the current build of Gitea. It is set in main.go from main.Version. | |||
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 | |||
// AppStartTime store time gitea has started | |||
AppStartTime time.Time | |||
@@ -40,7 +40,8 @@ var ( | |||
// AppWorkPath is used as the base path for several other paths. | |||
AppWorkPath string | |||
// Global setting objects | |||
// Other global setting objects | |||
CfgProvider ConfigProvider | |||
CustomPath string // Custom directory path | |||
CustomConf string | |||
@@ -48,6 +49,10 @@ var ( | |||
RunUser string | |||
IsProd 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) { | |||
@@ -108,8 +113,12 @@ func getWorkPath(appPath string) string { | |||
func init() { | |||
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 | |||
// 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)) | |||
var err error |
@@ -108,6 +108,7 @@ func GlobalInitInstalled(ctx context.Context) { | |||
} | |||
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("AppPath: %s", setting.AppPath) | |||
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("Configuration file: %s", setting.CustomConf) | |||
log.Info("Run Mode: %s", util.ToTitleCase(setting.RunMode)) | |||
log.Info("Gitea v%s%s", setting.AppVer, setting.AppBuiltWith) | |||
// Setup i18n | |||
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 | |||
// (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 = strings.Trim(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) { | |||
ctx.Redirect(ctx.Repo.RepoLink + "/compare/" + util.PathEscapeSegments(ctx.Repo.BranchName) + "..." + util.PathEscapeSegments(form.NewBranchName)) | |||
} else { | |||
@@ -617,25 +621,25 @@ func UploadFilePost(ctx *context.Context) { | |||
return | |||
} | |||
var newTreePath string | |||
for _, part := range treeNames { | |||
newTreePath = path.Join(newTreePath, part) | |||
entry, err := ctx.Repo.Commit.GetTreeEntryByPath(newTreePath) | |||
if err != nil { | |||
if git.IsErrNotExist(err) { | |||
// Means there is no item with that name, so we're good | |||
break | |||
if !ctx.Repo.Repository.IsEmpty { | |||
var newTreePath string | |||
for _, part := range treeNames { | |||
newTreePath = path.Join(newTreePath, part) | |||
entry, err := ctx.Repo.Commit.GetTreeEntryByPath(newTreePath) | |||
if err != nil { | |||
if git.IsErrNotExist(err) { | |||
break // Means there is no item with that name, so we're good | |||
} | |||
ctx.ServerError("Repo.Commit.GetTreeEntryByPath", err) | |||
return | |||
} | |||
ctx.ServerError("Repo.Commit.GetTreeEntryByPath", err) | |||
return | |||
} | |||
// User can only upload files to a directory. | |||
if !entry.IsDir() { | |||
ctx.Data["Err_TreePath"] = true | |||
ctx.RenderWithErr(ctx.Tr("repo.editor.directory_is_a_file", part), tplUploadFile, &form) | |||
return | |||
// User can only upload files to a directory, the directory name shouldn't be an existing file. | |||
if !entry.IsDir() { | |||
ctx.Data["Err_TreePath"] = true | |||
ctx.RenderWithErr(ctx.Tr("repo.editor.directory_is_a_file", part), tplUploadFile, &form) | |||
return | |||
} | |||
} | |||
} | |||
@@ -714,6 +718,10 @@ func UploadFilePost(ctx *context.Context) { | |||
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) { | |||
ctx.Redirect(ctx.Repo.RepoLink + "/compare/" + util.PathEscapeSegments(ctx.Repo.BranchName) + "..." + util.PathEscapeSegments(form.NewBranchName)) | |||
} 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) | |||
} | |||
// 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) | |||
if err != nil { | |||
ctx.ServerError("findReadmeFileInEntries", err) | |||
@@ -868,21 +858,25 @@ func renderRepoTopics(ctx *context.Context) { | |||
func renderCode(ctx *context.Context) { | |||
ctx.Data["PageIsViewCode"] = true | |||
ctx.Data["RepositoryUploadEnabled"] = setting.Repository.Upload.Enabled | |||
if ctx.Repo.Repository.IsEmpty { | |||
reallyEmpty := true | |||
if ctx.Repo.Commit == nil || ctx.Repo.Repository.IsEmpty || ctx.Repo.Repository.IsBroken() { | |||
showEmpty := true | |||
var err error | |||
if ctx.Repo.GitRepo != nil { | |||
reallyEmpty, err = ctx.Repo.GitRepo.IsEmpty() | |||
showEmpty, err = ctx.Repo.GitRepo.IsEmpty() | |||
if err != nil { | |||
ctx.ServerError("GitRepo.IsEmpty", err) | |||
return | |||
log.Error("GitRepo.IsEmpty: %v", err) | |||
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) | |||
return | |||
} | |||
// the repo is not really empty, so we should update the modal in database | |||
// such problem may be caused by: | |||
// 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) | |||
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 | |||
@@ -927,11 +929,9 @@ func renderCode(ctx *context.Context) { | |||
return | |||
} | |||
if !ctx.Repo.Repository.IsEmpty { | |||
checkCitationFile(ctx, entry) | |||
if ctx.Written() { | |||
return | |||
} | |||
checkCitationFile(ctx, entry) | |||
if ctx.Written() { | |||
return | |||
} | |||
renderLanguageStats(ctx) |
@@ -1184,7 +1184,7 @@ func RegisterRoutes(m *web.Route) { | |||
m.Post("/upload-file", repo.UploadFileToServer) | |||
m.Post("/upload-remove", web.Bind(forms.RemoveUploadFileForm{}), repo.RemoveUploadFileFromServer) | |||
}, repo.MustBeEditable, repo.MustBeAbleToUpload) | |||
}, context.RepoRef(), canEnableEditor, context.RepoMustNotBeArchived(), repo.MustBeNotEmpty) | |||
}, context.RepoRef(), canEnableEditor, context.RepoMustNotBeArchived()) | |||
m.Group("/branches", 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 | |||
// user could be hint to resend confirm email. | |||
// user could be hinted to resend confirm email. | |||
if user.ProhibitLogin { | |||
return nil, user_model.ErrUserProhibitLogin{ | |||
UID: user.ID, |
@@ -86,11 +86,22 @@ func UploadRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use | |||
return err | |||
} | |||
defer t.Close() | |||
if err := t.Clone(opts.OldBranch); err != nil { | |||
return err | |||
hasOldBranch := true | |||
if err = t.Clone(opts.OldBranch); err != nil { | |||
if !git.IsErrBranchNotExist(err) || !repo.IsEmpty { | |||
return err | |||
} | |||
if err = t.Init(); err != nil { | |||
return err | |||
} | |||
hasOldBranch = false | |||
opts.LastCommitID = "" | |||
} | |||
if err := t.SetDefaultIndex(); err != nil { | |||
return err | |||
if hasOldBranch { | |||
if err = t.SetDefaultIndex(); err != nil { | |||
return err | |||
} | |||
} | |||
var filename2attribute2info map[string]map[string]string |
@@ -39,6 +39,7 @@ | |||
</label> | |||
</div> | |||
</div> | |||
{{if not .Repository.IsEmpty}} | |||
<div class="field"> | |||
{{$pullRequestEnabled := .Repository.UnitEnabled $.Context $.UnitTypePullRequests}} | |||
{{$prUnit := .Repository.MustGetUnit $.Context $.UnitTypePullRequests}} | |||
@@ -65,6 +66,7 @@ | |||
<span class="text-muted js-quick-pull-normalization-info"></span> | |||
</div> | |||
</div> | |||
{{end}} | |||
</div> | |||
</div> | |||
<button id="commit-button" type="submit" class="ui green button"> |
@@ -21,8 +21,21 @@ | |||
<div class="ui attached guide table segment empty-repo-guide"> | |||
<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> | |||
<div class="ui action small input"> | |||
{{template "repo/clone_buttons" .}} | |||
<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" .}} | |||
</div> | |||
</div> | |||
</div> | |||
@@ -71,29 +71,27 @@ | |||
{{end}} | |||
<a href="{{.Repository.Link}}/find/{{.BranchNameSubURL}}" class="ui compact basic button">{{.locale.Tr "repo.find_file.go_to_file"}}</a> | |||
{{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}}> | |||
<span class="text">{{.locale.Tr "repo.editor.add_file"}}</span> | |||
<div class="menu"> | |||
{{if .CanAddFile}} | |||
<a class="item" href="{{.RepoLink}}/_new/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}"> | |||
{{.locale.Tr "repo.editor.new_file"}} | |||
</a> | |||
{{end}} | |||
{{if .CanUploadFile}} | |||
<a class="item" href="{{.RepoLink}}/_upload/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}"> | |||
{{.locale.Tr "repo.editor.upload_file"}} | |||
</a> | |||
{{end}} | |||
{{if .CanAddFile}} | |||
<a class="item" href="{{.RepoLink}}/_diffpatch/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}"> | |||
{{.locale.Tr "repo.editor.patch"}} | |||
</a> | |||
<a class="item" href="{{.RepoLink}}/_new/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}"> | |||
{{.locale.Tr "repo.editor.new_file"}} | |||
</a> | |||
{{if .RepositoryUploadEnabled}} | |||
<a class="item" href="{{.RepoLink}}/_upload/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}"> | |||
{{.locale.Tr "repo.editor.upload_file"}} | |||
</a> | |||
{{end}} | |||
<a class="item" href="{{.RepoLink}}/_diffpatch/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}"> | |||
{{.locale.Tr "repo.editor.patch"}} | |||
</a> | |||
</div> | |||
{{svg "octicon-triangle-down" 14 "dropdown icon"}} | |||
</button> | |||
{{end}} | |||
{{if and (eq $n 0) (.Repository.IsTemplate)}} | |||
<a role="button" class="ui primary compact button" href="{{AppSubUrl}}/repo/create?template_id={{.Repository.ID}}"> | |||
{{.locale.Tr "repo.use_template"}} |
@@ -4,12 +4,19 @@ | |||
package integration | |||
import ( | |||
"bytes" | |||
"io" | |||
"mime/multipart" | |||
"net/http" | |||
"testing" | |||
"code.gitea.io/gitea/models/db" | |||
repo_model "code.gitea.io/gitea/models/repo" | |||
"code.gitea.io/gitea/models/unittest" | |||
user_model "code.gitea.io/gitea/models/user" | |||
"code.gitea.io/gitea/modules/json" | |||
"code.gitea.io/gitea/modules/setting" | |||
"code.gitea.io/gitea/modules/test" | |||
"code.gitea.io/gitea/tests" | |||
"github.com/stretchr/testify/assert" | |||
@@ -17,7 +24,7 @@ import ( | |||
func TestEmptyRepo(t *testing.T) { | |||
defer tests.PrepareTestEnv(t)() | |||
subpaths := []string{ | |||
subPaths := []string{ | |||
"commits/master", | |||
"raw/foo", | |||
"commit/1ae57b34ccf7e18373", | |||
@@ -26,8 +33,75 @@ func TestEmptyRepo(t *testing.T) { | |||
emptyRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 5}) | |||
assert.True(t, emptyRepo.IsEmpty) | |||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: emptyRepo.OwnerID}) | |||
for _, subpath := range subpaths { | |||
req := NewRequestf(t, "GET", "/%s/%s/%s", owner.Name, emptyRepo.Name, subpath) | |||
for _, subPath := range subPaths { | |||
req := NewRequestf(t, "GET", "/%s/%s/%s", owner.Name, emptyRepo.Name, subPath) | |||
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) | |||
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() | |||
tests.WriterCloser.Reset() | |||
@@ -366,10 +369,12 @@ const NoExpectedStatus = -1 | |||
func MakeRequest(t testing.TB, req *http.Request, expectedStatus int) *httptest.ResponseRecorder { | |||
t.Helper() | |||
recorder := httptest.NewRecorder() | |||
if req.RemoteAddr == "" { | |||
req.RemoteAddr = "test-mock:12345" | |||
} | |||
c.ServeHTTP(recorder, req) | |||
if expectedStatus != NoExpectedStatus { | |||
if !assert.EqualValues(t, expectedStatus, recorder.Code, | |||
"Request: %s %s", req.Method, req.URL.String()) { | |||
if !assert.EqualValues(t, expectedStatus, recorder.Code, "Request: %s %s", req.Method, req.URL.String()) { | |||
logUnexpectedResponse(t, recorder) | |||
} | |||
} | |||
@@ -410,8 +415,10 @@ func logUnexpectedResponse(t testing.TB, recorder *httptest.ResponseRecorder) { | |||
return | |||
} else if len(respBytes) < 500 { | |||
// if body is short, just log the whole thing | |||
t.Log("Response:", string(respBytes)) | |||
t.Log("Response: ", string(respBytes)) | |||
return | |||
} else { | |||
t.Log("Response length: ", len(respBytes)) | |||
} | |||
// log the "flash" error message, if one exists |
@@ -10,7 +10,6 @@ import ( | |||
"os" | |||
"path" | |||
"path/filepath" | |||
"runtime" | |||
"testing" | |||
"code.gitea.io/gitea/models/db" | |||
@@ -30,29 +29,44 @@ import ( | |||
"github.com/stretchr/testify/assert" | |||
) | |||
func exitf(format string, args ...interface{}) { | |||
fmt.Printf(format+"\n", args...) | |||
os.Exit(1) | |||
} | |||
func InitTest(requireGitea bool) { | |||
giteaRoot := base.SetupGiteaRoot() | |||
if giteaRoot == "" { | |||
fmt.Println("Environment variable $GITEA_ROOT not set") | |||
os.Exit(1) | |||
exitf("Environment variable $GITEA_ROOT not set") | |||
} | |||
setting.AppWorkPath = giteaRoot | |||
if requireGitea { | |||
giteaBinary := "gitea" | |||
if runtime.GOOS == "windows" { | |||
if setting.IsWindows { | |||
giteaBinary += ".exe" | |||
} | |||
setting.AppPath = path.Join(giteaRoot, giteaBinary) | |||
if _, err := os.Stat(setting.AppPath); err != nil { | |||
fmt.Printf("Could not find gitea binary at %s\n", setting.AppPath) | |||
os.Exit(1) | |||
exitf("Could not find gitea binary at %s", setting.AppPath) | |||
} | |||
} | |||
giteaConf := os.Getenv("GITEA_CONF") | |||
if giteaConf == "" { | |||
fmt.Println("Environment variable $GITEA_CONF not set") | |||
os.Exit(1) | |||
} else if !path.IsAbs(giteaConf) { | |||
// By default, use sqlite.ini for testing, then IDE like GoLand can start the test process with debugger. | |||
// It's easier for developers to debug bugs step by step with a debugger. | |||
// 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) | |||
} else { | |||
setting.CustomConf = giteaConf | |||
@@ -69,8 +83,7 @@ func InitTest(requireGitea bool) { | |||
setting.LoadDBSetting() | |||
if err := storage.Init(); err != nil { | |||
fmt.Printf("Init storage failed: %v", err) | |||
os.Exit(1) | |||
exitf("Init storage failed: %v", err) | |||
} | |||
switch { | |||
@@ -221,7 +234,7 @@ func PrepareTestEnv(t testing.TB, skip ...int) func() { | |||
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 | |||
// within a single test this is required | |||
func ResetFixtures(t *testing.T) { |
@@ -1911,15 +1911,12 @@ | |||
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 { | |||
border-radius: 0; | |||
padding: 5px 10px; | |||
font-size: 1.2em; | |||
line-height: 1.4; | |||
flex: 1 | |||
} | |||
.repository.release #release-list { |