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
					
				| @ -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,16 +99,12 @@ 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 | ||||||
| 				"results": results, | 		} else { | ||||||
| 				"type":    "ErrRepoNotExist", |  | ||||||
| 				"err":     fmt.Sprintf("Cannot find repository %s/%s", results.OwnerName, results.RepoName), |  | ||||||
| 			}) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 			log.Error("Unable to get repository: %s/%s Error: %v", results.OwnerName, results.RepoName, err) | 			log.Error("Unable to get repository: %s/%s Error: %v", results.OwnerName, results.RepoName, err) | ||||||
| 			ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ | 			ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ | ||||||
| 				"results": results, | 				"results": results, | ||||||
| @ -116,6 +113,9 @@ func ServCommand(ctx *macaron.Context) { | |||||||
| 			}) | 			}) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if repoExist { | ||||||
| 		repo.OwnerName = ownerName | 		repo.OwnerName = ownerName | ||||||
| 		results.RepoID = repo.ID | 		results.RepoID = repo.ID | ||||||
| 
 | 
 | ||||||
| @ -137,6 +137,7 @@ func ServCommand(ctx *macaron.Context) { | |||||||
| 			}) | 			}) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	// Get the Public Key represented by the keyID
 | 	// Get the Public Key represented by the keyID
 | ||||||
| 	key, err := models.GetPublicKeyByID(keyID) | 	key, err := models.GetPublicKeyByID(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,6 +244,7 @@ func HTTP(ctx *context.Context) { | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		if repoExist { | ||||||
| 			perm, err := models.GetUserRepoPermission(repo, authUser) | 			perm, err := models.GetUserRepoPermission(repo, authUser) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				ctx.ServerError("GetUserRepoPermission", err) | 				ctx.ServerError("GetUserRepoPermission", err) | ||||||
| @ -258,13 +260,13 @@ func HTTP(ctx *context.Context) { | |||||||
| 				ctx.HandleText(http.StatusForbidden, "mirror repository is read-only") | 				ctx.HandleText(http.StatusForbidden, "mirror repository is read-only") | ||||||
| 				return | 				return | ||||||
| 			} | 			} | ||||||
|  | 		} | ||||||
| 
 | 
 | ||||||
| 		environ = []string{ | 		environ = []string{ | ||||||
| 			models.EnvRepoUsername + "=" + username, | 			models.EnvRepoUsername + "=" + username, | ||||||
| 			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 New Issue
	
	Block a user