Browse Source

#1773

add repo rename function
tags/v1.22.5.1^2
open_test 3 years ago
parent
commit
85b1663ab9
4 changed files with 369 additions and 0 deletions
  1. +6
    -0
      modules/auth/repo_form.go
  2. +264
    -0
      modules/repofiles/update.go
  3. +98
    -0
      routers/repo/editor.go
  4. +1
    -0
      routers/routes/routes.go

+ 6
- 0
modules/auth/repo_form.go View File

@@ -740,3 +740,9 @@ type CreateCourseForm struct {
func (f *CreateCourseForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}

// RenameRepoFileForm form for renaming repository file
type RenameRepoFileForm struct {
TreePath string `binding:"Required;MaxSize(500)"`
LastCommit string
}

+ 264
- 0
modules/repofiles/update.go View File

@@ -756,3 +756,267 @@ func createCommitRepoActions(repo *models.Repository, gitRepo *git.Repository, o
}
return actions, nil
}

// RenameRepoFileOptions
type RenameRepoFileOptions struct {
LastCommitID string
BranchName string
TreePath string
FromTreePath string
Message string
Author *IdentityOptions
Committer *IdentityOptions
}

// RenameRepoFile rename file in the given repository
func RenameRepoFile(repo *models.Repository, doer *models.User, opts *RenameRepoFileOptions) error {

// Branch must exist for this operation
if _, err := repo_module.GetBranch(repo, opts.BranchName); err != nil {
return err
}

// A NewBranch can be specified for the file to be created/updated in a new branch.
// Check to make sure the branch does not already exist, otherwise we can't proceed.
// If we aren't branching to a new branch, make sure user can commit to the given branch
protectedBranch, err := repo.GetBranchProtection(opts.BranchName)
if err != nil {
return err
}
if protectedBranch != nil {
if !protectedBranch.CanUserPush(doer.ID) {
return models.ErrUserCannotCommit{
UserName: doer.LowerName,
}
}
if protectedBranch.RequireSignedCommits {
_, _, err := repo.SignCRUDAction(doer, repo.RepoPath(), opts.BranchName)
if err != nil {
if !models.IsErrWontSign(err) {
return err
}
return models.ErrUserCannotCommit{
UserName: doer.LowerName,
}
}
}
patterns := protectedBranch.GetProtectedFilePatterns()
for _, pat := range patterns {
if pat.Match(strings.ToLower(opts.TreePath)) {
return models.ErrFilePathProtected{
Path: opts.TreePath,
}
}
}
}

// Check that the path given in opts.treePath is valid (not a git path)
treePath := CleanUploadFileName(opts.TreePath)
if treePath == "" {
return models.ErrFilenameInvalid{
Path: opts.TreePath,
}
}
// If there is a fromTreePath (we are copying it), also clean it up
fromTreePath := CleanUploadFileName(opts.FromTreePath)
if fromTreePath == "" && opts.FromTreePath != "" {
return models.ErrFilenameInvalid{
Path: opts.FromTreePath,
}
}

message := strings.TrimSpace(opts.Message)

author, committer := GetAuthorAndCommitterUsers(opts.Author, opts.Committer, doer)

t, err := NewTemporaryUploadRepository(repo)
if err != nil {
log.Error("%v", err)
}
defer t.Close()
if err := t.Clone(opts.BranchName); err != nil {
return err
}
if err := t.SetDefaultIndex(); err != nil {
return err
}

// Get the commit of the original branch
commit, err := t.GetBranchCommit(opts.BranchName)
if err != nil {
return err // Couldn't get a commit for the branch
}

lastCommitID, err := t.gitRepo.ConvertToSHA1(opts.LastCommitID)
if err != nil {
return fmt.Errorf("DeleteRepoFile: Invalid last commit ID: %v", err)
}
opts.LastCommitID = lastCommitID.String()

//encoding := "UTF-8"
//bom := false
//executable := false

fromTreeEntry, err := commit.GetTreeEntryByPath(fromTreePath)
if err != nil {
return err
}
if opts.LastCommitID == "" {
// When updating 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 models.ErrSHAOrCommitIDNotProvided{}
}
//获取
if fromTreeEntry.IsDir() {
tree, err := commit.Tree.SubTree(fromTreePath)
if err != nil {
return err
}
if err := moveAndAddDir(fromTreePath, treePath, opts.LastCommitID, tree, commit, t); err != nil {
return err
}
} else {
if err := moveAndAddFile(opts.FromTreePath, opts.TreePath, opts.LastCommitID, commit, t); err != nil {
return err
}
}

// Now write the tree
treeHash, err := t.WriteTree()
if err != nil {
return err
}

// Now commit the tree
commitHash, err := t.CommitTree(author, committer, treeHash, message)
if err != nil {
return err
}

// Then push this tree to NewBranch
if err := t.Push(doer, commitHash, opts.BranchName); err != nil {
log.Error("%T %v", err, err)
return err
}

return nil
}

func moveAndAddDir(oldTreePath, newTreePath, lastCommitID string, currentTree *git.Tree, currentBranchCommit *git.Commit, t *TemporaryUploadRepository) error {
entries, err := currentTree.ListEntries()
if err != nil {
return err
}
for _, v := range entries {
if v.IsDir() {
subTree, err := currentTree.SubTree(v.Name())
if err != nil {
return err
}
if err := moveAndAddDir(path.Join(oldTreePath, v.Name()), path.Join(newTreePath, v.Name()), lastCommitID, subTree, currentBranchCommit, t); err != nil {
return err
}
} else {
if err := moveAndAddFile(path.Join(oldTreePath, v.Name()), path.Join(newTreePath, v.Name()), lastCommitID, currentBranchCommit, t); err != nil {
return err
}
}
}
return nil
}

func moveAndAddFile(oldTreePath, newTreePath, lastCommitID string, currentBranchCommit *git.Commit, t *TemporaryUploadRepository) error {
fromEntry, err := currentBranchCommit.GetTreeEntryByPath(oldTreePath)
if err != nil {
return err
}
// 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 currentBranchCommit.ID.String() != lastCommitID {
if changed, err := currentBranchCommit.FileChangedSinceCommit(oldTreePath, lastCommitID); err != nil {
return err
} else if changed {
return models.ErrCommitIDDoesNotMatch{
GivenCommitID: lastCommitID,
CurrentCommitID: lastCommitID,
}
}
// The file wasn't modified, so we are good to delete it
}
executable := fromEntry.IsExecutable()

// For the path where this file will be created/updated, we need to make
// sure no parts of the path are existing files or links except for the last
// item in the path which is the file name, and that shouldn't exist IF it is
// a new file OR is being moved to a new path.
treePathParts := strings.Split(newTreePath, "/")
subTreePath := ""
for index, part := range treePathParts {
subTreePath = path.Join(subTreePath, part)
entry, err := currentBranchCommit.GetTreeEntryByPath(subTreePath)
if err != nil {
if git.IsErrNotExist(err) {
// Means there is no item with that name, so we're good
break
}
return err
}
if index < len(treePathParts)-1 {
if !entry.IsDir() {
return 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 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 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,
}
}

}

// Get the two paths (might be the same if not moving) from the index if they exist
filesInIndex, err := t.LsFiles(newTreePath, oldTreePath)
if err != nil {
return fmt.Errorf("UpdateRepoFile: %v", err)
}

// Remove the old path from the tree
if len(filesInIndex) > 0 {
for _, file := range filesInIndex {
if file == oldTreePath {
if err := t.RemoveFilesFromIndex(oldTreePath); err != nil {
return err
}
}
}
}

//获取oldTreePath的objectHash
objectHash := fmt.Sprint(fromEntry.Blob().ID)

// Add the object to the index
if executable {
if err := t.AddObjectToIndex("100755", objectHash, newTreePath); err != nil {
return err
}
} else {
if err := t.AddObjectToIndex("100644", objectHash, newTreePath); err != nil {
return err
}
}
return nil
}

+ 98
- 0
routers/repo/editor.go View File

@@ -9,6 +9,7 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"path"
"path/filepath"
"strings"
@@ -795,3 +796,100 @@ func GetClosestParentWithFiles(treePath string, commit *git.Commit) string {
}
return treePath
}

// RenameFilePost response for editing file
func RenameFilePost(ctx *context.Context, form auth.RenameRepoFileForm) {
renameFilePost(ctx, form)
}

