diff options
author | zeripath <art27@cantab.net> | 2019-05-11 16:29:17 +0100 |
---|---|---|
committer | techknowlogick <hello@techknowlogick.com> | 2019-05-11 11:29:17 -0400 |
commit | ce8de3533485eed0c56059d6334a5031a73eed67 (patch) | |
tree | 2f8e5c84441467269a98fb450ddfaca236cfc2e7 /models | |
parent | 34eee25bd42d19287e3e33afd169cc979ab61f37 (diff) | |
download | gitea-ce8de3533485eed0c56059d6334a5031a73eed67.tar.gz gitea-ce8de3533485eed0c56059d6334a5031a73eed67.zip |
Remove local clones & make hooks run on merge/edit/upload (#6672)
* Add options to git.Clone to make it more capable
* Begin the process of removing the local copy and tidy up
* Remove Wiki LocalCopy Checkouts
* Remove the last LocalRepo helpers
* Remove WithTemporaryFile
* Enable push-hooks for these routes
* Ensure tests cope with hooks
Signed-off-by: Andrew Thornton <art27@cantab.net>
* Remove Repository.LocalCopyPath()
* Move temporary repo to use the standard temporary path
* Fix the tests
Signed-off-by: Andrew Thornton <art27@cantab.net>
* Remove LocalWikiPath
* Fix missing remove
Signed-off-by: Andrew Thornton <art27@cantab.net>
* Use AppURL for Oauth user link (#6894)
* Use AppURL for Oauth user link
Fix #6843
* Update oauth.go
* Update oauth.go
* internal/ssh: ignore env command totally (#6825)
* ssh: ignore env command totally
* Remove commented code
Needed fix described in issue #6889
* Escape the commit message on issues update and title in telegram hook (#6901)
* update sdk to latest (#6903)
* improve description of branch protection (fix #6886) (#6906)
The branch protection description text were not quite accurate.
* Fix logging documentation (#6904)
* ENABLE_MACARON_REDIRECT should be REDIRECT_MACARON_LOG
* Allow DISABLE_ROUTER_LOG to be set in the [log] section
* [skip ci] Updated translations via Crowdin
* Move sdk structs to modules/structs (#6905)
* move sdk structs to moduels/structs
* fix tests
* fix fmt
* fix swagger
* fix vendor
Diffstat (limited to 'models')
-rw-r--r-- | models/helper_directory.go | 45 | ||||
-rw-r--r-- | models/helper_environment.go | 36 | ||||
-rw-r--r-- | models/pull.go | 63 | ||||
-rw-r--r-- | models/repo.go | 63 | ||||
-rw-r--r-- | models/repo_branch.go | 177 | ||||
-rw-r--r-- | models/repo_test.go | 21 | ||||
-rw-r--r-- | models/user.go | 11 | ||||
-rw-r--r-- | models/wiki.go | 231 | ||||
-rw-r--r-- | models/wiki_test.go | 49 |
9 files changed, 345 insertions, 351 deletions
diff --git a/models/helper_directory.go b/models/helper_directory.go new file mode 100644 index 0000000000..417402b41c --- /dev/null +++ b/models/helper_directory.go @@ -0,0 +1,45 @@ +// 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 models + +import ( + "fmt" + "os" + "path" + "path/filepath" + "time" + + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + + "github.com/Unknwon/com" +) + +// LocalCopyPath returns the local repository temporary copy path. +func LocalCopyPath() string { + if filepath.IsAbs(setting.Repository.Local.LocalCopyPath) { + return setting.Repository.Local.LocalCopyPath + } + return path.Join(setting.AppDataPath, setting.Repository.Local.LocalCopyPath) +} + +// CreateTemporaryPath creates a temporary path +func CreateTemporaryPath(prefix string) (string, error) { + timeStr := com.ToStr(time.Now().Nanosecond()) // SHOULD USE SOMETHING UNIQUE + basePath := path.Join(LocalCopyPath(), prefix+"-"+timeStr+".git") + if err := os.MkdirAll(filepath.Dir(basePath), os.ModePerm); err != nil { + log.Error("Unable to create temporary directory: %s (%v)", basePath, err) + return "", fmt.Errorf("Failed to create dir %s: %v", basePath, err) + } + return basePath, nil +} + +// RemoveTemporaryPath removes the temporary path +func RemoveTemporaryPath(basePath string) error { + if _, err := os.Stat(basePath); !os.IsNotExist(err) { + return os.RemoveAll(basePath) + } + return nil +} diff --git a/models/helper_environment.go b/models/helper_environment.go new file mode 100644 index 0000000000..283584cc52 --- /dev/null +++ b/models/helper_environment.go @@ -0,0 +1,36 @@ +// 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 models + +import ( + "fmt" + "os" + "strings" +) + +// PushingEnvironment returns an os environment to allow hooks to work on push +func PushingEnvironment(doer *User, repo *Repository) []string { + isWiki := "false" + if strings.HasSuffix(repo.Name, ".wiki") { + isWiki = "true" + } + + sig := doer.NewGitSig() + + return append(os.Environ(), + "GIT_AUTHOR_NAME="+sig.Name, + "GIT_AUTHOR_EMAIL="+sig.Email, + "GIT_COMMITTER_NAME="+sig.Name, + "GIT_COMMITTER_EMAIL="+sig.Email, + EnvRepoName+"="+repo.Name, + EnvRepoUsername+"="+repo.OwnerName, + EnvRepoIsWiki+"="+isWiki, + EnvPusherName+"="+doer.Name, + EnvPusherID+"="+fmt.Sprintf("%d", doer.ID), + ProtectedBranchRepoID+"="+fmt.Sprintf("%d", repo.ID), + "SSH_ORIGINAL_COMMAND=gitea-internal", + ) + +} diff --git a/models/pull.go b/models/pull.go index 7382cbd126..6f37145d9b 100644 --- a/models/pull.go +++ b/models/pull.go @@ -418,22 +418,21 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle go AddTestPullRequestTask(doer, pr.BaseRepo.ID, pr.BaseBranch, false) }() - headRepoPath := RepoPath(pr.HeadUserName, pr.HeadRepo.Name) - // Clone base repo. - tmpBasePath := path.Join(LocalCopyPath(), "merge-"+com.ToStr(time.Now().Nanosecond())+".git") - - if err := os.MkdirAll(path.Dir(tmpBasePath), os.ModePerm); err != nil { - return fmt.Errorf("Failed to create dir %s: %v", tmpBasePath, err) + tmpBasePath, err := CreateTemporaryPath("merge") + if err != nil { + return err } + defer RemoveTemporaryPath(tmpBasePath) - defer os.RemoveAll(tmpBasePath) + headRepoPath := RepoPath(pr.HeadUserName, pr.HeadRepo.Name) - var stderr string - if _, stderr, err = process.GetManager().ExecTimeout(5*time.Minute, - fmt.Sprintf("PullRequest.Merge (git clone): %s", tmpBasePath), - "git", "clone", "-s", "--no-checkout", "-b", pr.BaseBranch, baseGitRepo.Path, tmpBasePath); err != nil { - return fmt.Errorf("git clone: %s", stderr) + if err := git.Clone(baseGitRepo.Path, tmpBasePath, git.CloneRepoOptions{ + Shared: true, + NoCheckout: true, + Branch: pr.BaseBranch, + }); err != nil { + return fmt.Errorf("git clone: %v", err) } remoteRepoName := "head_repo" @@ -456,14 +455,14 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle if err := addCacheRepo(tmpBasePath, headRepoPath); err != nil { return fmt.Errorf("addCacheRepo [%s -> %s]: %v", headRepoPath, tmpBasePath, err) } - if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath, fmt.Sprintf("PullRequest.Merge (git remote add): %s", tmpBasePath), "git", "remote", "add", remoteRepoName, headRepoPath); err != nil { return fmt.Errorf("git remote add [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr) } // Fetch head branch - if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath, fmt.Sprintf("PullRequest.Merge (git fetch): %s", tmpBasePath), "git", "fetch", remoteRepoName); err != nil { return fmt.Errorf("git fetch [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr) @@ -487,14 +486,14 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle return fmt.Errorf("Writing sparse-checkout file to %s: %v", sparseCheckoutListPath, err) } - if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath, fmt.Sprintf("PullRequest.Merge (git config): %s", tmpBasePath), "git", "config", "--local", "core.sparseCheckout", "true"); err != nil { return fmt.Errorf("git config [core.sparsecheckout -> true]: %v", stderr) } // Read base branch index - if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath, fmt.Sprintf("PullRequest.Merge (git read-tree): %s", tmpBasePath), "git", "read-tree", "HEAD"); err != nil { return fmt.Errorf("git read-tree HEAD: %s", stderr) @@ -503,14 +502,14 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle // Merge commits. switch mergeStyle { case MergeStyleMerge: - if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath, fmt.Sprintf("PullRequest.Merge (git merge --no-ff --no-commit): %s", tmpBasePath), "git", "merge", "--no-ff", "--no-commit", trackingBranch); err != nil { return fmt.Errorf("git merge --no-ff --no-commit [%s]: %v - %s", tmpBasePath, err, stderr) } sig := doer.NewGitSig() - if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath, fmt.Sprintf("PullRequest.Merge (git merge): %s", tmpBasePath), "git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email), "-m", message); err != nil { @@ -518,50 +517,50 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle } case MergeStyleRebase: // Checkout head branch - if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath, fmt.Sprintf("PullRequest.Merge (git checkout): %s", tmpBasePath), "git", "checkout", "-b", stagingBranch, trackingBranch); err != nil { return fmt.Errorf("git checkout: %s", stderr) } // Rebase before merging - if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath, fmt.Sprintf("PullRequest.Merge (git rebase): %s", tmpBasePath), "git", "rebase", "-q", pr.BaseBranch); err != nil { return fmt.Errorf("git rebase [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr) } // Checkout base branch again - if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath, fmt.Sprintf("PullRequest.Merge (git checkout): %s", tmpBasePath), "git", "checkout", pr.BaseBranch); err != nil { return fmt.Errorf("git checkout: %s", stderr) } // Merge fast forward - if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath, fmt.Sprintf("PullRequest.Merge (git rebase): %s", tmpBasePath), "git", "merge", "--ff-only", "-q", stagingBranch); err != nil { return fmt.Errorf("git merge --ff-only [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr) } case MergeStyleRebaseMerge: // Checkout head branch - if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath, fmt.Sprintf("PullRequest.Merge (git checkout): %s", tmpBasePath), "git", "checkout", "-b", stagingBranch, trackingBranch); err != nil { return fmt.Errorf("git checkout: %s", stderr) } // Rebase before merging - if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath, fmt.Sprintf("PullRequest.Merge (git rebase): %s", tmpBasePath), "git", "rebase", "-q", pr.BaseBranch); err != nil { return fmt.Errorf("git rebase [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr) } // Checkout base branch again - if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath, fmt.Sprintf("PullRequest.Merge (git checkout): %s", tmpBasePath), "git", "checkout", pr.BaseBranch); err != nil { return fmt.Errorf("git checkout: %s", stderr) } // Prepare merge with commit - if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath, fmt.Sprintf("PullRequest.Merge (git merge): %s", tmpBasePath), "git", "merge", "--no-ff", "--no-commit", "-q", stagingBranch); err != nil { return fmt.Errorf("git merge --no-ff [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr) @@ -569,7 +568,7 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle // Set custom message and author and create merge commit sig := doer.NewGitSig() - if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath, fmt.Sprintf("PullRequest.Merge (git commit): %s", tmpBasePath), "git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email), "-m", message); err != nil { @@ -578,13 +577,13 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle case MergeStyleSquash: // Merge with squash - if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath, fmt.Sprintf("PullRequest.Merge (git squash): %s", tmpBasePath), "git", "merge", "-q", "--squash", trackingBranch); err != nil { return fmt.Errorf("git merge --squash [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr) } sig := pr.Issue.Poster.NewGitSig() - if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath, fmt.Sprintf("PullRequest.Merge (git squash): %s", tmpBasePath), "git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email), "-m", message); err != nil { @@ -594,10 +593,12 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle return ErrInvalidMergeStyle{pr.BaseRepo.ID, mergeStyle} } + env := PushingEnvironment(doer, pr.BaseRepo) + // Push back to upstream. - if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + if _, stderr, err := process.GetManager().ExecDirEnv(-1, tmpBasePath, fmt.Sprintf("PullRequest.Merge (git push): %s", tmpBasePath), - "git", "push", baseGitRepo.Path, pr.BaseBranch); err != nil { + env, "git", "push", baseGitRepo.Path, pr.BaseBranch); err != nil { return fmt.Errorf("git push: %s", stderr) } diff --git a/models/repo.go b/models/repo.go index 66c1bdbab1..2f87e2f514 100644 --- a/models/repo.go +++ b/models/repo.go @@ -518,7 +518,7 @@ func (repo *Repository) DeleteWiki() error { } func (repo *Repository) deleteWiki(e Engine) error { - wikiPaths := []string{repo.WikiPath(), repo.LocalWikiPath()} + wikiPaths := []string{repo.WikiPath()} for _, wikiPath := range wikiPaths { removeAllWithNotice(e, "Delete repository wiki", wikiPath) } @@ -749,56 +749,6 @@ func (repo *Repository) DescriptionHTML() template.HTML { return template.HTML(markup.Sanitize(string(desc))) } -// LocalCopyPath returns the local repository copy path. -func LocalCopyPath() string { - if filepath.IsAbs(setting.Repository.Local.LocalCopyPath) { - return setting.Repository.Local.LocalCopyPath - } - return path.Join(setting.AppDataPath, setting.Repository.Local.LocalCopyPath) -} - -// LocalCopyPath returns the local repository copy path for the given repo. -func (repo *Repository) LocalCopyPath() string { - return path.Join(LocalCopyPath(), com.ToStr(repo.ID)) -} - -// UpdateLocalCopyBranch pulls latest changes of given branch from repoPath to localPath. -// It creates a new clone if local copy does not exist. -// This function checks out target branch by default, it is safe to assume subsequent -// operations are operating against target branch when caller has confidence for no race condition. -func UpdateLocalCopyBranch(repoPath, localPath, branch string) error { - if !com.IsExist(localPath) { - if err := git.Clone(repoPath, localPath, git.CloneRepoOptions{ - Timeout: time.Duration(setting.Git.Timeout.Clone) * time.Second, - Branch: branch, - }); err != nil { - return fmt.Errorf("git clone %s: %v", branch, err) - } - } else { - _, err := git.NewCommand("fetch", "origin").RunInDir(localPath) - if err != nil { - return fmt.Errorf("git fetch origin: %v", err) - } - if len(branch) > 0 { - if err := git.Checkout(localPath, git.CheckoutOptions{ - Branch: branch, - }); err != nil { - return fmt.Errorf("git checkout %s: %v", branch, err) - } - - if err := git.ResetHEAD(localPath, true, "origin/"+branch); err != nil { - return fmt.Errorf("git reset --hard origin/%s: %v", branch, err) - } - } - } - return nil -} - -// UpdateLocalCopyBranch makes sure local copy of repository in given branch is up-to-date. -func (repo *Repository) UpdateLocalCopyBranch(branch string) error { - return UpdateLocalCopyBranch(repo.RepoPath(), repo.LocalCopyPath(), branch) -} - // PatchPath returns corresponding patch file path of repository by given issue ID. func (repo *Repository) PatchPath(index int64) (string, error) { return repo.patchPath(x, index) @@ -1583,12 +1533,10 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) error if err = os.Rename(RepoPath(owner.Name, repo.Name), RepoPath(newOwner.Name, repo.Name)); err != nil { return fmt.Errorf("rename repository directory: %v", err) } - removeAllWithNotice(sess, "Delete repository local copy", repo.LocalCopyPath()) // Rename remote wiki repository to new path and delete local copy. wikiPath := WikiPath(owner.Name, repo.Name) if com.IsExist(wikiPath) { - removeAllWithNotice(sess, "Delete repository wiki local copy", repo.LocalWikiPath()) if err = os.Rename(wikiPath, WikiPath(newOwner.Name, repo.Name)); err != nil { return fmt.Errorf("rename repository wiki: %v", err) } @@ -1633,20 +1581,11 @@ func ChangeRepositoryName(u *User, oldRepoName, newRepoName string) (err error) return fmt.Errorf("rename repository directory: %v", err) } - localPath := repo.LocalCopyPath() - if com.IsExist(localPath) { - _, err := git.NewCommand("remote", "set-url", "origin", newRepoPath).RunInDir(localPath) - if err != nil { - return fmt.Errorf("git remote set-url origin %s: %v", newRepoPath, err) - } - } - wikiPath := repo.WikiPath() if com.IsExist(wikiPath) { if err = os.Rename(wikiPath, WikiPath(u.Name, newRepoName)); err != nil { return fmt.Errorf("rename repository wiki: %v", err) } - RemoveAllWithNotice("Delete repository wiki local copy", repo.LocalWikiPath()) } sess := x.NewSession() diff --git a/models/repo_branch.go b/models/repo_branch.go index 0958e23974..08c881fc24 100644 --- a/models/repo_branch.go +++ b/models/repo_branch.go @@ -7,86 +7,11 @@ package models import ( "fmt" - "time" "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/setting" - - "github.com/Unknwon/com" + "code.gitea.io/gitea/modules/log" ) -// discardLocalRepoBranchChanges discards local commits/changes of -// given branch to make sure it is even to remote branch. -func discardLocalRepoBranchChanges(localPath, branch string) error { - if !com.IsExist(localPath) { - return nil - } - // No need to check if nothing in the repository. - if !git.IsBranchExist(localPath, branch) { - return nil - } - - refName := "origin/" + branch - if err := git.ResetHEAD(localPath, true, refName); err != nil { - return fmt.Errorf("git reset --hard %s: %v", refName, err) - } - return nil -} - -// DiscardLocalRepoBranchChanges discards the local repository branch changes -func (repo *Repository) DiscardLocalRepoBranchChanges(branch string) error { - return discardLocalRepoBranchChanges(repo.LocalCopyPath(), branch) -} - -// checkoutNewBranch checks out to a new branch from the a branch name. -func checkoutNewBranch(repoPath, localPath, oldBranch, newBranch string) error { - if err := git.Checkout(localPath, git.CheckoutOptions{ - Timeout: time.Duration(setting.Git.Timeout.Pull) * time.Second, - Branch: newBranch, - OldBranch: oldBranch, - }); err != nil { - return fmt.Errorf("git checkout -b %s %s: %v", newBranch, oldBranch, err) - } - return nil -} - -// CheckoutNewBranch checks out a new branch -func (repo *Repository) CheckoutNewBranch(oldBranch, newBranch string) error { - return checkoutNewBranch(repo.RepoPath(), repo.LocalCopyPath(), oldBranch, newBranch) -} - -// deleteLocalBranch deletes a branch from a local repo cache -// First checks out default branch to avoid trying to delete the currently checked out branch -func deleteLocalBranch(localPath, defaultBranch, deleteBranch string) error { - if !com.IsExist(localPath) { - return nil - } - - if !git.IsBranchExist(localPath, deleteBranch) { - return nil - } - - // Must NOT have branch currently checked out - // Checkout default branch first - if err := git.Checkout(localPath, git.CheckoutOptions{ - Timeout: time.Duration(setting.Git.Timeout.Pull) * time.Second, - Branch: defaultBranch, - }); err != nil { - return fmt.Errorf("git checkout %s: %v", defaultBranch, err) - } - - cmd := git.NewCommand("branch") - cmd.AddArguments("-D") - cmd.AddArguments(deleteBranch) - _, err := cmd.RunInDir(localPath) - return err -} - -// DeleteLocalBranch deletes a branch from the local repo -func (repo *Repository) DeleteLocalBranch(branchName string) error { - return deleteLocalBranch(repo.LocalCopyPath(), repo.DefaultBranch, branchName) -} - // CanCreateBranch returns true if repository meets the requirements for creating new branches. func (repo *Repository) CanCreateBranch() bool { return !repo.IsMirror @@ -137,92 +62,86 @@ func (repo *Repository) CheckBranchName(name string) error { // CreateNewBranch creates a new repository branch func (repo *Repository) CreateNewBranch(doer *User, oldBranchName, branchName string) (err error) { - repoWorkingPool.CheckIn(com.ToStr(repo.ID)) - defer repoWorkingPool.CheckOut(com.ToStr(repo.ID)) - // Check if branch name can be used if err := repo.CheckBranchName(branchName); err != nil { return err } - localPath := repo.LocalCopyPath() - - if err = discardLocalRepoBranchChanges(localPath, oldBranchName); err != nil { - return fmt.Errorf("discardLocalRepoChanges: %v", err) - } else if err = repo.UpdateLocalCopyBranch(oldBranchName); err != nil { - return fmt.Errorf("UpdateLocalCopyBranch: %v", err) + if !git.IsBranchExist(repo.RepoPath(), oldBranchName) { + return fmt.Errorf("OldBranch: %s does not exist. Cannot create new branch from this", oldBranchName) } - if err = repo.CheckoutNewBranch(oldBranchName, branchName); err != nil { - return fmt.Errorf("CreateNewBranch: %v", err) + basePath, err := CreateTemporaryPath("branch-maker") + if err != nil { + return err } + defer RemoveTemporaryPath(basePath) - if err = git.Push(localPath, git.PushOptions{ - Remote: "origin", - Branch: branchName, + if err := git.Clone(repo.RepoPath(), basePath, git.CloneRepoOptions{ + Bare: true, + Shared: true, }); err != nil { - return fmt.Errorf("Push: %v", err) + log.Error("Failed to clone repository: %s (%v)", repo.FullName(), err) + return fmt.Errorf("Failed to clone repository: %s (%v)", repo.FullName(), err) } - return nil -} + gitRepo, err := git.OpenRepository(basePath) + if err != nil { + log.Error("Unable to open temporary repository: %s (%v)", basePath, err) + return fmt.Errorf("Failed to open new temporary repository in: %s %v", basePath, err) + } -// updateLocalCopyToCommit pulls latest changes of given commit from repoPath to localPath. -// It creates a new clone if local copy does not exist. -// This function checks out target commit by default, it is safe to assume subsequent -// operations are operating against target commit when caller has confidence for no race condition. -func updateLocalCopyToCommit(repoPath, localPath, commit string) error { - if !com.IsExist(localPath) { - if err := git.Clone(repoPath, localPath, git.CloneRepoOptions{ - Timeout: time.Duration(setting.Git.Timeout.Clone) * time.Second, - }); err != nil { - return fmt.Errorf("git clone: %v", err) - } - } else { - _, err := git.NewCommand("fetch", "origin").RunInDir(localPath) - if err != nil { - return fmt.Errorf("git fetch origin: %v", err) - } - if err := git.ResetHEAD(localPath, true, "HEAD"); err != nil { - return fmt.Errorf("git reset --hard HEAD: %v", err) - } + if err = gitRepo.CreateBranch(branchName, oldBranchName); err != nil { + log.Error("Unable to create branch: %s from %s. (%v)", branchName, oldBranchName, err) + return fmt.Errorf("Unable to create branch: %s from %s. (%v)", branchName, oldBranchName, err) } - if err := git.Checkout(localPath, git.CheckoutOptions{ - Branch: commit, + + if err = git.Push(basePath, git.PushOptions{ + Remote: "origin", + Branch: branchName, + Env: PushingEnvironment(doer, repo), }); err != nil { - return fmt.Errorf("git checkout %s: %v", commit, err) + return fmt.Errorf("Push: %v", err) } - return nil -} -// updateLocalCopyToCommit makes sure local copy of repository is at given commit. -func (repo *Repository) updateLocalCopyToCommit(commit string) error { - return updateLocalCopyToCommit(repo.RepoPath(), repo.LocalCopyPath(), commit) + return nil } // CreateNewBranchFromCommit creates a new repository branch func (repo *Repository) CreateNewBranchFromCommit(doer *User, commit, branchName string) (err error) { - repoWorkingPool.CheckIn(com.ToStr(repo.ID)) - defer repoWorkingPool.CheckOut(com.ToStr(repo.ID)) - // Check if branch name can be used if err := repo.CheckBranchName(branchName); err != nil { return err } + basePath, err := CreateTemporaryPath("branch-maker") + if err != nil { + return err + } + defer RemoveTemporaryPath(basePath) - localPath := repo.LocalCopyPath() + if err := git.Clone(repo.RepoPath(), basePath, git.CloneRepoOptions{ + Bare: true, + Shared: true, + }); err != nil { + log.Error("Failed to clone repository: %s (%v)", repo.FullName(), err) + return fmt.Errorf("Failed to clone repository: %s (%v)", repo.FullName(), err) + } - if err = repo.updateLocalCopyToCommit(commit); err != nil { - return fmt.Errorf("UpdateLocalCopyBranch: %v", err) + gitRepo, err := git.OpenRepository(basePath) + if err != nil { + log.Error("Unable to open temporary repository: %s (%v)", basePath, err) + return fmt.Errorf("Failed to open new temporary repository in: %s %v", basePath, err) } - if err = repo.CheckoutNewBranch(commit, branchName); err != nil { - return fmt.Errorf("CheckoutNewBranch: %v", err) + if err = gitRepo.CreateBranch(branchName, commit); err != nil { + log.Error("Unable to create branch: %s from %s. (%v)", branchName, commit, err) + return fmt.Errorf("Unable to create branch: %s from %s. (%v)", branchName, commit, err) } - if err = git.Push(localPath, git.PushOptions{ + if err = git.Push(basePath, git.PushOptions{ Remote: "origin", Branch: branchName, + Env: PushingEnvironment(doer, repo), }); err != nil { return fmt.Errorf("Push: %v", err) } diff --git a/models/repo_test.go b/models/repo_test.go index a5b8cce9b9..eee3997868 100644 --- a/models/repo_test.go +++ b/models/repo_test.go @@ -5,11 +5,9 @@ package models import ( - "path" "testing" "code.gitea.io/gitea/modules/markup" - "code.gitea.io/gitea/modules/setting" "github.com/Unknwon/com" "github.com/stretchr/testify/assert" @@ -138,25 +136,6 @@ func TestRepoAPIURL(t *testing.T) { assert.Equal(t, "https://try.gitea.io/api/v1/repos/user12/repo10", repo.APIURL()) } -func TestRepoLocalCopyPath(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - - repo, err := GetRepositoryByID(10) - assert.NoError(t, err) - assert.NotNil(t, repo) - - // test default - repoID := com.ToStr(repo.ID) - expected := path.Join(setting.AppDataPath, setting.Repository.Local.LocalCopyPath, repoID) - assert.Equal(t, expected, repo.LocalCopyPath()) - - // test absolute setting - tempPath := "/tmp/gitea/local-copy-path" - expected = path.Join(tempPath, repoID) - setting.Repository.Local.LocalCopyPath = tempPath - assert.Equal(t, expected, repo.LocalCopyPath()) -} - func TestTransferOwnership(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) diff --git a/models/user.go b/models/user.go index 0c445b5afd..90ca189ef0 100644 --- a/models/user.go +++ b/models/user.go @@ -943,17 +943,6 @@ func ChangeUserName(u *User, newUserName string) (err error) { return fmt.Errorf("ChangeUsernameInPullRequests: %v", err) } - // Delete all local copies of repository wiki that user owns. - if err = x.BufferSize(setting.IterateBufferSize). - Where("owner_id=?", u.ID). - Iterate(new(Repository), func(idx int, bean interface{}) error { - repo := bean.(*Repository) - RemoveAllWithNotice("Delete repository wiki local copy", repo.LocalWikiPath()) - return nil - }); err != nil { - return fmt.Errorf("Delete repository wiki local copy: %v", err) - } - // Do not fail if directory does not exist if err = os.Rename(UserPath(u.Name), UserPath(newUserName)); err != nil && !os.IsNotExist(err) { return fmt.Errorf("Rename user directory: %v", err) diff --git a/models/wiki.go b/models/wiki.go index 0f5cdc20bd..bcf97c0765 100644 --- a/models/wiki.go +++ b/models/wiki.go @@ -6,15 +6,13 @@ package models import ( "fmt" - "io/ioutil" "net/url" "os" - "path" "path/filepath" "strings" "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/sync" "github.com/Unknwon/com" @@ -89,34 +87,6 @@ func (repo *Repository) InitWiki() error { return nil } -// LocalWikiPath returns the local wiki repository copy path. -func LocalWikiPath() string { - if filepath.IsAbs(setting.Repository.Local.LocalWikiPath) { - return setting.Repository.Local.LocalWikiPath - } - return path.Join(setting.AppDataPath, setting.Repository.Local.LocalWikiPath) -} - -// LocalWikiPath returns the path to the local wiki repository (?). -func (repo *Repository) LocalWikiPath() string { - return path.Join(LocalWikiPath(), com.ToStr(repo.ID)) -} - -// UpdateLocalWiki makes sure the local copy of repository wiki is up-to-date. -func (repo *Repository) updateLocalWiki() error { - // Don't pass branch name here because it fails to clone and - // checkout to a specific branch when wiki is an empty repository. - var branch = "" - if com.IsExist(repo.LocalWikiPath()) { - branch = "master" - } - return UpdateLocalCopyBranch(repo.WikiPath(), repo.LocalWikiPath(), branch) -} - -func discardLocalWikiChanges(localPath string) error { - return discardLocalRepoBranchChanges(localPath, "master") -} - // nameAllowed checks if a wiki name is allowed func nameAllowed(name string) error { for _, reservedName := range reservedWikiNames { @@ -132,7 +102,6 @@ func (repo *Repository) updateWikiPage(doer *User, oldWikiName, newWikiName, con if err = nameAllowed(newWikiName); err != nil { return err } - wikiWorkingPool.CheckIn(com.ToStr(repo.ID)) defer wikiWorkingPool.CheckOut(com.ToStr(repo.ID)) @@ -140,54 +109,113 @@ func (repo *Repository) updateWikiPage(doer *User, oldWikiName, newWikiName, con return fmt.Errorf("InitWiki: %v", err) } - localPath := repo.LocalWikiPath() - if err = discardLocalWikiChanges(localPath); err != nil { - return fmt.Errorf("discardLocalWikiChanges: %v", err) - } else if err = repo.updateLocalWiki(); err != nil { - return fmt.Errorf("UpdateLocalWiki: %v", err) + hasMasterBranch := git.IsBranchExist(repo.WikiPath(), "master") + + basePath, err := CreateTemporaryPath("update-wiki") + if err != nil { + return err } + defer RemoveTemporaryPath(basePath) - newWikiPath := path.Join(localPath, WikiNameToFilename(newWikiName)) + cloneOpts := git.CloneRepoOptions{ + Bare: true, + Shared: true, + } + + if hasMasterBranch { + cloneOpts.Branch = "master" + } - // If not a new file, show perform update not create. + if err := git.Clone(repo.WikiPath(), basePath, cloneOpts); err != nil { + log.Error("Failed to clone repository: %s (%v)", repo.FullName(), err) + return fmt.Errorf("Failed to clone repository: %s (%v)", repo.FullName(), err) + } + + gitRepo, err := git.OpenRepository(basePath) + if err != nil { + log.Error("Unable to open temporary repository: %s (%v)", basePath, err) + return fmt.Errorf("Failed to open new temporary repository in: %s %v", basePath, err) + } + + if hasMasterBranch { + if err := gitRepo.ReadTreeToIndex("HEAD"); err != nil { + log.Error("Unable to read HEAD tree to index in: %s %v", basePath, err) + return fmt.Errorf("Unable to read HEAD tree to index in: %s %v", basePath, err) + } + } + + newWikiPath := WikiNameToFilename(newWikiName) if isNew { - if com.IsExist(newWikiPath) { - return ErrWikiAlreadyExist{newWikiPath} + filesInIndex, err := gitRepo.LsFiles(newWikiPath) + if err != nil { + log.Error("%v", err) + return err + } + for _, file := range filesInIndex { + if file == newWikiPath { + return ErrWikiAlreadyExist{newWikiPath} + } } } else { - oldWikiPath := path.Join(localPath, WikiNameToFilename(oldWikiName)) - if err := os.Remove(oldWikiPath); err != nil { - return fmt.Errorf("Failed to remove %s: %v", oldWikiPath, err) + oldWikiPath := WikiNameToFilename(oldWikiName) + filesInIndex, err := gitRepo.LsFiles(oldWikiPath) + if err != nil { + log.Error("%v", err) + return err + } + found := false + for _, file := range filesInIndex { + if file == oldWikiPath { + found = true + break + } + } + if found { + err := gitRepo.RemoveFilesFromIndex(oldWikiPath) + if err != nil { + log.Error("%v", err) + return err + } } } - // SECURITY: if new file is a symlink to non-exist critical file, - // attack content can be written to the target file (e.g. authorized_keys2) - // as a new page operation. - // So we want to make sure the symlink is removed before write anything. - // The new file we created will be in normal text format. - if err = os.RemoveAll(newWikiPath); err != nil { + // FIXME: The wiki doesn't have lfs support at present - if this changes need to check attributes here + + objectHash, err := gitRepo.HashObject(strings.NewReader(content)) + if err != nil { + log.Error("%v", err) return err } - if err = ioutil.WriteFile(newWikiPath, []byte(content), 0666); err != nil { - return fmt.Errorf("WriteFile: %v", err) + if err := gitRepo.AddObjectToIndex("100644", objectHash, newWikiPath); err != nil { + log.Error("%v", err) + return err } - if len(message) == 0 { - message = "Update page '" + newWikiName + "'" + tree, err := gitRepo.WriteTree() + if err != nil { + log.Error("%v", err) + return err } - if err = git.AddChanges(localPath, true); err != nil { - return fmt.Errorf("AddChanges: %v", err) - } else if err = git.CommitChanges(localPath, git.CommitChangesOptions{ - Committer: doer.NewGitSig(), - Message: message, - }); err != nil { - return fmt.Errorf("CommitChanges: %v", err) - } else if err = git.Push(localPath, git.PushOptions{ + + commitTreeOpts := git.CommitTreeOpts{ + Message: message, + } + if hasMasterBranch { + commitTreeOpts.Parents = []string{"HEAD"} + } + commitHash, err := gitRepo.CommitTree(doer.NewGitSig(), tree, commitTreeOpts) + if err != nil { + log.Error("%v", err) + return err + } + + if err := git.Push(basePath, git.PushOptions{ Remote: "origin", - Branch: "master", + Branch: fmt.Sprintf("%s:%s%s", commitHash.String(), git.BranchPrefix, "master"), + Env: PushingEnvironment(doer, repo), }); err != nil { + log.Error("%v", err) return fmt.Errorf("Push: %v", err) } @@ -210,31 +238,74 @@ func (repo *Repository) DeleteWikiPage(doer *User, wikiName string) (err error) wikiWorkingPool.CheckIn(com.ToStr(repo.ID)) defer wikiWorkingPool.CheckOut(com.ToStr(repo.ID)) - localPath := repo.LocalWikiPath() - if err = discardLocalWikiChanges(localPath); err != nil { - return fmt.Errorf("discardLocalWikiChanges: %v", err) - } else if err = repo.updateLocalWiki(); err != nil { - return fmt.Errorf("UpdateLocalWiki: %v", err) + if err = repo.InitWiki(); err != nil { + return fmt.Errorf("InitWiki: %v", err) + } + + basePath, err := CreateTemporaryPath("update-wiki") + if err != nil { + return err } + defer RemoveTemporaryPath(basePath) - filename := path.Join(localPath, WikiNameToFilename(wikiName)) + if err := git.Clone(repo.WikiPath(), basePath, git.CloneRepoOptions{ + Bare: true, + Shared: true, + Branch: "master", + }); err != nil { + log.Error("Failed to clone repository: %s (%v)", repo.FullName(), err) + return fmt.Errorf("Failed to clone repository: %s (%v)", repo.FullName(), err) + } - if err := os.Remove(filename); err != nil { - return fmt.Errorf("Failed to remove %s: %v", filename, err) + gitRepo, err := git.OpenRepository(basePath) + if err != nil { + log.Error("Unable to open temporary repository: %s (%v)", basePath, err) + return fmt.Errorf("Failed to open new temporary repository in: %s %v", basePath, err) } + if err := gitRepo.ReadTreeToIndex("HEAD"); err != nil { + log.Error("Unable to read HEAD tree to index in: %s %v", basePath, err) + return fmt.Errorf("Unable to read HEAD tree to index in: %s %v", basePath, err) + } + + wikiPath := WikiNameToFilename(wikiName) + filesInIndex, err := gitRepo.LsFiles(wikiPath) + found := false + for _, file := range filesInIndex { + if file == wikiPath { + found = true + break + } + } + if found { + err := gitRepo.RemoveFilesFromIndex(wikiPath) + if err != nil { + return err + } + } else { + return os.ErrNotExist + } + + // FIXME: The wiki doesn't have lfs support at present - if this changes need to check attributes here + + tree, err := gitRepo.WriteTree() + if err != nil { + return err + } message := "Delete page '" + wikiName + "'" - if err = git.AddChanges(localPath, true); err != nil { - return fmt.Errorf("AddChanges: %v", err) - } else if err = git.CommitChanges(localPath, git.CommitChangesOptions{ - Committer: doer.NewGitSig(), - Message: message, - }); err != nil { - return fmt.Errorf("CommitChanges: %v", err) - } else if err = git.Push(localPath, git.PushOptions{ + commitHash, err := gitRepo.CommitTree(doer.NewGitSig(), tree, git.CommitTreeOpts{ + Message: message, + Parents: []string{"HEAD"}, + }) + if err != nil { + return err + } + + if err := git.Push(basePath, git.PushOptions{ Remote: "origin", - Branch: "master", + Branch: fmt.Sprintf("%s:%s%s", commitHash.String(), git.BranchPrefix, "master"), + Env: PushingEnvironment(doer, repo), }); err != nil { return fmt.Errorf("Push: %v", err) } diff --git a/models/wiki_test.go b/models/wiki_test.go index 5280b3ea01..991a3d95b9 100644 --- a/models/wiki_test.go +++ b/models/wiki_test.go @@ -5,13 +5,12 @@ package models import ( - "path" "path/filepath" "testing" + "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" - "github.com/Unknwon/com" "github.com/stretchr/testify/assert" ) @@ -145,13 +144,6 @@ func TestRepository_InitWiki(t *testing.T) { assert.True(t, repo2.HasWiki()) } -func TestRepository_LocalWikiPath(t *testing.T) { - PrepareTestEnv(t) - repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) - expected := filepath.Join(setting.AppDataPath, setting.Repository.Local.LocalWikiPath, "1") - assert.Equal(t, expected, repo.LocalWikiPath()) -} - func TestRepository_AddWikiPage(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) const wikiContent = "This is the wiki content" @@ -166,8 +158,15 @@ func TestRepository_AddWikiPage(t *testing.T) { t.Run("test wiki exist: "+wikiName, func(t *testing.T) { t.Parallel() assert.NoError(t, repo.AddWikiPage(doer, wikiName, wikiContent, commitMsg)) - expectedPath := path.Join(repo.LocalWikiPath(), WikiNameToFilename(wikiName)) - assert.True(t, com.IsExist(expectedPath)) + // Now need to show that the page has been added: + gitRepo, err := git.OpenRepository(repo.WikiPath()) + assert.NoError(t, err) + masterTree, err := gitRepo.GetTree("master") + assert.NoError(t, err) + wikiPath := WikiNameToFilename(wikiName) + entry, err := masterTree.GetTreeEntryByPath(wikiPath) + assert.NoError(t, err) + assert.Equal(t, wikiPath, entry.Name(), "%s not addded correctly", wikiName) }) } @@ -200,11 +199,20 @@ func TestRepository_EditWikiPage(t *testing.T) { } { PrepareTestEnv(t) assert.NoError(t, repo.EditWikiPage(doer, "Home", newWikiName, newWikiContent, commitMsg)) - newPath := path.Join(repo.LocalWikiPath(), WikiNameToFilename(newWikiName)) - assert.True(t, com.IsExist(newPath)) + + // Now need to show that the page has been added: + gitRepo, err := git.OpenRepository(repo.WikiPath()) + assert.NoError(t, err) + masterTree, err := gitRepo.GetTree("master") + assert.NoError(t, err) + wikiPath := WikiNameToFilename(newWikiName) + entry, err := masterTree.GetTreeEntryByPath(wikiPath) + assert.NoError(t, err) + assert.Equal(t, wikiPath, entry.Name(), "%s not editted correctly", newWikiName) + if newWikiName != "Home" { - oldPath := path.Join(repo.LocalWikiPath(), "Home.md") - assert.False(t, com.IsExist(oldPath)) + _, err := masterTree.GetTreeEntryByPath("Home.md") + assert.Error(t, err) } } } @@ -214,6 +222,13 @@ func TestRepository_DeleteWikiPage(t *testing.T) { repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) doer := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) assert.NoError(t, repo.DeleteWikiPage(doer, "Home")) - wikiPath := path.Join(repo.LocalWikiPath(), "Home.md") - assert.False(t, com.IsExist(wikiPath)) + + // Now need to show that the page has been added: + gitRepo, err := git.OpenRepository(repo.WikiPath()) + assert.NoError(t, err) + masterTree, err := gitRepo.GetTree("master") + assert.NoError(t, err) + wikiPath := WikiNameToFilename("Home") + _, err = masterTree.GetTreeEntryByPath(wikiPath) + assert.Error(t, err) } |