// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
// Package private includes all internal routes. The package name internal is ideal but Golang is not allowed, so we use private as package name instead.
package private
import (
"fmt"
"net/http"
"os"
"strings"
"code.gitea.io/gitea/models"
asymkey_model "code.gitea.io/gitea/models/asymkey"
git_model "code.gitea.io/gitea/models/git"
issues_model "code.gitea.io/gitea/models/issues"
perm_model "code.gitea.io/gitea/models/perm"
access_model "code.gitea.io/gitea/models/perm/access"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
gitea_context "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/web"
pull_service "code.gitea.io/gitea/services/pull"
)
type preReceiveContext struct {
* gitea_context . PrivateContext
// loadedPusher indicates that where the following information are loaded
loadedPusher bool
user * user_model . User // it's the org user if a DeployKey is used
userPerm access_model . Permission
deployKeyAccessMode perm_model . AccessMode
canCreatePullRequest bool
checkedCanCreatePullRequest bool
canWriteCode bool
checkedCanWriteCode bool
protectedTags [ ] * git_model . ProtectedTag
gotProtectedTags bool
env [ ] string
opts * private . HookOptions
branchName string
}
// CanWriteCode returns true if pusher can write code
func ( ctx * preReceiveContext ) CanWriteCode ( ) bool {
if ! ctx . checkedCanWriteCode {
if ! ctx . loadPusherAndPermission ( ) {
return false
}
ctx . canWriteCode = issues_model . CanMaintainerWriteToBranch ( ctx . userPerm , ctx . branchName , ctx . user ) || ctx . deployKeyAccessMode >= perm_model . AccessModeWrite
ctx . checkedCanWriteCode = true
}
return ctx . canWriteCode
}
// AssertCanWriteCode returns true if pusher can write code
func ( ctx * preReceiveContext ) AssertCanWriteCode ( ) bool {
if ! ctx . CanWriteCode ( ) {
if ctx . Written ( ) {
return false
}
ctx . JSON ( http . StatusForbidden , map [ string ] interface { } {
"err" : "User permission denied for writing." ,
} )
return false
}
return true
}
// CanCreatePullRequest returns true if pusher can create pull requests
func ( ctx * preReceiveContext ) CanCreatePullRequest ( ) bool {
if ! ctx . checkedCanCreatePullRequest {
if ! ctx . loadPusherAndPermission ( ) {
return false
}
ctx . canCreatePullRequest = ctx . userPerm . CanRead ( unit . TypePullRequests )
ctx . checkedCanCreatePullRequest = true
}
return ctx . canCreatePullRequest
}
// AssertCreatePullRequest returns true if can create pull requests
func ( ctx * preReceiveContext ) AssertCreatePullRequest ( ) bool {
if ! ctx . CanCreatePullRequest ( ) {
if ctx . Written ( ) {
return false
}
ctx . JSON ( http . StatusForbidden , map [ string ] interface { } {
"err" : "User permission denied for creating pull-request." ,
} )
return false
}
return true
}
// HookPreReceive checks whether a individual commit is acceptable
func HookPreReceive ( ctx * gitea_context . PrivateContext ) {
opts := web . GetForm ( ctx ) . ( * private . HookOptions )
ourCtx := & preReceiveContext {
PrivateContext : ctx ,
env : generateGitEnv ( opts ) , // Generate git environment for checking commits
opts : opts ,
}
// Iterate across the provided old commit IDs
for i := range opts . OldCommitIDs {
oldCommitID := opts . OldCommitIDs [ i ]
newCommitID := opts . NewCommitIDs [ i ]
refFullName := opts . RefFullNames [ i ]
switch {
case strings . HasPrefix ( refFullName , git . BranchPrefix ) :
preReceiveBranch ( ourCtx , oldCommitID , newCommitID , refFullName )
case strings . HasPrefix ( refFullName , git . TagPrefix ) :
preReceiveTag ( ourCtx , oldCommitID , newCommitID , refFullName )
case git . SupportProcReceive && strings . HasPrefix ( refFullName , git . PullRequestPrefix ) :
preReceivePullRequest ( ourCtx , oldCommitID , newCommitID , refFullName )
default :
ourCtx . AssertCanWriteCode ( )
}
if ctx . Written ( ) {
return
}
}
ctx . PlainText ( http . StatusOK , "ok" )
}
func preReceiveBranch ( ctx * preReceiveContext , oldCommitID , newCommitID , refFullName string ) {
branchName := strings . TrimPrefix ( refFullName , git . BranchPrefix )
ctx . branchName = branchName
if ! ctx . AssertCanWriteCode ( ) {
return
}
repo := ctx . Repo . Repository
gitRepo := ctx . Repo . GitRepo
if branchName == repo . DefaultBranch && newCommitID == git . EmptySHA {
log . Warn ( "Forbidden: Branch: %s is the default branch in %-v and cannot be deleted" , branchName , repo )
ctx . JSON ( http . StatusForbidden , private . Response {
Err : fmt . Sprintf ( "branch %s is the default branch and cannot be deleted" , branchName ) ,
} )
return
}
protectBranch , err := git_model . GetFirstMatchProtectedBranchRule ( ctx , repo . ID , branchName )
if err != nil {
log . Error ( "Unable to get protected branch: %s in %-v Error: %v" , branchName , repo , err )
ctx . JSON ( http . StatusInternalServerError , private . Response {
Err : err . Error ( ) ,
} )
return
}
// Allow pushes to non-protected branches
if protectBranch == nil {
return
}
protectBranch . Repo = repo
// This ref is a protected branch.
//
// First of all we need to enforce absolutely:
//
// 1. Detect and prevent deletion of the branch
if newCommitID == git . EmptySHA {
log . Warn ( "Forbidden: Branch: %s in %-v is protected from deletion" , branchName , repo )
ctx . JSON ( http . StatusForbidden , private . Response {
Err : fmt . Sprintf ( "branch %s is protected from deletion" , branchName ) ,
} )
return
}
// 2. Disallow force pushes to protected branches
if git . EmptySHA != oldCommitID {
output , _ , err := git . NewCommand ( ctx , "rev-list" , "--max-count=1" ) . AddDynamicArguments ( oldCommitID , "^" + newCommitID ) . RunStdString ( & git . RunOpts { Dir : repo . RepoPath ( ) , Env : ctx . env } )
if err != nil {
log . Error ( "Unable to detect force push between: %s and %s in %-v Error: %v" , oldCommitID , newCommitID , repo , err )
ctx . JSON ( http . StatusInternalServerError , private . Response {
Err : fmt . Sprintf ( "Fail to detect force push: %v" , err ) ,
} )
return
} else if len ( output ) > 0 {
log . Warn ( "Forbidden: Branch: %s in %-v is protected from force push" , branchName , repo )
ctx . JSON ( http . StatusForbidden , private . Response {
Err : fmt . Sprintf ( "branch %s is protected from force push" , branchName ) ,
} )
return
}
}
// 3. Enforce require signed commits
if protectBranch . RequireSignedCommits {
err := verifyCommits ( oldCommitID , newCommitID , gitRepo , ctx . env )
if err != nil {
if ! isErrUnverifiedCommit ( err ) {
log . Error ( "Unable to check commits from %s to %s in %-v: %v" , oldCommitID , newCommitID , repo , err )
ctx . JSON ( http . StatusInternalServerError , private . Response {
Err : fmt . Sprintf ( "Unable to check commits from %s to %s: %v" , oldCommitID , newCommitID , err ) ,
} )
return
}
unverifiedCommit := err . ( * errUnverifiedCommit ) . sha
log . Warn ( "Forbidden: Branch: %s in %-v is protected from unverified commit %s" , branchName , repo , unverifiedCommit )
ctx . JSON ( http . StatusForbidden , private . Response {
Err : fmt . Sprintf ( "branch %s is protected from unverified commit %s" , branchName , unverifiedCommit ) ,
} )
return
}
}
// Now there are several tests which can be overridden:
//
// 4. Check protected file patterns - this is overridable from the UI
changedProtectedfiles := false
protectedFilePath := ""
globs := protectBranch . GetProtectedFilePatterns ( )
if len ( globs ) > 0 {
_ , err := pull_service . CheckFileProtection ( gitRepo , oldCommitID , newCommitID , globs , 1 , ctx . env )
if err != nil {
if ! models . IsErrFilePathProtected ( err ) {
log . Error ( "Unable to check file protection for commits from %s to %s in %-v: %v" , oldCommitID , newCommitID , repo , err )
ctx . JSON ( http . StatusInternalServerError , private . Response {
Err : fmt . Sprintf ( "Unable to check file protection for commits from %s to %s: %v" , oldCommitID , newCommitID , err ) ,
} )
return
}
changedProtectedfiles = true
protectedFilePath = err . ( models . ErrFilePathProtected ) . Path
}
}
// 5. Check if the doer is allowed to push
var canPush bool
if ctx . opts . DeployKeyID != 0 {
canPush = ! changedProtectedfiles && protectBranch . CanPush && ( ! protectBranch . EnableWhitelist || protectBranch . WhitelistDeployKeys )
} else {
user , err := user_model . GetUserByID ( ctx , ctx . opts . UserID )
if err != nil {
log . Error ( "Unable to GetUserByID for commits from %s to %s in %-v: %v" , oldCommitID , newCommitID , repo , err )
ctx . JSON ( http . StatusInternalServerError , private . Response {
Err : fmt . Sprintf ( "Unable to GetUserByID for commits from %s to %s: %v" , oldCommitID , newCommitID , err ) ,
} )
return
}
canPush = ! changedProtectedfiles && protectBranch . CanUserPush ( ctx , user )
}
// 6. If we're not allowed to push directly
if ! canPush {
// Is this is a merge from the UI/API?
if ctx . opts . PullRequestID == 0 {
// 6a. If we're not merging from the UI/API then there are two ways we got here:
//
// We are changing a protected file and we're not allowed to do that
if changedProtectedfiles {
log . Warn ( "Forbidden: Branch: %s in %-v is protected from changing file %s" , branchName , repo , protectedFilePath )
ctx . JSON ( http . StatusForbidden , private . Response {
Err : fmt . Sprintf ( "branch %s is protected from changing file %s" , branchName , protectedFilePath ) ,
} )
return
}
// Allow commits that only touch unprotected files
globs := protectBranch . GetUnprotectedFilePatterns ( )
if len ( globs ) > 0 {
unprotectedFilesOnly , err := pull_service . CheckUnprotectedFiles ( gitRepo , oldCommitID , newCommitID , globs , ctx . env )
if err != nil {
log . Error ( "Unable to check file protection for commits from %s to %s in %-v: %v" , oldCommitID , newCommitID , repo , err )
ctx . JSON ( http . StatusInternalServerError , private . Response {
Err : fmt . Sprintf ( "Unable to check file protection for commits from %s to %s: %v" , oldCommitID , newCommitID , err ) ,
} )
return
}
if unprotectedFilesOnly {
// Commit only touches unprotected files, this is allowed
return
}
}
// Or we're simply not able to push to this protected branch
log . Warn ( "Forbidden: User %d is not allowed to push to protected branch: %s in %-v" , ctx . opts . UserID , branchName , repo )
ctx . JSON ( http . StatusForbidden , private . Response {
Err : fmt . Sprintf ( "Not allowed to push to protected branch %s" , branchName ) ,
} )
return
}
// 6b. Merge (from UI or API)
// Get the PR, user and permissions for the user in the repository
pr , err := issues_model . GetPullRequestByID ( ctx , ctx . opts . PullRequestID )
if err != nil {
log . Error ( "Unable to get PullRequest %d Error: %v" , ctx . opts . PullRequestID , err )
ctx . JSON ( http . StatusInternalServerError , private . Response {
Err : fmt . Sprintf ( "Unable to get PullRequest %d Error: %v" , ctx . opts . PullRequestID , err ) ,
} )
return
}
// although we should have called `loadPusherAndPermission` before, here we call it explicitly again because we need to access ctx.user below
if ! ctx . loadPusherAndPermission ( ) {
// if error occurs, loadPusherAndPermission had written the error response
return
}
// Now check if the user is allowed to merge PRs for this repository
// Note: we can use ctx.perm and ctx.user directly as they will have been loaded above
allowedMerge , err := pull_service . IsUserAllowedToMerge ( ctx , pr , ctx . userPerm , ctx . user )
if err != nil {
log . Error ( "Error calculating if allowed to merge: %v" , err )
ctx . JSON ( http . StatusInternalServerError , private . Response {
Err : fmt . Sprintf ( "Error calculating if allowed to merge: %v" , err ) ,
} )
return
}
if ! allowedMerge {
log . Warn ( "Forbidden: User %d is not allowed to push to protected branch: %s in %-v and is not allowed to merge pr #%d" , ctx . opts . UserID , branchName , repo , pr . Index )
ctx . JSON ( http . StatusForbidden , private . Response {
Err : fmt . Sprintf ( "Not allowed to push to protected branch %s" , branchName ) ,
} )
return
}
// If we're an admin for the repository we can ignore status checks, reviews and override protected files
if ctx . userPerm . IsAdmin ( ) {
return
}
// Now if we're not an admin - we can't overwrite protected files so fail now
if changedProtectedfiles {
log . Warn ( "Forbidden: Branch: %s in %-v is protected from changing file %s" , branchName , repo , protectedFilePath )
ctx . JSON ( http . StatusForbidden , private . Response {
Err : fmt . Sprintf ( "branch %s is protected from changing file %s" , branchName , protectedFilePath ) ,
} )
return
}
// Check all status checks and reviews are ok
if err := pull_service . CheckPullBranchProtections ( ctx , pr , true ) ; err != nil {
if models . IsErrDisallowedToMerge ( err ) {
log . Warn ( "Forbidden: User %d is not allowed push to protected branch %s in %-v and pr #%d is not ready to be merged: %s" , ctx . opts . UserID , branchName , repo , pr . Index , err . Error ( ) )
ctx . JSON ( http . StatusForbidden , private . Response {
Err : fmt . Sprintf ( "Not allowed to push to protected branch %s and pr #%d is not ready to be merged: %s" , branchName , ctx . opts . PullRequestID , err . Error ( ) ) ,
} )
return
}
log . Error ( "Unable to check if mergable: protected branch %s in %-v and pr #%d. Error: %v" , ctx . opts . UserID , branchName , repo , pr . Index , err )
ctx . JSON ( http . StatusInternalServerError , private . Response {
Err : fmt . Sprintf ( "Unable to get status of pull request %d. Error: %v" , ctx . opts . PullRequestID , err ) ,
} )
return
}
}
}
func preReceiveTag ( ctx * preReceiveContext , oldCommitID , newCommitID , refFullName string ) {
if ! ctx . AssertCanWriteCode ( ) {
return
}
tagName := strings . TrimPrefix ( refFullName , git . TagPrefix )
if ! ctx . gotProtectedTags {
var err error
ctx . protectedTags , err = git_model . GetProtectedTags ( ctx , ctx . Repo . Repository . ID )
if err != nil {
log . Error ( "Unable to get protected tags for %-v Error: %v" , ctx . Repo . Repository , err )
ctx . JSON ( http . StatusInternalServerError , private . Response {
Err : err . Error ( ) ,
} )
return
}
ctx . gotProtectedTags = true
}
isAllowed , err := git_model . IsUserAllowedToControlTag ( ctx , ctx . protectedTags , tagName , ctx . opts . UserID )
if err != nil {
ctx . JSON ( http . StatusInternalServerError , private . Response {
Err : err . Error ( ) ,
} )
return
}
if ! isAllowed {
log . Warn ( "Forbidden: Tag %s in %-v is protected" , tagName , ctx . Repo . Repository )
ctx . JSON ( http . StatusForbidden , private . Response {
Err : fmt . Sprintf ( "Tag %s is protected" , tagName ) ,
} )
return
}
}
func preReceivePullRequest ( ctx * preReceiveContext , oldCommitID , newCommitID , refFullName string ) {
if ! ctx . AssertCreatePullRequest ( ) {
return
}
if ctx . Repo . Repository . IsEmpty {
ctx . JSON ( http . StatusForbidden , map [ string ] interface { } {
"err" : "Can't create pull request for an empty repository." ,
} )
return
}
if ctx . opts . IsWiki {
ctx . JSON ( http . StatusForbidden , map [ string ] interface { } {
"err" : "Pull requests are not supported on the wiki." ,
} )
return
}
baseBranchName := refFullName [ len ( git . PullRequestPrefix ) : ]
baseBranchExist := false
if ctx . Repo . GitRepo . IsBranchExist ( baseBranchName ) {
baseBranchExist = true
}
if ! baseBranchExist {
for p , v := range baseBranchName {
if v == '/' && ctx . Repo . GitRepo . IsBranchExist ( baseBranchName [ : p ] ) && p != len ( baseBranchName ) - 1 {
baseBranchExist = true
break
}
}
}
if ! baseBranchExist {
ctx . JSON ( http . StatusForbidden , private . Response {
Err : fmt . Sprintf ( "Unexpected ref: %s" , refFullName ) ,
} )
return
}
}
func generateGitEnv ( opts * private . HookOptions ) ( env [ ] string ) {
env = os . Environ ( )
if opts . GitAlternativeObjectDirectories != "" {
env = append ( env ,
private . GitAlternativeObjectDirectories + "=" + opts . GitAlternativeObjectDirectories )
}
if opts . GitObjectDirectory != "" {
env = append ( env ,
private . GitObjectDirectory + "=" + opts . GitObjectDirectory )
}
if opts . GitQuarantinePath != "" {
env = append ( env ,
private . GitQuarantinePath + "=" + opts . GitQuarantinePath )
}
return env
}
// loadPusherAndPermission returns false if an error occurs, and it writes the error response
func ( ctx * preReceiveContext ) loadPusherAndPermission ( ) bool {
if ctx . loadedPusher {
return true
}
Implement actions (#21937)
Close #13539.
Co-authored by: @lunny @appleboy @fuxiaohei and others.
Related projects:
- https://gitea.com/gitea/actions-proto-def
- https://gitea.com/gitea/actions-proto-go
- https://gitea.com/gitea/act
- https://gitea.com/gitea/act_runner
### Summary
The target of this PR is to bring a basic implementation of "Actions",
an internal CI/CD system of Gitea. That means even though it has been
merged, the state of the feature is **EXPERIMENTAL**, and please note
that:
- It is disabled by default;
- It shouldn't be used in a production environment currently;
- It shouldn't be used in a public Gitea instance currently;
- Breaking changes may be made before it's stable.
**Please comment on #13539 if you have any different product design
ideas**, all decisions reached there will be adopted here. But in this
PR, we don't talk about **naming, feature-creep or alternatives**.
### ⚠️ Breaking
`gitea-actions` will become a reserved user name. If a user with the
name already exists in the database, it is recommended to rename it.
### Some important reviews
- What is `DEFAULT_ACTIONS_URL` in `app.ini` for?
- https://github.com/go-gitea/gitea/pull/21937#discussion_r1055954954
- Why the api for runners is not under the normal `/api/v1` prefix?
- https://github.com/go-gitea/gitea/pull/21937#discussion_r1061173592
- Why DBFS?
- https://github.com/go-gitea/gitea/pull/21937#discussion_r1061301178
- Why ignore events triggered by `gitea-actions` bot?
- https://github.com/go-gitea/gitea/pull/21937#discussion_r1063254103
- Why there's no permission control for actions?
- https://github.com/go-gitea/gitea/pull/21937#discussion_r1090229868
### What it looks like
<details>
#### Manage runners
<img width="1792" alt="image"
src="https://user-images.githubusercontent.com/9418365/205870657-c72f590e-2e08-4cd4-be7f-2e0abb299bbf.png">
#### List runs
<img width="1792" alt="image"
src="https://user-images.githubusercontent.com/9418365/205872794-50fde990-2b45-48c1-a178-908e4ec5b627.png">
#### View logs
<img width="1792" alt="image"
src="https://user-images.githubusercontent.com/9418365/205872501-9b7b9000-9542-4991-8f55-18ccdada77c3.png">
</details>
### How to try it
<details>
#### 1. Start Gitea
Clone this branch and [install from
source](https://docs.gitea.io/en-us/install-from-source).
Add additional configurations in `app.ini` to enable Actions:
```ini
[actions]
ENABLED = true
```
Start it.
If all is well, you'll see the management page of runners:
<img width="1792" alt="image"
src="https://user-images.githubusercontent.com/9418365/205877365-8e30a780-9b10-4154-b3e8-ee6c3cb35a59.png">
#### 2. Start runner
Clone the [act_runner](https://gitea.com/gitea/act_runner), and follow
the
[README](https://gitea.com/gitea/act_runner/src/branch/main/README.md)
to start it.
If all is well, you'll see a new runner has been added:
<img width="1792" alt="image"
src="https://user-images.githubusercontent.com/9418365/205878000-216f5937-e696-470d-b66c-8473987d91c3.png">
#### 3. Enable actions for a repo
Create a new repo or open an existing one, check the `Actions` checkbox
in settings and submit.
<img width="1792" alt="image"
src="https://user-images.githubusercontent.com/9418365/205879705-53e09208-73c0-4b3e-a123-2dcf9aba4b9c.png">
<img width="1792" alt="image"
src="https://user-images.githubusercontent.com/9418365/205879383-23f3d08f-1a85-41dd-a8b3-54e2ee6453e8.png">
If all is well, you'll see a new tab "Actions":
<img width="1792" alt="image"
src="https://user-images.githubusercontent.com/9418365/205881648-a8072d8c-5803-4d76-b8a8-9b2fb49516c1.png">
#### 4. Upload workflow files
Upload some workflow files to `.gitea/workflows/xxx.yaml`, you can
follow the [quickstart](https://docs.github.com/en/actions/quickstart)
of GitHub Actions. Yes, Gitea Actions is compatible with GitHub Actions
in most cases, you can use the same demo:
```yaml
name: GitHub Actions Demo
run-name: ${{ github.actor }} is testing out GitHub Actions 🚀
on: [push]
jobs:
Explore-GitHub-Actions:
runs-on: ubuntu-latest
steps:
- run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event."
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!"
- run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}."
- name: Check out repository code
uses: actions/checkout@v3
- run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
- name: List files in the repository
run: |
ls ${{ github.workspace }}
- run: echo "🍏 This job's status is ${{ job.status }}."
```
If all is well, you'll see a new run in `Actions` tab:
<img width="1792" alt="image"
src="https://user-images.githubusercontent.com/9418365/205884473-79a874bc-171b-4aaf-acd5-0241a45c3b53.png">
#### 5. Check the logs of jobs
Click a run and you'll see the logs:
<img width="1792" alt="image"
src="https://user-images.githubusercontent.com/9418365/205884800-994b0374-67f7-48ff-be9a-4c53f3141547.png">
#### 6. Go on
You can try more examples in [the
documents](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions)
of GitHub Actions, then you might find a lot of bugs.
Come on, PRs are welcome.
</details>
See also: [Feature Preview: Gitea
Actions](https://blog.gitea.io/2022/12/feature-preview-gitea-actions/)
---------
Co-authored-by: a1012112796 <1012112796@qq.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: delvh <dev.lh@web.de>
Co-authored-by: ChristopherHX <christopher.homberger@web.de>
Co-authored-by: John Olheiser <john.olheiser@gmail.com>
2 years ago
if ctx . opts . UserID == user_model . ActionsUserID {
ctx . user = user_model . NewActionsUser ( )
ctx . userPerm . AccessMode = perm_model . AccessMode ( ctx . opts . ActionPerm )
if err := ctx . Repo . Repository . LoadUnits ( ctx ) ; err != nil {
log . Error ( "Unable to get User id %d Error: %v" , ctx . opts . UserID , err )
ctx . JSON ( http . StatusInternalServerError , private . Response {
Err : fmt . Sprintf ( "Unable to get User id %d Error: %v" , ctx . opts . UserID , err ) ,
} )
return false
}
ctx . userPerm . Units = ctx . Repo . Repository . Units
ctx . userPerm . UnitsMode = make ( map [ unit . Type ] perm_model . AccessMode )
for _ , u := range ctx . Repo . Repository . Units {
ctx . userPerm . UnitsMode [ u . Type ] = ctx . userPerm . AccessMode
}
} else {
user , err := user_model . GetUserByID ( ctx , ctx . opts . UserID )
if err != nil {
log . Error ( "Unable to get User id %d Error: %v" , ctx . opts . UserID , err )
ctx . JSON ( http . StatusInternalServerError , private . Response {
Err : fmt . Sprintf ( "Unable to get User id %d Error: %v" , ctx . opts . UserID , err ) ,
} )
return false
}
ctx . user = user
userPerm , err := access_model . GetUserRepoPermission ( ctx , ctx . Repo . Repository , user )
if err != nil {
log . Error ( "Unable to get Repo permission of repo %s/%s of User %s: %v" , ctx . Repo . Repository . OwnerName , ctx . Repo . Repository . Name , user . Name , err )
ctx . JSON ( http . StatusInternalServerError , private . Response {
Err : fmt . Sprintf ( "Unable to get Repo permission of repo %s/%s of User %s: %v" , ctx . Repo . Repository . OwnerName , ctx . Repo . Repository . Name , user . Name , err ) ,
} )
return false
}
ctx . userPerm = userPerm
}
if ctx . opts . DeployKeyID != 0 {
deployKey , err := asymkey_model . GetDeployKeyByID ( ctx , ctx . opts . DeployKeyID )
if err != nil {
log . Error ( "Unable to get DeployKey id %d Error: %v" , ctx . opts . DeployKeyID , err )
ctx . JSON ( http . StatusInternalServerError , private . Response {
Err : fmt . Sprintf ( "Unable to get DeployKey id %d Error: %v" , ctx . opts . DeployKeyID , err ) ,
} )
return false
}
ctx . deployKeyAccessMode = deployKey . Mode
}
ctx . loadedPusher = true
return true
}