mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-11-26 15:25:43 +03:00
Push to create repo (#8419)
* Refactor Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add push-create to SSH serv Signed-off-by: jolheiser <john.olheiser@gmail.com> * Cannot push for another user unless admin Signed-off-by: jolheiser <john.olheiser@gmail.com> * Get owner in case admin pushes for another user Signed-off-by: jolheiser <john.olheiser@gmail.com> * Set new repo ID in result Signed-off-by: jolheiser <john.olheiser@gmail.com> * Update to service and use new org perms Signed-off-by: jolheiser <john.olheiser@gmail.com> * Move pushCreateRepo to services Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix import order Signed-off-by: jolheiser <john.olheiser@gmail.com> * Changes for @guillep2k * Check owner (not user) in SSH * Add basic tests for created repos (private, not empty) Signed-off-by: jolheiser <john.olheiser@gmail.com>
This commit is contained in:
parent
47c24be293
commit
6715677b2b
7 changed files with 219 additions and 51 deletions
|
@ -39,6 +39,9 @@ ACCESS_CONTROL_ALLOW_ORIGIN =
|
||||||
USE_COMPAT_SSH_URI = false
|
USE_COMPAT_SSH_URI = false
|
||||||
; Close issues as long as a commit on any branch marks it as fixed
|
; Close issues as long as a commit on any branch marks it as fixed
|
||||||
DEFAULT_CLOSE_ISSUES_VIA_COMMITS_IN_ANY_BRANCH = false
|
DEFAULT_CLOSE_ISSUES_VIA_COMMITS_IN_ANY_BRANCH = false
|
||||||
|
; Allow users to push local repositories to Gitea and have them automatically created for a user or an org
|
||||||
|
ENABLE_PUSH_CREATE_USER = false
|
||||||
|
ENABLE_PUSH_CREATE_ORG = false
|
||||||
|
|
||||||
[repository.editor]
|
[repository.editor]
|
||||||
; List of file extensions for which lines should be wrapped in the CodeMirror editor
|
; List of file extensions for which lines should be wrapped in the CodeMirror editor
|
||||||
|
|
|
@ -66,6 +66,8 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
|
||||||
default is not to present. **WARNING**: This maybe harmful to you website if you do not
|
default is not to present. **WARNING**: This maybe harmful to you website if you do not
|
||||||
give it a right value.
|
give it a right value.
|
||||||
- `DEFAULT_CLOSE_ISSUES_VIA_COMMITS_IN_ANY_BRANCH`: **false**: Close an issue if a commit on a non default branch marks it as closed.
|
- `DEFAULT_CLOSE_ISSUES_VIA_COMMITS_IN_ANY_BRANCH`: **false**: Close an issue if a commit on a non default branch marks it as closed.
|
||||||
|
- `ENABLE_PUSH_CREATE_USER`: **false**: Allow users to push local repositories to Gitea and have them automatically created for a user.
|
||||||
|
- `ENABLE_PUSH_CREATE_ORG`: **false**: Allow users to push local repositories to Gitea and have them automatically created for an org.
|
||||||
|
|
||||||
### Repository - Pull Request (`repository.pull-request`)
|
### Repository - Pull Request (`repository.pull-request`)
|
||||||
|
|
||||||
|
|
|
@ -75,6 +75,8 @@ func testGit(t *testing.T, u *url.URL) {
|
||||||
rawTest(t, &forkedUserCtx, little, big, littleLFS, bigLFS)
|
rawTest(t, &forkedUserCtx, little, big, littleLFS, bigLFS)
|
||||||
mediaTest(t, &forkedUserCtx, little, big, littleLFS, bigLFS)
|
mediaTest(t, &forkedUserCtx, little, big, littleLFS, bigLFS)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("PushCreate", doPushCreate(httpContext, u))
|
||||||
})
|
})
|
||||||
t.Run("SSH", func(t *testing.T) {
|
t.Run("SSH", func(t *testing.T) {
|
||||||
defer PrintCurrentTest(t)()
|
defer PrintCurrentTest(t)()
|
||||||
|
@ -113,6 +115,8 @@ func testGit(t *testing.T, u *url.URL) {
|
||||||
rawTest(t, &forkedUserCtx, little, big, littleLFS, bigLFS)
|
rawTest(t, &forkedUserCtx, little, big, littleLFS, bigLFS)
|
||||||
mediaTest(t, &forkedUserCtx, little, big, littleLFS, bigLFS)
|
mediaTest(t, &forkedUserCtx, little, big, littleLFS, bigLFS)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("PushCreate", doPushCreate(sshContext, sshURL))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -408,3 +412,57 @@ func doMergeFork(ctx, baseCtx APITestContext, baseBranch, headBranch string) fun
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func doPushCreate(ctx APITestContext, u *url.URL) func(t *testing.T) {
|
||||||
|
return func(t *testing.T) {
|
||||||
|
defer PrintCurrentTest(t)()
|
||||||
|
ctx.Reponame = fmt.Sprintf("repo-tmp-push-create-%s", u.Scheme)
|
||||||
|
u.Path = ctx.GitPath()
|
||||||
|
|
||||||
|
tmpDir, err := ioutil.TempDir("", ctx.Reponame)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
err = git.InitRepository(tmpDir, false)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = os.Create(filepath.Join(tmpDir, "test.txt"))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
err = git.AddChanges(tmpDir, true)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
err = git.CommitChanges(tmpDir, git.CommitChangesOptions{
|
||||||
|
Committer: &git.Signature{
|
||||||
|
Email: "user2@example.com",
|
||||||
|
Name: "User Two",
|
||||||
|
When: time.Now(),
|
||||||
|
},
|
||||||
|
Author: &git.Signature{
|
||||||
|
Email: "user2@example.com",
|
||||||
|
Name: "User Two",
|
||||||
|
When: time.Now(),
|
||||||
|
},
|
||||||
|
Message: fmt.Sprintf("Testing push create @ %v", time.Now()),
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = git.NewCommand("remote", "add", "origin", u.String()).RunInDir(tmpDir)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Push to create disabled
|
||||||
|
setting.Repository.EnablePushCreateUser = false
|
||||||
|
_, err = git.NewCommand("push", "origin", "master").RunInDir(tmpDir)
|
||||||
|
assert.Error(t, err)
|
||||||
|
|
||||||
|
// Push to create enabled
|
||||||
|
setting.Repository.EnablePushCreateUser = true
|
||||||
|
_, err = git.NewCommand("push", "origin", "master").RunInDir(tmpDir)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Fetch repo from database
|
||||||
|
repo, err := models.GetRepositoryByOwnerAndName(ctx.Username, ctx.Reponame)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.False(t, repo.IsEmpty)
|
||||||
|
assert.True(t, repo.IsPrivate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -35,6 +35,8 @@ var (
|
||||||
AccessControlAllowOrigin string
|
AccessControlAllowOrigin string
|
||||||
UseCompatSSHURI bool
|
UseCompatSSHURI bool
|
||||||
DefaultCloseIssuesViaCommitsInAnyBranch bool
|
DefaultCloseIssuesViaCommitsInAnyBranch bool
|
||||||
|
EnablePushCreateUser bool
|
||||||
|
EnablePushCreateOrg bool
|
||||||
|
|
||||||
// Repository editor settings
|
// Repository editor settings
|
||||||
Editor struct {
|
Editor struct {
|
||||||
|
@ -89,6 +91,8 @@ var (
|
||||||
AccessControlAllowOrigin: "",
|
AccessControlAllowOrigin: "",
|
||||||
UseCompatSSHURI: false,
|
UseCompatSSHURI: false,
|
||||||
DefaultCloseIssuesViaCommitsInAnyBranch: false,
|
DefaultCloseIssuesViaCommitsInAnyBranch: false,
|
||||||
|
EnablePushCreateUser: false,
|
||||||
|
EnablePushCreateOrg: false,
|
||||||
|
|
||||||
// Repository editor settings
|
// Repository editor settings
|
||||||
Editor: struct {
|
Editor: struct {
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/private"
|
"code.gitea.io/gitea/modules/private"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
repo_service "code.gitea.io/gitea/services/repository"
|
||||||
|
|
||||||
"gitea.com/macaron/macaron"
|
"gitea.com/macaron/macaron"
|
||||||
)
|
)
|
||||||
|
@ -98,44 +99,44 @@ func ServCommand(ctx *macaron.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now get the Repository and set the results section
|
// Now get the Repository and set the results section
|
||||||
|
repoExist := true
|
||||||
repo, err := models.GetRepositoryByOwnerAndName(results.OwnerName, results.RepoName)
|
repo, err := models.GetRepositoryByOwnerAndName(results.OwnerName, results.RepoName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if models.IsErrRepoNotExist(err) {
|
if models.IsErrRepoNotExist(err) {
|
||||||
ctx.JSON(http.StatusNotFound, map[string]interface{}{
|
repoExist = false
|
||||||
|
} else {
|
||||||
|
log.Error("Unable to get repository: %s/%s Error: %v", results.OwnerName, results.RepoName, err)
|
||||||
|
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
|
||||||
"results": results,
|
"results": results,
|
||||||
"type": "ErrRepoNotExist",
|
"type": "InternalServerError",
|
||||||
"err": fmt.Sprintf("Cannot find repository %s/%s", results.OwnerName, results.RepoName),
|
"err": fmt.Sprintf("Unable to get repository: %s/%s %v", results.OwnerName, results.RepoName, err),
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Error("Unable to get repository: %s/%s Error: %v", results.OwnerName, results.RepoName, err)
|
|
||||||
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
|
|
||||||
"results": results,
|
|
||||||
"type": "InternalServerError",
|
|
||||||
"err": fmt.Sprintf("Unable to get repository: %s/%s %v", results.OwnerName, results.RepoName, err),
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
repo.OwnerName = ownerName
|
|
||||||
results.RepoID = repo.ID
|
|
||||||
|
|
||||||
if repo.IsBeingCreated() {
|
|
||||||
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
|
|
||||||
"results": results,
|
|
||||||
"type": "InternalServerError",
|
|
||||||
"err": "Repository is being created, you could retry after it finished",
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We can shortcut at this point if the repo is a mirror
|
if repoExist {
|
||||||
if mode > models.AccessModeRead && repo.IsMirror {
|
repo.OwnerName = ownerName
|
||||||
ctx.JSON(http.StatusUnauthorized, map[string]interface{}{
|
results.RepoID = repo.ID
|
||||||
"results": results,
|
|
||||||
"type": "ErrMirrorReadOnly",
|
if repo.IsBeingCreated() {
|
||||||
"err": fmt.Sprintf("Mirror Repository %s/%s is read-only", results.OwnerName, results.RepoName),
|
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
|
||||||
})
|
"results": results,
|
||||||
return
|
"type": "InternalServerError",
|
||||||
|
"err": "Repository is being created, you could retry after it finished",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can shortcut at this point if the repo is a mirror
|
||||||
|
if mode > models.AccessModeRead && repo.IsMirror {
|
||||||
|
ctx.JSON(http.StatusUnauthorized, map[string]interface{}{
|
||||||
|
"results": results,
|
||||||
|
"type": "ErrMirrorReadOnly",
|
||||||
|
"err": fmt.Sprintf("Mirror Repository %s/%s is read-only", results.OwnerName, results.RepoName),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the Public Key represented by the keyID
|
// Get the Public Key represented by the keyID
|
||||||
|
@ -161,6 +162,16 @@ func ServCommand(ctx *macaron.Context) {
|
||||||
results.KeyID = key.ID
|
results.KeyID = key.ID
|
||||||
results.UserID = key.OwnerID
|
results.UserID = key.OwnerID
|
||||||
|
|
||||||
|
// If repo doesn't exist, deploy key doesn't make sense
|
||||||
|
if !repoExist && key.Type == models.KeyTypeDeploy {
|
||||||
|
ctx.JSON(http.StatusNotFound, map[string]interface{}{
|
||||||
|
"results": results,
|
||||||
|
"type": "ErrRepoNotExist",
|
||||||
|
"err": fmt.Sprintf("Cannot find repository %s/%s", results.OwnerName, results.RepoName),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Deploy Keys have ownerID set to 0 therefore we can't use the owner
|
// Deploy Keys have ownerID set to 0 therefore we can't use the owner
|
||||||
// So now we need to check if the key is a deploy key
|
// So now we need to check if the key is a deploy key
|
||||||
// We'll keep hold of the deploy key here for permissions checking
|
// We'll keep hold of the deploy key here for permissions checking
|
||||||
|
@ -220,7 +231,7 @@ func ServCommand(ctx *macaron.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't allow pushing if the repo is archived
|
// Don't allow pushing if the repo is archived
|
||||||
if mode > models.AccessModeRead && repo.IsArchived {
|
if repoExist && mode > models.AccessModeRead && repo.IsArchived {
|
||||||
ctx.JSON(http.StatusUnauthorized, map[string]interface{}{
|
ctx.JSON(http.StatusUnauthorized, map[string]interface{}{
|
||||||
"results": results,
|
"results": results,
|
||||||
"type": "ErrRepoIsArchived",
|
"type": "ErrRepoIsArchived",
|
||||||
|
@ -230,7 +241,7 @@ func ServCommand(ctx *macaron.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Permissions checking:
|
// Permissions checking:
|
||||||
if mode > models.AccessModeRead || repo.IsPrivate || setting.Service.RequireSignInView {
|
if repoExist && (mode > models.AccessModeRead || repo.IsPrivate || setting.Service.RequireSignInView) {
|
||||||
if key.Type == models.KeyTypeDeploy {
|
if key.Type == models.KeyTypeDeploy {
|
||||||
if deployKey.Mode < mode {
|
if deployKey.Mode < mode {
|
||||||
ctx.JSON(http.StatusUnauthorized, map[string]interface{}{
|
ctx.JSON(http.StatusUnauthorized, map[string]interface{}{
|
||||||
|
@ -265,6 +276,48 @@ func ServCommand(ctx *macaron.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We already know we aren't using a deploy key
|
||||||
|
if !repoExist {
|
||||||
|
owner, err := models.GetUserByName(ownerName)
|
||||||
|
if err != nil {
|
||||||
|
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
|
||||||
|
"results": results,
|
||||||
|
"type": "InternalServerError",
|
||||||
|
"err": fmt.Sprintf("Unable to get owner: %s %v", results.OwnerName, err),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if owner.IsOrganization() && !setting.Repository.EnablePushCreateOrg {
|
||||||
|
ctx.JSON(http.StatusForbidden, map[string]interface{}{
|
||||||
|
"results": results,
|
||||||
|
"type": "ErrForbidden",
|
||||||
|
"err": "Push to create is not enabled for organizations.",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !owner.IsOrganization() && !setting.Repository.EnablePushCreateUser {
|
||||||
|
ctx.JSON(http.StatusForbidden, map[string]interface{}{
|
||||||
|
"results": results,
|
||||||
|
"type": "ErrForbidden",
|
||||||
|
"err": "Push to create is not enabled for users.",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
repo, err = repo_service.PushCreateRepo(user, owner, results.RepoName)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("pushCreateRepo: %v", err)
|
||||||
|
ctx.JSON(http.StatusNotFound, map[string]interface{}{
|
||||||
|
"results": results,
|
||||||
|
"type": "ErrRepoNotExist",
|
||||||
|
"err": fmt.Sprintf("Cannot find repository: %s/%s", results.OwnerName, results.RepoName),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
results.RepoID = repo.ID
|
||||||
|
}
|
||||||
|
|
||||||
// Finally if we're trying to touch the wiki we should init it
|
// Finally if we're trying to touch the wiki we should init it
|
||||||
if results.IsWiki {
|
if results.IsWiki {
|
||||||
if err = repo.InitWiki(); err != nil {
|
if err = repo.InitWiki(); err != nil {
|
||||||
|
|
|
@ -28,6 +28,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/process"
|
"code.gitea.io/gitea/modules/process"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
repo_service "code.gitea.io/gitea/services/repository"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HTTP implmentation git smart HTTP protocol
|
// HTTP implmentation git smart HTTP protocol
|
||||||
|
@ -100,29 +101,29 @@ func HTTP(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
repoExist := true
|
||||||
repo, err := models.GetRepositoryByName(owner.ID, reponame)
|
repo, err := models.GetRepositoryByName(owner.ID, reponame)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if models.IsErrRepoNotExist(err) {
|
if models.IsErrRepoNotExist(err) {
|
||||||
redirectRepoID, err := models.LookupRepoRedirect(owner.ID, reponame)
|
if redirectRepoID, err := models.LookupRepoRedirect(owner.ID, reponame); err == nil {
|
||||||
if err == nil {
|
|
||||||
context.RedirectToRepo(ctx, redirectRepoID)
|
context.RedirectToRepo(ctx, redirectRepoID)
|
||||||
} else {
|
return
|
||||||
ctx.NotFoundOrServerError("GetRepositoryByName", models.IsErrRepoRedirectNotExist, err)
|
|
||||||
}
|
}
|
||||||
|
repoExist = false
|
||||||
} else {
|
} else {
|
||||||
ctx.ServerError("GetRepositoryByName", err)
|
ctx.ServerError("GetRepositoryByName", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't allow pushing if the repo is archived
|
// Don't allow pushing if the repo is archived
|
||||||
if repo.IsArchived && !isPull {
|
if repoExist && repo.IsArchived && !isPull {
|
||||||
ctx.HandleText(http.StatusForbidden, "This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.")
|
ctx.HandleText(http.StatusForbidden, "This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only public pull don't need auth.
|
// Only public pull don't need auth.
|
||||||
isPublicPull := !repo.IsPrivate && isPull
|
isPublicPull := repoExist && !repo.IsPrivate && isPull
|
||||||
var (
|
var (
|
||||||
askAuth = !isPublicPull || setting.Service.RequireSignInView
|
askAuth = !isPublicPull || setting.Service.RequireSignInView
|
||||||
authUser *models.User
|
authUser *models.User
|
||||||
|
@ -243,20 +244,22 @@ func HTTP(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
perm, err := models.GetUserRepoPermission(repo, authUser)
|
if repoExist {
|
||||||
if err != nil {
|
perm, err := models.GetUserRepoPermission(repo, authUser)
|
||||||
ctx.ServerError("GetUserRepoPermission", err)
|
if err != nil {
|
||||||
return
|
ctx.ServerError("GetUserRepoPermission", err)
|
||||||
}
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if !perm.CanAccess(accessMode, unitType) {
|
if !perm.CanAccess(accessMode, unitType) {
|
||||||
ctx.HandleText(http.StatusForbidden, "User permission denied")
|
ctx.HandleText(http.StatusForbidden, "User permission denied")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isPull && repo.IsMirror {
|
if !isPull && repo.IsMirror {
|
||||||
ctx.HandleText(http.StatusForbidden, "mirror repository is read-only")
|
ctx.HandleText(http.StatusForbidden, "mirror repository is read-only")
|
||||||
return
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
environ = []string{
|
environ = []string{
|
||||||
|
@ -264,7 +267,6 @@ func HTTP(ctx *context.Context) {
|
||||||
models.EnvRepoName + "=" + reponame,
|
models.EnvRepoName + "=" + reponame,
|
||||||
models.EnvPusherName + "=" + authUser.Name,
|
models.EnvPusherName + "=" + authUser.Name,
|
||||||
models.EnvPusherID + fmt.Sprintf("=%d", authUser.ID),
|
models.EnvPusherID + fmt.Sprintf("=%d", authUser.ID),
|
||||||
models.ProtectedBranchRepoID + fmt.Sprintf("=%d", repo.ID),
|
|
||||||
models.EnvIsDeployKey + "=false",
|
models.EnvIsDeployKey + "=false",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -279,6 +281,25 @@ func HTTP(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !repoExist {
|
||||||
|
if owner.IsOrganization() && !setting.Repository.EnablePushCreateOrg {
|
||||||
|
ctx.HandleText(http.StatusForbidden, "Push to create is not enabled for organizations.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !owner.IsOrganization() && !setting.Repository.EnablePushCreateUser {
|
||||||
|
ctx.HandleText(http.StatusForbidden, "Push to create is not enabled for users.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
repo, err = repo_service.PushCreateRepo(authUser, owner, reponame)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("pushCreateRepo: %v", err)
|
||||||
|
ctx.Status(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
environ = append(environ, models.ProtectedBranchRepoID+fmt.Sprintf("=%d", repo.ID))
|
||||||
|
|
||||||
w := ctx.Resp
|
w := ctx.Resp
|
||||||
r := ctx.Req.Request
|
r := ctx.Req.Request
|
||||||
cfg := &serviceConfig{
|
cfg := &serviceConfig{
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
package repository
|
package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models"
|
"code.gitea.io/gitea/models"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/notification"
|
"code.gitea.io/gitea/modules/notification"
|
||||||
|
@ -54,3 +56,28 @@ func DeleteRepository(doer *models.User, repo *models.Repository) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PushCreateRepo creates a repository when a new repository is pushed to an appropriate namespace
|
||||||
|
func PushCreateRepo(authUser, owner *models.User, repoName string) (*models.Repository, error) {
|
||||||
|
if !authUser.IsAdmin {
|
||||||
|
if owner.IsOrganization() {
|
||||||
|
if ok, err := owner.CanCreateOrgRepo(authUser.ID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if !ok {
|
||||||
|
return nil, fmt.Errorf("cannot push-create repository for org")
|
||||||
|
}
|
||||||
|
} else if authUser.ID != owner.ID {
|
||||||
|
return nil, fmt.Errorf("cannot push-create repository for another user")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repo, err := CreateRepository(authUser, owner, models.CreateRepoOptions{
|
||||||
|
Name: repoName,
|
||||||
|
IsPrivate: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return repo, nil
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue