2019-04-17 19:06:35 +03:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2019-04-17 19:06:35 +03:00
2022-09-02 22:18:23 +03:00
package integration
2019-04-17 19:06:35 +03:00
import (
2022-03-29 22:13:41 +03:00
stdCtx "context"
2019-04-17 19:06:35 +03:00
"encoding/base64"
"fmt"
"net/http"
2019-05-11 18:29:17 +03:00
"net/url"
2019-04-17 19:06:35 +03:00
"path/filepath"
"testing"
2019-12-24 05:33:52 +03:00
"time"
2019-04-17 19:06:35 +03:00
2023-01-18 00:46:03 +03:00
auth_model "code.gitea.io/gitea/models/auth"
2021-12-10 04:27:50 +03:00
repo_model "code.gitea.io/gitea/models/repo"
2021-11-16 11:53:21 +03:00
"code.gitea.io/gitea/models/unittest"
2021-11-24 12:49:20 +03:00
user_model "code.gitea.io/gitea/models/user"
2024-05-16 11:48:48 +03:00
"code.gitea.io/gitea/modules/git"
Simplify how git repositories are opened (#28937)
## Purpose
This is a refactor toward building an abstraction over managing git
repositories.
Afterwards, it does not matter anymore if they are stored on the local
disk or somewhere remote.
## What this PR changes
We used `git.OpenRepository` everywhere previously.
Now, we should split them into two distinct functions:
Firstly, there are temporary repositories which do not change:
```go
git.OpenRepository(ctx, diskPath)
```
Gitea managed repositories having a record in the database in the
`repository` table are moved into the new package `gitrepo`:
```go
gitrepo.OpenRepository(ctx, repo_model.Repo)
```
Why is `repo_model.Repository` the second parameter instead of file
path?
Because then we can easily adapt our repository storage strategy.
The repositories can be stored locally, however, they could just as well
be stored on a remote server.
## Further changes in other PRs
- A Git Command wrapper on package `gitrepo` could be created. i.e.
`NewCommand(ctx, repo_model.Repository, commands...)`. `git.RunOpts{Dir:
repo.RepoPath()}`, the directory should be empty before invoking this
method and it can be filled in the function only. #28940
- Remove the `RepoPath()`/`WikiPath()` functions to reduce the
possibility of mistakes.
---------
Co-authored-by: delvh <dev.lh@web.de>
2024-01-27 23:09:51 +03:00
"code.gitea.io/gitea/modules/gitrepo"
2019-04-17 19:06:35 +03:00
"code.gitea.io/gitea/modules/setting"
2019-05-11 13:21:34 +03:00
api "code.gitea.io/gitea/modules/structs"
2024-02-27 10:12:22 +03:00
"code.gitea.io/gitea/services/context"
2019-04-17 19:06:35 +03:00
"github.com/stretchr/testify/assert"
)
func getCreateFileOptions ( ) api . CreateFileOptions {
content := "This is new text"
contentEncoded := base64 . StdEncoding . EncodeToString ( [ ] byte ( content ) )
return api . CreateFileOptions {
FileOptions : api . FileOptions {
BranchName : "master" ,
NewBranchName : "master" ,
2019-06-29 18:19:24 +03:00
Message : "Making this new file new/file.txt" ,
2019-04-17 19:06:35 +03:00
Author : api . Identity {
2019-12-09 16:11:24 +03:00
Name : "Anne Doe" ,
Email : "annedoe@example.com" ,
2019-04-17 19:06:35 +03:00
} ,
Committer : api . Identity {
2019-12-09 16:11:24 +03:00
Name : "John Doe" ,
Email : "johndoe@example.com" ,
2019-04-17 19:06:35 +03:00
} ,
2019-12-24 05:33:52 +03:00
Dates : api . CommitDateOptions {
Author : time . Unix ( 946684810 , 0 ) ,
Committer : time . Unix ( 978307190 , 0 ) ,
} ,
2019-04-17 19:06:35 +03:00
} ,
2023-07-18 21:14:47 +03:00
ContentBase64 : contentEncoded ,
2019-04-17 19:06:35 +03:00
}
}
2022-07-30 11:09:04 +03:00
func getExpectedFileResponseForCreate ( repoFullName , commitID , treePath , latestCommitSHA string ) * api . FileResponse {
2019-04-17 19:06:35 +03:00
sha := "a635aa942442ddfdba07468cf9661c08fbdf0ebf"
2019-06-29 23:51:10 +03:00
encoding := "base64"
content := "VGhpcyBpcyBuZXcgdGV4dA=="
2022-03-28 22:48:41 +03:00
selfURL := setting . AppURL + "api/v1/repos/" + repoFullName + "/contents/" + treePath + "?ref=master"
htmlURL := setting . AppURL + repoFullName + "/src/branch/master/" + treePath
gitURL := setting . AppURL + "api/v1/repos/" + repoFullName + "/git/blobs/" + sha
downloadURL := setting . AppURL + repoFullName + "/raw/branch/master/" + treePath
2019-04-17 19:06:35 +03:00
return & api . FileResponse {
2019-06-29 23:51:10 +03:00
Content : & api . ContentsResponse {
2022-07-30 11:09:04 +03:00
Name : filepath . Base ( treePath ) ,
Path : treePath ,
SHA : sha ,
LastCommitSHA : latestCommitSHA ,
Size : 16 ,
Type : "file" ,
Encoding : & encoding ,
Content : & content ,
URL : & selfURL ,
HTMLURL : & htmlURL ,
GitURL : & gitURL ,
DownloadURL : & downloadURL ,
2019-04-17 19:06:35 +03:00
Links : & api . FileLinksResponse {
2019-06-29 23:51:10 +03:00
Self : & selfURL ,
GitURL : & gitURL ,
HTMLURL : & htmlURL ,
2019-04-17 19:06:35 +03:00
} ,
} ,
Commit : & api . FileCommitResponse {
CommitMeta : api . CommitMeta {
2022-03-28 22:48:41 +03:00
URL : setting . AppURL + "api/v1/repos/" + repoFullName + "/git/commits/" + commitID ,
2019-04-17 19:06:35 +03:00
SHA : commitID ,
} ,
2022-03-28 22:48:41 +03:00
HTMLURL : setting . AppURL + repoFullName + "/commit/" + commitID ,
2019-04-17 19:06:35 +03:00
Author : & api . CommitUser {
Identity : api . Identity {
2019-12-09 16:11:24 +03:00
Name : "Anne Doe" ,
Email : "annedoe@example.com" ,
2019-04-17 19:06:35 +03:00
} ,
2019-12-24 05:33:52 +03:00
Date : "2000-01-01T00:00:10Z" ,
2019-04-17 19:06:35 +03:00
} ,
Committer : & api . CommitUser {
Identity : api . Identity {
Name : "John Doe" ,
Email : "johndoe@example.com" ,
} ,
2019-12-24 05:33:52 +03:00
Date : "2000-12-31T23:59:50Z" ,
2019-04-17 19:06:35 +03:00
} ,
Message : "Updates README.md\n" ,
} ,
Verification : & api . PayloadCommitVerification {
Verified : false ,
2019-10-16 16:42:42 +03:00
Reason : "gpg.error.not_signed_commit" ,
2019-04-17 19:06:35 +03:00
Signature : "" ,
Payload : "" ,
} ,
}
}
2021-04-16 21:30:16 +03:00
func BenchmarkAPICreateFileSmall ( b * testing . B ) {
2023-08-12 19:30:16 +03:00
onGiteaRun ( b , func ( b * testing . B , u * url . URL ) {
user2 := unittest . AssertExistsAndLoadBean ( b , & user_model . User { ID : 2 } ) // owner of the repo1 & repo16
repo1 := unittest . AssertExistsAndLoadBean ( b , & repo_model . Repository { ID : 1 } ) // public repo
2021-04-16 21:30:16 +03:00
2023-08-12 19:30:16 +03:00
b . ResetTimer ( )
2021-04-16 21:30:16 +03:00
for n := 0 ; n < b . N ; n ++ {
treePath := fmt . Sprintf ( "update/file%d.txt" , n )
2023-08-12 19:30:16 +03:00
_ , _ = createFileInBranch ( user2 , repo1 , treePath , repo1 . DefaultBranch , treePath )
2021-04-16 21:30:16 +03:00
}
} )
}
func BenchmarkAPICreateFileMedium ( b * testing . B ) {
data := make ( [ ] byte , 10 * 1024 * 1024 )
2023-08-12 19:30:16 +03:00
onGiteaRun ( b , func ( b * testing . B , u * url . URL ) {
user2 := unittest . AssertExistsAndLoadBean ( b , & user_model . User { ID : 2 } ) // owner of the repo1 & repo16
repo1 := unittest . AssertExistsAndLoadBean ( b , & repo_model . Repository { ID : 1 } ) // public repo
2021-04-16 21:30:16 +03:00
b . ResetTimer ( )
for n := 0 ; n < b . N ; n ++ {
treePath := fmt . Sprintf ( "update/file%d.txt" , n )
copy ( data , treePath )
2023-08-12 19:30:16 +03:00
_ , _ = createFileInBranch ( user2 , repo1 , treePath , repo1 . DefaultBranch , treePath )
2021-04-16 21:30:16 +03:00
}
} )
}
2019-04-17 19:06:35 +03:00
func TestAPICreateFile ( t * testing . T ) {
2019-05-11 18:29:17 +03:00
onGiteaRun ( t , func ( t * testing . T , u * url . URL ) {
2022-08-16 05:22:25 +03:00
user2 := unittest . AssertExistsAndLoadBean ( t , & user_model . User { ID : 2 } ) // owner of the repo1 & repo16
2023-09-14 05:59:53 +03:00
org3 := unittest . AssertExistsAndLoadBean ( t , & user_model . User { ID : 3 } ) // owner of the repo3, is an org
2022-08-16 05:22:25 +03:00
user4 := unittest . AssertExistsAndLoadBean ( t , & user_model . User { ID : 4 } ) // owner of neither repos
repo1 := unittest . AssertExistsAndLoadBean ( t , & repo_model . Repository { ID : 1 } ) // public repo
repo3 := unittest . AssertExistsAndLoadBean ( t , & repo_model . Repository { ID : 3 } ) // public repo
repo16 := unittest . AssertExistsAndLoadBean ( t , & repo_model . Repository { ID : 16 } ) // private repo
2019-05-11 18:29:17 +03:00
fileID := 0
// Get user2's token
session := loginUser ( t , user2 . Name )
Redesign Scoped Access Tokens (#24767)
## Changes
- Adds the following high level access scopes, each with `read` and
`write` levels:
- `activitypub`
- `admin` (hidden if user is not a site admin)
- `misc`
- `notification`
- `organization`
- `package`
- `issue`
- `repository`
- `user`
- Adds new middleware function `tokenRequiresScopes()` in addition to
`reqToken()`
- `tokenRequiresScopes()` is used for each high-level api section
- _if_ a scoped token is present, checks that the required scope is
included based on the section and HTTP method
- `reqToken()` is used for individual routes
- checks that required authentication is present (but does not check
scope levels as this will already have been handled by
`tokenRequiresScopes()`
- Adds migration to convert old scoped access tokens to the new set of
scopes
- Updates the user interface for scope selection
### User interface example
<img width="903" alt="Screen Shot 2023-05-31 at 1 56 55 PM"
src="https://github.com/go-gitea/gitea/assets/23248839/654766ec-2143-4f59-9037-3b51600e32f3">
<img width="917" alt="Screen Shot 2023-05-31 at 1 56 43 PM"
src="https://github.com/go-gitea/gitea/assets/23248839/1ad64081-012c-4a73-b393-66b30352654c">
## tokenRequiresScopes Design Decision
- `tokenRequiresScopes()` was added to more reliably cover api routes.
For an incoming request, this function uses the given scope category
(say `AccessTokenScopeCategoryOrganization`) and the HTTP method (say
`DELETE`) and verifies that any scoped tokens in use include
`delete:organization`.
- `reqToken()` is used to enforce auth for individual routes that
require it. If a scoped token is not present for a request,
`tokenRequiresScopes()` will not return an error
## TODO
- [x] Alphabetize scope categories
- [x] Change 'public repos only' to a radio button (private vs public).
Also expand this to organizations
- [X] Disable token creation if no scopes selected. Alternatively, show
warning
- [x] `reqToken()` is missing from many `POST/DELETE` routes in the api.
`tokenRequiresScopes()` only checks that a given token has the correct
scope, `reqToken()` must be used to check that a token (or some other
auth) is present.
- _This should be addressed in this PR_
- [x] The migration should be reviewed very carefully in order to
minimize access changes to existing user tokens.
- _This should be addressed in this PR_
- [x] Link to api to swagger documentation, clarify what
read/write/delete levels correspond to
- [x] Review cases where more than one scope is needed as this directly
deviates from the api definition.
- _This should be addressed in this PR_
- For example:
```go
m.Group("/users/{username}/orgs", func() {
m.Get("", reqToken(), org.ListUserOrgs)
m.Get("/{org}/permissions", reqToken(), org.GetUserOrgsPermissions)
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser,
auth_model.AccessTokenScopeCategoryOrganization),
context_service.UserAssignmentAPI())
```
## Future improvements
- [ ] Add required scopes to swagger documentation
- [ ] Redesign `reqToken()` to be opt-out rather than opt-in
- [ ] Subdivide scopes like `repository`
- [ ] Once a token is created, if it has no scopes, we should display
text instead of an empty bullet point
- [ ] If the 'public repos only' option is selected, should read
categories be selected by default
Closes #24501
Closes #24799
Co-authored-by: Jonathan Tran <jon@allspice.io>
Co-authored-by: Kyle D <kdumontnu@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
2023-06-04 21:57:16 +03:00
token2 := getTokenForLoggedInUser ( t , session , auth_model . AccessTokenScopeWriteRepository , auth_model . AccessTokenScopeWriteUser )
2019-05-11 18:29:17 +03:00
// Get user4's token
session = loginUser ( t , user4 . Name )
Redesign Scoped Access Tokens (#24767)
## Changes
- Adds the following high level access scopes, each with `read` and
`write` levels:
- `activitypub`
- `admin` (hidden if user is not a site admin)
- `misc`
- `notification`
- `organization`
- `package`
- `issue`
- `repository`
- `user`
- Adds new middleware function `tokenRequiresScopes()` in addition to
`reqToken()`
- `tokenRequiresScopes()` is used for each high-level api section
- _if_ a scoped token is present, checks that the required scope is
included based on the section and HTTP method
- `reqToken()` is used for individual routes
- checks that required authentication is present (but does not check
scope levels as this will already have been handled by
`tokenRequiresScopes()`
- Adds migration to convert old scoped access tokens to the new set of
scopes
- Updates the user interface for scope selection
### User interface example
<img width="903" alt="Screen Shot 2023-05-31 at 1 56 55 PM"
src="https://github.com/go-gitea/gitea/assets/23248839/654766ec-2143-4f59-9037-3b51600e32f3">
<img width="917" alt="Screen Shot 2023-05-31 at 1 56 43 PM"
src="https://github.com/go-gitea/gitea/assets/23248839/1ad64081-012c-4a73-b393-66b30352654c">
## tokenRequiresScopes Design Decision
- `tokenRequiresScopes()` was added to more reliably cover api routes.
For an incoming request, this function uses the given scope category
(say `AccessTokenScopeCategoryOrganization`) and the HTTP method (say
`DELETE`) and verifies that any scoped tokens in use include
`delete:organization`.
- `reqToken()` is used to enforce auth for individual routes that
require it. If a scoped token is not present for a request,
`tokenRequiresScopes()` will not return an error
## TODO
- [x] Alphabetize scope categories
- [x] Change 'public repos only' to a radio button (private vs public).
Also expand this to organizations
- [X] Disable token creation if no scopes selected. Alternatively, show
warning
- [x] `reqToken()` is missing from many `POST/DELETE` routes in the api.
`tokenRequiresScopes()` only checks that a given token has the correct
scope, `reqToken()` must be used to check that a token (or some other
auth) is present.
- _This should be addressed in this PR_
- [x] The migration should be reviewed very carefully in order to
minimize access changes to existing user tokens.
- _This should be addressed in this PR_
- [x] Link to api to swagger documentation, clarify what
read/write/delete levels correspond to
- [x] Review cases where more than one scope is needed as this directly
deviates from the api definition.
- _This should be addressed in this PR_
- For example:
```go
m.Group("/users/{username}/orgs", func() {
m.Get("", reqToken(), org.ListUserOrgs)
m.Get("/{org}/permissions", reqToken(), org.GetUserOrgsPermissions)
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser,
auth_model.AccessTokenScopeCategoryOrganization),
context_service.UserAssignmentAPI())
```
## Future improvements
- [ ] Add required scopes to swagger documentation
- [ ] Redesign `reqToken()` to be opt-out rather than opt-in
- [ ] Subdivide scopes like `repository`
- [ ] Once a token is created, if it has no scopes, we should display
text instead of an empty bullet point
- [ ] If the 'public repos only' option is selected, should read
categories be selected by default
Closes #24501
Closes #24799
Co-authored-by: Jonathan Tran <jon@allspice.io>
Co-authored-by: Kyle D <kdumontnu@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
2023-06-04 21:57:16 +03:00
token4 := getTokenForLoggedInUser ( t , session , auth_model . AccessTokenScopeWriteRepository , auth_model . AccessTokenScopeWriteUser )
2019-05-11 18:29:17 +03:00
// Test creating a file in repo1 which user2 owns, try both with branch and empty branch
for _ , branch := range [ ... ] string {
"master" , // Branch
"" , // Empty branch
} {
createFileOptions := getCreateFileOptions ( )
createFileOptions . BranchName = branch
fileID ++
treePath := fmt . Sprintf ( "new/file%d.txt" , fileID )
2023-12-22 02:59:59 +03:00
req := NewRequestWithJSON ( t , "POST" , fmt . Sprintf ( "/api/v1/repos/%s/%s/contents/%s" , user2 . Name , repo1 . Name , treePath ) , & createFileOptions ) .
AddTokenAuth ( token2 )
2022-12-02 06:39:42 +03:00
resp := MakeRequest ( t , req , http . StatusCreated )
Simplify how git repositories are opened (#28937)
## Purpose
This is a refactor toward building an abstraction over managing git
repositories.
Afterwards, it does not matter anymore if they are stored on the local
disk or somewhere remote.
## What this PR changes
We used `git.OpenRepository` everywhere previously.
Now, we should split them into two distinct functions:
Firstly, there are temporary repositories which do not change:
```go
git.OpenRepository(ctx, diskPath)
```
Gitea managed repositories having a record in the database in the
`repository` table are moved into the new package `gitrepo`:
```go
gitrepo.OpenRepository(ctx, repo_model.Repo)
```
Why is `repo_model.Repository` the second parameter instead of file
path?
Because then we can easily adapt our repository storage strategy.
The repositories can be stored locally, however, they could just as well
be stored on a remote server.
## Further changes in other PRs
- A Git Command wrapper on package `gitrepo` could be created. i.e.
`NewCommand(ctx, repo_model.Repository, commands...)`. `git.RunOpts{Dir:
repo.RepoPath()}`, the directory should be empty before invoking this
method and it can be filled in the function only. #28940
- Remove the `RepoPath()`/`WikiPath()` functions to reduce the
possibility of mistakes.
---------
Co-authored-by: delvh <dev.lh@web.de>
2024-01-27 23:09:51 +03:00
gitRepo , _ := gitrepo . OpenRepository ( stdCtx . Background ( ) , repo1 )
2019-05-11 18:29:17 +03:00
commitID , _ := gitRepo . GetBranchCommitID ( createFileOptions . NewBranchName )
2022-07-30 11:09:04 +03:00
latestCommit , _ := gitRepo . GetCommitByPath ( treePath )
expectedFileResponse := getExpectedFileResponseForCreate ( "user2/repo1" , commitID , treePath , latestCommit . ID . String ( ) )
2019-05-11 18:29:17 +03:00
var fileResponse api . FileResponse
DecodeJSON ( t , resp , & fileResponse )
assert . EqualValues ( t , expectedFileResponse . Content , fileResponse . Content )
assert . EqualValues ( t , expectedFileResponse . Commit . SHA , fileResponse . Commit . SHA )
assert . EqualValues ( t , expectedFileResponse . Commit . HTMLURL , fileResponse . Commit . HTMLURL )
assert . EqualValues ( t , expectedFileResponse . Commit . Author . Email , fileResponse . Commit . Author . Email )
assert . EqualValues ( t , expectedFileResponse . Commit . Author . Name , fileResponse . Commit . Author . Name )
2019-12-24 05:33:52 +03:00
assert . EqualValues ( t , expectedFileResponse . Commit . Author . Date , fileResponse . Commit . Author . Date )
assert . EqualValues ( t , expectedFileResponse . Commit . Committer . Email , fileResponse . Commit . Committer . Email )
assert . EqualValues ( t , expectedFileResponse . Commit . Committer . Name , fileResponse . Commit . Committer . Name )
assert . EqualValues ( t , expectedFileResponse . Commit . Committer . Date , fileResponse . Commit . Committer . Date )
2019-11-13 10:01:19 +03:00
gitRepo . Close ( )
2019-05-11 18:29:17 +03:00
}
// Test creating a file in a new branch
2019-04-17 19:06:35 +03:00
createFileOptions := getCreateFileOptions ( )
2019-05-11 18:29:17 +03:00
createFileOptions . BranchName = repo1 . DefaultBranch
createFileOptions . NewBranchName = "new_branch"
2019-04-17 19:06:35 +03:00
fileID ++
treePath := fmt . Sprintf ( "new/file%d.txt" , fileID )
2023-12-22 02:59:59 +03:00
req := NewRequestWithJSON ( t , "POST" , fmt . Sprintf ( "/api/v1/repos/%s/%s/contents/%s" , user2 . Name , repo1 . Name , treePath ) , & createFileOptions ) .
AddTokenAuth ( token2 )
2022-12-02 06:39:42 +03:00
resp := MakeRequest ( t , req , http . StatusCreated )
2019-04-17 19:06:35 +03:00
var fileResponse api . FileResponse
DecodeJSON ( t , resp , & fileResponse )
2019-05-11 18:29:17 +03:00
expectedSHA := "a635aa942442ddfdba07468cf9661c08fbdf0ebf"
2019-06-29 23:51:10 +03:00
expectedHTMLURL := fmt . Sprintf ( setting . AppURL + "user2/repo1/src/branch/new_branch/new/file%d.txt" , fileID )
2019-06-08 17:31:11 +03:00
expectedDownloadURL := fmt . Sprintf ( setting . AppURL + "user2/repo1/raw/branch/new_branch/new/file%d.txt" , fileID )
2019-05-11 18:29:17 +03:00
assert . EqualValues ( t , expectedSHA , fileResponse . Content . SHA )
2019-06-29 23:51:10 +03:00
assert . EqualValues ( t , expectedHTMLURL , * fileResponse . Content . HTMLURL )
assert . EqualValues ( t , expectedDownloadURL , * fileResponse . Content . DownloadURL )
2019-06-29 18:19:24 +03:00
assert . EqualValues ( t , createFileOptions . Message + "\n" , fileResponse . Commit . Message )
// Test creating a file without a message
createFileOptions = getCreateFileOptions ( )
createFileOptions . Message = ""
fileID ++
treePath = fmt . Sprintf ( "new/file%d.txt" , fileID )
2023-12-22 02:59:59 +03:00
req = NewRequestWithJSON ( t , "POST" , fmt . Sprintf ( "/api/v1/repos/%s/%s/contents/%s" , user2 . Name , repo1 . Name , treePath ) , & createFileOptions ) .
AddTokenAuth ( token2 )
2022-12-02 06:39:42 +03:00
resp = MakeRequest ( t , req , http . StatusCreated )
2019-06-29 18:19:24 +03:00
DecodeJSON ( t , resp , & fileResponse )
2023-04-18 01:04:26 +03:00
expectedMessage := "Add " + treePath + "\n"
2019-06-29 18:19:24 +03:00
assert . EqualValues ( t , expectedMessage , fileResponse . Commit . Message )
2019-05-11 18:29:17 +03:00
// Test trying to create a file that already exists, should fail
createFileOptions = getCreateFileOptions ( )
treePath = "README.md"
2023-12-22 02:59:59 +03:00
req = NewRequestWithJSON ( t , "POST" , fmt . Sprintf ( "/api/v1/repos/%s/%s/contents/%s" , user2 . Name , repo1 . Name , treePath ) , & createFileOptions ) .
AddTokenAuth ( token2 )
2022-12-02 06:39:42 +03:00
resp = MakeRequest ( t , req , http . StatusUnprocessableEntity )
2019-05-11 18:29:17 +03:00
expectedAPIError := context . APIError {
Message : "repository file already exists [path: " + treePath + "]" ,
2019-06-13 00:07:24 +03:00
URL : setting . API . SwaggerURL ,
2019-05-11 18:29:17 +03:00
}
var apiError context . APIError
DecodeJSON ( t , resp , & apiError )
assert . Equal ( t , expectedAPIError , apiError )
// Test creating a file in repo1 by user4 who does not have write access
createFileOptions = getCreateFileOptions ( )
fileID ++
treePath = fmt . Sprintf ( "new/file%d.txt" , fileID )
2023-12-22 02:59:59 +03:00
req = NewRequestWithJSON ( t , "POST" , fmt . Sprintf ( "/api/v1/repos/%s/%s/contents/%s" , user2 . Name , repo16 . Name , treePath ) , & createFileOptions ) .
AddTokenAuth ( token4 )
2022-12-02 06:39:42 +03:00
MakeRequest ( t , req , http . StatusNotFound )
2019-04-17 19:06:35 +03:00
2019-05-11 18:29:17 +03:00
// Tests a repo with no token given so will fail
createFileOptions = getCreateFileOptions ( )
fileID ++
treePath = fmt . Sprintf ( "new/file%d.txt" , fileID )
2023-12-22 02:59:59 +03:00
req = NewRequestWithJSON ( t , "POST" , fmt . Sprintf ( "/api/v1/repos/%s/%s/contents/%s" , user2 . Name , repo16 . Name , treePath ) , & createFileOptions )
2022-12-02 06:39:42 +03:00
MakeRequest ( t , req , http . StatusNotFound )
2019-05-11 18:29:17 +03:00
// Test using access token for a private repo that the user of the token owns
createFileOptions = getCreateFileOptions ( )
fileID ++
treePath = fmt . Sprintf ( "new/file%d.txt" , fileID )
2023-12-22 02:59:59 +03:00
req = NewRequestWithJSON ( t , "POST" , fmt . Sprintf ( "/api/v1/repos/%s/%s/contents/%s" , user2 . Name , repo16 . Name , treePath ) , & createFileOptions ) .
AddTokenAuth ( token2 )
2022-12-02 06:39:42 +03:00
MakeRequest ( t , req , http . StatusCreated )
2019-05-11 18:29:17 +03:00
2023-09-14 05:59:53 +03:00
// Test using org repo "org3/repo3" where user2 is a collaborator
2019-05-11 18:29:17 +03:00
createFileOptions = getCreateFileOptions ( )
fileID ++
treePath = fmt . Sprintf ( "new/file%d.txt" , fileID )
2023-12-22 02:59:59 +03:00
req = NewRequestWithJSON ( t , "POST" , fmt . Sprintf ( "/api/v1/repos/%s/%s/contents/%s" , org3 . Name , repo3 . Name , treePath ) , & createFileOptions ) .
AddTokenAuth ( token2 )
2022-12-02 06:39:42 +03:00
MakeRequest ( t , req , http . StatusCreated )
2019-05-11 18:29:17 +03:00
2023-09-14 05:59:53 +03:00
// Test using org repo "org3/repo3" with no user token
2019-05-11 18:29:17 +03:00
createFileOptions = getCreateFileOptions ( )
fileID ++
treePath = fmt . Sprintf ( "new/file%d.txt" , fileID )
2023-12-22 02:59:59 +03:00
req = NewRequestWithJSON ( t , "POST" , fmt . Sprintf ( "/api/v1/repos/%s/%s/contents/%s" , org3 . Name , repo3 . Name , treePath ) , & createFileOptions )
2022-12-02 06:39:42 +03:00
MakeRequest ( t , req , http . StatusNotFound )
2019-05-11 18:29:17 +03:00
// Test using repo "user2/repo1" where user4 is a NOT collaborator
createFileOptions = getCreateFileOptions ( )
fileID ++
treePath = fmt . Sprintf ( "new/file%d.txt" , fileID )
2023-12-22 02:59:59 +03:00
req = NewRequestWithJSON ( t , "POST" , fmt . Sprintf ( "/api/v1/repos/%s/%s/contents/%s" , user2 . Name , repo1 . Name , treePath ) , & createFileOptions ) .
AddTokenAuth ( token4 )
2022-12-02 06:39:42 +03:00
MakeRequest ( t , req , http . StatusForbidden )
2022-03-28 22:48:41 +03:00
// Test creating a file in an empty repository
2024-05-16 11:48:48 +03:00
doAPICreateRepository ( NewAPITestContext ( t , "user2" , "empty-repo" , auth_model . AccessTokenScopeWriteRepository , auth_model . AccessTokenScopeWriteUser ) , true , git . Sha1ObjectFormat ) ( t ) // FIXME: use forEachObjectFormat
2022-03-28 22:48:41 +03:00
createFileOptions = getCreateFileOptions ( )
fileID ++
treePath = fmt . Sprintf ( "new/file%d.txt" , fileID )
2023-12-22 02:59:59 +03:00
req = NewRequestWithJSON ( t , "POST" , fmt . Sprintf ( "/api/v1/repos/%s/%s/contents/%s" , user2 . Name , "empty-repo" , treePath ) , & createFileOptions ) .
AddTokenAuth ( token2 )
2022-12-02 06:39:42 +03:00
resp = MakeRequest ( t , req , http . StatusCreated )
2022-08-16 05:22:25 +03:00
emptyRepo := unittest . AssertExistsAndLoadBean ( t , & repo_model . Repository { OwnerName : "user2" , Name : "empty-repo" } ) // public repo
Simplify how git repositories are opened (#28937)
## Purpose
This is a refactor toward building an abstraction over managing git
repositories.
Afterwards, it does not matter anymore if they are stored on the local
disk or somewhere remote.
## What this PR changes
We used `git.OpenRepository` everywhere previously.
Now, we should split them into two distinct functions:
Firstly, there are temporary repositories which do not change:
```go
git.OpenRepository(ctx, diskPath)
```
Gitea managed repositories having a record in the database in the
`repository` table are moved into the new package `gitrepo`:
```go
gitrepo.OpenRepository(ctx, repo_model.Repo)
```
Why is `repo_model.Repository` the second parameter instead of file
path?
Because then we can easily adapt our repository storage strategy.
The repositories can be stored locally, however, they could just as well
be stored on a remote server.
## Further changes in other PRs
- A Git Command wrapper on package `gitrepo` could be created. i.e.
`NewCommand(ctx, repo_model.Repository, commands...)`. `git.RunOpts{Dir:
repo.RepoPath()}`, the directory should be empty before invoking this
method and it can be filled in the function only. #28940
- Remove the `RepoPath()`/`WikiPath()` functions to reduce the
possibility of mistakes.
---------
Co-authored-by: delvh <dev.lh@web.de>
2024-01-27 23:09:51 +03:00
gitRepo , _ := gitrepo . OpenRepository ( stdCtx . Background ( ) , emptyRepo )
2022-03-28 22:48:41 +03:00
commitID , _ := gitRepo . GetBranchCommitID ( createFileOptions . NewBranchName )
2022-07-30 11:09:04 +03:00
latestCommit , _ := gitRepo . GetCommitByPath ( treePath )
expectedFileResponse := getExpectedFileResponseForCreate ( "user2/empty-repo" , commitID , treePath , latestCommit . ID . String ( ) )
2022-03-28 22:48:41 +03:00
DecodeJSON ( t , resp , & fileResponse )
assert . EqualValues ( t , expectedFileResponse . Content , fileResponse . Content )
assert . EqualValues ( t , expectedFileResponse . Commit . SHA , fileResponse . Commit . SHA )
assert . EqualValues ( t , expectedFileResponse . Commit . HTMLURL , fileResponse . Commit . HTMLURL )
assert . EqualValues ( t , expectedFileResponse . Commit . Author . Email , fileResponse . Commit . Author . Email )
assert . EqualValues ( t , expectedFileResponse . Commit . Author . Name , fileResponse . Commit . Author . Name )
assert . EqualValues ( t , expectedFileResponse . Commit . Author . Date , fileResponse . Commit . Author . Date )
assert . EqualValues ( t , expectedFileResponse . Commit . Committer . Email , fileResponse . Commit . Committer . Email )
assert . EqualValues ( t , expectedFileResponse . Commit . Committer . Name , fileResponse . Commit . Committer . Name )
assert . EqualValues ( t , expectedFileResponse . Commit . Committer . Date , fileResponse . Commit . Committer . Date )
gitRepo . Close ( )
2019-05-11 18:29:17 +03:00
} )
2019-04-17 19:06:35 +03:00
}