func renameFilePost(ctx *context.Context, form auth.RenameRepoFileForm) {
//需要校验参数 commitId和
canCommit := renderCommitRights(ctx)
branchName := ctx.Repo.BranchName
if ctx.HasError() {
ctx.HTML(200, tplEditFile)
return
}

// Cannot commit to a an existing branch if user doesn't have rights
if branchName == ctx.Repo.BranchName && !canCommit {
ctx.Data["Err_NewBranchName"] = true
ctx.Data["commit_choice"] = frmCommitChoiceNewBranch
ctx.RenderWithErr(ctx.Tr("repo.editor.cannot_commit_to_protected_branch", branchName), tplEditFile, &form)
return
}

message := ctx.Tr("repo.editor.update", form.TreePath)

if len(message) > 0 {
message += "\n\n" + message
}

if err := repofiles.RenameRepoFile(ctx.Repo.Repository, ctx.User, &repofiles.RenameRepoFileOptions{
LastCommitID: form.LastCommit,
BranchName: branchName,
FromTreePath: ctx.Repo.TreePath,
TreePath: form.TreePath,
Message: message,
}); err != nil {
// This is where we handle all the errors thrown by repofiles.CreateOrUpdateRepoFile
if git.IsErrNotExist(err) {
ctx.RenderWithErr(ctx.Tr("repo.editor.file_editing_no_longer_exists", ctx.Repo.TreePath), tplEditFile, &form)
} else if models.IsErrLFSFileLocked(err) {
ctx.Data["Err_TreePath"] = true
ctx.RenderWithErr(ctx.Tr("repo.editor.upload_file_is_locked", err.(models.ErrLFSFileLocked).Path, err.(models.ErrLFSFileLocked).UserName), tplEditFile, &form)
} else if models.IsErrFilenameInvalid(err) {
ctx.Data["Err_TreePath"] = true
ctx.RenderWithErr(ctx.Tr("repo.editor.filename_is_invalid", form.TreePath), tplEditFile, &form)
} else if models.IsErrFilePathInvalid(err) {
ctx.Data["Err_TreePath"] = true
if fileErr, ok := err.(models.ErrFilePathInvalid); ok {
switch fileErr.Type {
case git.EntryModeSymlink:
ctx.RenderWithErr(ctx.Tr("repo.editor.file_is_a_symlink", fileErr.Path), tplEditFile, &form)
case git.EntryModeTree:
ctx.RenderWithErr(ctx.Tr("repo.editor.filename_is_a_directory", fileErr.Path), tplEditFile, &form)
case git.EntryModeBlob:
ctx.RenderWithErr(ctx.Tr("repo.editor.directory_is_a_file", fileErr.Path), tplEditFile, &form)
default:
ctx.Error(500, err.Error())
}
} else {
ctx.Error(500, err.Error())
}
} else if models.IsErrRepoFileAlreadyExists(err) {
ctx.Data["Err_TreePath"] = true
ctx.RenderWithErr(ctx.Tr("repo.editor.file_already_exists", form.TreePath), tplEditFile, &form)
} else if git.IsErrBranchNotExist(err) {
// For when a user adds/updates a file to a branch that no longer exists
if branchErr, ok := err.(git.ErrBranchNotExist); ok {
ctx.RenderWithErr(ctx.Tr("repo.editor.branch_does_not_exist", branchErr.Name), tplEditFile, &form)
} else {
ctx.Error(500, err.Error())
}
} else if models.IsErrBranchAlreadyExists(err) {
// For when a user specifies a new branch that already exists
ctx.Data["Err_NewBranchName"] = true
if branchErr, ok := err.(models.ErrBranchAlreadyExists); ok {
ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplEditFile, &form)
} else {
ctx.Error(500, err.Error())
}
} else if models.IsErrCommitIDDoesNotMatch(err) {
ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_editing"), tplEditFile, &form)
} else if git.IsErrPushOutOfDate(err) {
ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_editing"), tplEditFile, &form)
} else if git.IsErrPushRejected(err) {
errPushRej := err.(*git.ErrPushRejected)
if len(errPushRej.Message) == 0 {
ctx.RenderWithErr(ctx.Tr("repo.editor.push_rejected_no_message"), tplEditFile, &form)
} else {
ctx.RenderWithErr(ctx.Tr("repo.editor.push_rejected", utils.SanitizeFlashErrorString(errPushRej.Message)), tplEditFile, &form)
}
} else {
ctx.RenderWithErr(ctx.Tr("repo.editor.fail_to_update_file", form.TreePath, utils.SanitizeFlashErrorString(err.Error())), tplEditFile, &form)
}
}
ctx.JSON(http.StatusOK, "success")

}

+ 1
- 0
routers/routes/routes.go View File

@@ -933,6 +933,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Combo("/_upload/*", repo.MustBeAbleToUpload).
Get(repo.UploadFile).
Post(bindIgnErr(auth.UploadRepoFileForm{}), repo.UploadFilePost)
m.Post("/_rename/*", bindIgnErr(auth.RenameRepoFileForm{}), repo.RenameFilePost)
}, context.RepoRefByType(context.RepoRefBranch), repo.MustBeEditable)
m.Group("", func() {
m.Post("/upload-file", repo.UploadFileToServer)


Loading…
Cancel
Save