Compare commits

...

78 Commits

Author SHA1 Message Date
yystopf 061d648dad 更改:readme无后缀也显示 2023-03-15 17:48:50 +08:00
yystopf 92d987d157 Merge branch 'develop' of https://code.gitlink.org.cn/Gitlink/gitea-1156 into develop 2023-02-22 17:43:03 +08:00
xxq250 eb45bfd59e 增加木兰开放作品许可协议 2023-02-21 17:49:14 +08:00
yystopf 4f42919095 fix 无法获取文件 2023-02-13 18:43:16 +08:00
yystopf 86b1d5c271 更改:组织导入项目权限校验移除 2022-12-15 16:37:33 +08:00
yystopf b0408b1f20 新增:仓库贡献者行数查询ref默认参数 2022-12-08 11:12:05 +08:00
yystopf 51eaed6519 新增:仓库贡献者行数查询 2022-12-08 10:01:49 +08:00
yystopf 5a640448d8 修复:无法生成swagger文档 2022-12-08 09:43:09 +08:00
yystopf ed6254f545 修复:标签列表无数据返回空数组 2022-10-27 16:53:30 +08:00
yystopf 1d998c5c54 新增:标签名称列表接口 2022-10-18 12:39:58 +08:00
yystopf 2834ba8281 新增:readme文件默认使用中文readme文件 2022-09-21 19:16:34 +08:00
yystopf 7134770a03 新增:添加全部或者移除全部团队项目 2022-08-30 09:58:55 +08:00
yystopf d4880ebd4e 新增:文件blame 上个版本的行号 2022-08-17 18:01:02 +08:00
yystopf 940f16f4fa 修复:移除ref转义 2022-08-11 11:41:59 +08:00
yystopf 24aae5fd00 新增: pr file 支持只显示文件名 2022-08-10 17:33:06 +08:00
yystopf 4c9f6a8400 修复 2022-08-10 14:53:45 +08:00
yystopf 78926ec43d Merge branch 'develop' of https://code.gitlink.org.cn/Gitlink/gitea-1156 into develop 2022-08-08 16:28:42 +08:00
yystopf 6eefa22e78 修复 2022-08-08 14:13:45 +08:00
yystopf f9ef9455d0 Merge pull request '同步代码(readme匹配,webhook类型,gitdiff内容)' (#60) from yystopf/gitea-1156:develop into develop 2022-08-08 11:51:06 +08:00
yystopf db945157cd 修复: 删除无用函数 2022-08-08 11:48:02 +08:00
yystopf e6022ba142 修复:readme文件匹配规则 2022-08-08 11:43:46 +08:00
yystopf cf39a4b405 新增:webhook type类型 2022-08-03 14:21:07 +08:00
yystopf ef32b08ee4 修复:join需要用换行符隔开 2022-08-03 10:40:19 +08:00
yystopf 5795a3f8d6 修复:notfound处理 2022-08-03 09:45:09 +08:00
yystopf 9d0fe5b008 修复: 处理diff方式 2022-08-02 20:16:54 +08:00
yystopf b128dae4bc 修复:解决效率问题 2022-08-02 19:28:06 +08:00
yystopf 66ed1b8f14 新增:版本diff新增diff信息 2022-08-02 18:59:48 +08:00
yystopf a69d22aa0f 修复:批量提交文件删除文件不返回结果 2022-07-28 13:55:50 +08:00
yystopf f433cede0a 修复 2022-07-26 09:39:15 +08:00
yystopf c22f59b45c Merge pull request '合并请求版本功能' (#59) from yystopf/gitea-1156:develop into develop 2022-07-22 15:06:55 +08:00
yystopf 677f1872f6 新增:pr版本根据filepath获取diff 2022-07-22 14:06:24 +08:00
yystopf 6cad6ff212 新增: 获取pr版本的diff接口及文档 2022-07-21 18:38:01 +08:00
yystopf 8757d15065 新增: pr 版本列表接口及文档 2022-07-21 15:10:58 +08:00
yystopf 727f203fd3 修复: pr 版本名修改以及hook新增pr版本 2022-07-21 13:54:53 +08:00
yystopf c1c985e193 新增: pr 版本名修改以及hook新增pr版本 2022-07-20 11:26:49 +08:00
yystopf 57bc507770 新增: pr diff 模型 2022-07-20 09:28:59 +08:00
yystopf b124e6764b 修复: commit diff from to参数交换以及api文档修复 2022-07-20 09:28:17 +08:00
yystopf 9c6139bc61 Merge pull request 'CloudIDE代码阅读功能部分api' (#58) from yystopf/gitea-1156:hh_code_read into develop 2022-07-14 14:26:29 +08:00
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
66 changed files with 4885 additions and 153 deletions

16
.vscode_bak/launch.json Normal file
View File

@ -0,0 +1,16 @@
{
// 使 IntelliSense
//
// 访: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch Package",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${fileDirname}",
"output": "./debug"
}
]
}

15
.vscode_bak/settings.json Normal file
View File

@ -0,0 +1,15 @@
{
"go.formatTool": "goimports",
"go.testFlags": [
"-v"
],
"go.gocodeFlags": [
"-builtin",
"-ignore-case",
"-unimported-packages",
"-exclude-docs"
],
"go.inferGopath": false,
"go.goroot": "/Users/virus/.gvm/gos/go1.16.5",
"go.gopath": "/Users/virus/.gvm/pkgsets/go1.16.5/global",
}

View File

@ -327,6 +327,12 @@ 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),
// v191 -> v192
NewMigration("Create pullrequest diff table", createPullRequestDiffTable),
}
// 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))
}

37
models/migrations/v191.go Normal file
View File

