Compare commits
78 Commits
Author | SHA1 | Date |
---|---|---|
|
061d648dad | |
|
92d987d157 | |
|
eb45bfd59e | |
|
4f42919095 | |
|
86b1d5c271 | |
|
b0408b1f20 | |
|
51eaed6519 | |
|
5a640448d8 | |
|
ed6254f545 | |
|
1d998c5c54 | |
|
2834ba8281 | |
|
7134770a03 | |
|
d4880ebd4e | |
|
940f16f4fa | |
|
24aae5fd00 | |
|
4c9f6a8400 | |
|
78926ec43d | |
|
6eefa22e78 | |
|
f9ef9455d0 | |
|
db945157cd | |
|
e6022ba142 | |
|
cf39a4b405 | |
|
ef32b08ee4 | |
|
5795a3f8d6 | |
|
9d0fe5b008 | |
|
b128dae4bc | |
|
66ed1b8f14 | |
|
a69d22aa0f | |
|
f433cede0a | |
|
c22f59b45c | |
|
677f1872f6 | |
|
6cad6ff212 | |
|
8757d15065 | |
|
727f203fd3 | |
|
c1c985e193 | |
|
57bc507770 | |
|
b124e6764b | |
|
9c6139bc61 | |
|
a7eebc5c59 | |
|
bfd5804dd0 | |
|
445a5f7d77 | |
|
8e5f72bc41 | |
|
63ef5921d1 | |
|
93de827180 | |
|
f2d50f4cb8 | |
|
4a21b9f7ac | |
|
a3b1439a52 | |
|
cf23fdb592 | |
|
620df5904e | |
|
62d7840464 | |
|
6e74832c30 | |
|
c1f5a08987 | |
|
199e816395 | |
|
a217bcf43d | |
|
6917b164da | |
|
059487b243 | |
|
2e1e54f254 | |
|
f22c45d49c | |
|
b02fec5020 | |
|
97f325e855 | |
|
0ccfbe7a5d | |
|
8eb90e81f1 | |
|
c0d676e560 | |
|
082d59adae | |
|
5bb1d6667b | |
|
2115033586 | |
|
80e992aa49 | |
|
ab21895bc5 | |
|
49b36b1100 | |
|
be6365c3d5 | |
|
135fb19498 | |
|
a4ae6c7d65 | |
|
d59fbd0053 | |
|
dbb0de14cc | |
|
364a2c1366 | |
|
d6e2682a08 | |
|
88b3eafe77 | |
|
a813f92d31 |
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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",
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
}
|
|
@ -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))
|
||||
}
|
|
@ -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))
|
||||
}
|
|
@ -82,6 +82,7 @@ func init() {
|
|||
new(Action),
|
||||
new(Issue),
|
||||
new(PullRequest),
|
||||
new(PullRequestVersion),
|
||||
new(Comment),
|
||||
new(Attachment),
|
||||
new(Label),
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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"`
|
||||
|
|
|
@ -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"`
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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),
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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" {
|
||||
|
|
|
@ -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 you’re 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 you’re 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 you’re 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
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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"`
|
||||
|
|
|
@ -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"`
|
||||
}
|
|
@ -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] }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"`
|
||||
}
|
|
@ -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"`
|
||||
|
|
|
@ -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, Contributor’s 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) it’s 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
|
|
@ -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, Contributor’s 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) it’s 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
|
|
@ -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, Contributor’s 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) it’s 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
|
|
@ -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, Contributor’s 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) it’s 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
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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 repository’s 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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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" {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -593,6 +593,7 @@ type DiffFile struct {
|
|||
IsIncomplete bool
|
||||
IsIncompleteLineTooLong bool
|
||||
IsProtected bool
|
||||
Diff string
|
||||
}
|
||||
|
||||
// GetType returns type of diff 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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue