gitea_hat/routers/hat/repo/file.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
}