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
This commit is contained in:
zeripath 2019-05-11 16:29:17 +01:00 committed by techknowlogick
parent 34eee25bd4
commit ce8de35334
33 changed files with 1652 additions and 1417 deletions

View file

@ -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
}

View file

@ -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",
)
}

View file

@ -418,22 +418,21 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
go AddTestPullRequestTask(doer, pr.BaseRepo.ID, pr.BaseBranch, false)
}()
// Clone base repo.
tmpBasePath, err := CreateTemporaryPath("merge")
if err != nil {
return err
}
defer RemoveTemporaryPath(tmpBasePath)
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)
}
defer os.RemoveAll(tmpBasePath)
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)
}

View file

@ -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()

View file

@ -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,29 +62,44 @@ 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.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 = git.Push(localPath, git.PushOptions{
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.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.Push(basePath, git.PushOptions{
Remote: "origin",
Branch: branchName,
Env: PushingEnvironment(doer, repo),
}); err != nil {
return fmt.Errorf("Push: %v", err)
}
@ -167,62 +107,41 @@ func (repo *Repository) CreateNewBranch(doer *User, oldBranchName, branchName st
return nil
}
// 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 := git.Checkout(localPath, git.CheckoutOptions{
Branch: commit,
}); err != nil {
return fmt.Errorf("git checkout %s: %v", commit, 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)
}
// 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 = repo.updateLocalCopyToCommit(commit); err != nil {
return fmt.Errorf("UpdateLocalCopyBranch: %v", err)
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.CheckoutNewBranch(commit, branchName); err != nil {
return fmt.Errorf("CheckoutNewBranch: %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 = git.Push(localPath, git.PushOptions{
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(basePath, git.PushOptions{
Remote: "origin",
Branch: branchName,
Env: PushingEnvironment(doer, repo),
}); err != nil {
return fmt.Errorf("Push: %v", err)
}

View file

@ -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())

View file

@ -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)

View file

@ -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)
cloneOpts := git.CloneRepoOptions{
Bare: true,
Shared: true,
}
newWikiPath := path.Join(localPath, WikiNameToFilename(newWikiName))
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)
}
filename := path.Join(localPath, WikiNameToFilename(wikiName))
basePath, err := CreateTemporaryPath("update-wiki")
if err != nil {
return err
}
defer RemoveTemporaryPath(basePath)
if err := os.Remove(filename); err != nil {
return fmt.Errorf("Failed to remove %s: %v", filename, err)
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)
}
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)
}

View file

@ -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)
}