354 lines
10 KiB
Go
354 lines
10 KiB
Go
package repo
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"code.gitea.io/gitea/models"
|
|
repo_model "code.gitea.io/gitea/models/repo"
|
|
unit_model "code.gitea.io/gitea/models/unit"
|
|
|
|
"code.gitea.io/gitea/modules/context"
|
|
"code.gitea.io/gitea/modules/git"
|
|
api "code.gitea.io/gitea/modules/structs"
|
|
"code.gitea.io/gitea/modules/util"
|
|
"code.gitea.io/gitea/modules/web"
|
|
files_service "code.gitea.io/gitea/services/repository/files"
|
|
hat_api "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/structs"
|
|
hat_files_service "code.gitlink.org.cn/Gitlink/gitea_hat.git/services/repository/files"
|
|
)
|
|
|
|
func BatchChangeFile(ctx *context.APIContext) {
|
|
apiBatchOpts := web.GetForm(ctx).(*hat_api.BatchChangeFileOptions)
|
|
|
|
if ctx.Repo.Repository.IsEmpty {
|
|
ctx.Error(http.StatusUnprocessableEntity, "RepoIsEmpty", fmt.Errorf("repo is empty"))
|
|
return
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
func createOrUpdateOrDeleteFiles(ctx *context.APIContext, apiBatchOpts *hat_api.BatchChangeFileOptions) (*hat_api.BatchFileResponse, error) {
|
|
if !canWriteFiles(ctx.Repo) {
|
|
return nil, repo_model.ErrUserDoesNotHaveAccessToRepo{
|
|
UserID: ctx.Doer.ID,
|
|
RepoName: ctx.Repo.Repository.LowerName,
|
|
}
|
|
}
|
|
fileChan := make(chan hat_files_service.BatchSingleFileOption)
|
|
stopChan := make(chan bool)
|
|
errChan := make(chan error)
|
|
exchangeOption := &hat_files_service.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 <- hat_files_service.BatchSingleFileOption{
|
|
Content: f.Content,
|
|
TreePath: f.FilePath,
|
|
ActionType: hat_files_service.ToFileActionType(f.ActionType),
|
|
}
|
|
}
|
|
exchangeOption.StopChan <- true
|
|
}()
|
|
opts := &hat_files_service.BatchUpdateFileOptions{
|
|
Message: apiBatchOpts.Header.Message,
|
|
OldBranch: apiBatchOpts.Header.BranchName,
|
|
NewBranch: apiBatchOpts.Header.NewBranchName,
|
|
Commiter: &files_service.IdentityOptions{
|
|
Name: apiBatchOpts.Header.Committer.Name,
|
|
Email: apiBatchOpts.Header.Committer.Email,
|
|
},
|
|
Author: &files_service.IdentityOptions{
|
|
Name: apiBatchOpts.Header.Author.Name,
|
|
Email: apiBatchOpts.Header.Author.Email,
|
|
},
|
|
Dates: &files_service.CommitDateOptions{
|
|
Author: apiBatchOpts.Header.Dates.Author,
|
|
Committer: apiBatchOpts.Header.Dates.Committer,
|
|
},
|
|
Signoff: apiBatchOpts.Header.Signoff,
|
|
}
|
|
|
|
return hat_files_service.CreateOrUpdateOrDeleteRepofiles(ctx, ctx.Repo.Repository, ctx.Doer, opts, exchangeOption)
|
|
}
|
|
|
|
func handleCreateOrUpdateFileError(ctx *context.APIContext, err error) {
|
|
if models.IsErrUserCannotCommit(err) || models.IsErrFilePathProtected(err) {
|
|
ctx.Error(http.StatusForbidden, "Access", err)
|
|
return
|
|
}
|
|
if models.IsErrBranchAlreadyExists(err) || models.IsErrFilenameInvalid(err) || models.IsErrSHADoesNotMatch(err) ||
|
|
models.IsErrFilePathInvalid(err) || models.IsErrRepoFileAlreadyExists(err) {
|
|
ctx.Error(http.StatusUnprocessableEntity, "Invalid", err)
|
|
return
|
|
}
|
|
if models.IsErrBranchDoesNotExist(err) || git.IsErrBranchNotExist(err) {
|
|
ctx.Error(http.StatusNotFound, "BranchDoesNotExist", err)
|
|
return
|
|
}
|
|
|
|
ctx.Error(http.StatusInternalServerError, "UpdateFile", err)
|
|
}
|
|
|
|
func GetReadmeContents(ctx *context.APIContext) {
|
|
if !canReadFiles(ctx.Repo) {
|
|
ctx.Error(http.StatusInternalServerError, "canReadFiles", repo_model.ErrUserDoesNotHaveAccessToRepo{
|
|
UserID: ctx.ContextUser.ID,
|
|
RepoName: ctx.Repo.Repository.LowerName,
|
|
})
|
|
return
|
|
}
|
|
|
|
ref := ctx.Params(":ref")
|
|
readmeSortArr := []string{"readme", "readme.en.md", "readme_en.md", "readme.md", "readme.ch.md", "readme_ch.md", "readme.zh.md", "readme_zh.md", "readme.cn.md", "readme_cn.md"}
|
|
readmePath := ""
|
|
readmePathInArrIndex := 0
|
|
filesListInterface, err := hat_files_service.GetContentsOrList(ctx, ctx.Repo.Repository, "", ref)
|
|
if err != nil {
|
|
ctx.Error(http.StatusInternalServerError, "GetContentsOrList", err)
|
|
return
|
|
}
|
|
filesList, ok := filesListInterface.([]*hat_api.ContentsResponse)
|
|
if ok {
|
|
for _, file := range filesList {
|
|
for i, sortItem := range readmeSortArr {
|
|
if strings.ToLower(file.Name) == sortItem {
|
|
if readmePath != "" && readmePathInArrIndex > i {
|
|
continue
|
|
} else {
|
|
readmePathInArrIndex = i
|
|
readmePath = file.Name
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if fileList, err := files_service.GetContentsOrList(ctx, ctx.Repo.Repository, readmePath, ref); err != nil {
|
|
if git.IsErrNotExist(err) {
|
|
ctx.NotFound("GetContentsOrList", err)
|
|
return
|
|
}
|
|
ctx.Error(http.StatusInternalServerError, "GetContentsOrList", err)
|
|
|
|
} else {
|
|
ctx.JSON(http.StatusOK, fileList)
|
|
}
|
|
}
|
|
|
|
func canWriteFiles(r *context.Repository) bool {
|
|
return r.Permission.CanWrite(unit_model.UnitCode.Type) && !r.Repository.IsMirror && !r.Repository.IsArchived
|
|
}
|
|
|
|
func canReadFiles(r *context.Repository) bool {
|
|
return r.Permission.CanRead(unit_model.UnitCode.Type)
|
|
}
|
|
|
|
func GetReadmeContentsByPath(ctx *context.APIContext) {
|
|
if !canReadFiles(ctx.Repo) {
|
|
ctx.Error(http.StatusInternalServerError, "canReadFiles", repo_model.ErrUserDoesNotHaveAccessToRepo{
|
|
UserID: ctx.ContextUser.ID,
|
|
RepoName: ctx.Repo.Repository.LowerName,
|
|
})
|
|
return
|
|
}
|
|
treePath := ctx.Params("*")
|
|
ref := ctx.Params(":ref")
|
|
readmeSortArr := []string{"readme", "readme.en.md", "readme_en.md", "readme.md", "readme.ch.md", "readme_ch.md", "readme.zh.md", "readme_zh.md", "readme.cn.md", "readme_cn.md"}
|
|
readmePath := ""
|
|
readmePathInArrIndex := 0
|
|
filesListInterface, err := files_service.GetContentsOrList(ctx, ctx.Repo.Repository, treePath, ref)
|
|
if err != nil {
|
|
ctx.Error(http.StatusInternalServerError, "GetContentsOrList", err)
|
|
return
|
|
}
|
|
filesList, ok := filesListInterface.([]*api.ContentsResponse)
|
|
if ok {
|
|
for _, file := range filesList {
|
|
for i, sortItem := range readmeSortArr {
|
|
if strings.ToLower(file.Name) == sortItem {
|
|
if readmePath != "" && readmePathInArrIndex > i {
|
|
continue
|
|
} else {
|
|
readmePathInArrIndex = i
|
|
readmePath = file.Name
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
newTreePath := treePath + "/" + readmePath
|
|
if fileList, err := files_service.GetContentsOrList(ctx, ctx.Repo.Repository, newTreePath, ref); err != nil {
|
|
if git.IsErrNotExist(err) {
|
|
ctx.NotFound("GetContentsOrList", err)
|
|
return
|
|
}
|
|
ctx.Error(http.StatusInternalServerError, "GetContentsOrList", err)
|
|
|
|
} else {
|
|
ctx.JSON(http.StatusOK, fileList)
|
|
}
|
|
}
|
|
|
|
func GetContents(ctx *context.APIContext) {
|
|
if !canReadFiles(ctx.Repo) {
|
|
ctx.Error(http.StatusInternalServerError, "GetContents", repo_model.ErrUserDoesNotHaveAccessToRepo{
|
|
UserID: ctx.Doer.ID,
|
|
RepoName: ctx.Repo.Repository.Name,
|
|
})
|
|
return
|
|
}
|
|
|
|
treePath := ctx.Params("*")
|
|
ref := ctx.FormTrim("ref")
|
|
|
|
if fileList, err := hat_files_service.GetContentsOrList(ctx, ctx.Repo.Repository, treePath, ref); err != nil {
|
|
if git.IsErrNotExist(err) {
|
|
ctx.NotFound("GetContentsOrList", err)
|
|
return
|
|
}
|
|
ctx.Error(http.StatusInternalServerError, "GetContentsOrList", err)
|
|
} else {
|
|
ctx.JSON(http.StatusOK, fileList)
|
|
}
|
|
}
|
|
|
|
func GetContentsList(ctx *context.APIContext) {
|
|
GetContents(ctx)
|
|
}
|
|
|
|
func FindFiles(ctx *context.APIContext) {
|
|
treePath := ctx.Repo.TreePath
|
|
|
|
ref := ctx.FormString("ref")
|
|
if ref == "" {
|
|
ref = ctx.Repo.Repository.DefaultBranch
|
|
}
|
|
|
|
keyWords := ctx.FormString("q")
|
|
var fileList []*SearchFileItem
|
|
var err error
|
|
fileList, err = FindFileFromPath(ctx, treePath, ref, keyWords)
|
|
if err != nil {
|
|
ctx.Error(http.StatusInternalServerError, "FindFileFromPath", err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, fileList)
|
|
}
|
|
|
|
type SearchFileItem struct {
|
|
Name string `json:"name"`
|
|
Path string `json:"path"`
|
|
SHA string `json:"sha"`
|
|
Type string `json:"type"`
|
|
Size int64 `json:"size"`
|
|
URL *string `json:"url"`
|
|
HTMLURL *string `json:"html_url"`
|
|
}
|
|
|
|
func FindFileFromPath(ctx *context.APIContext, treePath, ref, key string) (fileList []*SearchFileItem, err error) {
|
|
// get the commit object for the ref
|
|
commit, err := ctx.Repo.GitRepo.GetCommit(ref)
|
|
if err != nil {
|
|
ctx.ServerError("GetCommit", err)
|
|
return nil, err
|
|
}
|
|
tree, err2 := commit.SubTree(treePath)
|
|
if err2 != nil {
|
|
ctx.ServerError("SubTree", err)
|
|
return nil, err2
|
|
}
|
|
entries, err3 := tree.ListEntriesRecursiveFast()
|
|
if err3 != nil {
|
|
ctx.ServerError("ListEntries", err3)
|
|
return nil, err3
|
|
}
|
|
fileList = make([]*SearchFileItem, 0)
|
|
|
|
for _, entry := range entries {
|
|
if entry.IsDir() {
|
|
continue
|
|
}
|
|
filename := filepath.Base(entry.Name())
|
|
if strings.Contains(strings.ToLower(filename), strings.ToLower(key)) || key == "" {
|
|
name := entry.Name()
|
|
treePath = name
|
|
|
|
selfURL, err := url.Parse(ctx.Repo.Repository.APIURL() + "/contents/" + util.PathEscapeSegments(treePath) + "?ref=" + url.QueryEscape(ref))
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
selfURLString := selfURL.String()
|
|
|
|
refType := ctx.Repo.GitRepo.GetRefType(ref)
|
|
if refType == "invalid" {
|
|
return nil, fmt.Errorf("no commit found for the ref [ref: %s]", ref)
|
|
}
|
|
// htmlURL, err := url.Parse(fmt.Sprintf("%s/src/%s/%s/%s", ctx.Repo.Repository.HTMLURL(), refType, ref, treePath))
|
|
htmlURL, err := url.Parse(ctx.Repo.Repository.APIURL() + "/src/" + url.QueryEscape(string(refType)) + "/" + url.QueryEscape(ref) + "/" + util.PathEscapeSegments(treePath))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
htmlURLString := htmlURL.String()
|
|
item := &SearchFileItem{
|
|
Name: filename,
|
|
Path: treePath,
|
|
SHA: entry.ID.String(),
|
|
Size: entry.Size(),
|
|
URL: &selfURLString,
|
|
HTMLURL: &htmlURLString,
|
|
}
|
|
// Now populate the rest of the ContentsResponse based on entry type
|
|
if entry.IsRegular() || entry.IsExecutable() {
|
|
item.Type = "file"
|
|
} else if entry.IsDir() {
|
|
item.Type = "dir"
|
|
} else if entry.IsLink() {
|
|
item.Type = "symlink"
|
|
} else if entry.IsSubModule() {
|
|
item.Type = "submodule"
|
|
}
|
|
fileList = append(fileList, item)
|
|
|
|
}
|
|
}
|
|
return
|
|
|
|
}
|