CloudIDE代码阅读功能部分api #58
|
@ -12,6 +12,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/process"
|
"code.gitea.io/gitea/modules/process"
|
||||||
)
|
)
|
||||||
|
@ -22,6 +23,25 @@ type BlamePart struct {
|
||||||
Lines []string
|
Lines []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ApiBlameCommit struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Author *Signature `json:"author"`
|
||||||
|
Commiter *Signature `json:"commiter"`
|
||||||
|
CommitMessage string `json:"commit_message"`
|
||||||
|
Parents []string `json:"parents"`
|
||||||
|
AuthoredTime time.Time `json:"authored_time"`
|
||||||
|
CommittedTime time.Time `json:"committed_time"`
|
||||||
|
CreatedTime time.Time `json:"created_time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ApiBlamePart struct {
|
||||||
|
Sha string `json:"-"`
|
||||||
|
Commit *ApiBlameCommit `json:"commit"`
|
||||||
|
CurrentNumber int `json:"current_number"`
|
||||||
|
EffectLine int `json:"effect_line"`
|
||||||
|
Lines []string `json:"lines"`
|
||||||
|
}
|
||||||
|
|
||||||
// BlameReader returns part of file blame one by one
|
// BlameReader returns part of file blame one by one
|
||||||
type BlameReader struct {
|
type BlameReader struct {
|
||||||
cmd *exec.Cmd
|
cmd *exec.Cmd
|
||||||
|
@ -34,6 +54,95 @@ type BlameReader struct {
|
||||||
|
|
||||||
var shaLineRegex = regexp.MustCompile("^([a-z0-9]{40})")
|
var shaLineRegex = regexp.MustCompile("^([a-z0-9]{40})")
|
||||||
|
|
||||||
|
func GetBlameCommit(repo *Repository, sha string) *ApiBlameCommit {
|
||||||
|
commit, err := repo.GetCommit(sha)
|
||||||
|
var apiParents []string
|
||||||
|
for i := 0; i < commit.ParentCount(); i++ {
|
||||||
|
sha, _ := commit.ParentID(i)
|
||||||
|
apiParents = append(apiParents, sha.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return &ApiBlameCommit{}
|
||||||
|
} else {
|
||||||
|
return &ApiBlameCommit{
|
||||||
|
ID: sha,
|
||||||
|
Author: commit.Author,
|
||||||
|
Commiter: commit.Committer,
|
||||||
|
CommitMessage: commit.CommitMessage,
|
||||||
|
Parents: apiParents,
|
||||||
|
AuthoredTime: commit.Author.When,
|
||||||
|
CommittedTime: commit.Committer.When,
|
||||||
|
CreatedTime: commit.Committer.When,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *BlameReader) NextApiPart(repo *Repository) (*ApiBlamePart, error) {
|
||||||
|
var blamePart *ApiBlamePart
|
||||||
|
|
||||||
|
reader := r.reader
|
||||||
|
effectLine := 0
|
||||||
|
|
||||||
|
if r.lastSha != nil {
|
||||||
|
blamePart = &ApiBlamePart{*r.lastSha, GetBlameCommit(repo, *r.lastSha), 0, effectLine, make([]string, 0)}
|
||||||
|
}
|
||||||
|
|
||||||
|
var line []byte
|
||||||
|
var isPrefix bool
|
||||||
|
var err error
|
||||||
|
|
||||||
|
for err != io.EOF {
|
||||||
|
line, isPrefix, err = reader.ReadLine()
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return blamePart, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(line) == 0 {
|
||||||
|
// isPrefix will be false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := shaLineRegex.FindSubmatch(line)
|
||||||
|
if lines != nil {
|
||||||
|
sha1 := string(lines[1])
|
||||||
|
|
||||||
|
if blamePart == nil {
|
||||||
|
blamePart = &ApiBlamePart{sha1, GetBlameCommit(repo, sha1), 0, effectLine, make([]string, 0)}
|
||||||
|
}
|
||||||
|
|
||||||
|
if blamePart.Sha != sha1 {
|
||||||
|
r.lastSha = &sha1
|
||||||
|
// need to munch to end of line...
|
||||||
|
for isPrefix {
|
||||||
|
_, isPrefix, err = reader.ReadLine()
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return blamePart, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return blamePart, nil
|
||||||
|
}
|
||||||
|
} else if line[0] == '\t' {
|
||||||
|
code := line[1:]
|
||||||
|
effectLine += 1
|
||||||
|
blamePart.Lines = append(blamePart.Lines, string(code))
|
||||||
|
}
|
||||||
|
blamePart.EffectLine = effectLine
|
||||||
|
|
||||||
|
// need to munch to end of line...
|
||||||
|
for isPrefix {
|
||||||
|
_, isPrefix, err = reader.ReadLine()
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return blamePart, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r.lastSha = nil
|
||||||
|
|
||||||
|
return blamePart, nil
|
||||||
|
}
|
||||||
|
|
||||||
// NextPart returns next part of blame (sequential code lines with the same commit)
|
// NextPart returns next part of blame (sequential code lines with the same commit)
|
||||||
func (r *BlameReader) NextPart() (*BlamePart, error) {
|
func (r *BlameReader) NextPart() (*BlamePart, error) {
|
||||||
var blamePart *BlamePart
|
var blamePart *BlamePart
|
||||||
|
|
|
@ -15,6 +15,21 @@ import (
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func GetBatchFileResponseFromCommit(repo *models.Repository, commit *git.Commit, branch string, treeNames []string) (*api.BatchFileResponse, error) {
|
||||||
|
fileCommitResponse, _ := GetFileCommitResponse(repo, commit)
|
||||||
|
verification := GetPayloadCommitVerification(commit)
|
||||||
|
batchFileResponse := &api.BatchFileResponse{
|
||||||
|
Commit: fileCommitResponse,
|
||||||
|
Verification: verification,
|
||||||
|
}
|
||||||
|
for _, treeName := range treeNames {
|
||||||
|
fileContent, _ := GetContents(repo, treeName, branch, false)
|
||||||
|
batchFileResponse.Contents = append(batchFileResponse.Contents, fileContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
return batchFileResponse, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetFileResponseFromCommit Constructs a FileResponse from a Commit object
|
// GetFileResponseFromCommit Constructs a FileResponse from a Commit object
|
||||||
func GetFileResponseFromCommit(repo *models.Repository, commit *git.Commit, branch, treeName string) (*api.FileResponse, error) {
|
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
|
fileContents, _ := GetContents(repo, treeName, branch, false) // ok if fails, then will be nil
|
||||||
|
|
|
@ -20,11 +20,30 @@ import (
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/structs"
|
"code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
"github.com/gobwas/glob"
|
||||||
|
|
||||||
stdcharset "golang.org/x/net/html/charset"
|
stdcharset "golang.org/x/net/html/charset"
|
||||||
"golang.org/x/text/transform"
|
"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
|
// IdentityOptions for a person's identity like an author or committer
|
||||||
type IdentityOptions struct {
|
type IdentityOptions struct {
|
||||||
Name string
|
Name string
|
||||||
|
@ -54,6 +73,31 @@ type UpdateRepoFileOptions struct {
|
||||||
Signoff bool
|
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) {
|
func detectEncodingAndBOM(entry *git.TreeEntry, repo *models.Repository) (string, bool) {
|
||||||
reader, err := entry.Blob().DataAsync()
|
reader, err := entry.Blob().DataAsync()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -263,7 +307,7 @@ func CreateOrUpdateRepoFile(repo *models.Repository, doer *models.User, opts *Up
|
||||||
} else if changed {
|
} else if changed {
|
||||||
return nil, models.ErrCommitIDDoesNotMatch{
|
return nil, models.ErrCommitIDDoesNotMatch{
|
||||||
GivenCommitID: opts.LastCommitID,
|
GivenCommitID: opts.LastCommitID,
|
||||||
CurrentCommitID: opts.LastCommitID,
|
CurrentCommitID: commit.ID.String(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// The file wasn't modified, so we are good to delete it
|
// 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
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -30,6 +30,19 @@ type CreateFileOptions struct {
|
||||||
Content string `json:"content"`
|
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)
|
// 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)
|
// 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 {
|
type DeleteFileOptions struct {
|
||||||
|
@ -106,6 +119,12 @@ type FileResponse struct {
|
||||||
Verification *PayloadCommitVerification `json:"verification"`
|
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
|
// FileDeleteResponse contains information about a repo's file that was deleted
|
||||||
type FileDeleteResponse struct {
|
type FileDeleteResponse struct {
|
||||||
Content interface{} `json:"content"` // to be set to nil
|
Content interface{} `json:"content"` // to be set to nil
|
||||||
|
|
|
@ -1032,6 +1032,9 @@ func Routes() *web.Route {
|
||||||
m.Put("", bind(api.UpdateFileOptions{}), repo.UpdateFile)
|
m.Put("", bind(api.UpdateFileOptions{}), repo.UpdateFile)
|
||||||
m.Delete("", bind(api.DeleteFileOptions{}), repo.DeleteFile)
|
m.Delete("", bind(api.DeleteFileOptions{}), repo.DeleteFile)
|
||||||
}, reqRepoWriter(models.UnitTypeCode), reqToken())
|
}, reqRepoWriter(models.UnitTypeCode), reqToken())
|
||||||
|
m.Group("/batch", func() {
|
||||||
|
m.Post("", bind(api.BatchChangeFileOptions{}), repo.BatchChangeFile)
|
||||||
|
}, reqRepoWriter(models.UnitTypeCode), reqToken())
|
||||||
}, reqRepoReader(models.UnitTypeCode))
|
}, reqRepoReader(models.UnitTypeCode))
|
||||||
m.Get("/signing-key.gpg", misc.SigningKey)
|
m.Get("/signing-key.gpg", misc.SigningKey)
|
||||||
m.Group("/topics", func() {
|
m.Group("/topics", func() {
|
||||||
|
@ -1045,6 +1048,7 @@ func Routes() *web.Route {
|
||||||
m.Get("/issue_templates", context.ReferencesGitRepo(false), repo.GetIssueTemplates)
|
m.Get("/issue_templates", context.ReferencesGitRepo(false), repo.GetIssueTemplates)
|
||||||
m.Get("/languages", reqRepoReader(models.UnitTypeCode), repo.GetLanguages)
|
m.Get("/languages", reqRepoReader(models.UnitTypeCode), repo.GetLanguages)
|
||||||
m.Get("/diffs", context.RepoRef(), repo.GetRepoDiffs)
|
m.Get("/diffs", context.RepoRef(), repo.GetRepoDiffs)
|
||||||
|
m.Get("/blame", context.RepoRef(), repo.GetRepoRefBlame)
|
||||||
}, repoAssignment())
|
}, repoAssignment())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,115 @@
|
||||||
|
package repo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models"
|
||||||
|
"code.gitea.io/gitea/modules/context"
|
||||||
|
"code.gitea.io/gitea/modules/git"
|
||||||
|
)
|
||||||
|
|
||||||
|
type APIBlameResponse struct {
|
||||||
|
FileSize int64 `json:"file_size"`
|
||||||
|
FileName string `json:"file_name"`
|
||||||
|
NumberLines int `json:"num_lines"`
|
||||||
|
BlameParts []git.ApiBlamePart `json:"blame_parts"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetRepoRefBlame(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /repos/{owner}/{repo}/blame repository repoGetRefBlame
|
||||||
|
// ---
|
||||||
|
// summary: Get blame from a repository by sha and filepath***
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: sha
|
||||||
|
// in: query
|
||||||
|
// description: repo commit sha or branch
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: filepath
|
||||||
|
// in: query
|
||||||
|
// description: filepath in repository
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// 200:
|
||||||
|
// description: success
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
if ctx.Repo.Repository.IsEmpty {
|
||||||
|
ctx.NotFound()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var commit *git.Commit
|
||||||
|
if sha := ctx.QueryTrim("sha"); len(sha) > 0 {
|
||||||
|
var err error
|
||||||
|
commit, err = ctx.Repo.GitRepo.GetCommit(sha)
|
||||||
|
if err != nil {
|
||||||
|
if git.IsErrNotExist(err) {
|
||||||
|
ctx.NotFound()
|
||||||
|
} else {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println(commit)
|
||||||
|
|
||||||
|
filepath := ctx.QueryTrim("filepath")
|
||||||
|
fmt.Println(filepath)
|
||||||
|
entry, err := commit.GetTreeEntryByPath(filepath)
|
||||||
|
if err != nil {
|
||||||
|
ctx.NotFoundOrServerError("commit.GetTreeEntryByPath", git.IsErrNotExist, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
blob := entry.Blob()
|
||||||
|
numLines, err := blob.GetBlobLineCount()
|
||||||
|
if err != nil {
|
||||||
|
ctx.NotFound("GetBlobLineCount", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
blameReader, err := git.CreateBlameReader(ctx, models.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name), commit.ID.String(), filepath)
|
||||||
|
if err != nil {
|
||||||
|
ctx.NotFound("CreateBlameReader", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer blameReader.Close()
|
||||||
|
|
||||||
|
blameParts := make([]git.ApiBlamePart, 0)
|
||||||
|
currentNumber := 1
|
||||||
|
for {
|
||||||
|
blamePart, err := blameReader.NextApiPart(ctx.Repo.GitRepo)
|
||||||
|
if err != nil {
|
||||||
|
ctx.NotFound("NextPart", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if blamePart == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
blamePart.CurrentNumber = currentNumber
|
||||||
|
blameParts = append(blameParts, *blamePart)
|
||||||
|
currentNumber += blamePart.EffectLine
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, APIBlameResponse{
|
||||||
|
FileSize: blob.Size(),
|
||||||
|
FileName: blob.Name(),
|
||||||
|
NumberLines: numLines,
|
||||||
|
BlameParts: blameParts,
|
||||||
|
})
|
||||||
|
}
|
|
@ -539,7 +539,34 @@ func GetFileAllCommits(ctx *context.APIContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取 commit diff
|
// 获取 commit diff
|
||||||
|
// Diff get diffs by commit on a repository
|
||||||
func Diff(ctx *context.APIContext) {
|
func Diff(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /repos/{owner}/{repo}/commits/{sha}/diff repository repoGetDiffs***
|
||||||
|
// ---
|
||||||
|
// summary: Get diffs by commit from a repository
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: sha
|
||||||
|
// in: path
|
||||||
|
// description: name of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// 200:
|
||||||
|
// description: success
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
commitID := ctx.Params(":sha")
|
commitID := ctx.Params(":sha")
|
||||||
|
|
||||||
|
|
|
@ -280,11 +280,76 @@ func CreateFile(ctx *context.APIContext) {
|
||||||
|
|
||||||
if fileResponse, err := createOrUpdateFile(ctx, opts); err != nil {
|
if fileResponse, err := createOrUpdateFile(ctx, opts); err != nil {
|
||||||
handleCreateOrUpdateFileError(ctx, err)
|
handleCreateOrUpdateFileError(ctx, err)
|
||||||
|
return
|
||||||
} else {
|
} else {
|
||||||
ctx.JSON(http.StatusCreated, fileResponse)
|
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
|
// UpdateFile handles API call for updating a file
|
||||||
func UpdateFile(ctx *context.APIContext) {
|
func UpdateFile(ctx *context.APIContext) {
|
||||||
// swagger:operation PUT /repos/{owner}/{repo}/contents/{filepath} repository repoUpdateFile
|
// swagger:operation PUT /repos/{owner}/{repo}/contents/{filepath} repository repoUpdateFile
|
||||||
|
@ -392,6 +457,59 @@ func handleCreateOrUpdateFileError(ctx *context.APIContext, err error) {
|
||||||
ctx.Error(http.StatusInternalServerError, "UpdateFile", err)
|
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
|
// Called from both CreateFile or UpdateFile to handle both
|
||||||
func createOrUpdateFile(ctx *context.APIContext, opts *repofiles.UpdateRepoFileOptions) (*api.FileResponse, error) {
|
func createOrUpdateFile(ctx *context.APIContext, opts *repofiles.UpdateRepoFileOptions) (*api.FileResponse, error) {
|
||||||
if !canWriteFiles(ctx.Repo) {
|
if !canWriteFiles(ctx.Repo) {
|
||||||
|
|
|
@ -119,6 +119,9 @@ type swaggerParameterBodies struct {
|
||||||
// in:body
|
// in:body
|
||||||
CreateFileOptions api.CreateFileOptions
|
CreateFileOptions api.CreateFileOptions
|
||||||
|
|
||||||
|
// in:body
|
||||||
|
BatchChangeFileOptions api.BatchChangeFileOptions
|
||||||
|
|
||||||
// in:body
|
// in:body
|
||||||
UpdateFileOptions api.UpdateFileOptions
|
UpdateFileOptions api.UpdateFileOptions
|
||||||
|
|
||||||
|
|
|
@ -2424,6 +2424,56 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/repos/{owner}/{repo}/blame": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "Get blame from a repository by sha and filepath***",
|
||||||
|
"operationId": "repoGetRefBlame",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "owner of the repo",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the repo",
|
||||||
|
"name": "repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "repo commit sha or branch",
|
||||||
|
"name": "sha",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "filepath in repository",
|
||||||
|
"name": "filepath",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "success"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/repos/{owner}/{repo}/branch_name_set": {
|
"/repos/{owner}/{repo}/branch_name_set": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
|
@ -3419,6 +3469,59 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/repos/{owner}/{repo}/contents/batch": {
|
||||||
|
"post": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "Change some files in a repository***",
|
||||||
|
"operationId": "repoBatchChangeFile",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "owner of the repo",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the repo",
|
||||||
|
"name": "repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/BatchChangeFileOptions"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"$ref": "#/responses/BatchFileResponse"
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/repos/{owner}/{repo}/contents/{filepath}": {
|
"/repos/{owner}/{repo}/contents/{filepath}": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
|
@ -13414,6 +13517,50 @@
|
||||||
},
|
},
|
||||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
},
|
},
|
||||||
|
"BatchChangeFileOptions": {
|
||||||
|
"description": "BatchCreateFileOptions options for creating more files",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"files": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"action_type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"create",
|
||||||
|
"update",
|
||||||
|
"delete"
|
||||||
|
],
|
||||||
|
"x-go-name": "ActionType"
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "Content"
|
||||||
|
},
|
||||||
|
"encoding": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"text",
|
||||||
|
"base64"
|
||||||
|
],
|
||||||
|
"x-go-name": "Encoding"
|
||||||
|
},
|
||||||
|
"file_path": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "FilePath"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-name": "Files"
|
||||||
|
},
|
||||||
|
"header": {
|
||||||
|
"$ref": "#/definitions/FileOptions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
|
},
|
||||||
"Branch": {
|
"Branch": {
|
||||||
"description": "Branch represents a repository branch",
|
"description": "Branch represents a repository branch",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
@ -16064,6 +16211,42 @@
|
||||||
},
|
},
|
||||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
},
|
},
|
||||||
|
"FileOptions": {
|
||||||
|
"description": "FileOptions options for all file APIs",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"author": {
|
||||||
|
"$ref": "#/definitions/Identity"
|
||||||
|
},
|
||||||
|
"branch": {
|
||||||
|
"description": "branch (optional) to base this file from. if not given, the default branch is used",
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "BranchName"
|
||||||
|
},
|
||||||
|
"committer": {
|
||||||
|
"$ref": "#/definitions/Identity"
|
||||||
|
},
|
||||||
|
"dates": {
|
||||||
|
"$ref": "#/definitions/CommitDateOptions"
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"description": "message (optional) for the commit of this file. if not supplied, a default message will be used",
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "Message"
|
||||||
|
},
|
||||||
|
"new_branch": {
|
||||||
|
"description": "new_branch (optional) will make a new branch from `branch` before creating the file",
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "NewBranchName"
|
||||||
|
},
|
||||||
|
"signoff": {
|
||||||
|
"description": "Add a Signed-off-by trailer by the committer at the end of the commit log message.",
|
||||||
|
"type": "boolean",
|
||||||
|
"x-go-name": "Signoff"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
|
},
|
||||||
"FileResponse": {
|
"FileResponse": {
|
||||||
"description": "FileResponse contains information about a repo's file",
|
"description": "FileResponse contains information about a repo's file",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|
Loading…
Reference in New Issue