Compare commits

...

40 Commits

Author SHA1 Message Date
yystopf a7eebc5c59 新增: 响应结构体 2022-07-14 14:23:29 +08:00
yystopf bfd5804dd0 修复: 保护分支patterns处理 2022-07-13 18:18:23 +08:00
yystopf 445a5f7d77 新增: 批量逻辑 2022-07-13 18:13:47 +08:00
yystopf 8e5f72bc41 新增: 删除文件逻辑 2022-07-13 17:16:06 +08:00
yystopf 63ef5921d1 新增: 批量更改文件函数定义 2022-07-12 16:11:50 +08:00
yystopf 93de827180 新增: 批量更改文件统一接口以及结构体定义 2022-07-12 11:43:02 +08:00
yystopf f2d50f4cb8 新增:批量修改文件api文档生成 2022-07-06 18:16:00 +08:00
yystopf 4a21b9f7ac 修复: blame 响应结构体 2022-07-06 14:49:31 +08:00
yystopf a3b1439a52 新增:获取单文件blame信息 2022-07-06 14:10:12 +08:00
yystopf cf23fdb592 新增:commit diff增加api文档 2022-07-05 14:31:02 +08:00
yystopf 620df5904e 新增:diff信息接口 2022-07-01 18:15:46 +08:00
xiaoxiaoqiong 62d7840464 pulls/files 增加参数忽略files输出,少了参数 2022-06-30 17:57:16 +08:00
xiaoxiaoqiong 6e74832c30 pulls/files 增加参数忽略files输出 2022-06-30 17:46:18 +08:00
yystopf c1f5a08987 新增:wiki列表可获取文件夹 2022-06-08 17:40:10 +08:00
yystopf 199e816395 新增: 同步新版gitea wiki接口 2022-06-07 11:35:25 +08:00
yystopf a217bcf43d 新增: 内部页面需要登录才能访问 2022-05-30 15:35:25 +08:00
yystopf 6917b164da 修复 drone适配并取消管理员才能登录 2022-05-27 10:02:08 +08:00
yystopf 059487b243 fix: url must query encode 2022-05-23 15:42:51 +08:00
yystopf 2e1e54f254 fix: update pullrequest assignees remove blank data 2022-05-06 15:03:44 +08:00
yystopf f22c45d49c add: compare some field 2022-04-28 18:50:31 +08:00
yystopf b02fec5020 Merge pull request '增加索引以及修复搜索分支分页问题' (#56) from yystopf/gitea-1156:develop into develop 2022-04-27 18:11:06 +08:00
yystopf 97f325e855 fix: branch search paginate 2022-04-27 18:09:45 +08:00
yystopf 0ccfbe7a5d add: index about some tables 2022-04-27 18:08:42 +08:00
wonderful 8eb90e81f1 wiki名中带段横线不能显示问题 2022-04-27 11:08:51 +08:00
hang c0d676e560 fix wiki normalizeName 2022-04-27 11:06:03 +08:00
yystopf 082d59adae add: name query for branches 2022-04-26 16:00:48 +08:00
wonderful 5bb1d6667b Merge pull request '提升返回文件内容响应速度' (#54) from wonderful/gitea-1156:develop into develop 2022-04-25 18:11:09 +08:00
hang 2115033586 fix content 2022-04-25 18:09:26 +08:00
wonderful 80e992aa49 修改创建 hook接口 2022-04-25 14:55:57 +08:00
hang ab21895bc5 fix create hooks 2022-04-25 14:53:49 +08:00
wonderful 49b36b1100 提交文件 2022-04-25 14:12:18 +08:00
hang be6365c3d5 add 2022-04-25 14:10:25 +08:00
wonderful 135fb19498 分支分页 2022-04-25 11:37:48 +08:00
hang a4ae6c7d65 banches slice 2022-04-25 11:35:41 +08:00
wonderful d59fbd0053 修复文件名前缀带空格不能编辑和删除问题 2022-04-12 14:11:43 +08:00
hang dbb0de14cc Modify the file name with spaces and cannot be deleted 2022-04-12 14:07:09 +08:00
yystopf 364a2c1366 fix: tag count error 2022-04-07 18:11:42 +08:00
yystopf d6e2682a08 Merge branch 'develop' of https://code.gitlink.org.cn/Gitlink/gitea-1156 into develop 2022-04-02 09:40:15 +08:00
yystopf 88b3eafe77 fix: wiki repo is not closed 2022-04-02 09:39:45 +08:00
xiaoxiaoqiong a813f92d31 fixed: file_commits分页大小参数无效 2022-03-18 16:23:07 +08:00
44 changed files with 2959 additions and 112 deletions

View File

@ -327,6 +327,10 @@ var migrations = []Migration{
NewMigration("Drop unneeded webhook related columns", dropWebhookColumns),
// v188 -> v189
NewMigration("Add key is verified to gpg key", addKeyIsVerified),
// v189 -> v190
NewMigration("Add index about user email", addIndexAboutUserEmail),
// v190 -> v191
NewMigration("Add index about access token last eight", addIndexAboutAccessTokenLastEight),
}
// GetCurrentDBVersion returns the current db version

20
models/migrations/v189.go Normal file
View File

@ -0,0 +1,20 @@
/*
* @Description: Do not edit
* @Date: 2022-04-27 17:58:22
* @LastEditors: viletyy
* @Author: viletyy
* @LastEditTime: 2022-04-27 18:04:33
* @FilePath: /gitea-1156/models/migrations/v189.go
*/
package migrations
import "xorm.io/xorm"
func addIndexAboutUserEmail(x *xorm.Engine) error {
type User struct {
Email string `xorm:"INDEX NOT NULL"`
}
return x.Sync2(new(User))
}

20
models/migrations/v190.go Normal file
View File

@ -0,0 +1,20 @@
/*
* @Description: Do not edit
* @Date: 2022-04-27 18:04:31
* @LastEditors: viletyy
* @Author: viletyy
* @LastEditTime: 2022-04-27 18:05:38
* @FilePath: /gitea-1156/models/migrations/v190.go
*/
package migrations
import "xorm.io/xorm"
func addIndexAboutAccessTokenLastEight(x *xorm.Engine) error {
type AccessToken struct {
TokenLastEight string `xorm:"INDEX token_last_eight"`
}
return x.Sync2(new(AccessToken))
}

View File

@ -24,7 +24,7 @@ type AccessToken struct {
Token string `xorm:"-"`
TokenHash string `xorm:"UNIQUE"` // sha256 of token
TokenSalt string
TokenLastEight string `xorm:"token_last_eight"`
TokenLastEight string `xorm:"INDEX token_last_eight"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`

View File

@ -97,7 +97,7 @@ type User struct {
Name string `xorm:"UNIQUE NOT NULL"`
FullName string
// Email is the primary email address (to be used for communication)
Email string `xorm:"NOT NULL"`
Email string `xorm:"INDEX NOT NULL"`
KeepEmailPrivate bool
EmailNotificationsPreference string `xorm:"VARCHAR(20) NOT NULL DEFAULT 'enabled'"`
Passwd string `xorm:"NOT NULL"`

View File

@ -229,6 +229,22 @@ func (ctx *APIContext) SetLinkHeader(total, pageSize int) {
}
}
// SetTotalCountHeader set "X-Total-Count" header
func (ctx *APIContext) SetTotalCountHeader(total int64) {
ctx.RespHeader().Set("X-Total-Count", fmt.Sprint(total))
ctx.AppendAccessControlExposeHeaders("X-Total-Count")
}
// AppendAccessControlExposeHeaders append headers by name to "Access-Control-Expose-Headers" header
func (ctx *APIContext) AppendAccessControlExposeHeaders(names ...string) {
val := ctx.RespHeader().Get("Access-Control-Expose-Headers")
if len(val) != 0 {
ctx.RespHeader().Set("Access-Control-Expose-Headers", fmt.Sprintf("%s, %s", val, strings.Join(names, ", ")))
} else {
ctx.RespHeader().Set("Access-Control-Expose-Headers", strings.Join(names, ", "))
}
}
// RequireCSRF requires a validated a CSRF token
func (ctx *APIContext) RequireCSRF() {
headerToken := ctx.Req.Header.Get(ctx.csrf.GetHeaderName())

View File

@ -333,6 +333,11 @@ func (ctx *Context) HandleText(status int, title string) {
ctx.PlainText(status, []byte(title))
}
// RespHeader returns the response header
func (ctx *Context) RespHeader() http.Header {
return ctx.Resp.Header()
}
// ServeContent serves content to http request
func (ctx *Context) ServeContent(name string, r io.ReadSeeker, params ...interface{}) {
modtime := time.Now()

81
modules/convert/wiki.go Normal file
View File

@ -0,0 +1,81 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package convert
import (
"time"
model "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
wiki_service "code.gitea.io/gitea/services/wiki"
)
// ToWikiCommit convert a git commit into a WikiCommit
func ToWikiCommit(commit *git.Commit) *api.WikiCommit {
return &api.WikiCommit{
ID: commit.ID.String(),
Author: &api.CommitUser{
Identity: api.Identity{
Name: commit.Author.Name,
Email: commit.Author.Email,
},
Date: commit.Author.When.UTC().Format(time.RFC3339),
},
Committer: &api.CommitUser{
Identity: api.Identity{
Name: commit.Committer.Name,
Email: commit.Committer.Email,
},
Date: commit.Committer.When.UTC().Format(time.RFC3339),
},
Message: commit.CommitMessage,
}
}
// ToWikiCommitList convert a list of git commits into a WikiCommitList
func ToWikiCommitList(commits []*git.Commit, total int64) *api.WikiCommitList {
result := make([]*api.WikiCommit, len(commits))
for i := range commits {
result[i] = ToWikiCommit(commits[i])
}
return &api.WikiCommitList{
WikiCommits: result,
Count: total,
}
}
// ToWikiPageMetaData converts meta information to a WikiPageMetaData
func ToWikiPageMetaData(title string, lastCommit *git.Commit, repo *model.Repository) *api.WikiPageMetaData {
suburl := wiki_service.NameToSubURL(title)
return &api.WikiPageMetaData{
Title: title,
HTMLURL: util.URLJoin(repo.HTMLURL(), "wiki", suburl),
SubURL: suburl,
LastCommit: ToWikiCommit(lastCommit),
}
}
// author hui.he
func RegularToWikiPageMetaData(title string, lastCommit *git.Commit, repo *model.Repository) *api.WikiListMetaData {
suburl := wiki_service.NameToSubURL(title)
return &api.WikiListMetaData{
Type: "file",
Name: title,
HTMLURL: util.URLJoin(repo.HTMLURL(), "wiki", suburl),
SubURL: suburl,
LastCommit: ToWikiCommit(lastCommit),
}
}
// author hui.he
func DirToWikiPageMetaData(name string, lastCommit *git.Commit, repo *model.Repository) *api.WikiListMetaData {
return &api.WikiListMetaData{
Type: "dir",
Name: name,
LastCommit: ToWikiCommit(lastCommit),
}
}

View File

@ -12,6 +12,7 @@ import (
"os"
"os/exec"
"regexp"
"time"
"code.gitea.io/gitea/modules/process"
)
@ -22,6 +23,25 @@ type BlamePart struct {
Lines []string
}
type ApiBlameCommit struct {
ID string `json:"id"`
Author *Signature `json:"author"`
Commiter *Signature `json:"commiter"`
CommitMessage string `json:"commit_message"`
Parents []string `json:"parents"`
AuthoredTime time.Time `json:"authored_time"`
CommittedTime time.Time `json:"committed_time"`
CreatedTime time.Time `json:"created_time"`
}
type ApiBlamePart struct {
Sha string `json:"-"`
Commit *ApiBlameCommit `json:"commit"`
CurrentNumber int `json:"current_number"`
EffectLine int `json:"effect_line"`
Lines []string `json:"lines"`
}
// BlameReader returns part of file blame one by one
type BlameReader struct {
cmd *exec.Cmd
@ -34,6 +54,95 @@ type BlameReader struct {
var shaLineRegex = regexp.MustCompile("^([a-z0-9]{40})")
func GetBlameCommit(repo *Repository, sha string) *ApiBlameCommit {
commit, err := repo.GetCommit(sha)
var apiParents []string
for i := 0; i < commit.ParentCount(); i++ {
sha, _ := commit.ParentID(i)
apiParents = append(apiParents, sha.String())
}
if err != nil {
return &ApiBlameCommit{}
} else {
return &ApiBlameCommit{
ID: sha,
Author: commit.Author,
Commiter: commit.Committer,
CommitMessage: commit.CommitMessage,
Parents: apiParents,
AuthoredTime: commit.Author.When,
CommittedTime: commit.Committer.When,
CreatedTime: commit.Committer.When,
}
}
}
func (r *BlameReader) NextApiPart(repo *Repository) (*ApiBlamePart, error) {
var blamePart *ApiBlamePart
reader := r.reader
effectLine := 0
if r.lastSha != nil {
blamePart = &ApiBlamePart{*r.lastSha, GetBlameCommit(repo, *r.lastSha), 0, effectLine, make([]string, 0)}
}
var line []byte
var isPrefix bool
var err error
for err != io.EOF {
line, isPrefix, err = reader.ReadLine()
if err != nil && err != io.EOF {
return blamePart, err
}
if len(line) == 0 {
// isPrefix will be false
continue
}
lines := shaLineRegex.FindSubmatch(line)
if lines != nil {
sha1 := string(lines[1])
if blamePart == nil {
blamePart = &ApiBlamePart{sha1, GetBlameCommit(repo, sha1), 0, effectLine, make([]string, 0)}
}
if blamePart.Sha != sha1 {
r.lastSha = &sha1
// need to munch to end of line...
for isPrefix {
_, isPrefix, err = reader.ReadLine()
if err != nil && err != io.EOF {
return blamePart, err
}
}
return blamePart, nil
}
} else if line[0] == '\t' {
code := line[1:]
effectLine += 1
blamePart.Lines = append(blamePart.Lines, string(code))
}
blamePart.EffectLine = effectLine
// need to munch to end of line...
for isPrefix {
_, isPrefix, err = reader.ReadLine()
if err != nil && err != io.EOF {
return blamePart, err
}
}
}
r.lastSha = nil
return blamePart, nil
}
// NextPart returns next part of blame (sequential code lines with the same commit)
func (r *BlameReader) NextPart() (*BlamePart, error) {
var blamePart *BlamePart

View File

@ -193,7 +193,7 @@ func (c *Commit) CommitsByRange(page, pageSize int) (*list.List, error) {
// CommitsByFileAndRange returns the specific page page commits before current revision and file, every page's number default by CommitsRangeSize
func (c *Commit) CommitsByFileAndRange(file string, page, pageSize int) (*list.List, error) {
return c.repo.CommitsByFileAndRange(c.ID.String(), file, page)
return c.repo.CommitsByFileAndRange(c.ID.String(), file, page, pageSize)
}
// CommitsBefore returns all the commits before current revision

View File

@ -52,6 +52,25 @@ func (repo *Repository) parsePrettyFormatLogToList(logs []byte) (*list.List, err
return l, nil
}
func (repo *Repository) parsePrettyFormatLogToCommits(logs []byte) ([]*Commit, error) {
var commits []*Commit
if len(logs) == 0 {
return commits, nil
}
parts := bytes.Split(logs, []byte{'\n'})
for _, commitID := range parts {
commit, err := repo.GetCommit(string(commitID))
if err != nil {
return nil, err
}
commits = append(commits, commit)
}
return commits, nil
}
// IsRepoURLAccessible checks if given repository URL is accessible.
func IsRepoURLAccessible(url string) bool {
_, err := NewCommand("ls-remote", "-q", "-h", url, "HEAD").Run()

View File

@ -77,6 +77,32 @@ func (repo *Repository) GetBranch(branch string) (*Branch, error) {
}, nil
}
// GetBranchesByPath returns a branch by it's path
// if limit = 0 it will not limit
func GetSearchBranchesByPath(path, search string, skip, limit int) ([]*Branch, int, error) {
gitRepo, err := OpenRepository(path)
if err != nil {
return nil, 0, err
}
defer gitRepo.Close()
brs, countAll, err := gitRepo.GetSearchBranches(search, skip, limit)
if err != nil {
return nil, 0, err
}
branches := make([]*Branch, len(brs))
for i := range brs {
branches[i] = &Branch{
Path: path,
Name: brs[i],
gitRepo: gitRepo,
}
}
return branches, countAll, nil
}
// GetBranchesByPath returns a branch by it's path
// if limit = 0 it will not limit
func GetBranchesByPath(path string, skip, limit int) ([]*Branch, int, error) {

View File

@ -59,12 +59,101 @@ func (repo *Repository) IsBranchExist(name string) bool {
return repo.IsReferenceExist(BranchPrefix + name)
}
func (repo *Repository) GetSearchBranches(search string, skip, limit int) ([]string, int, error) {
return callShowSearchRef(repo.Path, BranchPrefix, "--heads", search, skip, limit)
}
// GetBranches returns branches from the repository, skipping skip initial branches and
// returning at most limit branches, or all branches if limit is 0.
func (repo *Repository) GetBranches(skip, limit int) ([]string, int, error) {
return callShowRef(repo.Path, BranchPrefix, "--heads", skip, limit)
}
func callShowSearchRef(repoPath, prefix, arg, search string, skip, limit int) (branchNames []string, countAll int, err error) {
stdoutReader, stdoutWriter := io.Pipe()
defer func() {
_ = stdoutReader.Close()
_ = stdoutWriter.Close()
}()
go func() {
stderrBuilder := &strings.Builder{}
err := NewCommand("show-ref", arg).RunInDirPipeline(repoPath, stdoutWriter, stderrBuilder)
if err != nil {
if stderrBuilder.Len() == 0 {
_ = stdoutWriter.Close()
return
}
_ = stdoutWriter.CloseWithError(ConcatenateError(err, stderrBuilder.String()))
} else {
_ = stdoutWriter.Close()
}
}()
i := 0
bufReader := bufio.NewReader(stdoutReader)
for i < skip {
line, isPrefix, err := bufReader.ReadLine()
if err == io.EOF {
return branchNames, i, nil
}
if err != nil {
return nil, 0, err
}
branchName := strings.TrimPrefix(strings.Split(string(line), " ")[1], prefix)
if len(branchName) > 0 {
branchName = branchName[:len(branchName)-1]
}
isSeached := strings.Contains(branchName, search)
if !isPrefix && isSeached {
i++
}
}
for limit == 0 || i < skip+limit {
branchName, err := bufReader.ReadString('\n')
if err == io.EOF {
// This shouldn't happen... but we'll tolerate it for the sake of peace
return branchNames, i, nil
}
if err != nil {
return nil, i, err
}
branchName = strings.TrimPrefix(strings.Split(string(branchName), " ")[1], prefix)
if len(branchName) > 0 {
branchName = branchName[:len(branchName)-1]
}
isSeached := strings.Contains(branchName, search)
if isSeached {
i++
branchNames = append(branchNames, branchName)
}
}
// count all refs
for limit != 0 {
line, isPrefix, err := bufReader.ReadLine()
if err == io.EOF {
return branchNames, i, nil
}
if err != nil {
return nil, 0, err
}
branchName := strings.TrimPrefix(strings.Split(string(line), " ")[1], prefix)
if len(branchName) > 0 {
branchName = branchName[:len(branchName)-1]
}
isSeached := strings.Contains(branchName, search)
if !isPrefix && isSeached {
i++
}
}
return branchNames, i, nil
}
// callShowRef return refs, if limit = 0 it will not limit
func callShowRef(repoPath, prefix, arg string, skip, limit int) (branchNames []string, countAll int, err error) {
stdoutReader, stdoutWriter := io.Pipe()

View File

@ -214,7 +214,7 @@ func (repo *Repository) GetFirstAndLastCommitByPath(revision, relpath string) (*
}
// CommitsByFileAndRange return the commits according revision file and the page
func (repo *Repository) CommitsByFileAndRange(revision, file string, page int) (*list.List, error) {
func (repo *Repository) CommitsByFileAndRange(revision, file string, page int, pageSize int) (*list.List, error) {
skip := (page - 1) * setting.Git.CommitsRangeSize
stdoutReader, stdoutWriter := io.Pipe()
@ -223,9 +223,12 @@ func (repo *Repository) CommitsByFileAndRange(revision, file string, page int) (
_ = stdoutWriter.Close()
}()
go func() {
if pageSize <= 0 {
pageSize = setting.Git.CommitsRangeSize
}
stderr := strings.Builder{}
err := NewCommand("log", revision, "--follow",
"--max-count="+strconv.Itoa(setting.Git.CommitsRangeSize*page),
"--max-count="+strconv.Itoa(pageSize*page),
prettyLogFormat, "--", file).
RunInDirPipeline(repo.Path, stdoutWriter, &stderr)
if err != nil {
@ -263,6 +266,15 @@ func (repo *Repository) CommitsByFileAndRangeNoFollow(revision, file string, pag
return repo.parsePrettyFormatLogToList(stdout)
}
func (repo *Repository) NewCommitsByFileAndRangeNoFollow(revision, file string, page int) ([]*Commit, error) {
stdout, err := NewCommand("log", revision, "--skip="+strconv.Itoa((page-1)*50),
"--max-count="+strconv.Itoa(setting.Git.CommitsRangeSize), prettyLogFormat, "--", file).RunInDirBytes(repo.Path)
if err != nil {
return nil, err
}
return repo.parsePrettyFormatLogToCommits(stdout)
}
// FilesCountBetween return the number of files changed between two commits
func (repo *Repository) FilesCountBetween(startCommitID, endCommitID string) (int, error) {
stdout, err := NewCommand("diff", "--name-only", startCommitID+"..."+endCommitID).RunInDir(repo.Path)

View File

@ -1,3 +1,10 @@
/*
* @Descripttion:
* @Author: hang
* @version:
* @Date: 2021-10-28 18:21:53
* @LastEditors: hang
*/
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
@ -5,6 +12,8 @@
package repofiles
import (
"net/url"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/setting"
@ -37,3 +46,25 @@ func GetBlobBySHA(repo *models.Repository, sha string) (*api.GitBlobResponse, er
Content: content,
}, nil
}
// GetBlobBySHA get the GitBlobResponse of a repository using a sha hash.
func GetBlobBySHANew(repo *models.Repository, gitRepo *git.Repository, sha string) (*api.GitBlobResponse, error) {
gitBlob, err := gitRepo.GetBlob(sha)
if err != nil {
return nil, err
}
content := ""
if gitBlob.Size() <= setting.API.DefaultMaxBlobSize {
content, err = gitBlob.GetBlobContentBase64()
if err != nil {
return nil, err
}
}
return &api.GitBlobResponse{
SHA: gitBlob.ID.String(),
URL: repo.APIURL() + "/git/blobs/" + url.PathEscape(gitBlob.ID.String()),
Size: gitBlob.Size(),
Encoding: "base64",
Content: content,
}, nil
}

View File

@ -46,8 +46,7 @@ func GetContentsOrList(ctx context.Context, repo *models.Repository, treePath, r
if ref == "" {
ref = repo.DefaultBranch
}
origRef := ref
origRef := fmt.Sprintf("%s", url.PathEscape(ref))
// Check that the path given in opts.treePath is valid (not a git path)
cleanTreePath := CleanUploadFileName(treePath)
if cleanTreePath == "" && treePath != "" {
@ -103,18 +102,20 @@ func GetContentsOrList(ctx context.Context, repo *models.Repository, treePath, r
subTreePath := path.Join(treePath, name)
fileContentResponse, err := GetContents(repo, subTreePath, origRef, true)
for _, commitInfo := range commitsInfo {
if commitInfo.Entry.Name() == fileContentResponse.Name {
var entryCommit *git.Commit
entryCommit = commitInfo.Commit
if e.IsSubModule() {
entryCommit = commitInfo.SubModuleFile.Commit
if commitInfo.Entry != nil && fileContentResponse != nil {
if commitInfo.Entry.Name() == fileContentResponse.Name {
var entryCommit *git.Commit
entryCommit = commitInfo.Commit
if e.IsSubModule() {
entryCommit = commitInfo.SubModuleFile.Commit
}
fileContentResponse.LatestCommit = api.ContentsResponseCommit{
Message: entryCommit.CommitMessage,
LatestCommitSha: entryCommit.ID.String(),
Created: entryCommit.Author.When.Unix(),
}
break
}
fileContentResponse.LatestCommit = api.ContentsResponseCommit{
Message: entryCommit.CommitMessage,
LatestCommitSha: entryCommit.ID.String(),
Created: entryCommit.Author.When.Unix(),
}
break
}
}
if err != nil {
@ -167,8 +168,8 @@ func GetContents(repo *models.Repository, treePath, ref string, forList bool) (*
if refType == "invalid" {
return nil, fmt.Errorf("no commit found for the ref [ref: %s]", ref)
}
selfURL, err := url.Parse(fmt.Sprintf("%s/contents/%s?ref=%s", repo.APIURL(), treePath, origRef))
// selfURL, err := url.Parse(fmt.Sprintf("%s/contents/%s?ref=%s", repo.APIURL(), treePath, origRef))
selfURL, err := url.Parse(url.PathEscape(fmt.Sprintf("%s/contents/%s?ref=%s", repo.APIURL(), treePath, origRef)))
if err != nil {
return nil, err
}
@ -189,7 +190,7 @@ func GetContents(repo *models.Repository, treePath, ref string, forList bool) (*
// Now populate the rest of the ContentsResponse based on entry type
if entry.IsRegular() || entry.IsExecutable() {
contentsResponse.Type = string(ContentTypeRegular)
if blobResponse, err := GetBlobBySHA(repo, entry.ID.String()); err != nil {
if blobResponse, err := GetBlobBySHANew(repo, gitRepo, entry.ID.String()); err != nil {
return nil, err
} else if !forList {
// We don't show the content if we are getting a list of FileContentResponses
@ -219,7 +220,8 @@ func GetContents(repo *models.Repository, treePath, ref string, forList bool) (*
// Handle links
if entry.IsRegular() || entry.IsLink() {
ref = fmt.Sprintf("%s", url.PathEscape(ref))
downloadURL, err := url.Parse(fmt.Sprintf("%s/raw/%s/%s/%s", repo.HTMLURL(), refType, ref, treePath))
// downloadURL, err := url.Parse(fmt.Sprintf("%s/raw/%s/%s/%s", repo.HTMLURL(), refType, ref, treePath))
downloadURL, err := url.Parse(url.PathEscape(fmt.Sprintf("%s/raw/%s/%s/%s", repo.HTMLURL(), refType, ref, treePath)))
if err != nil {
return nil, err
}
@ -228,15 +230,16 @@ func GetContents(repo *models.Repository, treePath, ref string, forList bool) (*
}
if !entry.IsSubModule() {
ref = fmt.Sprintf("%s", url.PathEscape(ref))
htmlURL, err := url.Parse(fmt.Sprintf("%s/src/%s/%s/%s", repo.HTMLURL(), refType, ref, treePath))
// htmlURL, err := url.Parse(fmt.Sprintf("%s/src/%s/%s/%s", repo.HTMLURL(), refType, ref, treePath))
htmlURL, err := url.Parse(url.PathEscape(fmt.Sprintf("%s/src/%s/%s/%s", repo.HTMLURL(), refType, ref, treePath)))
if err != nil {
return nil, err
}
htmlURLString := htmlURL.String()
contentsResponse.HTMLURL = &htmlURLString
contentsResponse.Links.HTMLURL = &htmlURLString
gitURL, err := url.Parse(fmt.Sprintf("%s/git/blobs/%s", repo.APIURL(), entry.ID.String()))
// gitURL, err := url.Parse(fmt.Sprintf("%s/git/blobs/%s", repo.APIURL(), entry.ID.String()))
gitURL, err := url.Parse(url.PathEscape(fmt.Sprintf("%s/git/blobs/%s", repo.APIURL(), entry.ID.String())))
if err != nil {
return nil, err
}

View File

@ -15,6 +15,21 @@ import (
api "code.gitea.io/gitea/modules/structs"
)
func GetBatchFileResponseFromCommit(repo *models.Repository, commit *git.Commit, branch string, treeNames []string) (*api.BatchFileResponse, error) {
fileCommitResponse, _ := GetFileCommitResponse(repo, commit)
verification := GetPayloadCommitVerification(commit)
batchFileResponse := &api.BatchFileResponse{
Commit: fileCommitResponse,
Verification: verification,
}
for _, treeName := range treeNames {
fileContent, _ := GetContents(repo, treeName, branch, false)
batchFileResponse.Contents = append(batchFileResponse.Contents, fileContent)
}
return batchFileResponse, nil
}
// GetFileResponseFromCommit Constructs a FileResponse from a Commit object
func GetFileResponseFromCommit(repo *models.Repository, commit *git.Commit, branch, treeName string) (*api.FileResponse, error) {
fileContents, _ := GetContents(repo, treeName, branch, false) // ok if fails, then will be nil

View File

@ -1,3 +1,10 @@
/*
* @Descripttion:
* @Author: hang
* @version:
* @Date: 2021-10-28 18:21:53
* @LastEditors: hang
*/
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.package repofiles
@ -12,7 +19,7 @@ import (
// CleanUploadFileName Trims a filename and returns empty string if it is a .git directory
func CleanUploadFileName(name string) string {
// Rebase the filename
name = strings.Trim(path.Clean("/"+name), " /")
name = strings.Trim(path.Clean("/"+name), "/")
// Git disallows any filenames to have a .git directory in them.
for _, part := range strings.Split(name, "/") {
if strings.ToLower(part) == ".git" {

View File

@ -20,11 +20,30 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"github.com/gobwas/glob"
stdcharset "golang.org/x/net/html/charset"
"golang.org/x/text/transform"
)
type FileActionType int
const (
ActionTypeCreate FileActionType = iota + 1
ActionTypeUpdate
ActionTypeDelete
)
var fileActionTypes = map[string]FileActionType{
"create": ActionTypeCreate,
"update": ActionTypeUpdate,
"delete": ActionTypeDelete,
}
func ToFileActionType(name string) FileActionType {
return fileActionTypes[name]
}
// IdentityOptions for a person's identity like an author or committer
type IdentityOptions struct {
Name string
@ -54,6 +73,31 @@ type UpdateRepoFileOptions struct {
Signoff bool
}
type ExchangeFileOption struct {
FileChan chan BatchSingleFileOption
StopChan chan bool
ErrChan chan error
}
type BatchSingleFileOption struct {
Content string
TreePath string
FromTreePath string
ActionType FileActionType
}
type BatchUpdateFileOptions struct {
Files []BatchSingleFileOption
LastCommitID string
OldBranch string
NewBranch string
Message string
SHA string
Author *IdentityOptions
Commiter *IdentityOptions
Dates *CommitDateOptions
Signoff bool
}
func detectEncodingAndBOM(entry *git.TreeEntry, repo *models.Repository) (string, bool) {
reader, err := entry.Blob().DataAsync()
if err != nil {
@ -263,7 +307,7 @@ func CreateOrUpdateRepoFile(repo *models.Repository, doer *models.User, opts *Up
} else if changed {
return nil, models.ErrCommitIDDoesNotMatch{
GivenCommitID: opts.LastCommitID,
CurrentCommitID: opts.LastCommitID,
CurrentCommitID: commit.ID.String(),
}
}
// The file wasn't modified, so we are good to delete it
@ -466,3 +510,403 @@ func CreateOrUpdateRepoFile(repo *models.Repository, doer *models.User, opts *Up
}
return file, nil
}
func CreateOrUpdateOrDeleteRepofiles(repo *models.Repository, doer *models.User, opts *BatchUpdateFileOptions, exchange *ExchangeFileOption) (*structs.BatchFileResponse, error) {
var protectedPatterns []glob.Glob
if opts.OldBranch == "" {
opts.OldBranch = repo.DefaultBranch
}
if opts.NewBranch == "" {
opts.NewBranch = opts.OldBranch
}
// oldBranch must exist for this operation
if _, err := repo_module.GetBranch(repo, opts.OldBranch); err != nil {
return nil, err
}
if opts.NewBranch != opts.OldBranch {
existingBranch, err := repo_module.GetBranch(repo, opts.NewBranch)
if existingBranch != nil {
return nil, models.ErrBranchAlreadyExists{
BranchName: opts.NewBranch,
}
}
if err != nil && !git.IsErrBranchNotExist(err) {
return nil, err
}
} else {
protectedBranch, err := repo.GetBranchProtection(opts.OldBranch)
if err != nil {
return nil, err
}
if protectedBranch != nil {
if !protectedBranch.CanUserPush(doer.ID) {
return nil, models.ErrUserCannotCommit{
UserName: doer.LowerName,
}
}
if protectedBranch.RequireSignedCommits {
_, _, _, err := repo.SignCRUDAction(doer, repo.RepoPath(), opts.OldBranch)
if err != nil {
if !models.IsErrWontSign(err) {
return nil, err
}
return nil, models.ErrUserCannotCommit{
UserName: doer.LowerName,
}
}
}
protectedPatterns = protectedBranch.GetProtectedFilePatterns()
}
}
message := strings.TrimSpace(opts.Message)
author, commiter := GetAuthorAndCommitterUsers(opts.Author, opts.Commiter, doer)
t, err := NewTemporaryUploadRepository(repo)
if err != nil {
log.Error("%v", err)
}
defer t.Close()
if err := t.Clone(opts.OldBranch); err != nil {
return nil, err
}
if err := t.SetDefaultIndex(); err != nil {
return nil, err
}
// Get the commit of the original branch
commit, err := t.GetBranchCommit(opts.OldBranch)
if err != nil {
return nil, err
}
if opts.LastCommitID == "" {
opts.LastCommitID = commit.ID.String()
} else {
lastCommitID, err := t.gitRepo.ConvertToSHA1(opts.LastCommitID)
if err != nil {
return nil, fmt.Errorf("ConvertToSHA1: Invalid last commit ID: %v", err)
}
opts.LastCommitID = lastCommitID.String()
}
var commitHash string
var treeNames []string
for {
select {
case file := <-exchange.FileChan:
for _, pat := range protectedPatterns {
if pat.Match(strings.ToLower(file.TreePath)) {
return nil, models.ErrFilePathProtected{
Path: file.TreePath,
}
}
}
optTreePath := file.TreePath
optFromTreePath := file.FromTreePath
optActionType := file.ActionType
optContent := file.Content
if optTreePath != "" && optFromTreePath == "" {
optFromTreePath = optTreePath
}
treePath := CleanUploadFileName(optTreePath)
if treePath == "" {
return nil, models.ErrFilenameInvalid{
Path: optTreePath,
}
}
fromTreePath := CleanUploadFileName(optFromTreePath)
if fromTreePath == "" && optFromTreePath != "" {
return nil, models.ErrFilenameInvalid{
Path: optFromTreePath,
}
}
if optActionType == ActionTypeDelete {
// Get the files in the index
filesInIndex, err := t.LsFiles(optTreePath)
if err != nil {
return nil, fmt.Errorf("DeleteRepoFile: %v", err)
}
// Find the file we want to delete in the index
inFilelist := false
for _, file := range filesInIndex {
if file == optTreePath {
inFilelist = true
break
}
}
if !inFilelist {
return nil, models.ErrRepoFileDoesNotExist{
Path: optTreePath,
}
}
// Get the entry of treePath and check if the SHA given is the same as the file
entry, err := commit.GetTreeEntryByPath(treePath)
if err != nil {
return nil, err
}
if opts.SHA != "" {
// If a SHA was given and the SHA given doesn't match the SHA of the fromTreePath, throw error
if opts.SHA != entry.ID.String() {
return nil, models.ErrSHADoesNotMatch{
Path: treePath,
GivenSHA: opts.SHA,
CurrentSHA: entry.ID.String(),
}
}
} else if opts.LastCommitID != "" {
// If a lastCommitID was given and it doesn't match the commitID of the head of the branch throw
// an error, but only if we aren't creating a new branch.
if commit.ID.String() != opts.LastCommitID && opts.OldBranch == opts.NewBranch {
// CommitIDs don't match, but we don't want to throw a ErrCommitIDDoesNotMatch unless
// this specific file has been edited since opts.LastCommitID
if changed, err := commit.FileChangedSinceCommit(treePath, opts.LastCommitID); err != nil {
return nil, err
} else if changed {
return nil, models.ErrCommitIDDoesNotMatch{
GivenCommitID: opts.LastCommitID,
CurrentCommitID: opts.LastCommitID,
}
}
// The file wasn't modified, so we are good to delete it
}
} else {
// When deleting a file, a lastCommitID or SHA needs to be given to make sure other commits haven't been
// made. We throw an error if one wasn't provided.
return nil, models.ErrSHAOrCommitIDNotProvided{}
}
// Remove the file from the index
if err := t.RemoveFilesFromIndex(optTreePath); err != nil {
return nil, err
}
} else {
encoding := "UTF-8"
bom := false
executable := false
if optActionType == ActionTypeUpdate {
fromEntry, err := commit.GetTreeEntryByPath(fromTreePath)
if err != nil {
return nil, err
}
if opts.SHA != "" {
if opts.SHA != fromEntry.ID.String() {
return nil, models.ErrSHADoesNotMatch{
Path: optTreePath,
GivenSHA: opts.SHA,
CurrentSHA: fromEntry.ID.String(),
}
}
} else if opts.LastCommitID != "" {
if commit.ID.String() != opts.LastCommitID && opts.OldBranch == opts.NewBranch {
if changed, err := commit.FileChangedSinceCommit(treePath, opts.LastCommitID); err != nil {
return nil, err
} else if changed {
return nil, models.ErrCommitIDDoesNotMatch{
GivenCommitID: opts.LastCommitID,
CurrentCommitID: opts.LastCommitID,
}
}
}
} else {
return nil, models.ErrSHAOrCommitIDNotProvided{}
}
encoding, bom = detectEncodingAndBOM(fromEntry, repo)
executable = fromEntry.IsExecutable()
}
treePathParts := strings.Split(treePath, "/")
subTreePath := ""
for index, part := range treePathParts {
subTreePath = path.Join(subTreePath, part)
entry, err := commit.GetTreeEntryByPath(subTreePath)
if err != nil {
if git.IsErrNotExist(err) {
break
}
return nil, err
}
if index < len(treePathParts)-1 {
if !entry.IsDir() {
return nil, models.ErrFilePathInvalid{
Message: fmt.Sprintf("a file exists where youre trying to create a subdirectory [path: %s]", subTreePath),
Path: subTreePath,
Name: part,
Type: git.EntryModeBlob,
}
}
} else if entry.IsLink() {
return nil, models.ErrFilePathInvalid{
Message: fmt.Sprintf("a symbolic link exists where youre trying to create a subdirectory [path: %s]", subTreePath),
Path: subTreePath,
Name: part,
Type: git.EntryModeSymlink,
}
} else if entry.IsDir() {
return nil, models.ErrFilePathInvalid{
Message: fmt.Sprintf("a directory exists where youre trying to create a file [path: %s]", subTreePath),
Path: subTreePath,
Name: part,
Type: git.EntryModeTree,
}
} else if fromTreePath != treePath || optActionType == ActionTypeCreate {
return nil, models.ErrRepoFileAlreadyExists{
Path: treePath,
}
}
}
// Get the two paths (might be the same if not moving) from the index if they exist
filesInIndex, err := t.LsFiles(optTreePath, optFromTreePath)
if err != nil {
return nil, fmt.Errorf("UpdateRepoFile: %v", err)
}
if optActionType == ActionTypeCreate {
for _, file := range filesInIndex {
if file == optTreePath {
return nil, models.ErrRepoFileAlreadyExists{
Path: optTreePath,
}
}
}
}
// Remove the old path from the tree
if fromTreePath != treePath && len(filesInIndex) > 0 {
for _, file := range filesInIndex {
if file == fromTreePath {
if err := t.RemoveFilesFromIndex(optFromTreePath); err != nil {
return nil, err
}
}
}
}
content := optContent
if bom {
content = string(charset.UTF8BOM) + content
}
if encoding != "UTF-8" {
charsetEncoding, _ := stdcharset.Lookup(encoding)
if charsetEncoding != nil {
result, _, err := transform.String(charsetEncoding.NewEncoder(), content)
if err != nil {
log.Error("Error re-encoding %s (%s) as %s - will stay as UTF-8: %v", optTreePath, optFromTreePath, encoding, err)
result = content
}
content = result
} else {
log.Error("Unknown encoding: %s", encoding)
}
}
optContent = content
var lfsMetaObject *models.LFSMetaObject
if setting.LFS.StartServer {
filename2attribute2info, err := t.gitRepo.CheckAttribute(git.CheckAttributeOpts{
Attributes: []string{"filter"},
Filenames: []string{treePath},
})
if err != nil {
return nil, err
}
if filename2attribute2info[treePath] != nil && filename2attribute2info[treePath]["filter"] == "lfs" {
pointer, err := lfs.GeneratePointer(strings.NewReader(optContent))
if err != nil {
return nil, err
}
lfsMetaObject = &models.LFSMetaObject{Pointer: pointer, RepositoryID: repo.ID}
content = pointer.StringContent()
}
}
objectHash, err := t.HashObject(strings.NewReader(content))
if err != nil {
return nil, err
}
if executable {
if err := t.AddObjectToIndex("100755", objectHash, treePath); err != nil {
return nil, err
}
} else {
if err := t.AddObjectToIndex("100644", objectHash, treePath); err != nil {
return nil, err
}
}
if lfsMetaObject != nil {
lfsMetaObject, err = models.NewLFSMetaObject(lfsMetaObject)
if err != nil {
return nil, err
}
contentStore := lfs.NewContentStore()
exist, err := contentStore.Exists(lfsMetaObject.Pointer)
if err != nil {
return nil, err
}
if !exist {
if err := contentStore.Put(lfsMetaObject.Pointer, strings.NewReader(optContent)); err != nil {
if _, err2 := repo.RemoveLFSMetaObjectByOid(lfsMetaObject.Oid); err2 != nil {
return nil, fmt.Errorf("Error whilst removing failed inserted LFS object %s: %v (Prev Error: %v)", lfsMetaObject.Oid, err2, err)
}
return nil, err
}
}
}
}
opts.Files = append(opts.Files, file)
treeNames = append(treeNames, file.TreePath)
case err := <-exchange.ErrChan:
return nil, err
case _ = <-exchange.StopChan:
goto end
}
}
end:
// Now write the tree
treeHash, err := t.WriteTree()
if err != nil {
return nil, err
}
// Now commit the tree
if opts.Dates != nil {
commitHash, err = t.CommitTreeWithDate(author, commiter, treeHash, message, opts.Signoff, opts.Dates.Author, opts.Dates.Committer)
} else {
commitHash, err = t.CommitTree(author, commiter, treeHash, message, opts.Signoff)
}
if err != nil {
return nil, err
}
if err := t.Push(doer, commitHash, opts.NewBranch); err != nil {
log.Error("%T %v", err, err)
return nil, err
}
commit, err = t.GetCommit(commitHash)
if err != nil {
return nil, err
}
file, err := GetBatchFileResponseFromCommit(repo, commit, opts.NewBranch, treeNames)
if err != nil {
return nil, err
}
return file, nil
}

View File

@ -25,6 +25,10 @@ func GetBranch(repo *models.Repository, branch string) (*git.Branch, error) {
return gitRepo.GetBranch(branch)
}
func GetSearchBranches(repo *models.Repository, search string, skip, limit int) ([]*git.Branch, int, error) {
return git.GetSearchBranchesByPath(repo.RepoPath(), search, skip, limit)
}
// GetBranches returns branches from the repository, skipping skip initial branches and
// returning at most limit branches, or all branches if limit is 0.
func GetBranches(repo *models.Repository, skip, limit int) ([]*git.Branch, int, error) {

View File

@ -54,18 +54,18 @@ func (bk BranchKind) Title() string {
type BranchesSlice struct {
BranchName string `json:"branch_name"`
// BranchKind int `json:"branch_kind"`
Branches []Branch `json:"branches"`
Branches []*Branch `json:"branches"`
}
// sort by branchkind
type SortBranch []Branch
type SortBranch []*Branch
func (s SortBranch) Len() int { return len(s) }
func (s SortBranch) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s SortBranch) Less(i, j int) bool { return s[i].BranchKind < s[j].BranchKind }
// sort by CommiTime of the branch
type SortBranchTime []Branch
type SortBranchTime []*Branch
func (s SortBranchTime) Len() int { return len(s) }
func (s SortBranchTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] }

View File

@ -30,6 +30,19 @@ type CreateFileOptions struct {
Content string `json:"content"`
}
// BatchCreateFileOptions options for creating more files
type BatchChangeFileOptions struct {
Header FileOptions `json:"header"`
Files []struct {
// enum: text,base64
Encoding string `json:"encoding"`
FilePath string `json:"file_path"`
Content string `json:"content"`
// enum: create,update,delete
ActionType string `json:"action_type"`
} `json:"files"`
}
// DeleteFileOptions options for deleting files (used for other File structs below)
// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)
type DeleteFileOptions struct {
@ -106,6 +119,12 @@ type FileResponse struct {
Verification *PayloadCommitVerification `json:"verification"`
}
type BatchFileResponse struct {
Contents []*ContentsResponse `json:"contents"`
Commit *FileCommitResponse `json:"commit"`
Verification *PayloadCommitVerification `json:"verification"`
}
// FileDeleteResponse contains information about a repo's file that was deleted
type FileDeleteResponse struct {
Content interface{} `json:"content"` // to be set to nil

View File

@ -0,0 +1,55 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package structs
// WikiCommit page commit/revision
type WikiCommit struct {
ID string `json:"sha"`
Author *CommitUser `json:"author"`
Committer *CommitUser `json:"commiter"`
Message string `json:"message"`
}
// WikiPage a wiki page
type WikiPage struct {
*WikiPageMetaData
// Page content, base64 encoded
ContentBase64 string `json:"content_base64"`
CommitCount int64 `json:"commit_count"`
Sidebar string `json:"sidebar"`
Footer string `json:"footer"`
}
// WikiPageMetaData wiki page meta information
type WikiPageMetaData struct {
Title string `json:"title"`
HTMLURL string `json:"html_url"`
SubURL string `json:"sub_url"`
LastCommit *WikiCommit `json:"last_commit"`
}
type WikiListMetaData struct {
Type string `json:"type"`
Name string `json:"name"`
HTMLURL string `json:"html_url"`
SubURL string `json:"sub_url"`
LastCommit *WikiCommit `json:"last_commit"`
}
// CreateWikiPageOptions form for creating wiki
type CreateWikiPageOptions struct {
// page title. leave empty to keep unchanged
Title string `json:"title"`
// content must be base64 encoded
ContentBase64 string `json:"content_base64"`
// optional commit message summarizing the change
Message string `json:"message"`
}
// WikiCommitList commit/revision list
type WikiCommitList struct {
WikiCommits []*WikiCommit `json:"commits"`
Count int64 `json:"count"`
}

View File

@ -6,13 +6,13 @@ type WikiesResponse struct {
}
type WikiMeta struct {
Name string `json:"name"`
Commit WikiCommit `json:"commit"`
FirstCommit WikiCommit `json:"-"`
Name string `json:"name"`
Commit OldWikiCommit `json:"commit"`
FirstCommit OldWikiCommit `json:"-"`
//WikiCloneLink CloneLink `json:"wiki_clone_link"`
}
type WikiCommit struct {
type OldWikiCommit struct {
ID string `json:"id"`
Message string `json:"message"`
Author WikiUser `json:"author"`

View File

@ -826,12 +826,12 @@ func Routes() *web.Route {
})
}, reqToken(), reqAdmin())
m.Group("/wikies", func() {
m.Combo("").Get(repo.ListWikiPages).
Post(bind(api.WikiOption{}), repo.CreateWiki)
m.Combo("").Get(repo.OldListWikiPages).
Post(bind(api.WikiOption{}), repo.OldCreateWiki)
m.Group("/{page}", func() {
m.Combo("").Get(repo.GetWiki).
Patch(bind(api.WikiOption{}), repo.EditWiki).
Delete(repo.DeleteWiki)
m.Combo("").Get(repo.OldGetWiki).
Patch(bind(api.WikiOption{}), repo.OldEditWiki).
Delete(repo.OldDeleteWiki)
})
})
m.Group("/readme", func() {
@ -859,6 +859,15 @@ func Routes() *web.Route {
m.Combo("").Get(repo.ListTrackedTimesByRepository)
m.Combo("/{timetrackingusername}").Get(repo.ListTrackedTimesByUser)
}, mustEnableIssues, reqToken())
m.Group("/wiki", func() {
m.Combo("/page/{pageName}").
Get(repo.GetWikiPage).
Patch(mustNotBeArchived, bind(api.CreateWikiPageOptions{}), repo.EditWikiPage).
Delete(mustNotBeArchived, repo.DeleteWikiPage)
m.Get("/revisions/{pageName}", repo.ListPageRevisions)
m.Post("/new", mustNotBeArchived, bind(api.CreateWikiPageOptions{}), repo.NewWikiPage)
m.Get("/pages", repo.ListWikiPages)
})
m.Group("/issues", func() {
m.Combo("").Get(repo.ListIssues).
Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueOption{}), repo.CreateIssue)
@ -1023,6 +1032,9 @@ func Routes() *web.Route {
m.Put("", bind(api.UpdateFileOptions{}), repo.UpdateFile)
m.Delete("", bind(api.DeleteFileOptions{}), repo.DeleteFile)
}, reqRepoWriter(models.UnitTypeCode), reqToken())
m.Group("/batch", func() {
m.Post("", bind(api.BatchChangeFileOptions{}), repo.BatchChangeFile)
}, reqRepoWriter(models.UnitTypeCode), reqToken())
}, reqRepoReader(models.UnitTypeCode))
m.Get("/signing-key.gpg", misc.SigningKey)
m.Group("/topics", func() {
@ -1035,6 +1047,8 @@ func Routes() *web.Route {
}, reqAnyRepoReader())
m.Get("/issue_templates", context.ReferencesGitRepo(false), repo.GetIssueTemplates)
m.Get("/languages", reqRepoReader(models.UnitTypeCode), repo.GetLanguages)
m.Get("/diffs", context.RepoRef(), repo.GetRepoDiffs)
m.Get("/blame", context.RepoRef(), repo.GetRepoRefBlame)
}, repoAssignment())
})

View File

@ -0,0 +1,115 @@
package repo
import (
"fmt"
"net/http"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
)
type APIBlameResponse struct {
FileSize int64 `json:"file_size"`
FileName string `json:"file_name"`
NumberLines int `json:"num_lines"`
BlameParts []git.ApiBlamePart `json:"blame_parts"`
}
func GetRepoRefBlame(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/blame repository repoGetRefBlame
// ---
// summary: Get blame from a repository by sha and filepath***
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: sha
// in: query
// description: repo commit sha or branch
// type: string
// required: true
// - name: filepath
// in: query
// description: filepath in repository
// type: string
// required: true
// responses:
// 200:
// description: success
// "404":
// "$ref": "#/responses/notFound"
if ctx.Repo.Repository.IsEmpty {
ctx.NotFound()
return
}
var commit *git.Commit
if sha := ctx.QueryTrim("sha"); len(sha) > 0 {
var err error
commit, err = ctx.Repo.GitRepo.GetCommit(sha)
if err != nil {
if git.IsErrNotExist(err) {
ctx.NotFound()
} else {
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
}
return
}
}
fmt.Println(commit)
filepath := ctx.QueryTrim("filepath")
fmt.Println(filepath)
entry, err := commit.GetTreeEntryByPath(filepath)
if err != nil {
ctx.NotFoundOrServerError("commit.GetTreeEntryByPath", git.IsErrNotExist, err)
return
}
blob := entry.Blob()
numLines, err := blob.GetBlobLineCount()
if err != nil {
ctx.NotFound("GetBlobLineCount", err)
return
}
blameReader, err := git.CreateBlameReader(ctx, models.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name), commit.ID.String(), filepath)
if err != nil {
ctx.NotFound("CreateBlameReader", err)
return
}
defer blameReader.Close()
blameParts := make([]git.ApiBlamePart, 0)
currentNumber := 1
for {
blamePart, err := blameReader.NextApiPart(ctx.Repo.GitRepo)
if err != nil {
ctx.NotFound("NextPart", err)
return
}
if blamePart == nil {
break
}
blamePart.CurrentNumber = currentNumber
blameParts = append(blameParts, *blamePart)
currentNumber += blamePart.EffectLine
}
ctx.JSON(http.StatusOK, APIBlameResponse{
FileSize: blob.Size(),
FileName: blob.Name(),
NumberLines: numLines,
BlameParts: blameParts,
})
}

View File

@ -244,6 +244,10 @@ func ListBranches(ctx *context.APIContext) {
// description: name of the repo
// type: string
// required: true
// - name: name
// in: query
// description: name of the branch
// type: string
// - name: page
// in: query
// description: page number of results to return (1-based)
@ -256,9 +260,10 @@ func ListBranches(ctx *context.APIContext) {
// "200":
// "$ref": "#/responses/BranchList"
searchName := ctx.Query("name")
listOptions := utils.GetListOptions(ctx)
skip, _ := listOptions.GetStartEnd()
branches, totalNumOfBranches, err := repo_module.GetBranches(ctx.Repo.Repository, skip, listOptions.PageSize)
branches, totalNumOfBranches, err := repo_module.GetSearchBranches(ctx.Repo.Repository, searchName, skip, listOptions.PageSize)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetBranches", err)
return
@ -307,21 +312,27 @@ func ListBranchesSlice(ctx *context.APIContext) {
// description: name of the repo
// type: string
// required: true
// - name: page
// in: query
// description: page number of results to return (1-based)
// type: integer
// - name: limit
// in: query
// description: page size of results
// type: integer
// responses:
// "200":
// "$ref": "#/responses/BranchList"
// listOptions := utils.GetListOptions(ctx)
// skip, _ := listOptions.GetStartEnd()
// branches, totalNumOfBranches, err := repo_module.GetBranches(ctx.Repo.Repository, skip, listOptions.PageSize)
listOptions := utils.GetListOptions(ctx)
skip, _ := listOptions.GetStartEnd()
branches, totalNumOfBranches, err := repo_module.GetBranchesNoLimit(ctx.Repo.Repository)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetBranches", err)
return
}
apiBranches := make([]*api.Branch, len(branches))
apiBranchesList := []api.Branch{}
// apiBranchesSlice := []api.Branch{}
for i := range branches {
c, err := branches[i].GetCommit()
if err != nil {
@ -338,19 +349,38 @@ func ListBranchesSlice(ctx *context.APIContext) {
ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
return
}
apiBranchesList = append(apiBranchesList, *apiBranches[i])
sort.Sort(api.SortBranch(apiBranchesList))
}
sort.Sort(api.SortBranch(apiBranches))
branchSlice := pageate(apiBranches, skip, listOptions.PageSize)
BranchesSlice := BranchesSliceByProtection(ctx, branchSlice)
// ctx.SetLinkHeader(int(totalNumOfBranches), listOptions.PageSize)
ctx.SetLinkHeader(int(totalNumOfBranches), listOptions.PageSize)
ctx.Header().Set("X-Total-Count", fmt.Sprintf("%d", totalNumOfBranches))
ctx.Header().Set("Access-Control-Expose-Headers", "X-Total-Count, Link")
// ctx.JSON(http.StatusOK, &apiBranches)
ctx.JSON(http.StatusOK, BranchesSliceByProtection(ctx, apiBranchesList))
ctx.JSON(http.StatusOK, &BranchesSlice)
}
func BranchesSliceByProtection(ctx *context.APIContext, branchList []api.Branch) []api.BranchesSlice {
func pageate(branchSlice []*api.Branch, skip, pageSize int) []*api.Branch {
limit := func() int {
if skip+pageSize > len(branchSlice) {
return len(branchSlice)
} else {
return skip + pageSize
}
}
start := func() int {
if skip > len(branchSlice) {
return len(branchSlice)
} else {
return skip
}
}
return branchSlice[start():limit()]
}
func BranchesSliceByProtection(ctx *context.APIContext, branchList []*api.Branch) []api.BranchesSlice {
// group by protection
sort.Sort(api.SortBranch(branchList))
branchSlice := make([]api.BranchesSlice, 0)

View File

@ -539,7 +539,34 @@ func GetFileAllCommits(ctx *context.APIContext) {
}
// 获取 commit diff
// Diff get diffs by commit on a repository
func Diff(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/commits/{sha}/diff repository repoGetDiffs***
// ---
// summary: Get diffs by commit from a repository
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: sha
// in: path
// description: name of the repo
// type: string
// required: true
// responses:
// 200:
// description: success
// "404":
// "$ref": "#/responses/notFound"
commitID := ctx.Params(":sha")

View File

@ -0,0 +1,81 @@
package repo
import (
"net/http"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/gitdiff"
)
// GetRepoDiffs get diffs by from\to on a repository
func GetRepoDiffs(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/diffs repository repoGetDiffs
// ---
// summary: Get diffs from a repository
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: from
// in: query
// description: "from branch or sha"
// type: string
// required: false
// - name: to
// in: query
// description: "to branch or sha"
// type: string
// required: false
// responses:
// 200:
// description: success
// "404":
// "$ref": "#/responses/notFound"
if ctx.Repo.Repository.IsEmpty {
ctx.NotFound()
return
}
if from := ctx.QueryTrim("from"); len(from) > 0 {
var err error
_, err = ctx.Repo.GitRepo.GetCommit(from)
if err != nil {
if git.IsErrNotExist(err) {
ctx.NotFound()
} else {
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
}
return
}
}
if to := ctx.QueryTrim("to"); len(to) > 0 {
var err error
_, err = ctx.Repo.GitRepo.GetCommit(to)
if err != nil {
if git.IsErrNotExist(err) {
ctx.NotFound()
} else {
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
}
return
}
}
if diffs, err := gitdiff.GetDiffRange(ctx.Repo.GitRepo, ctx.QueryTrim("from"), ctx.QueryTrim("to"), setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles); err == nil {
ctx.JSON(200, diffs)
} else {
ctx.Error(http.StatusInternalServerError, "GetDiffRange", err)
}
}

View File

@ -280,11 +280,76 @@ func CreateFile(ctx *context.APIContext) {
if fileResponse, err := createOrUpdateFile(ctx, opts); err != nil {
handleCreateOrUpdateFileError(ctx, err)
return
} else {
ctx.JSON(http.StatusCreated, fileResponse)
}
}
// BatchChangeFile handles API call for change some files***
func BatchChangeFile(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/contents/batch repository repoBatchChangeFile
// ---
// summary: Change some files in a repository***
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: body
// in: body
// required: true
// schema:
// "$ref": "#/definitions/BatchChangeFileOptions"
// responses:
// "201":
// "$ref": "#/responses/BatchFileResponse"
// "403":
// "$ref": "#/responses/error"
// "404":
// "$ref": "#/responses/notFound"
// "422":
// "$ref": "#/responses/error"
apiBatchOpts := web.GetForm(ctx).(*api.BatchChangeFileOptions)
fmt.Println(apiBatchOpts)
if ctx.Repo.Repository.IsEmpty {
ctx.Error(http.StatusUnprocessableEntity, "RepoIsEmpty", fmt.Errorf("repo is empty"))
}
if apiBatchOpts.Header.BranchName == "" {
apiBatchOpts.Header.BranchName = ctx.Repo.Repository.DefaultBranch
}
if apiBatchOpts.Header.Message == "" {
apiBatchOpts.Header.Message = time.Now().Format("RFC3339")
}
if apiBatchOpts.Header.Dates.Author.IsZero() {
apiBatchOpts.Header.Dates.Author = time.Now()
}
if apiBatchOpts.Header.Dates.Committer.IsZero() {
apiBatchOpts.Header.Dates.Committer = time.Now()
}
if batchFileResponse, err := createOrUpdateOrDeleteFiles(ctx, apiBatchOpts); err != nil {
handleCreateOrUpdateFileError(ctx, err)
} else {
ctx.JSON(http.StatusOK, batchFileResponse)
}
}
// UpdateFile handles API call for updating a file
func UpdateFile(ctx *context.APIContext) {
// swagger:operation PUT /repos/{owner}/{repo}/contents/{filepath} repository repoUpdateFile
@ -392,6 +457,59 @@ func handleCreateOrUpdateFileError(ctx *context.APIContext, err error) {
ctx.Error(http.StatusInternalServerError, "UpdateFile", err)
}
func createOrUpdateOrDeleteFiles(ctx *context.APIContext, apiBatchOpts *api.BatchChangeFileOptions) (*api.BatchFileResponse, error) {
if !canWriteFiles(ctx.Repo) {
return nil, models.ErrUserDoesNotHaveAccessToRepo{
UserID: ctx.User.ID,
RepoName: ctx.Repo.Repository.LowerName,
}
}
fileChan := make(chan repofiles.BatchSingleFileOption)
stopChan := make(chan bool)
errChan := make(chan error)
exchangeOption := &repofiles.ExchangeFileOption{
FileChan: fileChan,
StopChan: stopChan,
ErrChan: errChan,
}
go func() {
for _, f := range apiBatchOpts.Files {
if f.Encoding == "base64" {
content, err := base64.StdEncoding.DecodeString(f.Content)
exchangeOption.ErrChan <- err
f.Content = string(content)
}
exchangeOption.FileChan <- repofiles.BatchSingleFileOption{
Content: f.Content,
TreePath: f.FilePath,
ActionType: repofiles.ToFileActionType(f.ActionType),
}
}
exchangeOption.StopChan <- true
}()
opts := &repofiles.BatchUpdateFileOptions{
Message: apiBatchOpts.Header.Message,
OldBranch: apiBatchOpts.Header.BranchName,
NewBranch: apiBatchOpts.Header.NewBranchName,
Commiter: &repofiles.IdentityOptions{
Name: apiBatchOpts.Header.Committer.Name,
Email: apiBatchOpts.Header.Committer.Email,
},
Author: &repofiles.IdentityOptions{
Name: apiBatchOpts.Header.Author.Name,
Email: apiBatchOpts.Header.Author.Email,
},
Dates: &repofiles.CommitDateOptions{
Author: apiBatchOpts.Header.Dates.Author,
Committer: apiBatchOpts.Header.Dates.Committer,
},
Signoff: apiBatchOpts.Header.Signoff,
}
return repofiles.CreateOrUpdateOrDeleteRepofiles(ctx.Repo.Repository, ctx.User, opts, exchangeOption)
}
// Called from both CreateFile or UpdateFile to handle both
func createOrUpdateFile(ctx *context.APIContext, opts *repofiles.UpdateRepoFileOptions) (*api.FileResponse, error) {
if !canWriteFiles(ctx.Repo) {

View File

@ -1383,7 +1383,39 @@ func GetPullCommits(ctx *context.APIContext) {
ctx.JSON(200, result)
}
// GetPullFiles gets all files with a given PR
func GetPullFiles(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/pulls/{index}/files repository repoListPullRequestsFiles
// ---
// summary: List a repo's pull requests files
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: index
// in: path
// description: index of the pull request to get
// type: integer
// format: int64
// required: true
// - name: not-need-files
// in: query
// description: "not need responses files"
// type: string
// enum: [true, false]
// responses:
// "200":
// "$ref": "#/responses/Diff"
pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
issue := pr.Issue
@ -1430,6 +1462,22 @@ func GetPullFiles(ctx *context.APIContext) {
endCommitID = headCommitID
ctx.Data["WhitespaceBehavior"] = ""
if ctx.Query("not-need-files") == "true" {
diff := &gitdiff.Diff{Files: make([]*gitdiff.DiffFile, 0)}
shortstatArgs := []string{startCommitID + "..." + endCommitID}
if len(startCommitID) == 0 || startCommitID == git.EmptySHA {
shortstatArgs = []string{git.EmptyTreeSHA, endCommitID}
}
diff.NumFiles, diff.TotalAddition, diff.TotalDeletion, err = git.GetDiffShortStat(gitRepo.Path, shortstatArgs...)
if err != nil && strings.Contains(err.Error(), "no merge base") {
// git >= 2.28 now returns an error if base and head have become unrelated.
// previously it would return the results of git diff --shortstat base head so let's try that...
shortstatArgs = []string{startCommitID, endCommitID}
diff.NumFiles, diff.TotalAddition, diff.TotalDeletion, err = git.GetDiffShortStat(gitRepo.Path, shortstatArgs...)
}
ctx.JSON(200, diff)
return
}
diff, err := gitdiff.GetDiffRangeWithWhitespaceBehavior(gitRepo,
startCommitID, endCommitID, setting.Git.MaxGitDiffLines,
setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles,

View File

@ -1198,17 +1198,20 @@ func CompareDiff(ctx *context.APIContext) {
}
different := struct {
Commits []CompareCommit
Diff interface{}
LatestSha string
Commits []CompareCommit
Diff interface{}
CommitsCount int
LatestSha string
}{
Commits: result,
Diff: ctx.Context.Data["Diff"],
}
if len(different.Commits) != 0 {
different.LatestSha = different.Commits[0].Sha
}
// if len(different.Commits) != 0 {
// different.LatestSha = different.Commits[0].Sha
// }
different.CommitsCount = compareInfo.Commits.Len()
different.LatestSha = compareInfo.HeadCommitID
ctx.JSON(200, different)
}

View File

@ -301,7 +301,9 @@ func BranchTagCount(ctx *context.APIContext) {
// "200":
// "$ref": "#/responses/RepoBranchAndTagCount"
tagsCount, err := ctx.Repo.GitRepo.GetTagCount() // tags info
tagsCount, err := models.GetReleaseCountByRepoID(ctx.Repo.Repository.ID, models.FindReleasesOptions{
IncludeTags: true,
})
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetTagCount", err)
return
@ -336,12 +338,17 @@ func BranchNameSet(ctx *context.APIContext) {
// description: name of the repo
// type: string
// required: true
// - name: name
// in: query
// description: name of the branch
// type: string
// responses:
// "200":
// "$ref": "#/responses/BranchNameSet"
searchName := ctx.Query("name")
repo := ctx.Repo.Repository
branches, _, err := repo_module.GetBranches(repo, 0, 0) //get count of the branch
branches, _, err := repo_module.GetSearchBranches(repo, searchName, 0, 0) //get count of the branch
if err != nil {
ctx.ServerError("GetBranches", err)
return

View File

@ -2,15 +2,22 @@ package repo
import (
"bytes"
"encoding/base64"
"fmt"
"net/http"
"net/url"
"sort"
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
webWiki "code.gitea.io/gitea/routers/web/repo"
"github.com/russross/blackfriday/v2"
@ -18,7 +25,525 @@ import (
wiki_service "code.gitea.io/gitea/services/wiki"
)
// NewWikiPage response for wiki create request
func NewWikiPage(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/wiki/new repository repoCreateWikiPage
// ---
// summary: Create a wiki page
// consumes:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/CreateWikiPageOptions"
// responses:
// "201":
// "$ref": "#/responses/WikiPage"
// "400":
// "$ref": "#/responses/error"
// "403":
// "$ref": "#/responses/forbidden"
form := web.GetForm(ctx).(*api.CreateWikiPageOptions)
if util.IsEmptyString(form.Title) {
ctx.Error(http.StatusBadRequest, "emptyTitle", nil)
return
}
wikiName := wiki_service.NormalizeWikiName(form.Title)
if len(form.Message) == 0 {
form.Message = fmt.Sprintf("Add '%s'", form.Title)
}
content, err := base64.StdEncoding.DecodeString(form.ContentBase64)
if err != nil {
ctx.Error(http.StatusBadRequest, "invalid base64 encoding of content", err)
return
}
form.ContentBase64 = string(content)
if err := wiki_service.AddWikiPage(ctx.User, ctx.Repo.Repository, wikiName, form.ContentBase64, form.Message); err != nil {
if models.IsErrWikiReservedName(err) {
ctx.Error(http.StatusBadRequest, "IsErrWikiReservedName", err)
} else if models.IsErrWikiAlreadyExist(err) {
ctx.Error(http.StatusBadRequest, "IsErrWikiAlreadyExists", err)
} else {
ctx.Error(http.StatusInternalServerError, "AddWikiPage", err)
}
return
}
wikiPage := getWikiPage(ctx, wikiName)
if !ctx.Written() {
ctx.JSON(http.StatusCreated, wikiPage)
}
}
// EditWikiPage response for wiki modify request
func EditWikiPage(ctx *context.APIContext) {
// swagger:operation PATCH /repos/{owner}/{repo}/wiki/page/{pageName} repository repoEditWikiPage
// ---
// summary: Edit a wiki page
// consumes:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: pageName
// in: path
// description: name of the page
// type: string
// required: true
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/CreateWikiPageOptions"
// responses:
// "200":
// "$ref": "#/responses/WikiPage"
// "400":
// "$ref": "#/responses/error"
// "403":
// "$ref": "#/responses/forbidden"
form := web.GetForm(ctx).(*api.CreateWikiPageOptions)
oldWikiName := wiki_service.NormalizeWikiName(ctx.Params(":pageName"))
newWikiName := wiki_service.NormalizeWikiName(form.Title)
if len(newWikiName) == 0 {
newWikiName = oldWikiName
}
if len(form.Message) == 0 {
form.Message = fmt.Sprintf("Update '%s'", newWikiName)
}
content, err := base64.RawStdEncoding.DecodeString(form.ContentBase64)
if err != nil {
ctx.Error(http.StatusBadRequest, "invalid base64 encoding of content", err)
return
}
form.ContentBase64 = string(content)
if err := wiki_service.EditWikiPage(ctx.User, ctx.Repo.Repository, oldWikiName, newWikiName, form.ContentBase64, form.Message); err != nil {
ctx.Error(http.StatusInternalServerError, "EditWikiPage", err)
return
}
wikiPage := getWikiPage(ctx, newWikiName)
if !ctx.Written() {
ctx.JSON(http.StatusOK, wikiPage)
}
}
func getWikiPage(ctx *context.APIContext, title string) *api.WikiPage {
title = wiki_service.NormalizeWikiName(title)
wikiRepo, commit := findWikiRepoCommit(ctx)
if wikiRepo != nil {
defer wikiRepo.Close()
}
if ctx.Written() {
return nil
}
// lookup filename in wiki - get filecontent, real filename
content, pageFilename := wikiContentsByName(ctx, commit, title, false)
if ctx.Written() {
return nil
}
sidebarContent, _ := wikiContentsByName(ctx, commit, "_Sidebar", true)
if ctx.Written() {
return nil
}
footerContent, _ := wikiContentsByName(ctx, commit, "_Footer", true)
if ctx.Written() {
return nil
}
// get commit count - wiki revisions
commitsCount, _ := wikiRepo.FileCommitsCount("master", pageFilename)
// Get last change information
lastCommit, err := wikiRepo.GetCommitByPath(pageFilename)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetCommitByPath", err)
return nil
}
return &api.WikiPage{
WikiPageMetaData: convert.ToWikiPageMetaData(title, lastCommit, ctx.Repo.Repository),
ContentBase64: content,
CommitCount: commitsCount,
Sidebar: sidebarContent,
Footer: footerContent,
}
}
// DeleteWikiPage delete wiki page
func DeleteWikiPage(ctx *context.APIContext) {
// swagger:operation DELETE /repos/{owner}/{repo}/wiki/page/{pageName} repository repoDeleteWikiPage
// ---
// summary: Delete a wiki page
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: pageName
// in: path
// description: name of the page
// type: string
// required: true
// responses:
// "204":
// "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
wikiName := wiki_service.NormalizeWikiName(ctx.Params(":pageName"))
if err := wiki_service.DeleteWikiPage(ctx.User, ctx.Repo.Repository, wikiName); err != nil {
if err.Error() == "file does not exist" {
ctx.NotFound(err)
return
}
ctx.Error(http.StatusInternalServerError, "DeleteWikiPage", err)
return
}
ctx.Status(http.StatusNoContent)
}
// ListWikiPages get wiki pages list
func ListWikiPages(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/wiki/pages repository repoGetWikiPages
// ---
// summary: Get all wiki pages
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: filepath
// in: query
// description: path of the file
// type: string
// required: false
// - name: page
// in: query
// description: page number of results to return (1-based)
// type: integer
// - name: limit
// in: query
// description: page size of results
// type: integer
// responses:
// "200":
// "$ref": "#/responses/WikiPageList"
// "404":
// "$ref": "#/responses/notFound"
wikiRepo, commit := findWikiRepoCommit(ctx)
if wikiRepo != nil {
defer wikiRepo.Close()
}
if ctx.Written() {
return
}
page := ctx.QueryInt("page")
if page <= 1 {
page = 1
}
limit := ctx.QueryInt("limit")
if limit <= 1 {
limit = setting.API.DefaultPagingNum
}
skip := (page - 1) * limit
max := page * limit
filePath := ctx.Query("filepath")
var entries []*git.TreeEntry
var err error
if filePath == "" {
entries, err = commit.ListEntries()
} else {
tree, subTreeErr := commit.SubTree(filePath)
if subTreeErr != nil {
ctx.ServerError("SubTree", err)
return
}
entries, err = tree.ListEntries()
}
if err != nil {
ctx.ServerError("ListEntries", err)
return
}
lists := make([]*api.WikiListMetaData, 0, len(entries))
for i, entry := range entries {
if i < skip || i >= max || (!entry.IsRegular() && !entry.IsDir()) {
continue
}
if entry.IsRegular() {
c, err := wikiRepo.GetCommitByPath(fmt.Sprintf("%s/%s", filePath, entry.Name()))
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
return
}
wikiName, err := wiki_service.FilenameToName(entry.Name())
if err != nil {
if models.IsErrWikiInvalidFileName(err) {
continue
}
ctx.Error(http.StatusInternalServerError, "WikiFilenameToName", err)
return
}
lists = append(lists, convert.RegularToWikiPageMetaData(wikiName, c, ctx.Repo.Repository))
}
if entry.IsDir() {
c, err := wikiRepo.GetCommitByPath(fmt.Sprintf("%s/%s", filePath, entry.Name()))
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
return
}
lists = append(lists, convert.DirToWikiPageMetaData(entry.Name(), c, ctx.Repo.Repository))
}
}
ctx.SetTotalCountHeader(int64(len(entries)))
ctx.JSON(http.StatusOK, lists)
}
// GetWikiPage get single wiki page
func GetWikiPage(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/wiki/page/{pageName} repository repoGetWikiPage
// ---
// summary: Get a wiki page
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: pageName
// in: path
// description: name of the page
// type: string
// required: true
// responses:
// "200":
// "$ref": "#/responses/WikiPage"
// "404":
// "$ref": "#/responses/notFound"
// get requested pagename
pageName := wiki_service.NormalizeWikiName(ctx.Params(":pageName"))
wikiPage := getWikiPage(ctx, pageName)
if !ctx.Written() {
ctx.JSON(http.StatusOK, wikiPage)
}
}
// ListPageRevisions renders file revision list of wiki page
func ListPageRevisions(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/wiki/revisions/{pageName} repository repoGetWikiPageRevisions
// ---
// summary: Get revisions of a wiki page
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: pageName
// in: path
// description: name of the page
// type: string
// required: true
// - name: page
// in: query
// description: page number of results to return (1-based)
// type: integer
// responses:
// "200":
// "$ref": "#/responses/WikiCommitList"
// "404":
// "$ref": "#/responses/notFound"
wikiRepo, commit := findWikiRepoCommit(ctx)
if wikiRepo != nil {
defer wikiRepo.Close()
}
if ctx.Written() {
return
}
// get requested pagename
pageName := wiki_service.NormalizeWikiName(ctx.Params(":pageName"))
if len(pageName) == 0 {
pageName = "Home"
}
// lookup filename in wiki - get filecontent, gitTree entry , real filename
_, pageFilename := wikiContentsByName(ctx, commit, pageName, false)
if ctx.Written() {
return
}
// get commit count - wiki revisions
commitsCount, _ := wikiRepo.FileCommitsCount("master", pageFilename)
page := ctx.QueryInt("page")
if page <= 1 {
page = 1
}
// get Commit Count
commitsHistory, err := wikiRepo.NewCommitsByFileAndRangeNoFollow("master", pageFilename, page)
if err != nil {
ctx.Error(http.StatusInternalServerError, "CommitsByFileAndRangeNoFollow", err)
return
}
ctx.SetTotalCountHeader(commitsCount)
ctx.JSON(http.StatusOK, convert.ToWikiCommitList(commitsHistory, commitsCount))
}
// findEntryForFile finds the tree entry for a target filepath.
func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, error) {
entry, err := commit.GetTreeEntryByPath(target)
if err != nil {
return nil, err
}
if entry != nil {
return entry, nil
}
// Then the unescaped, shortest alternative
var unescapedTarget string
if unescapedTarget, err = url.QueryUnescape(target); err != nil {
return nil, err
}
return commit.GetTreeEntryByPath(unescapedTarget)
}
// findWikiRepoCommit opens the wiki repo and returns the latest commit, writing to context on error.
// The caller is responsible for closing the returned repo again
func findWikiRepoCommit(ctx *context.APIContext) (*git.Repository, *git.Commit) {
wikiRepo, err := git.OpenRepository(ctx.Repo.Repository.WikiPath())
if err != nil {
if git.IsErrNotExist(err) || err.Error() == "no such file or directory" {
ctx.NotFound(err)
} else {
ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
}
return nil, nil
}
commit, err := wikiRepo.GetBranchCommit("master")
if err != nil {
if git.IsErrNotExist(err) {
ctx.NotFound(err)
} else {
ctx.Error(http.StatusInternalServerError, "GetBranchCommit", err)
}
return wikiRepo, nil
}
return wikiRepo, commit
}
// wikiContentsByEntry returns the contents of the wiki page referenced by the
// given tree entry, encoded with base64. Writes to ctx if an error occurs.
func wikiContentsByEntry(ctx *context.APIContext, entry *git.TreeEntry) string {
blob := entry.Blob()
if blob.Size() > setting.API.DefaultMaxBlobSize {
return ""
}
content, err := blob.GetBlobContentBase64()
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetBlobContentBase64", err)
return ""
}
return content
}
// wikiContentsByName returns the contents of a wiki page, along with a boolean
// indicating whether the page exists. Writes to ctx if an error occurs.
func wikiContentsByName(ctx *context.APIContext, commit *git.Commit, wikiName string, isSidebarOrFooter bool) (string, string) {
pageFilename := wiki_service.NameToFilename(wikiName)
entry, err := findEntryForFile(commit, pageFilename)
if err != nil {
if git.IsErrNotExist(err) {
if !isSidebarOrFooter {
ctx.NotFound()
}
} else {
ctx.ServerError("findEntryForFile", err)
}
return "", ""
}
return wikiContentsByEntry(ctx, entry), pageFilename
}
func OldListWikiPages(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/wikies repository repoWikiList
// ---
// summary: List the wikies in a repository
@ -88,7 +613,7 @@ func ListWikiPages(ctx *context.APIContext) {
},
WikiMeta: api.WikiMeta{
Name: wikiName,
Commit: api.WikiCommit{
Commit: api.OldWikiCommit{
Author: api.WikiUser{
Name: lastCommit.Author.Name,
Email: lastCommit.Author.Email,
@ -102,7 +627,7 @@ func ListWikiPages(ctx *context.APIContext) {
ID: lastCommit.ID.String(),
Message: lastCommit.Message(),
},
FirstCommit: api.WikiCommit{
FirstCommit: api.OldWikiCommit{
Author: api.WikiUser{
Name: firstCommit.Author.Name,
Email: firstCommit.Author.Email,
@ -126,7 +651,7 @@ func ListWikiPages(ctx *context.APIContext) {
ctx.JSON(http.StatusOK, pages)
}
func CreateWiki(ctx *context.APIContext) {
func OldCreateWiki(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/wikies repository repoCreateWiki
// ---
// summary: Create a wiki in a repository
@ -174,6 +699,11 @@ func CreateWiki(ctx *context.APIContext) {
return
}
wikiRepo, commit, _ := webWiki.FindWikiRepoCommit(ctx.Context)
defer func() {
if wikiRepo != nil {
wikiRepo.Close()
}
}()
data, entry, pageFilename, _ := webWiki.WikiContentsByName(ctx.Context, commit, form.Name)
metas := ctx.Repo.Repository.ComposeDocumentMetas()
@ -206,7 +736,7 @@ func CreateWiki(ctx *context.APIContext) {
},
WikiMeta: api.WikiMeta{
Name: form.Name,
Commit: api.WikiCommit{
Commit: api.OldWikiCommit{
Author: api.WikiUser{
Name: c.Author.Name,
Email: c.Author.Email,
@ -229,7 +759,7 @@ func CreateWiki(ctx *context.APIContext) {
}
func GetWiki(ctx *context.APIContext) {
func OldGetWiki(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/wikies/{pagename} repository repoGetWiki
// ---
// summary: Get a Wiki
@ -256,6 +786,11 @@ func GetWiki(ctx *context.APIContext) {
// "$ref": "#/responses/Wiki"
wikiRepo, commit, _ := webWiki.FindWikiRepoCommit(ctx.Context)
defer func() {
if wikiRepo != nil {
wikiRepo.Close()
}
}()
wikiCloneWiki := ctx.Repo.Repository.WikiCloneLink()
@ -305,7 +840,7 @@ func GetWiki(ctx *context.APIContext) {
},
WikiMeta: api.WikiMeta{
Name: pageName,
Commit: api.WikiCommit{
Commit: api.OldWikiCommit{
Author: api.WikiUser{
Name: c.Author.Name,
Email: c.Author.Email,
@ -327,7 +862,7 @@ func GetWiki(ctx *context.APIContext) {
ctx.JSON(http.StatusOK, wiki)
}
func EditWiki(ctx *context.APIContext) {
func OldEditWiki(ctx *context.APIContext) {
// swagger:operation PATCH /repos/{owner}/{repo}/wikies/{pagename} repository repoEditWiki
// ---
// summary: Edit a wiki in a repository
@ -366,6 +901,11 @@ func EditWiki(ctx *context.APIContext) {
return
}
wikiRepo, commit, _ := webWiki.FindWikiRepoCommit(ctx.Context)
defer func() {
if wikiRepo != nil {
wikiRepo.Close()
}
}()
if _, _, _, noEntry := webWiki.WikiContentsByName(ctx.Context, commit, oldWikiName); noEntry {
ctx.Error(http.StatusNotFound, "WikiNotFound", "wiki不存在")
@ -385,7 +925,12 @@ func EditWiki(ctx *context.APIContext) {
ctx.Error(http.StatusInternalServerError, "EditWikiPage", err)
return
}
_, newCommit, _ := webWiki.FindWikiRepoCommit(ctx.Context)
wikiRepo, newCommit, _ := webWiki.FindWikiRepoCommit(ctx.Context)
defer func() {
if wikiRepo != nil {
wikiRepo.Close()
}
}()
data, entry, pageFilename, _ := webWiki.WikiContentsByName(ctx.Context, newCommit, newWikiName)
c, err := wikiRepo.GetCommitByPath(entry.Name())
if err != nil {
@ -415,7 +960,7 @@ func EditWiki(ctx *context.APIContext) {
wiki := api.WikiResponse{
WikiMeta: api.WikiMeta{
Name: form.Name,
Commit: api.WikiCommit{
Commit: api.OldWikiCommit{
Author: api.WikiUser{
Name: c.Author.Name,
Email: c.Author.Email,
@ -436,7 +981,7 @@ func EditWiki(ctx *context.APIContext) {
}
ctx.JSON(http.StatusOK, wiki)
}
func DeleteWiki(ctx *context.APIContext) {
func OldDeleteWiki(ctx *context.APIContext) {
// swagger:operation DELETE /repos/{owner}/{repo}/wikies/{pagename} repository repoDeleteWiki
// ---
// summary: Delete a wiki in a repository

View File

@ -119,6 +119,9 @@ type swaggerParameterBodies struct {
// in:body
CreateFileOptions api.CreateFileOptions
// in:body
BatchChangeFileOptions api.BatchChangeFileOptions
// in:body
UpdateFileOptions api.UpdateFileOptions
@ -172,4 +175,7 @@ type swaggerParameterBodies struct {
// in:body
UserSettingsOptions api.UserSettingsOptions
// in:body
CreateWikiPageOptions api.CreateWikiPageOptions
}

View File

@ -8,6 +8,7 @@ import (
"code.gitea.io/gitea/models"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/viewfile"
"code.gitea.io/gitea/services/gitdiff"
)
// Repository
@ -169,6 +170,13 @@ type swaggerResponsePullRequest struct {
Body api.PullRequest `json:"body"`
}
// Diff
// swagger:response Diff
type swaggerResponseDiff struct {
// in:body
Body gitdiff.Diff `json:"body"`
}
// PullRequestList
// swagger:response PullRequestList
type swaggerResponsePullRequestList struct {
@ -325,6 +333,13 @@ type swaggerFileResponse struct {
Body api.FileResponse `json:"body"`
}
// WikiPageList
// swagger:response WikiPageList
type swaggerWikiPageList struct {
// in:body
Body []api.WikiListMetaData `json:"body"`
}
// ContentsResponse
// swagger:response ContentsResponse
type swaggerContentsResponse struct {

View File

@ -66,10 +66,10 @@ func CheckCreateHookOption(ctx *context.APIContext, form *api.CreateHookOption)
ctx.Error(http.StatusUnprocessableEntity, "", "Invalid content type")
return false
}
if !models.IsValidHookHttpMethod(form.Config["http_method"]) {
ctx.Error(http.StatusUnprocessableEntity, "", "Invalid http method")
return false
}
// if !models.IsValidHookHttpMethod(form.Config["http_method"]) {
// ctx.Error(http.StatusUnprocessableEntity, "", "Invalid http method")
// return false
// }
return true
}
@ -137,8 +137,9 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, orgID, repoID
},
BranchFilter: form.BranchFilter,
},
IsActive: form.Active,
Type: models.HookType(form.Type),
BranchFilter: form.BranchFilter,
IsActive: form.Active,
Type: models.HookType(form.Type),
}
if w.Type == models.SLACK {
channel, ok := form.Config["channel"]

View File

@ -230,11 +230,12 @@ func FileHistory(ctx *context.Context) {
}
page := ctx.QueryInt("page")
limit := ctx.QueryInt("limit")
if page <= 1 {
page = 1
}
commits, err := ctx.Repo.GitRepo.CommitsByFileAndRange(branchName, fileName, page)
commits, err := ctx.Repo.GitRepo.CommitsByFileAndRange(branchName, fileName, page, limit)
if err != nil {
ctx.ServerError("CommitsByFileAndRange", err)
return

View File

@ -727,7 +727,7 @@ func UploadFilePost(ctx *context.Context) {
func cleanUploadFileName(name string) string {
// Rebase the filename
name = strings.Trim(path.Clean("/"+name), " /")
name = strings.Trim(path.Clean("/"+name), "/")
// Git disallows any filenames to have a .git directory in them.
for _, part := range strings.Split(name, "/") {
if strings.ToLower(part) == ".git" {

View File

@ -174,19 +174,19 @@ func SignInPost(ctx *context.Context) {
}
form := web.GetForm(ctx).(*forms.SignInForm)
if user, err := models.GetUserByName(form.UserName); models.IsErrUserNotExist(err) {
ctx.RenderWithErr(ctx.Tr("form.username_password_incorrect"), tplSignIn, &form)
log.Info("Failed authentication attempt for %s from %s: %v", form.UserName, ctx.RemoteAddr(), err)
return
} else {
// If this user not is administrator
// Instead, tip error
if !user.IsAdmin {
ctx.RenderWithErr(ctx.Tr("form.User is not an administrator"), tplSignIn, &form)
log.Info("Failed authentiation attempt for %s from %s ", form.UserName, ctx.RemoteAddr())
return
}
}
// if user, err := models.GetUserByName(form.UserName); models.IsErrUserNotExist(err) {
// ctx.RenderWithErr(ctx.Tr("form.username_password_incorrect"), tplSignIn, &form)
// log.Info("Failed authentication attempt for %s from %s: %v", form.UserName, ctx.RemoteAddr(), err)
// return
// } else {
// // If this user not is administrator
// // Instead, tip error
// if !user.IsAdmin {
// ctx.RenderWithErr(ctx.Tr("form.User is not an administrator"), tplSignIn, &form)
// log.Info("Failed authentiation attempt for %s from %s ", form.UserName, ctx.RemoteAddr())
// return
// }
// }
u, err := models.UserSignIn(form.UserName, form.Password)
if err != nil {

View File

@ -233,10 +233,10 @@ func RegisterRoutes(m *web.Route) {
m.Get("", func(ctx *context.Context) {
ctx.Redirect(setting.AppSubURL + "/explore/repos")
})
m.Get("/repos", explore.Repos)
m.Get("/users", explore.Users)
m.Get("/organizations", explore.Organizations)
m.Get("/code", explore.Code)
m.Get("/repos", reqSignIn, explore.Repos)
m.Get("/users", reqSignIn, explore.Users)
m.Get("/organizations", reqSignIn, explore.Organizations)
m.Get("/code", reqSignIn, explore.Code)
}, ignExploreSignIn)
m.Get("/issues", reqSignIn, user.Issues)
m.Get("/pulls", reqSignIn, user.Pulls)
@ -457,7 +457,7 @@ func RegisterRoutes(m *web.Route) {
// ***** END: Admin *****
m.Group("", func() {
m.Get("/{username}", user.Profile)
m.Get("/{username}", reqSignIn, user.Profile)
m.Get("/attachments/{uuid}", repo.GetAttachment)
}, ignSignIn)

View File

@ -94,6 +94,9 @@ func UpdateAssignees(issue *models.Issue, oneAssignee string, multipleAssignees
// Loop through all assignees to add them
for _, assigneeName := range multipleAssignees {
if assigneeName == "" {
continue
}
assignee, err := models.GetUserByName(assigneeName)
if err != nil {
return err

View File

@ -6,17 +6,18 @@
package wiki
import (
"errors"
"fmt"
"net/url"
"os"
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/sync"
"code.gitea.io/gitea/modules/util"
"errors"
"fmt"
"net/url"
"os"
"strings"
)
var (
@ -40,7 +41,7 @@ func NameToSubURL(name string) string {
// NormalizeWikiName normalizes a wiki name
func NormalizeWikiName(name string) string {
return strings.ReplaceAll(name, "-", " ")
return strings.ReplaceAll(name, "-", "-")
}
// NameToFilename converts a wiki name to its corresponding filename.
@ -80,7 +81,6 @@ func InitWiki(repo *models.Repository) error {
if repo.HasWiki() {
return nil
}
if err := git.InitRepository(repo.WikiPath(), true); err != nil {
return fmt.Errorf("InitRepository: %v", err)
} else if err = repo_module.CreateDelegateHooks(repo.WikiPath()); err != nil {
@ -206,7 +206,7 @@ func updateWikiPage(doer *models.User, repo *models.Repository, oldWikiName, new
}
// FIXME: The wiki doesn't have lfs support at present - if this changes need to check attributes here
// content = strings.Replace(content, "<br/>", "\n", -1)
objectHash, err := gitRepo.HashObject(strings.NewReader(content))
if err != nil {
log.Error("%v", err)

View File

@ -2424,6 +2424,56 @@
}
}
},
"/repos/{owner}/{repo}/blame": {
"get": {
"produces": [
"application/json"
],
"tags": [
"repository"
],
"summary": "Get blame from a repository by sha and filepath***",
"operationId": "repoGetRefBlame",
"parameters": [
{
"type": "string",
"description": "owner of the repo",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "name of the repo",
"name": "repo",
"in": "path",
"required": true
},
{
"type": "string",
"description": "repo commit sha or branch",
"name": "sha",
"in": "query",
"required": true
},
{
"type": "string",
"description": "filepath in repository",
"name": "filepath",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "success"
},
"404": {
"$ref": "#/responses/notFound"
}
}
}
},
"/repos/{owner}/{repo}/branch_name_set": {
"get": {
"produces": [
@ -2448,6 +2498,12 @@
"name": "repo",
"in": "path",
"required": true
},
{
"type": "string",
"description": "name of the branch",
"name": "name",
"in": "query"
}
],
"responses": {
@ -2736,6 +2792,12 @@
"in": "path",
"required": true
},
{
"type": "string",
"description": "name of the branch",
"name": "name",
"in": "query"
},
{
"type": "integer",
"description": "page number of results to return (1-based)",
@ -2827,6 +2889,18 @@
"name": "repo",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "page number of results to return (1-based)",
"name": "page",
"in": "query"
},
{
"type": "integer",
"description": "page size of results",
"name": "limit",
"in": "query"
}
],
"responses": {
@ -3395,6 +3469,59 @@
}
}
},
"/repos/{owner}/{repo}/contents/batch": {
"post": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"repository"
],
"summary": "Change some files in a repository***",
"operationId": "repoBatchChangeFile",
"parameters": [
{
"type": "string",
"description": "owner of the repo",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "name of the repo",
"name": "repo",
"in": "path",
"required": true
},
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/BatchChangeFileOptions"
}
}
],
"responses": {
"201": {
"$ref": "#/responses/BatchFileResponse"
},
"403": {
"$ref": "#/responses/error"
},
"404": {
"$ref": "#/responses/notFound"
},
"422": {
"$ref": "#/responses/error"
}
}
}
},
"/repos/{owner}/{repo}/contents/{filepath}": {
"get": {
"produces": [
@ -3696,6 +3823,54 @@
}
}
},
"/repos/{owner}/{repo}/diffs": {
"get": {
"produces": [
"application/json"
],
"tags": [
"repository"
],
"summary": "Get diffs from a repository",
"operationId": "repoGetDiffs",
"parameters": [
{
"type": "string",
"description": "owner of the repo",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "name of the repo",
"name": "repo",
"in": "path",
"required": true
},
{
"type": "string",
"description": "from branch or sha",
"name": "from",
"in": "query"
},
{
"type": "string",
"description": "to branch or sha",
"name": "to",
"in": "query"
}
],
"responses": {
"200": {
"description": "success"
},
"404": {
"$ref": "#/responses/notFound"
}
}
}
},
"/repos/{owner}/{repo}/editorconfig/{filepath}": {
"get": {
"produces": [
@ -7902,6 +8077,57 @@
}
}
},
"/repos/{owner}/{repo}/pulls/{index}/files": {
"get": {
"produces": [
"application/json"
],
"tags": [
"repository"
],
"summary": "List a repo's pull requests files",
"operationId": "repoListPullRequestsFiles",
"parameters": [
{
"type": "string",
"description": "owner of the repo",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "name of the repo",
"name": "repo",
"in": "path",
"required": true
},
{
"type": "integer",
"format": "int64",
"description": "index of the pull request to get",
"name": "index",
"in": "path",
"required": true
},
{
"enum": [
true,
false
],
"type": "string",
"description": "not need responses files",
"name": "not-need-files",
"in": "query"
}
],
"responses": {
"200": {
"$ref": "#/responses/Diff"
}
}
}
},
"/repos/{owner}/{repo}/pulls/{index}/merge": {
"get": {
"produces": [
@ -10417,6 +10643,290 @@
}
}
},
"/repos/{owner}/{repo}/wiki/new": {
"post": {
"consumes": [
"application/json"
],
"tags": [
"repository"
],
"summary": "Create a wiki page",
"operationId": "repoCreateWikiPage",
"parameters": [
{
"type": "string",
"description": "owner of the repo",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "name of the repo",
"name": "repo",
"in": "path",
"required": true
},
{
"name": "body",
"in": "body",
"schema": {
"$ref": "#/definitions/CreateWikiPageOptions"
}
}
],
"responses": {
"201": {
"$ref": "#/responses/WikiPage"
},
"400": {
"$ref": "#/responses/error"
},
"403": {
"$ref": "#/responses/forbidden"
}
}
}
},
"/repos/{owner}/{repo}/wiki/page/{pageName}": {
"get": {
"produces": [
"application/json"
],
"tags": [
"repository"
],
"summary": "Get a wiki page",
"operationId": "repoGetWikiPage",
"parameters": [
{
"type": "string",
"description": "owner of the repo",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "name of the repo",
"name": "repo",
"in": "path",
"required": true
},
{
"type": "string",
"description": "name of the page",
"name": "pageName",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"$ref": "#/responses/WikiPage"
},
"404": {
"$ref": "#/responses/notFound"
}
}
},
"delete": {
"tags": [
"repository"
],
"summary": "Delete a wiki page",
"operationId": "repoDeleteWikiPage",
"parameters": [
{
"type": "string",
"description": "owner of the repo",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "name of the repo",
"name": "repo",
"in": "path",
"required": true
},
{
"type": "string",
"description": "name of the page",
"name": "pageName",
"in": "path",
"required": true
}
],
"responses": {
"204": {
"$ref": "#/responses/empty"
},
"403": {
"$ref": "#/responses/forbidden"
},
"404": {
"$ref": "#/responses/notFound"
}
}
},
"patch": {
"consumes": [
"application/json"
],
"tags": [
"repository"
],
"summary": "Edit a wiki page",
"operationId": "repoEditWikiPage",
"parameters": [
{
"type": "string",
"description": "owner of the repo",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "name of the repo",
"name": "repo",
"in": "path",
"required": true
},
{
"type": "string",
"description": "name of the page",
"name": "pageName",
"in": "path",
"required": true
},
{
"name": "body",
"in": "body",
"schema": {
"$ref": "#/definitions/CreateWikiPageOptions"
}
}
],
"responses": {
"200": {
"$ref": "#/responses/WikiPage"
},
"400": {
"$ref": "#/responses/error"
},
"403": {
"$ref": "#/responses/forbidden"
}
}
}
},
"/repos/{owner}/{repo}/wiki/pages": {
"get": {
"produces": [
"application/json"
],
"tags": [
"repository"
],
"summary": "Get all wiki pages",
"operationId": "repoGetWikiPages",
"parameters": [
{
"type": "string",
"description": "owner of the repo",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "name of the repo",
"name": "repo",
"in": "path",
"required": true
},
{
"type": "string",
"description": "path of the file",
"name": "filepath",
"in": "query"
},
{
"type": "integer",
"description": "page number of results to return (1-based)",
"name": "page",
"in": "query"
},
{
"type": "integer",
"description": "page size of results",
"name": "limit",
"in": "query"
}
],
"responses": {
"200": {
"$ref": "#/responses/WikiPageList"
},
"404": {
"$ref": "#/responses/notFound"
}
}
}
},
"/repos/{owner}/{repo}/wiki/revisions/{pageName}": {
"get": {
"produces": [
"application/json"
],
"tags": [
"repository"
],
"summary": "Get revisions of a wiki page",
"operationId": "repoGetWikiPageRevisions",
"parameters": [
{
"type": "string",
"description": "owner of the repo",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "name of the repo",
"name": "repo",
"in": "path",
"required": true
},
{
"type": "string",
"description": "name of the page",
"name": "pageName",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "page number of results to return (1-based)",
"name": "page",
"in": "query"
}
],
"responses": {
"200": {
"$ref": "#/responses/WikiCommitList"
},
"404": {
"$ref": "#/responses/notFound"
}
}
}
},
"/repos/{owner}/{repo}/wikies": {
"get": {
"produces": [
@ -13007,6 +13517,50 @@
},
"x-go-package": "code.gitea.io/gitea/modules/structs"
},
"BatchChangeFileOptions": {
"description": "BatchCreateFileOptions options for creating more files",
"type": "object",
"properties": {
"files": {
"type": "array",
"items": {
"type": "object",
"properties": {
"action_type": {
"type": "string",
"enum": [
"create",
"update",
"delete"
],
"x-go-name": "ActionType"
},
"content": {
"type": "string",
"x-go-name": "Content"
},
"encoding": {
"type": "string",
"enum": [
"text",
"base64"
],
"x-go-name": "Encoding"
},
"file_path": {
"type": "string",
"x-go-name": "FilePath"
}
}
},
"x-go-name": "Files"
},
"header": {
"$ref": "#/definitions/FileOptions"
}
},
"x-go-package": "code.gitea.io/gitea/modules/structs"
},
"Branch": {
"description": "Branch represents a repository branch",
"type": "object",
@ -14490,6 +15044,28 @@
},
"x-go-package": "code.gitea.io/gitea/modules/structs"
},
"CreateWikiPageOptions": {
"description": "CreateWikiPageOptions form for creating wiki",
"type": "object",
"properties": {
"content_base64": {
"description": "content must be base64 encoded",
"type": "string",
"x-go-name": "ContentBase64"
},
"message": {
"description": "optional commit message summarizing the change",
"type": "string",
"x-go-name": "Message"
},
"title": {
"description": "page title. leave empty to keep unchanged",
"type": "string",
"x-go-name": "Title"
}
},
"x-go-package": "code.gitea.io/gitea/modules/structs"
},
"Cron": {
"description": "Cron represents a Cron task",
"type": "object",
@ -14624,6 +15200,187 @@
},
"x-go-package": "code.gitea.io/gitea/modules/structs"
},
"Diff": {
"type": "object",
"title": "Diff represents a difference between two git trees.",
"properties": {
"Files": {
"type": "array",
"items": {
"$ref": "#/definitions/DiffFile"
}
},
"IsIncomplete": {
"type": "boolean"
},
"NumFiles": {
"type": "integer",
"format": "int64",
"x-go-name": "TotalDeletion"
}
},
"x-go-package": "code.gitea.io/gitea/services/gitdiff"
},
"DiffFile": {
"type": "object",
"title": "DiffFile represents a file diff.",
"properties": {
"Addition": {
"type": "integer",
"format": "int64",
"x-go-name": "Deletion"
},
"Index": {
"type": "integer",
"format": "int64"
},
"IsAmbiguous": {
"type": "boolean"
},
"IsBin": {
"type": "boolean"
},
"IsCreated": {
"type": "boolean"
},
"IsDeleted": {
"type": "boolean"
},
"IsIncomplete": {
"type": "boolean"
},
"IsIncompleteLineTooLong": {
"type": "boolean"
},
"IsLFSFile": {
"type": "boolean"
},
"IsProtected": {
"type": "boolean"
},
"IsRenamed": {
"type": "boolean"
},
"IsSubmodule": {
"type": "boolean"
},
"Name": {
"type": "string"
},
"OldName": {
"type": "string"
},
"Sections": {
"type": "array",
"items": {
"$ref": "#/definitions/DiffSection"
}
},
"Type": {
"$ref": "#/definitions/DiffFileType"
}
},
"x-go-package": "code.gitea.io/gitea/services/gitdiff"
},
"DiffFileType": {
"type": "integer",
"format": "uint8",
"title": "DiffFileType represents the type of a DiffFile.",
"x-go-package": "code.gitea.io/gitea/services/gitdiff"
},
"DiffLine": {
"type": "object",
"title": "DiffLine represents a line difference in a DiffSection.",
"properties": {
"Comments": {
"type": "array",
"items": {
"$ref": "#/definitions/Comment"
}
},
"Content": {
"type": "string"
},
"LeftIdx": {
"type": "integer",
"format": "int64"
},
"Match": {
"type": "integer",
"format": "int64"
},
"RightIdx": {
"type": "integer",
"format": "int64"
},
"SectionInfo": {
"$ref": "#/definitions/DiffLineSectionInfo"
},
"Type": {
"$ref": "#/definitions/DiffLineType"
}
},
"x-go-package": "code.gitea.io/gitea/services/gitdiff"
},
"DiffLineSectionInfo": {
"description": "DiffLineSectionInfo represents diff line section meta data",
"type": "object",
"properties": {
"LastLeftIdx": {
"type": "integer",
"format": "int64"
},
"LastRightIdx": {
"type": "integer",
"format": "int64"
},
"LeftHunkSize": {
"type": "integer",
"format": "int64"
},
"LeftIdx": {
"type": "integer",
"format": "int64"
},
"Path": {
"type": "string"
},
"RightHunkSize": {
"type": "integer",
"format": "int64"
},
"RightIdx": {
"type": "integer",
"format": "int64"
}
},
"x-go-package": "code.gitea.io/gitea/services/gitdiff"
},
"DiffLineType": {
"type": "integer",
"format": "uint8",
"title": "DiffLineType represents the type of a DiffLine.",
"x-go-package": "code.gitea.io/gitea/services/gitdiff"
},
"DiffSection": {
"type": "object",
"title": "DiffSection represents a section of a DiffFile.",
"properties": {
"FileName": {
"type": "string"
},
"Lines": {
"type": "array",
"items": {
"$ref": "#/definitions/DiffLine"
}
},
"Name": {
"type": "string"
}
},
"x-go-package": "code.gitea.io/gitea/services/gitdiff"
},
"DismissPullReviewOptions": {
"description": "DismissPullReviewOptions are options to dismiss a pull review",
"type": "object",
@ -15454,6 +16211,42 @@
},
"x-go-package": "code.gitea.io/gitea/modules/structs"
},
"FileOptions": {
"description": "FileOptions options for all file APIs",
"type": "object",
"properties": {
"author": {
"$ref": "#/definitions/Identity"
},
"branch": {
"description": "branch (optional) to base this file from. if not given, the default branch is used",
"type": "string",
"x-go-name": "BranchName"
},
"committer": {
"$ref": "#/definitions/Identity"
},
"dates": {
"$ref": "#/definitions/CommitDateOptions"
},
"message": {
"description": "message (optional) for the commit of this file. if not supplied, a default message will be used",
"type": "string",
"x-go-name": "Message"
},
"new_branch": {
"description": "new_branch (optional) will make a new branch from `branch` before creating the file",
"type": "string",
"x-go-name": "NewBranchName"
},
"signoff": {
"description": "Add a Signed-off-by trailer by the committer at the end of the commit log message.",
"type": "boolean",
"x-go-name": "Signoff"
}
},
"x-go-package": "code.gitea.io/gitea/modules/structs"
},
"FileResponse": {
"description": "FileResponse contains information about a repo's file",
"type": "object",
@ -16630,6 +17423,23 @@
},
"x-go-package": "code.gitea.io/gitea/modules/structs"
},
"OldWikiCommit": {
"type": "object",
"properties": {
"author": {
"$ref": "#/definitions/WikiUser"
},
"id": {
"type": "string",
"x-go-name": "ID"
},
"message": {
"type": "string",
"x-go-name": "Message"
}
},
"x-go-package": "code.gitea.io/gitea/modules/structs"
},
"Organization": {
"description": "Organization represents an organization",
"type": "object",
@ -18226,18 +19036,47 @@
"x-go-package": "code.gitea.io/gitea/modules/structs"
},
"WikiCommit": {
"description": "WikiCommit page commit/revision",
"type": "object",
"properties": {
"author": {
"$ref": "#/definitions/WikiUser"
"$ref": "#/definitions/CommitUser"
},
"id": {
"type": "string",
"x-go-name": "ID"
"commiter": {
"$ref": "#/definitions/CommitUser"
},
"message": {
"type": "string",
"x-go-name": "Message"
},
"sha": {
"type": "string",
"x-go-name": "ID"
}
},
"x-go-package": "code.gitea.io/gitea/modules/structs"
},
"WikiListMetaData": {
"type": "object",
"properties": {
"html_url": {
"type": "string",
"x-go-name": "HTMLURL"
},
"last_commit": {
"$ref": "#/definitions/WikiCommit"
},
"name": {
"type": "string",
"x-go-name": "Name"
},
"sub_url": {
"type": "string",
"x-go-name": "SubURL"
},
"type": {
"type": "string",
"x-go-name": "Type"
}
},
"x-go-package": "code.gitea.io/gitea/modules/structs"
@ -18264,7 +19103,7 @@
"type": "object",
"properties": {
"commit": {
"$ref": "#/definitions/WikiCommit"
"$ref": "#/definitions/OldWikiCommit"
},
"commit_counts": {
"type": "integer",
@ -18312,7 +19151,7 @@
"type": "object",
"properties": {
"commit": {
"$ref": "#/definitions/WikiCommit"
"$ref": "#/definitions/OldWikiCommit"
},
"name": {
"type": "string",
@ -18532,6 +19371,12 @@
}
}
},
"Diff": {
"description": "Diff",
"schema": {
"$ref": "#/definitions/Diff"
}
},
"EmailList": {
"description": "EmailList",
"schema": {
@ -19102,6 +19947,15 @@
"$ref": "#/definitions/WikiesResponse"
}
},
"WikiPageList": {
"description": "WikiPageList",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/WikiListMetaData"
}
}
},
"conflict": {
"description": "APIConflict is a conflict empty response"
},
@ -19150,7 +20004,7 @@
"parameterBodies": {
"description": "parameterBodies",
"schema": {
"$ref": "#/definitions/UserSettingsOptions"
"$ref": "#/definitions/CreateWikiPageOptions"
}
},
"redirect": {
@ -19236,4 +20090,4 @@
"TOTPHeader": []
}
]
}
}