@ -0,0 +1,37 @@
/*
* @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 (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/timeutil"
"xorm.io/xorm"
)
func createPullRequestDiffTable(x *xorm.Engine) error {
type PullRequestVersion struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"INDEX"`
Repo *models.Repository `xorm:"-"`
PullID int64 `xorm:"INDEX"`
Pull *models.PullRequest `xorm:"-"`
AddLineNum int
CommitsCount int
DelLineNum int
FilesCount int
HeadCommitID string `xorm:"VARCHAR(40)"`
BaseCommitID string `xorm:"VARCHAR(40)"`
StartCommitID string `xorm:"VARCHAR(40)"`
CreatedUnix timeutil.TimeStamp `xorm:"created INDEX"`
UpdatedUnix timeutil.TimeStamp `xorm:"updated INDEX"`
}
return x.Sync2(new(PullRequestVersion))
}

View File

@ -82,6 +82,7 @@ func init() {
new(Action),
new(Issue),
new(PullRequest),
new(PullRequestVersion),
new(Comment),
new(Attachment),
new(Label),

111
models/pull_diff_version.go Normal file
View File

@ -0,0 +1,111 @@
package models
import (
"fmt"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil"
"xorm.io/xorm"
)
type PullRequestVersion struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"INDEX"`
Repo *Repository `xorm:"-"`
PullID int64 `xorm:"INDEX"`
Pull *PullRequest `xorm:"-"`
AddLineNum int
CommitsCount int
DelLineNum int
FilesCount int
HeadCommitID string `xorm:"VARCHAR(40)"`
BaseCommitID string `xorm:"VARCHAR(40)"`
StartCommitID string `xorm:"VARCHAR(40)"`
CreatedUnix timeutil.TimeStamp `xorm:"created INDEX"`
UpdatedUnix timeutil.TimeStamp `xorm:"updated INDEX"`
}
func listPullRequestVersionStatement(prID int64, opts *ListOptions) (*xorm.Session, error) {
sess := x.Where("pull_request_version.pull_id=?", prID)
sess.Join("INNER", "repository", "pull_request_version.repo_id = repository.id")
return sess, nil
}
func PullRequestVersions(prID int64, opts *ListOptions) ([]*PullRequestVersion, int64, error) {
if opts.Page <= 0 {
opts.Page = 1
}
countSession, err := listPullRequestVersionStatement(prID, opts)
if err != nil {
log.Error("listPullRequestVersionStatement: %v", err)
return nil, 0, err
}
maxResults, err := countSession.Count(new(PullRequestVersion))
if err != nil {
log.Error("Count pull_request_versions: %v", err)
return nil, maxResults, err
}
findSession, err := listPullRequestVersionStatement(prID, opts)
if err != nil {
log.Error("listPullRequestVersionStatement: %v", err)
return nil, 0, err
}
findSession = opts.setSessionPagination(findSession)
prvs := make([]*PullRequestVersion, 0, opts.PageSize)
return prvs, maxResults, findSession.Find(&prvs)
}
func GetPullRequestLastVersionByPullRequest(pr *PullRequest) (*PullRequestVersion, error) {
prv := &PullRequestVersion{
RepoID: pr.BaseRepoID,
PullID: pr.ID,
}
has, err := x.Desc("created_unix").Get(prv)
if err != nil {
return nil, err
} else if !has {
return nil, ErrPullRequestNotExist{}
}
return prv, nil
}
func GetPullRequestVersionByID(prID, id int64) (*PullRequestVersion, error) {
if id < 1 {
return nil, ErrPullRequestNotExist{}
}
prv := &PullRequestVersion{
PullID: prID,
ID: id,
}
has, err := x.Get(prv)
if err != nil {
return nil, err
} else if !has {
return nil, ErrPullRequestNotExist{}
}
return prv, nil
}
// NewPullRequestDiff creates new pull request diff version for repository.
func NewPullRequestVersion(repo *Repository, pr *PullRequest, addLineNum, commitsCount, delLineNUm, filesCount int, headCommitID, baseCommitID, StartCommitID string) (err error) {
var version PullRequestVersion
version.PullID = pr.ID
version.RepoID = repo.ID
version.AddLineNum = addLineNum
version.CommitsCount = commitsCount
version.DelLineNum = delLineNUm
version.FilesCount = filesCount
version.HeadCommitID = headCommitID
version.BaseCommitID = baseCommitID
version.StartCommitID = StartCommitID
if _, err = x.Insert(version); err != nil {
return fmt.Errorf("insert pull version repo: %v", err)
}
return nil
}

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

@ -130,6 +130,7 @@ const (
MSTEAMS HookType = "msteams"
FEISHU HookType = "feishu"
MATRIX HookType = "matrix"
JIANMU HookType = "jianmu"
)
// HookStatus is the status of a web hook

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()

View File

@ -0,0 +1,24 @@
package convert
import (
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/models"
)
func ToAPIPullRequestVersion(prv *models.PullRequestVersion) *api.PullRequestVersion {
apiPullRequestVersion := &api.PullRequestVersion{
ID: prv.ID,
AddLineNum: prv.AddLineNum,
DelLineNum: prv.DelLineNum,
CommitsCount: prv.CommitsCount,
FilesCount: prv.FilesCount,
HeadCommitSha: prv.HeadCommitID,
BaseCommitSha: prv.BaseCommitID,
StartCommitSha: prv.StartCommitID,
CreatedAt: prv.CreatedUnix.AsTimePtr(),
UpdatedAt: prv.UpdatedUnix.AsTimePtr(),
}
return apiPullRequestVersion
}

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,9 @@ import (
"os"
"os/exec"
"regexp"
"strconv"
"strings"
"time"
"code.gitea.io/gitea/modules/process"
)
@ -22,6 +25,26 @@ 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"`
PreviousNumber int `json:"previous_number"`
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 +57,98 @@ 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, 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 {
lineString := string(line)
sha1 := string(lines[1])
lineString = strings.Replace(lineString, sha1, "", 1)
lineArray := strings.Split(lineString, " ")
previousNumber, _ := strconv.Atoi(lineArray[1])
if blamePart == nil {
blamePart = &ApiBlamePart{sha1, GetBlameCommit(repo, sha1), previousNumber, 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

@ -217,6 +217,15 @@ func (repo *Repository) GetDiff(base, head string, w io.Writer) error {
RunInDirPipeline(repo.Path, w, nil)
}
func (repo *Repository) GetDiffFileOnlyName(base, head string) (string, error) {
return NewCommand("diff", "--name-only", base, head).RunInDir(repo.Path)
}
func (repo *Repository) GetDiffStringByFilePath(base, head, filepath string) (string, error) {
return NewCommand("diff", "-p", base, head, "--", filepath).
RunInDir(repo.Path)
}
// GetPatch generates and returns format-patch data between given revisions.
func (repo *Repository) GetPatch(base, head string, w io.Writer) error {
stderr := new(bytes.Buffer)

View File

@ -17,20 +17,22 @@ import (
// CodeActivityStats represents git statistics data
type CodeActivityStats struct {
AuthorCount int64
CommitCount int64
ChangedFiles int64
Additions int64
Deletions int64
CommitCountInAllBranches int64
Authors []*CodeActivityAuthor
AuthorCount int64 `json:"author_count"`
CommitCount int64 `json:"commit_count"`
ChangedFiles int64 `json:"changed_files"`
Additions int64 `json:"additions"`
Deletions int64 `json:"deletions"`
CommitCountInAllBranches int64 `json:"commit_count_in_all_branches"`
Authors []*CodeActivityAuthor `json:"authors"`
}
// CodeActivityAuthor represents git statistics data for commit authors
type CodeActivityAuthor struct {
Name string
Email string
Commits int64
Name string `json:"name"`
Email string `json:"email"`
Commits int64 `json:"commits"`
Additions int64 `json:"additions"`
Deletions int64 `json:"deletions"`
}
// GetCodeActivityStats returns code statistics for activity page
@ -151,3 +153,123 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string)
return stats, nil
}
func (repo *Repository) GetCodeActivityStatsWithoutSince(branch string) (*CodeActivityStats, error) {
stats := &CodeActivityStats{}
stdout, err := NewCommand("rev-list", "--count", "--no-merges", "--branches=*", "--date=iso").RunInDirBytes(repo.Path)
if err != nil {
return nil, err
}
c, err := strconv.ParseInt(strings.TrimSpace(string(stdout)), 10, 64)
if err != nil {
return nil, err
}
stats.CommitCountInAllBranches = c
stdoutReader, stdoutWriter, err := os.Pipe()
if err != nil {
return nil, err
}
defer func() {
_ = stdoutReader.Close()
_ = stdoutWriter.Close()
}()
args := []string{"log", "--numstat", "--no-merges", "--pretty=format:---%n%h%n%aN%n%aE%n", "--date=iso"}
if len(branch) == 0 {
args = append(args, "--branches=*")
} else {
args = append(args, "--first-parent", branch)
}
stderr := new(strings.Builder)
err = NewCommand(args...).RunInDirTimeoutEnvFullPipelineFunc(
nil, -1, repo.Path,
stdoutWriter, stderr, nil,
func(ctx context.Context, cancel context.CancelFunc) error {
_ = stdoutWriter.Close()
scanner := bufio.NewScanner(stdoutReader)
scanner.Split(bufio.ScanLines)
stats.CommitCount = 0
stats.Additions = 0
stats.Deletions = 0
authors := make(map[string]*CodeActivityAuthor)
files := make(map[string]bool)
var author string
var currentAuthor *CodeActivityAuthor
p := 0
for scanner.Scan() {
l := strings.TrimSpace(scanner.Text())
if l == "---" {
p = 1
} else if p == 0 {
continue
} else {
p++
}
if p > 4 && len(l) == 0 {
continue
}
switch p {
case 1: // Separator
case 2: // Commit sha-1
stats.CommitCount++
case 3: // Author
author = l
case 4: // E-mail
email := strings.ToLower(l)
if _, ok := authors[email]; !ok {
authors[email] = &CodeActivityAuthor{
Name: author,
Email: email,
Commits: 0,
}
}
authors[email].Commits++
currentAuthor = authors[email]
default: // Changed file
if parts := strings.Fields(l); len(parts) >= 3 {
if parts[0] != "-" {
if c, err := strconv.ParseInt(strings.TrimSpace(parts[0]), 10, 64); err == nil {
currentAuthor.Additions += c
stats.Additions += c
}
}
if parts[1] != "-" {
if c, err := strconv.ParseInt(strings.TrimSpace(parts[1]), 10, 64); err == nil {
currentAuthor.Deletions += c
stats.Deletions += c
}
}
if _, ok := files[parts[2]]; !ok {
files[parts[2]] = true
}
}
}
}
a := make([]*CodeActivityAuthor, 0, len(authors))
for _, v := range authors {
a = append(a, v)
}
// Sort authors descending depending on commit count
sort.Slice(a, func(i, j int) bool {
return a[i].Commits > a[j].Commits
})
stats.AuthorCount = int64(len(authors))
stats.ChangedFiles = int64(len(files))
stats.Authors = a
_ = stdoutReader.Close()
return nil
})
if err != nil {
return nil, fmt.Errorf("Failed to get GetCodeActivityStats for repository.\nError: %w\nStderr: %s", err, stderr)
}
return stats, 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.
@ -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", 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 {
@ -131,7 +132,7 @@ func GetContents(repo *models.Repository, treePath, ref string, forList bool) (*
if ref == "" {
ref = repo.DefaultBranch
}
origRef := fmt.Sprintf("%s", url.PathEscape(ref))
origRef := fmt.Sprintf("%s", ref)
// Check that the path given in opts.treePath is valid (not a git path)
cleanTreePath := CleanUploadFileName(treePath)
@ -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
@ -218,8 +219,9 @@ 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))
ref = fmt.Sprintf("%s", ref)
// 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
}
@ -227,16 +229,17 @@ func GetContents(repo *models.Repository, treePath, ref string, forList bool) (*
contentsResponse.DownloadURL = &downloadURLString
}
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))
ref = fmt.Sprintf("%s", ref)
// 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,24 @@ 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)
if fileContent == nil {
continue
}
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

@ -58,7 +58,7 @@ type CreateHookOptionConfig map[string]string
// CreateHookOption options when create a hook
type CreateHookOption struct {
// required: true
// enum: dingtalk,discord,gitea,gogs,msteams,slack,telegram,feishu
// enum: dingtalk,discord,gitea,gogs,msteams,slack,telegram,feishu,jianmu
Type string `json:"type" binding:"Required"`
// required: true
Config CreateHookOptionConfig `json:"config" binding:"Required"`

View File

@ -0,0 +1,16 @@
package structs
import "time"
type PullRequestVersion struct {
ID int64 `json:"id"`
AddLineNum int `json:"add_line_num"`
DelLineNum int `json:"del_line_num"`
CommitsCount int `json:"commits_count"`
FilesCount int `json:"files_count"`
BaseCommitSha string `json:"base_commit_sha"`
HeadCommitSha string `json:"head_commit_sha"`
StartCommitSha string `json:"start_commit_sha"`
CreatedAt *time.Time `json:"created_at"`
UpdatedAt *time.Time `json:"updated_at"`
}

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

@ -0,0 +1,147 @@
木兰开放作品许可协议 署名第1版
木兰开放作品许可协议 署名第1版
2022年12月 http://license.coscl.org.cn/MulanOWLBYv1
“您”对“本作品”的复制、使用、修改及“传播”受木兰开放作品许可协议 署名第1版以下简称“本许可协议”的如下条款的约束
0. 定义
“本作品”是指依据“本许可协议”提供许可的受版权法保护的智力成果,包括但不限于文字作品、音乐作品、美术作品、建筑作品、摄影作品、视听作品、图形作品、模型作品等。
“演绎作品”是指基于“本作品”创作的作品,包括但不限于对“本作品”全部或部分进行改编、翻译、注释、编排等。
“贡献者”是指“本许可协议”下“本作品”相关权利的许可人,包括版权权利人和其授权的自然人或“组织”。
“您”是指“本许可协议”下“本作品”相关权利的被许可人,是行使“本许可协议”授予的权利的自然人或“组织”。“您的”具有对应含义。
“组织”是指法人、非法人组织及其关联实体。此处的“关联实体”是指对“本许可协议”下的行为方而言控制、受控制或与其共同受控制的机构。此处的“控制”是指拥有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。
“传播”是指通过任何媒介向他人提供作品的行为,包括但不限于发行、出租、展览、表演、放映、通过信息网络提供或以其他方式提供作品。
“有效技术措施”是指根据适用法域的法律,版权权利人为避免作品未经授权使用而采取的禁止使用者规避的技术措施。
1. 授予版权许可
在“您”遵守“本许可协议”的前提下,每个“贡献者”根据“本许可协议”授予“您”永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,供“您”以复制、修改、“传播”等方式利用“本作品”。
2. 无其他许可
除“本许可协议”明确规定外,“本许可协议”不提供包括商标、专利在内的任何其他许可。
3. “传播”条件
“您”可以任何方式“传播”“本作品”或“您的”“演绎作品”,但应当满足以下条件:
1“您”必须随附“本作品”或“您的”“演绎作品”提供“本许可协议”的完整文本或网址
2“您”必须保留“本作品”或“您”所使用部分的来源网址、“贡献者”署名和/或版权声明(“贡献者”要求移除的情形除外)、修改声明、专利声明、商标声明及免责声明;
3若“您”基于“本作品”创作“演绎作品”并“传播”的“您”必须以合理方式声明“本作品”已被修改
4“您”不得对“本作品”施加任何的约束或采用任何“有效技术措施”以限制接收“本作品”的其他人在“本许可协议”下所享有的权利若“您”接收的“本作品”被施加前述约束或“有效技术措施”“您”有权进行移除或规避。
“您”可以以合理方式标注“您”所使用的“本作品”或其部分的标题、版本等信息若“您”使用的“本作品”或其部分或“您的”“演绎作品”是通过信息网络“传播”本条第1-3项中所述文本或网址可以使用超链接。
4. 违约与终止
4.1 若“您”违反“本许可协议”,任何“贡献者”有权书面通知“您”终止其根据“本许可协议”授予“您”的许可。该“贡献者”根据“本许可协议”授予“您”的许可自“您”接到其终止通知之日起终止。仅在如下三种情形下,即使“您”收到“贡献者”的通知也并不终止其授予“您”的许可:
1“您”在接到该终止通知之前已停止所有违反行为
2“您”是首次收到该“贡献者”根据“本许可协议”发出的书面终止通知并且“您”在收到该通知后30天内已停止所有违反行为
3“贡献者”明示恢复授予“您”的许可。
4.2 即使“您”在“本许可协议”下被授予的许可终止,只要从“您”处直接或间接接收“本作品”的其他人遵守“本许可协议”的规定,他们根据“本许可协议”享有的权利不受影响。
4.3 “本许可协议”第0、4、5、6条不因“本许可协议”终止而失效。
5. 免责声明与责任限制
“本作品”在提供时不带有任何明示或默示的担保。在任何情况下,“贡献者”不对任何人因使用“本作品”而引发的任何直接或间接损失承担任何责任,不论该等损失因何种原因导致或者基于何种法律理论,即使其曾被告知有该等损失的可能性。
6. 语言
“本许可协议”以中英文双语表述,中英文版本具有同等法律效力。若中英文版本存在任何不一致,以中文版为准。
条款结束
Mulan Open Works License Attribution, Version 1
Mulan Open Works License Attribution, Version 1
December 2022 http://license.coscl.org.cn/MulanOWLBYv1
Your reproduction, use, modification and Dissemination of This Work shall be subject to Mulan Open Works License Attribution, Version 1 (This License) with following terms and conditions:
0. Definition
This Work means intellectual achievement protected by copyright law that is licensed under This License, including but not limited to a written work, a musical work, a fine art work, an architecture work, a photographic work, an audiovisual work, a graphic work, and a model work.
Adapted Work means a work that is created based on This Work, including but not limited to modification, translation, annotation, or arrangement of This Work in whole or in part.
Contributor means the licensor(s) of the rights related to This Work under This License, including the copyright holder(s) and its authorized individual(s) or Organization(s).
You means the licensee of the rights related to This Work under This License, who is an individual or Organization exercising the rights granted under This License. Your has a corresponding meaning.
Organization means any legal entity(ies), unincorporated organization(s), and their affiliate(s). Aforesaid “affiliate” means any entity that controls, is controlled by, or is under common control with any party under This License. Aforesaid “control” means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity.
Disseminate (or Dissemination) means the act of making works available to others through any medium, including but not limited to distribution, lease, exhibition, performance, projection, providing works through information networks or by any other means providing works.
Effective Technical Measures means those technical measures taken by copyright holders to prevent unauthorized use of work, from which circumvention by users are prohibited under laws of applicable jurisdiction.
1. Grant of Copyright License
Subject to Your compliance with the terms and conditions of This License, each Contributor hereby grants You, according to This License, a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to copy, modify, Disseminate, and in other manner use This Work.
2. No Other License
Except for those as expressly stated in This License, This License does not provide any other licenses, including trademark license or patent license.
3. Dissemination Conditions
You may Disseminate This Work or Your Adapted Work in any manner, provided that the following conditions are met:
(1) You must provide the text or URI of This License with This Work or Your Adapted Work;
(2) You must retain any URI, Contributors attribution and/or copyright statements (unless the Contributor requires removal), modification statements, patent statements, trademark statements, and disclaimer statements of This Work or parts thereof that You use;
(3) If You create an Adapted Work based on This Work and Disseminate it, You must indicate in a reasonable manner that You modified This Work;
(4) You must not exert any restrictions on or apply any Effective Technical Measures to This Work to restrict any others who receive This Work from exercising the rights granted under This License; if This Work You received had been exerted foregoing restrictions or applied Effective Technical Measures, You are entitled to remove or circumvent them.
You may indicate in a reasonable manner the information such as the title or version of This Work or parts thereof that You use; if This Work or parts thereof that You use or Your Adapted Work is Disseminated through information networks, You may use hyperlinks for provision of the foregoing texts or URIs in (1)-(3) of Section 4.
4. Breach and Termination
4.1 If You breached This License, any Contributor has the right to notify You in writing to terminate its license granted to You under This License. The license granted to You by such Contributor terminates upon Your receipt of such notice of termination. Notwithstanding the foregoing, Your license will not be terminated even if You received a notice of termination from Contributor, under three circumstances as set forth below:
(1) You have cured all the breaches prior to receipt of such notice of termination; or,
(2) its Your first time to receive a notice of termination from such Contributor pursuant to This License, and You have cured all the breaches within 30 days of receipt of such notice; or,
(3) Contributor has expressly reinstated the license granted to You.
4.2 Termination of Your license under This License shall not affect any rights under This License granted to any others who directly or indirectly receive This Work from You, provided that they comply with the terms and conditions of This License.
4.3 Sections 0, 4, 5, and 6 survive termination of This License.
5. Disclaimer of Warranty and Limitation of Liability
THIS WORK IS PROVIDED WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING BUT NOT LIMITED TO ANY DIRECT OR INDIRECT DAMAGES ARISING FROM YOUR USE OR INABILITY TO USE THIS WORK, NO MATTER HOW IT IS CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF SUCH CONTRIBUTOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
6. Language
THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN CASE OF DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION SHALL PREVAIL.
END OF THE TERMS AND CONDITIONS

View File

@ -0,0 +1,163 @@
木兰开放作品许可协议 署名-专利许可第1版
木兰开放作品许可协议 署名-专利许可第1版
2022年12月 http://license.coscl.org.cn/MulanOWLBYPLv1
“您”对“本作品”的复制、使用、修改及“传播”受木兰开放作品许可协议 署名-专利许可第1版以下简称“本许可协议”的如下条款的约束
0. 定义
“本作品”是指依据“本许可协议”提供许可的受版权法保护的智力成果,包括但不限于文字作品、音乐作品、美术作品、建筑作品、摄影作品、视听作品、图形作品、模型作品等。
“演绎作品”是指基于“本作品”创作的作品,包括但不限于对“本作品”全部或部分进行改编、翻译、注释、编排等。
“贡献者”是指“本许可协议”下“本作品”相关权利的许可人,包括版权权利人和其授权的自然人或“组织”。
“您”是指“本许可协议”下“本作品”相关权利的被许可人,是行使“本许可协议”授予的权利的自然人或“组织”。“您的”具有对应含义。
“组织”是指法人、非法人组织及其关联实体。此处的“关联实体”是指对“本许可协议”下的行为方而言控制、受控制或与其共同受控制的机构。此处的“控制”是指拥有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。
“传播”是指通过任何媒介向他人提供作品的行为,包括但不限于发行、出租、展览、表演、放映、通过信息网络提供或以其他方式提供作品。
“有效技术措施”是指根据适用法域的法律,版权权利人为避免作品未经授权使用而采取的禁止使用者规避的技术措施。
1. 授予版权许可
在“您”遵守“本许可协议”的前提下,每个“贡献者”根据“本许可协议”授予“您”永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,供“您”以复制、修改、“传播”等方式利用“本作品”。
2. 授予专利许可
2.1 在“您”遵守“本许可协议”的前提下,每个“贡献者”根据“本许可协议”授予“您”永久性的、全球性的、免费的、非独占的、不可撤销的(根据“本许可协议”规定提前终止的除外)专利许可,供“您”依据“本作品”制造、委托制造产品,使用、销售、许诺销售、进口该产品。前述专利许可仅限于“贡献者”现在或将来拥有或控制的、其在“本作品”中享有版权的部分所覆盖的、依据“本作品”本身制造、委托制造产品,使用、销售、许诺销售、进口该产品而必然会侵犯的专利权利要求或外观设计特征。
2.2 若“您”直接或间接地、就“本作品”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或其他专利维权行动,指控其侵犯专利权,则“本许可协议”授予“您”的专利许可自“您”提起诉讼或发起维权行动之日终止。
2.3 第2.1条和2.2条中的专利包括外观设计适用法域的法律对外观设计的保护不纳入专利保护范围的不影响第2.1条中“贡献者”对外观设计的许可及第2.2条中对“您”的外观设计许可的终止。
3. 无其他许可
除“本许可协议”明确规定外,“本许可协议”不提供包括商标在内的任何其他许可。
4.“传播”条件
“您”可以任何方式“传播”“本作品”或“您的”“演绎作品”,但应当满足以下条件:
1“您”必须随附“本作品”或“您的”“演绎作品”提供“本许可协议”的完整文本或网址
2“您”必须保留“本作品”或“您”所使用部分的来源网址、“贡献者”署名和/或版权声明(“贡献者”要求移除的情形除外)、修改声明、专利声明、商标声明及免责声明;
3若“您”基于“本作品”创作“演绎作品”并“传播”的“您”必须以合理方式声明“本作品”已被修改
4“您”不得对“本作品”施加任何的约束或采用任何“有效技术措施”以限制接收“本作品”的其他人在“本许可协议”下所享有的权利若“您”接收的“本作品”被施加前述约束或“有效技术措施”“您”有权进行移除或规避。
“您”可以以合理方式标注“您”所使用的“本作品”或其部分的标题、版本等信息若“您”使用的“本作品”或其部分或“您的”“演绎作品”是通过信息网络“传播”本条第1-3项中所述文本或网址可以使用超链接。
5. 违约与终止
5.1 若“您”违反“本许可协议”,任何“贡献者”有权书面通知“您”终止其根据“本许可协议”授予“您”的许可。该“贡献者”根据“本许可协议”授予“您”的许可自“您”接到其终止通知之日起终止。仅在如下三种情形下,即使“您”收到“贡献者”的通知也并不终止其授予“您”的许可:
1“您”在接到该终止通知之前已停止所有违反行为
2“您”是首次收到该“贡献者”根据“本许可协议”发出的书面终止通知并且“您”在收到该通知后30天内已停止所有违反行为
3“贡献者”明示恢复授予“您”的许可。
5.2 即使“您”在“本许可协议”下被授予的许可终止,只要从“您”处直接或间接接收“本作品”的其他人遵守“本许可协议”的规定,他们根据“本许可协议”享有的权利不受影响。
5.3 “本许可协议”第0、5、6、7条不因“本许可协议”终止而失效。
6. 免责声明与责任限制
“本作品”在提供时不带有任何明示或默示的担保。在任何情况下,“贡献者”不对任何人因使用“本作品”而引发的任何直接或间接损失承担任何责任,不论该等损失因何种原因导致或者基于何种法律理论,即使其曾被告知有该等损失的可能性。
7. 语言
“本许可协议”以中英文双语表述,中英文版本具有同等法律效力。若中英文版本存在任何不一致,以中文版为准。
条款结束
Mulan Open Works License Attribution-PatentLicensed, Version 1
Mulan Open Works License Attribution-PatentLicensed, Version 1
December 2022 http://license.coscl.org.cn/MulanOWLBYPLv1
Your reproduction, use, modification and Dissemination of This Work shall be subject to Mulan Open Works License Attribution-PatentLicensed, Version 1 (This License) with following terms and conditions:
0. Definition
This Work means intellectual achievement protected by copyright law that is licensed under This License, including but not limited to a written work, a musical work, a fine art work, an architecture work, a photographic work, an audiovisual work, a graphic work, and a model work.
Adapted Work means a work that is created based on This Work, including but not limited to modification, translation, annotation, or arrangement of This Work in whole or in part.
Contributor means the licensor(s) of the rights related to This Work under This License, including the copyright holder(s) and its authorized individual(s) or Organization(s).
You means the licensee of the rights related to This Work under This License, who is an individual or Organization exercising the rights granted under This License. Your has a corresponding meaning.
Organization means any legal entity(ies), unincorporated organization(s), and their affiliate(s). Aforesaid “affiliate” means any entity that controls, is controlled by, or is under common control with any party under This License. Aforesaid “control” means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity.
Disseminate (or Dissemination) means the act of making works available to others through any medium, including but not limited to distribution, lease, exhibition, performance, projection, providing works through information networks or by any other means providing works.
Effective Technical Measures means those technical measures taken by copyright holders to prevent unauthorized use of work, from which circumvention by users are prohibited under laws of applicable jurisdiction.
1. Grant of Copyright License
Subject to Your compliance with the terms and conditions of This License, each Contributor hereby grants You, according to This License, a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to copy, modify, Disseminate, and in other manner use This Work.
2. Grant of Patent License
2.1 Subject to Your compliance with the terms and conditions of This License, each Contributor hereby grants You, according to This License, a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (unless terminated early according to This License) patent license for You to make, have made, use, sell, offer for sale and import any products based on This Work. The foregoing patent license applies only to patent claims or design features that are owned or controlled by the Contributor now or in future, covered by the copyrighted portion of This Work and necessarily infringed by making, having made, using, selling, offering for sale, or importing the products based on This Work.
2.2 If You directly or indirectly institute patent litigation (including a cross-claim or counterclaim in a litigation) or other patent enforcement activities against anyone with respect to This Work, accusing them of infringement of patents, then any patent license granted to You under This License for This Work shall terminate as of the date such litigation or activity is filed or taken.
2.3 The patents in Sections 2.1 and 2.2 include design; if design is not protected under patent law of the applicable jurisdiction, the Contributor's license of design in Section 2.1 and the termination of Your license of design in Section 2.2 remains unaffected.
3. No Other License
Except for those as expressly stated in This License, This License does not provide any other licenses, including trademark license.
4. Dissemination Conditions
You may Disseminate This Work or Your Adapted Work in any manner, provided that the following conditions are met:
(1) You must provide the text or URI of This License with This Work or Your Adapted Work;
(2) You must retain any URI, Contributors attribution and/or copyright statements (unless the Contributor requires removal), modification statements, patent statements, trademark statements, and disclaimer statements of This Work or parts thereof that You use;
(3) If You create an Adapted Work based on This Work and Disseminate it, You must indicate in a reasonable manner that You modified This Work;
(4) You must not exert any restrictions on or apply any Effective Technical Measures to This Work to restrict any others who receive This Work from exercising the rights granted under This License; if This Work You received had been exerted foregoing restrictions or applied Effective Technical Measures, You are entitled to remove or circumvent them.
You may indicate in a reasonable manner the information such as the title or version of This Work or parts thereof that You use; if This Work or parts thereof that You use or Your Adapted Work is Disseminated through information networks, You may use hyperlinks for provision of the foregoing texts or URIs in (1)-(3) of Section 4.
5. Breach and Termination
5.1 If You breached This License, any Contributor has the right to notify You in writing to terminate its license granted to You under This License. The license granted to You by such Contributor terminates upon Your receipt of such notice of termination. Notwithstanding the foregoing, Your license will not be terminated even if You received a notice of termination from Contributor, under three circumstances as set forth below:
(1) You have cured all the breaches prior to receipt of such notice of termination; or,
(2) its Your first time to receive a notice of termination from such Contributor pursuant to This License, and You have cured all the breaches within 30 days of receipt of such notice; or,
(3) Contributor has expressly reinstated the license granted to You.
5.2 Termination of Your license under This License shall not affect any rights under This License granted to any others who directly or indirectly receive This Work from You, provided that they comply with the terms and conditions of This License.
5.3 Sections 0, 5, 6, and 7 survive termination of This License.
6. Disclaimer of Warranty and Limitation of Liability
THIS WORK IS PROVIDED WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING BUT NOT LIMITED TO ANY DIRECT OR INDIRECT DAMAGES ARISING FROM YOUR USE OR INABILITY TO USE THIS WORK, NO MATTER HOW IT IS CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF SUCH CONTRIBUTOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
7. Language
THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN CASE OF DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION SHALL PREVAIL.
END OF THE TERMS AND CONDITIONS

View File

@ -0,0 +1,168 @@
木兰开放作品许可协议 署名-专利许可-相同方式共享第1版
木兰开放作品许可协议 署名-专利许可-相同方式共享第1版
2022年12月 http://license.coscl.org.cn/MulanOWLBYPLSAv1
“您”对“本作品”的复制、使用、修改及“传播”受木兰开放作品许可协议 署名-专利许可-相同方式共享第1版以下简称“本许可协议”的如下条款的约束
0. 定义
“本作品”是指依据“本许可协议”提供许可的受版权法保护的智力成果,包括但不限于文字作品、音乐作品、美术作品、建筑作品、摄影作品、视听作品、图形作品、模型作品等。
“演绎作品”是指基于“本作品”创作的作品,包括但不限于对“本作品”全部或部分进行改编、翻译、注释、编排等。
“贡献者”是指“本许可协议”下“本作品”相关权利的许可人,包括版权权利人和其授权的自然人或“组织”。
“您”是指“本许可协议”下“本作品”相关权利的被许可人,是行使“本许可协议”授予的权利的自然人或“组织”。“您的”具有对应含义。
“组织”是指法人、非法人组织及其关联实体。此处的“关联实体”是指对“本许可协议”下的行为方而言控制、受控制或与其共同受控制的机构。此处的“控制”是指拥有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。
“传播”是指通过任何媒介向他人提供作品的行为,包括但不限于发行、出租、展览、表演、放映、通过信息网络提供或以其他方式提供作品。
“兼容的许可协议”是指在http://license.coscl.org.cn/faq列出且经木兰社区官方认可为与“本许可协议”兼容的许可协议。
“有效技术措施”是指根据适用法域的法律,版权权利人为避免作品未经授权使用而采取的禁止使用者规避的技术措施。
1. 授予版权许可
在“您”遵守“本许可协议”的前提下,每个“贡献者”根据“本许可协议”授予“您”永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,供“您”以复制、修改、“传播”等方式利用“本作品”。
2. 授予专利许可
2.1 在“您”遵守“本许可协议”的前提下,每个“贡献者”根据“本许可协议”授予“您”永久性的、全球性的、免费的、非独占的、不可撤销的(根据“本许可协议”规定提前终止的除外)专利许可,供“您”依据“本作品”制造、委托制造产品,使用、销售、许诺销售、进口该产品。前述专利许可仅限于“贡献者”现在或将来拥有或控制的、其在“本作品”中享有版权的部分所覆盖的、依据“本作品”本身制造、委托制造产品,使用、销售、许诺销售、进口该产品而必然会侵犯的专利权利要求或外观设计特征。
2.2 若“您”直接或间接地、就“本作品”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或其他专利维权行动,指控其侵犯专利权,则“本许可协议”授予“您”的专利许可自“您”提起诉讼或发起维权行动之日终止。
2.3 第2.1条和2.2条中的专利包括外观设计适用法域的法律对外观设计的保护不纳入专利保护范围的不影响第2.1条中“贡献者”对外观设计的许可及第2.2条中对“您”的外观设计许可的终止。
3. 无其他许可
除“本许可协议”明确规定外,“本许可协议”不提供包括商标在内的任何其他许可。
4.“传播”条件
“您”可以任何方式“传播”“本作品”或“您的”“演绎作品”,但应当满足以下条件:
1“您”必须随附“本作品”或“您的”“演绎作品”提供“本许可协议”的完整文本或网址
2“您”必须保留“本作品”或“您”所使用部分的来源网址、“贡献者”署名和/或版权声明(“贡献者”要求移除的情形除外)、修改声明、专利声明、商标声明及免责声明;
3若“您”基于“本作品”创作“演绎作品”并“传播”的“您”必须以合理方式声明“本作品”已被修改“您”还必须将“您的”“演绎作品”许可在“本许可协议”或“兼容的许可协议”下并提供所适用的许可协议文本或网址
4“您”不得对“本作品”或“演绎作品”施加任何的约束或采用任何“有效技术措施”以限制接收“本作品”或“演绎作品”的其他人在对应作品所适用的“本许可协议”或“兼容的许可协议”下所享有的权利若“您”接收的“本作品”被施加前述约束或“有效技术措施”“您”有权进行移除或规避。
“您”可以以合理方式标注“您”所使用的“本作品”或其部分的标题、版本等信息若“您”使用的“本作品”或其部分或“您的”“演绎作品”是通过信息网络“传播”本条第1-3项中所述文本或网址可以使用超链接。
5. 违约与终止
5.1 若“您”违反“本许可协议”,任何“贡献者”有权书面通知“您”终止其根据“本许可协议”授予“您”的许可。该“贡献者”根据“本许可协议”授予“您”的许可自“您”接到其终止通知之日起终止。仅在如下三种情形下,即使“您”收到“贡献者”的通知也并不终止其授予“您”的许可:
1“您”在接到该终止通知之前已停止所有违反行为
2“您”是首次收到该“贡献者”根据“本许可协议”发出的书面终止通知并且“您”在收到该通知后30天内已停止所有违反行为
3“贡献者”明示恢复授予“您”的许可。
5.2 即使“您”在“本许可协议”下被授予的许可终止,只要从“您”处直接或间接接收“本作品”的其他人遵守“本许可协议”的规定,他们根据“本许可协议”享有的权利不受影响。
5.3 “本许可协议”第0、5、6、7条不因“本许可协议”终止而失效。
6. 免责声明与责任限制
“本作品”在提供时不带有任何明示或默示的担保。在任何情况下,“贡献者”不对任何人因使用“本作品”而引发的任何直接或间接损失承担任何责任,不论该等损失因何种原因导致或者基于何种法律理论,即使其曾被告知有该等损失的可能性。
7. 语言
“本许可协议”以中英文双语表述,中英文版本具有同等法律效力。若中英文版本存在任何不一致,以中文版为准。
条款结束
Mulan Open Works License Attribution-PatentLicensed-ShareAlike, Version 1
Mulan Open Works License Attribution-PatentLicensed-ShareAlike, Version 1
December 2022 http://license.coscl.org.cn/MulanOWLBYPLSAv1
Your reproduction, use, modification and Dissemination of This Work shall be subject to Mulan Open Works License Attribution-PatentLicensed-ShareAlike, Version 1 (This License) with following terms and conditions:
0. Definition
This Work means intellectual achievement protected by copyright law that is licensed under This License, including but not limited to a written work, a musical work, a fine art work, an architecture work, a photographic work, an audiovisual work, a graphic work, and a model work.
Adapted Work means a work that is created based on This Work, including but not limited to modification, translation, annotation, or arrangement of This Work in whole or in part.
Contributor means the licensor(s) of the rights related to This Work under This License, including the copyright holder(s) and its authorized individual(s) or Organization(s).
You means the licensee of the rights related to This Work under This License, who is an individual or Organization exercising the rights granted under This License. Your has a corresponding meaning.
Organization means any legal entity(ies), unincorporated organization(s), and their affiliate(s). Aforesaid “affiliate” means any entity that controls, is controlled by, or is under common control with any party under This License. Aforesaid “control” means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity.
Disseminate (or Dissemination) means the act of making works available to others through any medium, including but not limited to distribution, lease, exhibition, performance, projection, providing works through information networks or by any other means providing works.
Compatible License means any license that is listed on http://license.coscl.org.cn/faq and officially approved by Mulan Community as compatible with This License.
Effective Technical Measures means those technical measures taken by copyright holders to prevent unauthorized use of work, from which circumvention by users are prohibited under laws of applicable jurisdiction.
1. Grant of Copyright License
Subject to Your compliance with the terms and conditions of This License, each Contributor hereby grants You, according to This License, a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to copy, modify, Disseminate, and in other manner use This Work.
2. Grant of Patent License
2.1 Subject to Your compliance with the terms and conditions of This License, each Contributor hereby grants You, according to This License, a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (unless terminated early according to This License) patent license for You to make, have made, use, sell, offer for sale and import any products based on This Work. The foregoing patent license applies only to patent claims or design features that are owned or controlled by the Contributor now or in future, covered by the copyrighted portion of This Work and necessarily infringed by making, having made, using, selling, offering for sale, or importing the products based on This Work.
2.2 If You directly or indirectly institute patent litigation (including a cross-claim or counterclaim in a litigation) or other patent enforcement activities against anyone with respect to This Work, accusing them of infringement of patents, then any patent license granted to You under This License for This Work shall terminate as of the date such litigation or activity is filed or taken.
2.3 The patents in Sections 2.1 and 2.2 include design; if design is not protected under patent law of the applicable jurisdiction, the Contributor's license of design in Section 2.1 and the termination of Your license of design in Section 2.2 remains unaffected.
3. No Other License
Except for those as expressly stated in This License, This License does not provide any other licenses, including trademark license.
4. Dissemination Conditions
You may Disseminate This Work or Your Adapted Work in any manner, provided that the following conditions are met:
(1) You must provide the text or URI of This License with This Work or Your Adapted Work;
(2) You must retain any URI, Contributors attribution and/or copyright statements (unless the Contributor requires removal), modification statements, patent statements, trademark statements, and disclaimer statements of This Work or parts thereof that You use;
(3) If You create an Adapted Work based on This Work and Disseminate it, You must indicate in a reasonable manner that You modified This Work; You must license Your Adapted Work under This License or any Compatible License and provide text or URI of the applied license;
(4) You must not exert any restrictions on or apply any Effective Technical Measures to This Work or the Adapted Work to restrict any others who receive This Work or Adapted Work from exercising the rights granted under This License or any Compatible License; if This Work You received had been exerted foregoing restrictions or applied Effective Technical Measures, You are entitled to remove or circumvent them.
You may indicate in a reasonable manner the information such as the title or version of This Work or parts thereof that You use; if This Work or parts thereof that You use or Your Adapted Work is Disseminated through information networks, You may use hyperlinks for provision of the foregoing texts or URIs in (1)-(3) of Section 4.
5. Breach and Termination
5.1 If You breached This License, any Contributor has the right to notify You in writing to terminate its license granted to You under This License. The license granted to You by such Contributor terminates upon Your receipt of such notice of termination. Notwithstanding the foregoing, Your license will not be terminated even if You received a notice of termination from Contributor, under three circumstances as set forth below:
(1) You have cured all the breaches prior to receipt of such notice of termination; or,
(2) its Your first time to receive a notice of termination from such Contributor pursuant to This License, and You have cured all the breaches within 30 days of receipt of such notice; or,
(3) Contributor has expressly reinstated the license granted to You.
5.2 Termination of Your license under This License shall not affect any rights under This License granted to any others who directly or indirectly receive This Work from You, provided that they comply with the terms and conditions of This License.
5.3 Sections 0, 5, 6, and 7 survive termination of This License.
6. Disclaimer of Warranty and Limitation of Liability
THIS WORK IS PROVIDED WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING BUT NOT LIMITED TO ANY DIRECT OR INDIRECT DAMAGES ARISING FROM YOUR USE OR INABILITY TO USE THIS WORK, NO MATTER HOW IT IS CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF SUCH CONTRIBUTOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
7. Language
THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN CASE OF DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION SHALL PREVAIL.
END OF THE TERMS AND CONDITIONS

View File

@ -0,0 +1,152 @@
木兰开放作品许可协议 署名-相同方式共享第1版
木兰开放作品许可协议 署名-相同方式共享第1版
2022年12月 http://license.coscl.org.cn/MulanOWLBYSAv1
“您”对“本作品”的复制、使用、修改及“传播”受木兰开放作品许可协议 署名-相同方式共享第1版以下简称“本许可协议”的如下条款的约束
0. 定义
“本作品”是指依据“本许可协议”提供许可的受版权法保护的智力成果,包括但不限于文字作品、音乐作品、美术作品、建筑作品、摄影作品、视听作品、图形作品、模型作品等。
“演绎作品”是指基于“本作品”创作的作品,包括但不限于对“本作品”全部或部分进行改编、翻译、注释、编排等。
“贡献者”是指“本许可协议”下“本作品”相关权利的许可人,包括版权权利人和其授权的自然人或“组织”。
“您”是指“本许可协议”下“本作品”相关权利的被许可人,是行使“本许可协议”授予的权利的自然人或“组织”。“您的”具有对应含义。
“组织”是指法人、非法人组织及其关联实体。此处的“关联实体”是指对“本许可协议”下的行为方而言控制、受控制或与其共同受控制的机构。此处的“控制”是指拥有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。
“传播”是指通过任何媒介向他人提供作品的行为,包括但不限于发行、出租、展览、表演、放映、通过信息网络提供或以其他方式提供作品。
“兼容的许可协议”是指在http://license.coscl.org.cn/faq列出且经木兰社区官方认可为与“本许可协议”兼容的许可协议。
“有效技术措施”是指根据适用法域的法律,版权权利人为避免作品未经授权使用而采取的禁止使用者规避的技术措施。
1. 授予版权许可
在“您”遵守“本许可协议”的前提下,每个“贡献者”根据“本许可协议”授予“您”永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,供“您”以复制、修改、“传播”等方式利用“本作品”。
2. 无其他许可
除“本许可协议”明确规定外,“本许可协议”不提供包括商标、专利在内的任何其他许可。
3. “传播”条件
“您”可以任何方式“传播”“本作品”或“您的”“演绎作品”,但应当满足以下条件:
1“您”必须随附“本作品”或“您的”“演绎作品”提供“本许可协议”的完整文本或网址
2“您”必须保留“本作品”或“您”所使用部分的来源网址、“贡献者”署名和/或版权声明(“贡献者”要求移除的情形除外)、修改声明、专利声明、商标声明及免责声明;
3若“您”基于“本作品”创作“演绎作品”并“传播”的“您”必须以合理方式声明“本作品”已被修改“您”还必须将“您的”“演绎作品”许可在“本许可协议”或“兼容的许可协议”下并提供所适用的许可协议文本或网址
4“您”不得对“本作品”或“演绎作品”施加任何的约束或采用任何“有效技术措施”以限制接收“本作品”或“演绎作品”的其他人在对应作品所适用的“本许可协议”或“兼容的许可协议”下所享有的权利若“您”接收的“本作品”被施加前述约束或“有效技术措施”“您”有权进行移除或规避。
“您”可以以合理方式标注“您”所使用的“本作品”或其部分的标题、版本等信息若“您”使用的“本作品”或其部分或“您的”“演绎作品”是通过信息网络“传播”本条第1-3项中所述文本或网址可以使用超链接。
4. 违约与终止
4.1 若“您”违反“本许可协议”,任何“贡献者”有权书面通知“您”终止其根据“本许可协议”授予“您”的许可。该“贡献者”根据“本许可协议”授予“您”的许可自“您”接到其终止通知之日起终止。仅在如下三种情形下,即使“您”收到“贡献者”的通知也并不终止其授予“您”的许可:
1“您”在接到该终止通知之前已停止所有违反行为
2“您”是首次收到该“贡献者”根据“本许可协议”发出的书面终止通知并且“您”在收到该通知后30天内已停止所有违反行为
3“贡献者”明示恢复授予“您”的许可。
4.2 即使“您”在“本许可协议”下被授予的许可终止,只要从“您”处直接或间接接收“本作品”的其他人遵守“本许可协议”的规定,他们根据“本许可协议”享有的权利不受影响。
4.3 “本许可协议”第0、4、5、6条不因“本许可协议”终止而失效。
5. 免责声明与责任限制
“本作品”在提供时不带有任何明示或默示的担保。在任何情况下,“贡献者”不对任何人因使用“本作品”而引发的任何直接或间接损失承担任何责任,不论该等损失因何种原因导致或者基于何种法律理论,即使其曾被告知有该等损失的可能性。
6. 语言
“本许可协议”以中英文双语表述,中英文版本具有同等法律效力。若中英文版本存在任何不一致,以中文版为准。
条款结束
Mulan Open Works License Attribution-ShareAlike, Version 1
Mulan Open Works License Attribution-ShareAlike, Version 1
December 2022 http://license.coscl.org.cn/MulanOWLBYSAv1
Your reproduction, use, modification and Dissemination of This Work shall be subject to Mulan Open Works License Attribution-ShareAlike, Version 1 (This License) with following terms and conditions:
0. Definition
This Work means intellectual achievement protected by copyright law that is licensed under This License, including but not limited to a written work, a musical work, a fine art work, an architecture work, a photographic work, an audiovisual work, a graphic work, and a model work.
Adapted Work means a work that is created based on This Work, including but not limited to modification, translation, annotation, or arrangement of This Work in whole or in part.
Contributor means the licensor(s) of the rights related to This Work under This License, including the copyright holder(s) and its authorized individual(s) or Organization(s).
You means the licensee of the rights related to This Work under This License, who is an individual or Organization exercising the rights granted under This License. Your has a corresponding meaning.
Organization means any legal entity(ies), unincorporated organization(s), and their affiliate(s). Aforesaid “affiliate” means any entity that controls, is controlled by, or is under common control with any party under This License. Aforesaid “control” means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity.
Disseminate (or Dissemination) means the act of making works available to others through any medium, including but not limited to distribution, lease, exhibition, performance, projection, providing works through information networks or by any other means providing works.
Compatible License means any license that is listed on http://license.coscl.org.cn/faq and officially approved by Mulan Community as compatible with This License.
Effective Technical Measures means those technical measures taken by copyright holders to prevent unauthorized use of work, from which circumvention by users are prohibited under laws of applicable jurisdiction.
1. Grant of Copyright License
Subject to Your compliance with the terms and conditions of This License, each Contributor hereby grants You, according to This License, a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to copy, modify, Disseminate, and in other manner use This Work.
2. No Other License
Except for those as expressly stated in This License, This License does not provide any other licenses, including trademark license or patent license.
3. Dissemination Conditions
You may Disseminate This Work or Your Adapted Work in any manner, provided that the following conditions are met:
(1) You must provide the text or URI of This License with This Work or Your Adapted Work;
(2) You must retain any URI, Contributors attribution and/or copyright statements (unless the Contributor requires removal), modification statements, patent statements, trademark statements, and disclaimer statements of This Work or parts thereof that You use;
(3) If You create an Adapted Work based on This Work and Disseminate it, You must indicate in a reasonable manner that You modified This Work; You must license Your Adapted Work under This License or any Compatible License and provide text or URI of the applied license;
(4) You must not exert any restrictions on or apply any Effective Technical Measures to This Work or the Adapted Work to restrict any others who receive This Work or Adapted Work from exercising the rights granted under This License or any Compatible License; if This Work You received had been exerted foregoing restrictions or applied Effective Technical Measures, You are entitled to remove or circumvent them.
You may indicate in a reasonable manner the information such as the title or version of This Work or parts thereof that You use; if This Work or parts thereof that You use or Your Adapted Work is Disseminated through information networks, You may use hyperlinks for provision of the foregoing texts or URIs in (1)-(3) of Section 4.
4. Breach and Termination
4.1 If You breached This License, any Contributor has the right to notify You in writing to terminate its license granted to You under This License. The license granted to You by such Contributor terminates upon Your receipt of such notice of termination. Notwithstanding the foregoing, Your license will not be terminated even if You received a notice of termination from Contributor, under three circumstances as set forth below:
(1) You have cured all the breaches prior to receipt of such notice of termination; or,
(2) its Your first time to receive a notice of termination from such Contributor pursuant to This License, and You have cured all the breaches within 30 days of receipt of such notice; or,
(3) Contributor has expressly reinstated the license granted to You.
4.2 Termination of Your license under This License shall not affect any rights under This License granted to any others who directly or indirectly receive This Work from You, provided that they comply with the terms and conditions of This License.
4.3 Sections 0, 4, 5, and 6 survive termination of This License.
5. Disclaimer of Warranty and Limitation of Liability
THIS WORK IS PROVIDED WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING BUT NOT LIMITED TO ANY DIRECT OR INDIRECT DAMAGES ARISING FROM YOUR USE OR INABILITY TO USE THIS WORK, NO MATTER HOW IT IS CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF SUCH CONTRIBUTOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
6. Language
THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN CASE OF DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION SHALL PREVAIL.
END OF THE TERMS AND CONDITIONS

View File

@ -760,6 +760,10 @@ func Routes() *web.Route {
m.Get("/commits", context.RepoRef(), repo.GetPullCommits)
m.Get("/files", context.RepoRef(), repo.GetPullFiles)
m.Get("/issues", context.RepoRef(), repo.GetPullIssues)
m.Group("/versions", func() {
m.Get("", repo.ListPullRequestVersions)
m.Get("/{versionId}/diff", context.RepoRef(), repo.GetPullRequestVersionDiff)
})
})
m.Get("/compare/*", repo.MustBeNotEmpty, reqRepoCodeReader,
repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.CompareDiff)
@ -800,7 +804,7 @@ func Routes() *web.Route {
Put(reqAdmin(), repo.AddTeam).
Delete(reqAdmin(), repo.DeleteTeam)
}, reqToken())
m.Get("/raw/*", context.RepoRefForAPI, reqRepoReader(models.UnitTypeCode), repo.GetRawFile)
m.Get("/raw/*", context.ReferencesGitRepo(true), context.RepoRefForAPI, reqRepoReader(models.UnitTypeCode), repo.GetRawFile)
m.Get("/archive/*", reqRepoReader(models.UnitTypeCode), repo.GetArchive)
m.Get("/find", viewfile.FindFiles)
@ -826,12 +830,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() {
@ -840,6 +844,7 @@ func Routes() *web.Route {
})
m.Get("/commits_slice", repo.GetAllCommitsSliceByTime)
m.Get("/branch_name_set", repo.BranchNameSet)
m.Get("/tag_name_set", context.RepoRefForAPI, repo.TagNameSet)
m.Group("/branch_tag_count", func() {
m.Get("", repo.BranchTagCount)
}, reqRepoReader(models.UnitTypeCode), context.ReferencesGitRepo(true))
@ -859,6 +864,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 +1037,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 +1052,10 @@ 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)
m.Get("/code_stats", context.RepoRefForAPI, repo.ListCodeStats)
}, repoAssignment())
})
@ -1093,6 +1114,8 @@ func Routes() *web.Route {
})
m.Group("/repos", func() {
m.Get("", org.GetTeamRepos)
m.Put("/{org}", org.AddTeamAllRepository)
m.Delete("/{org}", org.RemoveTeamAllRepository)
m.Combo("/{org}/{reponame}").
Put(org.AddTeamRepository).
Delete(org.RemoveTeamRepository)

View File

@ -514,6 +514,45 @@ func getRepositoryByParams(ctx *context.APIContext) *models.Repository {
return repo
}
// AddTeamAllRepository api for adding all repository to a team
func AddTeamAllRepository(ctx *context.APIContext) {
// swagger:operation PUT /teams/{id}/repos/{org} organization orgAddTeamAllRepository
// ---
// summary: Add all repository to a team ***
// produces:
// - application/json
// parameters:
// - name: id
// in: path
// description: id of the team
// type: integer
// format: int64
// required: true
// - name: org
// in: path
// description: organization that owns the repo to add
// type: string
// required: true
// responses:
// "204":
// "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"
if ctx.Written() {
return
}
if ctx.Org.Team.IncludesAllRepositories {
ctx.Error(http.StatusForbidden, "", "Team include all repository not be allow to edit")
return
}
if err := ctx.Org.Team.AddAllRepositories(); err != nil {
ctx.Error(http.StatusInternalServerError, "AddAllRepositories", err)
return
}
ctx.Status(http.StatusNoContent)
}
// AddTeamRepository api for adding a repository to a team
func AddTeamRepository(ctx *context.APIContext) {
// swagger:operation PUT /teams/{id}/repos/{org}/{repo} organization orgAddTeamRepository
@ -562,6 +601,45 @@ func AddTeamRepository(ctx *context.APIContext) {
ctx.Status(http.StatusNoContent)
}
// RemoveTeamAllRepository api for removing all repository from a team
func RemoveTeamAllRepository(ctx *context.APIContext) {
// swagger:operation DELETE /teams/{id}/repos/{org} organization orgRemoveTeamAllRepository
// ---
// summary: Remove all repository from a team ***
// description: This dos not delete the repository, it only removes the repository from the team.
// produces:
// - application/json
// parameters:
// - name: id
// in: path
// description: id of the team
// type: integer
// format: int64
// required: true
// - name: org
// in: path
// description: organization that owns the repo to remove
// type: string
// required: true
// responses:
// "204":
// "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"
if ctx.Written() {
return
}
if ctx.Org.Team.IncludesAllRepositories {
ctx.Error(http.StatusForbidden, "", "Team include all repository not be allow to edit")
return
}
if err := ctx.Org.Team.RemoveAllRepositories(); err != nil {
ctx.Error(http.StatusInternalServerError, "RemoveAllRepositories", err)
return
}
ctx.Status(http.StatusNoContent)
}
// RemoveTeamRepository api for removing a repository from a team
func RemoveTeamRepository(ctx *context.APIContext) {
// swagger:operation DELETE /teams/{id}/repos/{org}/{repo} organization orgRemoveTeamRepository

View File

@ -0,0 +1,112 @@
package repo
import (
"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
}
}
filepath := ctx.QueryTrim("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 repoCommitGetDiffs
// ---
// 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:
// "$ref": "#/responses/Diff"
// "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("to"), ctx.QueryTrim("from"), setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles); err == nil {
ctx.JSON(200, diffs)
} else {
ctx.Error(http.StatusInternalServerError, "GetDiffRange", err)
}
}

View File

@ -80,6 +80,8 @@ func GetRawFile(ctx *context.APIContext) {
}
return
}
ctx.Repo.Commit = commit
ctx.Repo.TreePath = ctx.Params("*")
}
blob, err := commit.GetBlobByPath(ctx.Repo.TreePath)
@ -280,11 +282,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 +459,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) {
@ -643,8 +763,29 @@ func GetReadmeContents(ctx *context.APIContext) {
// treePath := ctx.Params("*")
ref := ctx.QueryTrim("ref")
readmePath := "README.md"
readmezhPath := ""
filesListInterface, _ := repofiles.GetContentsOrList(ctx, ctx.Repo.Repository, "", ref)
filesList, ok := filesListInterface.([]*api.ContentsResponse)
if ok {
for _, file := range filesList {
if strings.ToLower(file.Name) == "readme.md" {
readmePath = file.Name
}
if strings.ToLower(file.Name) == "readme_zh.md" {
readmezhPath = file.Name
}
if strings.ToLower(file.Name) == "readme" {
readmePath = file.Name
}
}
}
if fileList, err := repofiles.GetContentsOrList(ctx, ctx.Repo.Repository, "README.md", ref); err != nil {
if readmezhPath != "" {
readmePath = readmezhPath
}
if fileList, err := repofiles.GetContentsOrList(ctx, ctx.Repo.Repository, readmePath, ref); err != nil {
if git.IsErrNotExist(err) {
ctx.NotFound("GetContentsOrList", err)
return
@ -698,7 +839,29 @@ func GetReadmeContentsByPath(ctx *context.APIContext) {
treePath := ctx.Params("*")
ref := ctx.QueryTrim("ref")
newTreePath := treePath + "/" + "README.md"
readmePath := "README.md"
readmezhPath := ""
filesListInterface, _ := repofiles.GetContentsOrList(ctx, ctx.Repo.Repository, treePath, ref)
filesList, ok := filesListInterface.([]*api.ContentsResponse)
if ok {
for _, file := range filesList {
if strings.ToLower(file.Name) == "readme.md" {
readmePath = file.Name
}
if strings.ToLower(file.Name) == "readme_zh.md" {
readmezhPath = file.Name
}
if strings.ToLower(file.Name) == "readme" {
readmePath = file.Name
}
}
}
if readmezhPath != "" {
readmePath = readmezhPath
}
newTreePath := treePath + "/" + readmePath
if fileList, err := repofiles.GetContentsOrList(ctx, ctx.Repo.Repository, newTreePath, ref); err != nil {
if git.IsErrNotExist(err) {
ctx.NotFound("GetContentsOrList", err)

View File

@ -84,17 +84,17 @@ func Migrate(ctx *context.APIContext) {
return
}
if repoOwner.IsOrganization() {
// Check ownership of organization.
isOwner, err := repoOwner.IsOwnedBy(ctx.User.ID)
if err != nil {
ctx.Error(http.StatusInternalServerError, "IsOwnedBy", err)
return
} else if !isOwner {
ctx.Error(http.StatusForbidden, "", "Given user is not owner of organization.")
return
}
}
// if repoOwner.IsOrganization() {
// // Check ownership of organization.
// isOwner, err := repoOwner.IsOwnedBy(ctx.User.ID)
// if err != nil {
// ctx.Error(http.StatusInternalServerError, "IsOwnedBy", err)
// return
// } else if !isOwner {
// ctx.Error(http.StatusForbidden, "", "Given user is not owner of organization.")
// return
// }
// }
}
remoteAddr, err := forms.ParseRemoteAddr(form.CloneAddr, form.AuthUsername, form.AuthPassword)

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,18 +1462,52 @@ func GetPullFiles(ctx *context.APIContext) {
endCommitID = headCommitID
ctx.Data["WhitespaceBehavior"] = ""
diff, err := gitdiff.GetDiffRangeWithWhitespaceBehavior(gitRepo,
startCommitID, endCommitID, setting.Git.MaxGitDiffLines,
setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles,
gitdiff.GetWhitespaceFlag(ctx.Data["WhitespaceBehavior"].(string)))
if err != nil {
ctx.ServerError("GetDiffRangeWithWhitespaceBehavior", err)
return
}
if err = diff.LoadComments(issue, ctx.User); err != nil {
ctx.ServerError("LoadComments", err)
return
diff := &gitdiff.Diff{Files: make([]*gitdiff.DiffFile, 0)}
if ctx.Query("not-need-files") == "true" || ctx.Query("only-file-name") == "true" {
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, _ = git.GetDiffShortStat(gitRepo.Path, shortstatArgs...)
}
if ctx.Query("only-file-name") == "true" {
var diffString string
var err error
if len(startCommitID) == 0 || startCommitID == git.EmptySHA {
diffString, err = ctx.Repo.GitRepo.GetDiffFileOnlyName(git.EmptyTreeSHA, headCommitID)
} else {
diffString, err = ctx.Repo.GitRepo.GetDiffFileOnlyName(startCommitID, headCommitID)
}
if err == nil {
for _, fileName := range strings.Split(diffString, "\n") {
if fileName != "" {
diff.Files = append(diff.Files, &gitdiff.DiffFile{
Name: fileName,
})
}
}
}
}
} else {
diff, err = gitdiff.GetDiffRangeWithWhitespaceBehavior(gitRepo,
startCommitID, endCommitID, setting.Git.MaxGitDiffLines,
setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles,
gitdiff.GetWhitespaceFlag(ctx.Data["WhitespaceBehavior"].(string)))
if err != nil {
ctx.ServerError("GetDiffRangeWithWhitespaceBehavior", err)
return
}
if err = diff.LoadComments(issue, ctx.User); err != nil {
ctx.ServerError("LoadComments", err)
return
}
}
fileDiff := struct {

View File

@ -0,0 +1,197 @@
package repo
import (
"fmt"
"net/http"
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/utils"
"code.gitea.io/gitea/services/gitdiff"
)
// ListPullRequestVersions returns a list of all PR versions
func ListPullRequestVersions(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/pulls/{index}/versions repository repoListPullRequestVersions
// ---
// summary: List a repos's pull versions requests***
// 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: 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/PullRequestVersionList"
// "404":
// "$ref": "#/responses/notFound"
pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if models.IsErrPullRequestNotExist(err) {
ctx.NotFound()
} else {
ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
}
}
lisOptions := utils.GetListOptions(ctx)
prvs, maxResults, err := models.PullRequestVersions(pr.ID, &lisOptions)
if err != nil {
ctx.Error(http.StatusInternalServerError, "PullRequestVersions", err)
return
}
apiPrvs := make([]*api.PullRequestVersion, len(prvs))
for i := range prvs {
apiPrvs[i] = convert.ToAPIPullRequestVersion(prvs[i])
}
ctx.SetLinkHeader(int(maxResults), lisOptions.PageSize)
ctx.Header().Set("X-Total", fmt.Sprintf("%d", maxResults))
ctx.Header().Set("X-Total-Count", fmt.Sprintf("%d", maxResults))
ctx.Header().Set("Access-Control-Expose-Headers", "X-Total-Count, Link, X-Total")
ctx.JSON(http.StatusOK, &apiPrvs)
}
// GetPullRequestVersionDiff
func GetPullRequestVersionDiff(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/pulls/{index}/versions/{versionId}/diff repository repoGetPullRequestVersionDiff
// ---
// summary: Get a pull request version diffs***
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: owner 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: versionId
// in: path
// description: id of the pull request version to get
// type: integer
// format: int64
// required: true
// - name: filepath
// in: query
// description: path of the file
// type: string
// required: false
// responses:
// "200":
// "$ref": "#/responses/Diff"
// "404":
// "$ref": "#/responses/notFound"
if ctx.Repo.Repository.IsEmpty {
ctx.NotFound()
return
}
pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if models.IsErrPullRequestNotExist(err) {
ctx.NotFound()
} else {
ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
}
return
}
prv, err := models.GetPullRequestVersionByID(pr.ID, ctx.ParamsInt64(":versionId"))
if err != nil {
if models.IsErrPullRequestNotExist(err) {
ctx.NotFound()
} else {
ctx.Error(http.StatusInternalServerError, "GetPullRequestVersionByID", err)
}
return
}
diffs, err := gitdiff.GetDiffRange(ctx.Repo.GitRepo, prv.BaseCommitID, prv.HeadCommitID, setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetDiffRange", err)
}
filepath := ctx.QueryTrim("filepath")
if filepath == "" {
ctx.JSON(http.StatusOK, diffs)
} else {
var targetDiffFile *gitdiff.DiffFile
for _, file := range diffs.Files {
if file.Name == filepath {
if diffString, err := ctx.Repo.GitRepo.GetDiffStringByFilePath(prv.BaseCommitID, prv.HeadCommitID, file.Name); err == nil {
stringArray := strings.Split(diffString, "\n")
var afterStrings []string
for _, item := range stringArray {
switch {
case strings.HasPrefix(item, "diff --git"):
continue
case strings.HasPrefix(item, "index"):
continue
case strings.HasPrefix(item, "---"):
continue
case strings.HasPrefix(item, "+++"):
continue
case strings.HasPrefix(item, "new file mode"):
continue
case strings.HasPrefix(item, "deleted file mode"):
continue
case strings.HasSuffix(item, "No newline at end of file"):
continue
default:
afterStrings = append(afterStrings, item)
}
}
file.Diff = strings.Join(afterStrings, "\n")
}
targetDiffFile = file
break
}
}
if targetDiffFile == nil {
ctx.NotFound()
} else {
ctx.JSON(http.StatusOK, targetDiffFile)
}
}
}

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)
}
@ -1582,3 +1585,47 @@ func getBranchesForRepo(user *models.User, repo *models.Repository) (bool, []str
}
// end by qiubing
func ListCodeStats(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/code_stats repository repoListCodeStats
// ---
// summary: List a repo's code stats
// 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: ref
// in: query
// description: "The name of the commit/branch/tag. Default the repositorys default branch (usually master)"
// type: string
// required: false
// responses:
// "200":
// "$ref": "#/responses/PullRequestList"
if ctx.Repo.Repository.IsEmpty {
ctx.NotFound()
return
}
ref := ctx.QueryTrim("ref")
if ref == "" {
ref = ctx.Repo.Repository.DefaultBranch
}
stats, err := ctx.Repo.GitRepo.GetCodeActivityStatsWithoutSince(ref)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetCodeActivityStatsWithoutSince", err)
return
}
ctx.JSON(http.StatusOK, stats)
}

View File

@ -10,6 +10,7 @@ import (
"math"
"net/http"
"strconv"
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
@ -301,7 +302,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 +339,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
@ -364,3 +372,53 @@ func BranchNameSet(ctx *context.APIContext) {
ctx.JSON(http.StatusOK, result)
}
func TagNameSet(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/tag_name_set repository repoTagNameSet
// ---
// summary: List a repository's tag name***
// 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: name
// in: query
// description: name of the tag
// type: string
// responses:
// "200":
// "$ref": "#/responses/BranchNameSet"
searchName := ctx.Query("name")
tags, err := ctx.Repo.GitRepo.GetTags()
if err != nil {
ctx.ServerError("GetTags", err)
return
}
var tagNameSet = make([]string, 0)
for _, tag := range tags {
log.Info("tag is \n", tag)
if searchName == "" {
if strings.Contains(tag, searchName) {
tagNameSet = append(tagNameSet, tag)
}
} else {
tagNameSet = append(tagNameSet, tag)
}
}
ctx.JSON(http.StatusOK, tagNameSet)
}

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,535 @@ 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() {
var commit *git.Commit
if filePath == "" {
commit, err = wikiRepo.GetCommitByPath(entry.Name())
} else {
commit, 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, commit, ctx.Repo.Repository))
}
if entry.IsDir() {
var commit *git.Commit
if filePath == "" {
commit, err = wikiRepo.GetCommitByPath(entry.Name())
} else {
commit, 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(), commit, 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 +623,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 +637,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 +661,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 +709,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 +746,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 +769,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 +796,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 +850,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 +872,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 +911,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 +935,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 +970,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 +991,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 {
@ -176,6 +184,13 @@ type swaggerResponsePullRequestList struct {
Body []api.PullRequest `json:"body"`
}
// PullRequestVersionList
// swagger:response PullRequestVersionList
type swaggerResponsePullRequestVersionList struct {
// in:body
Body []api.PullRequestVersion `json:"body"`
}
// PullReview
// swagger:response PullReview
type swaggerResponsePullReview struct {
@ -325,6 +340,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

@ -593,6 +593,7 @@ type DiffFile struct {
IsIncomplete bool
IsIncompleteLineTooLong bool
IsProtected bool
Diff string
}
// GetType returns type of diff file.

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

@ -19,8 +19,10 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification"
"code.gitea.io/gitea/modules/queue"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/gitdiff"
)
// prQueue represents a queue to handle update pull request tests
@ -234,6 +236,49 @@ func handle(data ...queue.Data) {
}
continue
}
// 创建pr版本
lastPrv, err := models.GetPullRequestLastVersionByPullRequest(pr)
if err != nil {
log.Error("get pull_request last version error: %v", err)
continue
}
err = pr.LoadIssue()
if err != nil {
log.Error("pullrequest load issue error: %v", err)
continue
}
pull := pr.Issue
pull.PullRequest = pr
baseGitRepo, err := git.OpenRepository(pr.BaseRepo.RepoPath())
if err != nil {
log.Error("git.OpenRepository err:%v", err)
continue
}
defer baseGitRepo.Close()
compareInfo, err := baseGitRepo.GetCompareInfo(pr.BaseRepo.RepoPath(),
git.BranchPrefix+pr.BaseBranch, pr.GetGitRefName())
if err != nil {
log.Error("baseGitRepo.GetCompareInfo err: %v", err)
continue
}
diffs, err := gitdiff.GetDiffRange(baseGitRepo, compareInfo.BaseCommitID, compareInfo.HeadCommitID, setting.Git.MaxGitDiffFiles, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles)
if err != nil {
log.Error("gitdiff.GetDiffRange err: %v", err)
continue
}
err = pull.LoadRepo()
if err != nil {
log.Error("pull.loadRepo err: %v", err)
continue
}
err = models.NewPullRequestVersion(pull.Repo, pr, diffs.TotalAddition, compareInfo.Commits.Len(), diffs.TotalDeletion, compareInfo.NumFiles, compareInfo.HeadCommitID, compareInfo.BaseCommitID, lastPrv.HeadCommitID)
if err != nil {
log.Error("models.NewPullRequestVersion err: %v", err)
continue
}
checkAndUpdateStatus(pr)
}
}

View File

@ -19,6 +19,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/gitdiff"
issue_service "code.gitea.io/gitea/services/issue"
jsoniter "github.com/json-iterator/go"
)
@ -104,6 +105,15 @@ func NewPullRequest(repo *models.Repository, pull *models.Issue, labelIDs []int6
_, _ = models.CreateComment(ops)
}
diffs, err := gitdiff.GetDiffRange(baseGitRepo, compareInfo.BaseCommitID, compareInfo.HeadCommitID, setting.Git.MaxGitDiffFiles, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles)
if err != nil {
return err
}
// create diff version
if err := models.NewPullRequestVersion(repo, pr, diffs.TotalAddition, compareInfo.Commits.Len(), diffs.TotalDeletion, compareInfo.NumFiles, compareInfo.HeadCommitID, compareInfo.BaseCommitID, compareInfo.BaseCommitID); err != nil {
return err
}
return nil
}

View File

@ -65,6 +65,12 @@ func IsValidHookTaskType(name string) bool {
if name == models.GITEA || name == models.GOGS {
return true
}
// 建木devops
if name == models.JIANMU {
return true
}
_, ok := webhooks[models.HookType(name)]
return ok
}
@ -135,7 +141,7 @@ func prepareWebhook(w *models.Webhook, repo *models.Repository, event models.Hoo
// Avoid sending "0 new commits" to non-integration relevant webhooks (e.g. slack, discord, etc.).
// Integration webhooks (e.g. drone) still receive the required data.
if pushEvent, ok := p.(*api.PushPayload); ok &&
w.Type != models.GITEA && w.Type != models.GOGS &&
w.Type != models.GITEA && w.Type != models.GOGS && w.Type != models.JIANMU &&
len(pushEvent.Commits) == 0 {
return nil
}

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)

File diff suppressed because it is too large Load Diff