* Change tests to make it possible to run TestGit with 1.7.2 * Make merge run on 1.7.2 * Fix tracking and staging branch name problem * Ensure that git 1.7.2 works on tests * ensure that there is no chance for conflicts * Fix-up missing merge issues * Final rm * Ensure LFS filters run on the tests * Do not sign commits from temp repo * Restore tracking fetch change * Apply suggestions from code review * Update modules/repofiles/temp_repo.gotags/v1.11.0-dev
@@ -12,7 +12,9 @@ import ( | |||
"net/http" | |||
"net/url" | |||
"os" | |||
"path" | |||
"path/filepath" | |||
"strings" | |||
"testing" | |||
"time" | |||
@@ -37,7 +39,12 @@ func withKeyFile(t *testing.T, keyname string, callback func(string)) { | |||
err = ssh.GenKeyPair(keyFile) | |||
assert.NoError(t, err) | |||
err = ioutil.WriteFile(path.Join(tmpDir, "ssh"), []byte("#!/bin/bash\n"+ | |||
"ssh -o \"UserKnownHostsFile=/dev/null\" -o \"StrictHostKeyChecking=no\" -o \"IdentitiesOnly=yes\" -i \""+keyFile+"\" \"$@\""), 0700) | |||
assert.NoError(t, err) | |||
//Setup ssh wrapper | |||
os.Setenv("GIT_SSH", path.Join(tmpDir, "ssh")) | |||
os.Setenv("GIT_SSH_COMMAND", | |||
"ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o IdentitiesOnly=yes -i \""+keyFile+"\"") | |||
os.Setenv("GIT_SSH_VARIANT", "ssh") | |||
@@ -54,6 +61,24 @@ func createSSHUrl(gitPath string, u *url.URL) *url.URL { | |||
return &u2 | |||
} | |||
func allowLFSFilters() []string { | |||
// Now here we should explicitly allow lfs filters to run | |||
globalArgs := git.GlobalCommandArgs | |||
filteredLFSGlobalArgs := make([]string, len(git.GlobalCommandArgs)) | |||
j := 0 | |||
for _, arg := range git.GlobalCommandArgs { | |||
if strings.Contains(arg, "lfs") { | |||
j-- | |||
} else { | |||
filteredLFSGlobalArgs[j] = arg | |||
j++ | |||
} | |||
} | |||
filteredLFSGlobalArgs = filteredLFSGlobalArgs[:j] | |||
git.GlobalCommandArgs = filteredLFSGlobalArgs | |||
return globalArgs | |||
} | |||
func onGiteaRun(t *testing.T, callback func(*testing.T, *url.URL)) { | |||
prepareTestEnv(t, 1) | |||
s := http.Server{ | |||
@@ -79,7 +104,9 @@ func onGiteaRun(t *testing.T, callback func(*testing.T, *url.URL)) { | |||
func doGitClone(dstLocalPath string, u *url.URL) func(*testing.T) { | |||
return func(t *testing.T) { | |||
oldGlobals := allowLFSFilters() | |||
assert.NoError(t, git.Clone(u.String(), dstLocalPath, git.CloneRepoOptions{})) | |||
git.GlobalCommandArgs = oldGlobals | |||
assert.True(t, com.IsExist(filepath.Join(dstLocalPath, "README.md"))) | |||
} | |||
} | |||
@@ -140,7 +167,9 @@ func doGitCreateBranch(dstPath, branch string) func(*testing.T) { | |||
func doGitCheckoutBranch(dstPath string, args ...string) func(*testing.T) { | |||
return func(t *testing.T) { | |||
oldGlobals := allowLFSFilters() | |||
_, err := git.NewCommand(append([]string{"checkout"}, args...)...).RunInDir(dstPath) | |||
git.GlobalCommandArgs = oldGlobals | |||
assert.NoError(t, err) | |||
} | |||
} | |||
@@ -154,7 +183,9 @@ func doGitMerge(dstPath string, args ...string) func(*testing.T) { | |||
func doGitPull(dstPath string, args ...string) func(*testing.T) { | |||
return func(t *testing.T) { | |||
oldGlobals := allowLFSFilters() | |||
_, err := git.NewCommand(append([]string{"pull"}, args...)...).RunInDir(dstPath) | |||
git.GlobalCommandArgs = oldGlobals | |||
assert.NoError(t, err) | |||
} | |||
} |
@@ -19,6 +19,7 @@ import ( | |||
"code.gitea.io/gitea/models" | |||
"code.gitea.io/gitea/modules/git" | |||
"code.gitea.io/gitea/modules/setting" | |||
api "code.gitea.io/gitea/modules/structs" | |||
"github.com/stretchr/testify/assert" | |||
@@ -135,6 +136,11 @@ func standardCommitAndPushTest(t *testing.T, dstPath string) (little, big string | |||
func lfsCommitAndPushTest(t *testing.T, dstPath string) (littleLFS, bigLFS string) { | |||
t.Run("LFS", func(t *testing.T) { | |||
PrintCurrentTest(t) | |||
setting.CheckLFSVersion() | |||
if !setting.LFS.StartServer { | |||
t.Skip() | |||
return | |||
} | |||
prefix := "lfs-data-file-" | |||
_, err := git.NewCommand("lfs").AddArguments("install").RunInDir(dstPath) | |||
assert.NoError(t, err) | |||
@@ -142,6 +148,21 @@ func lfsCommitAndPushTest(t *testing.T, dstPath string) (littleLFS, bigLFS strin | |||
assert.NoError(t, err) | |||
err = git.AddChanges(dstPath, false, ".gitattributes") | |||
assert.NoError(t, err) | |||
oldGlobals := allowLFSFilters() | |||
err = git.CommitChanges(dstPath, git.CommitChangesOptions{ | |||
Committer: &git.Signature{ | |||
Email: "user2@example.com", | |||
Name: "User Two", | |||
When: time.Now(), | |||
}, | |||
Author: &git.Signature{ | |||
Email: "user2@example.com", | |||
Name: "User Two", | |||
When: time.Now(), | |||
}, | |||
Message: fmt.Sprintf("Testing commit @ %v", time.Now()), | |||
}) | |||
git.GlobalCommandArgs = oldGlobals | |||
littleLFS, bigLFS = commitAndPushTest(t, dstPath, prefix) | |||
@@ -185,20 +206,25 @@ func rawTest(t *testing.T, ctx *APITestContext, little, big, littleLFS, bigLFS s | |||
resp := session.MakeRequest(t, req, http.StatusOK) | |||
assert.Equal(t, littleSize, resp.Body.Len()) | |||
req = NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", littleLFS)) | |||
resp = session.MakeRequest(t, req, http.StatusOK) | |||
assert.NotEqual(t, littleSize, resp.Body.Len()) | |||
assert.Contains(t, resp.Body.String(), models.LFSMetaFileIdentifier) | |||
setting.CheckLFSVersion() | |||
if setting.LFS.StartServer { | |||
req = NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", littleLFS)) | |||
resp = session.MakeRequest(t, req, http.StatusOK) | |||
assert.NotEqual(t, littleSize, resp.Body.Len()) | |||
assert.Contains(t, resp.Body.String(), models.LFSMetaFileIdentifier) | |||
} | |||
if !testing.Short() { | |||
req = NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", big)) | |||
resp = session.MakeRequest(t, req, http.StatusOK) | |||
assert.Equal(t, bigSize, resp.Body.Len()) | |||
req = NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", bigLFS)) | |||
resp = session.MakeRequest(t, req, http.StatusOK) | |||
assert.NotEqual(t, bigSize, resp.Body.Len()) | |||
assert.Contains(t, resp.Body.String(), models.LFSMetaFileIdentifier) | |||
if setting.LFS.StartServer { | |||
req = NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", bigLFS)) | |||
resp = session.MakeRequest(t, req, http.StatusOK) | |||
assert.NotEqual(t, bigSize, resp.Body.Len()) | |||
assert.Contains(t, resp.Body.String(), models.LFSMetaFileIdentifier) | |||
} | |||
} | |||
}) | |||
} | |||
@@ -217,18 +243,23 @@ func mediaTest(t *testing.T, ctx *APITestContext, little, big, littleLFS, bigLFS | |||
resp := session.MakeRequestNilResponseRecorder(t, req, http.StatusOK) | |||
assert.Equal(t, littleSize, resp.Length) | |||
req = NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", littleLFS)) | |||
resp = session.MakeRequestNilResponseRecorder(t, req, http.StatusOK) | |||
assert.Equal(t, littleSize, resp.Length) | |||
setting.CheckLFSVersion() | |||
if setting.LFS.StartServer { | |||
req = NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", littleLFS)) | |||
resp = session.MakeRequestNilResponseRecorder(t, req, http.StatusOK) | |||
assert.Equal(t, littleSize, resp.Length) | |||
} | |||
if !testing.Short() { | |||
req = NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", big)) | |||
resp = session.MakeRequestNilResponseRecorder(t, req, http.StatusOK) | |||
assert.Equal(t, bigSize, resp.Length) | |||
req = NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", bigLFS)) | |||
resp = session.MakeRequestNilResponseRecorder(t, req, http.StatusOK) | |||
assert.Equal(t, bigSize, resp.Length) | |||
if setting.LFS.StartServer { | |||
req = NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", bigLFS)) | |||
resp = session.MakeRequestNilResponseRecorder(t, req, http.StatusOK) | |||
assert.Equal(t, bigSize, resp.Length) | |||
} | |||
} | |||
}) | |||
} | |||
@@ -274,6 +305,8 @@ func generateCommitWithNewData(size int, repoPath, email, fullName, prefix strin | |||
} | |||
//Commit | |||
// Now here we should explicitly allow lfs filters to run | |||
oldGlobals := allowLFSFilters() | |||
err = git.AddChanges(repoPath, false, filepath.Base(tmpFile.Name())) | |||
if err != nil { | |||
return "", err | |||
@@ -291,6 +324,7 @@ func generateCommitWithNewData(size int, repoPath, email, fullName, prefix strin | |||
}, | |||
Message: fmt.Sprintf("Testing commit @ %v", time.Now()), | |||
}) | |||
git.GlobalCommandArgs = oldGlobals | |||
return filepath.Base(tmpFile.Name()), err | |||
} | |||
@@ -58,6 +58,11 @@ func storeObjectInRepo(t *testing.T, repositoryID int64, content *[]byte) string | |||
func doLfs(t *testing.T, content *[]byte, expectGzip bool) { | |||
prepareTestEnv(t) | |||
setting.CheckLFSVersion() | |||
if !setting.LFS.StartServer { | |||
t.Skip() | |||
return | |||
} | |||
repo, err := models.GetRepositoryByOwnerAndName("user2", "repo1") | |||
assert.NoError(t, err) | |||
oid := storeObjectInRepo(t, repo.ID, content) | |||
@@ -1093,7 +1093,7 @@ func CleanUpMigrateInfo(repo *Repository) (*Repository, error) { | |||
} | |||
} | |||
_, err := git.NewCommand("remote", "remove", "origin").RunInDir(repoPath) | |||
_, err := git.NewCommand("remote", "rm", "origin").RunInDir(repoPath) | |||
if err != nil && !strings.HasPrefix(err.Error(), "exit status 128 - fatal: No such remote ") { | |||
return repo, fmt.Errorf("CleanUpMigrateInfo: %v", err) | |||
} | |||
@@ -165,7 +165,7 @@ func (repo *Repository) AddRemote(name, url string, fetch bool) error { | |||
// RemoveRemote removes a remote from repository. | |||
func (repo *Repository) RemoveRemote(name string) error { | |||
_, err := NewCommand("remote", "remove", name).RunInDir(repo.Path) | |||
_, err := NewCommand("remote", "rm", name).RunInDir(repo.Path) | |||
return err | |||
} | |||
@@ -6,10 +6,13 @@ | |||
package git | |||
import ( | |||
"bytes" | |||
"fmt" | |||
"os" | |||
"strings" | |||
"time" | |||
"github.com/mcuadros/go-version" | |||
) | |||
func (repo *Repository) getTree(id SHA1) (*Tree, error) { | |||
@@ -61,6 +64,11 @@ type CommitTreeOpts struct { | |||
// CommitTree creates a commit from a given tree id for the user with provided message | |||
func (repo *Repository) CommitTree(sig *Signature, tree *Tree, opts CommitTreeOpts) (SHA1, error) { | |||
binVersion, err := BinVersion() | |||
if err != nil { | |||
return SHA1{}, err | |||
} | |||
commitTimeStr := time.Now().Format(time.RFC3339) | |||
// Because this may call hooks we should pass in the environment | |||
@@ -78,20 +86,24 @@ func (repo *Repository) CommitTree(sig *Signature, tree *Tree, opts CommitTreeOp | |||
cmd.AddArguments("-p", parent) | |||
} | |||
cmd.AddArguments("-m", opts.Message) | |||
messageBytes := new(bytes.Buffer) | |||
_, _ = messageBytes.WriteString(opts.Message) | |||
_, _ = messageBytes.WriteString("\n") | |||
if opts.KeyID != "" { | |||
cmd.AddArguments(fmt.Sprintf("-S%s", opts.KeyID)) | |||
} | |||
if opts.NoGPGSign { | |||
if version.Compare(binVersion, "2.0.0", ">=") && opts.NoGPGSign { | |||
cmd.AddArguments("--no-gpg-sign") | |||
} | |||
res, err := cmd.RunInDirWithEnv(repo.Path, env) | |||
stdout := new(bytes.Buffer) | |||
stderr := new(bytes.Buffer) | |||
err = cmd.RunInDirTimeoutEnvFullPipeline(env, -1, repo.Path, stdout, stderr, messageBytes) | |||
if err != nil { | |||
return SHA1{}, err | |||
return SHA1{}, concatenateError(err, stderr.String()) | |||
} | |||
return NewIDFromString(strings.TrimSpace(res)) | |||
return NewIDFromString(strings.TrimSpace(stdout.String())) | |||
} |
@@ -1,4 +1,5 @@ | |||
// Copyright 2014 The Gogs Authors. All rights reserved. | |||
// 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. | |||
@@ -9,6 +10,7 @@ import ( | |||
"context" | |||
"errors" | |||
"fmt" | |||
"io" | |||
"os/exec" | |||
"sync" | |||
"time" | |||
@@ -93,6 +95,14 @@ func (pm *Manager) ExecDir(timeout time.Duration, dir, desc, cmdName string, arg | |||
// Returns its complete stdout and stderr | |||
// outputs and an error, if any (including timeout) | |||
func (pm *Manager) ExecDirEnv(timeout time.Duration, dir, desc string, env []string, cmdName string, args ...string) (string, string, error) { | |||
return pm.ExecDirEnvStdIn(timeout, dir, desc, env, nil, cmdName, args...) | |||
} | |||
// ExecDirEnvStdIn runs a command in given path and environment variables with provided stdIN, and waits for its completion | |||
// up to the given timeout (or DefaultTimeout if -1 is given). | |||
// Returns its complete stdout and stderr | |||
// outputs and an error, if any (including timeout) | |||
func (pm *Manager) ExecDirEnvStdIn(timeout time.Duration, dir, desc string, env []string, stdIn io.Reader, cmdName string, args ...string) (string, string, error) { | |||
if timeout == -1 { | |||
timeout = 60 * time.Second | |||
} | |||
@@ -108,6 +118,10 @@ func (pm *Manager) ExecDirEnv(timeout time.Duration, dir, desc string, env []str | |||
cmd.Env = env | |||
cmd.Stdout = stdOut | |||
cmd.Stderr = stdErr | |||
if stdIn != nil { | |||
cmd.Stdin = stdIn | |||
} | |||
if err := cmd.Start(); err != nil { | |||
return "", "", err | |||
} | |||
@@ -21,6 +21,8 @@ import ( | |||
"code.gitea.io/gitea/modules/process" | |||
"code.gitea.io/gitea/modules/setting" | |||
"code.gitea.io/gitea/services/gitdiff" | |||
"github.com/mcuadros/go-version" | |||
) | |||
// TemporaryUploadRepository is a type to wrap our upload repositories as a shallow clone | |||
@@ -254,6 +256,11 @@ func (t *TemporaryUploadRepository) CommitTree(author, committer *models.User, t | |||
authorSig := author.NewGitSig() | |||
committerSig := committer.NewGitSig() | |||
binVersion, err := git.BinVersion() | |||
if err != nil { | |||
return "", fmt.Errorf("Unable to get git version: %v", err) | |||
} | |||
// FIXME: Should we add SSH_ORIGINAL_COMMAND to this | |||
// Because this may call hooks we should pass in the environment | |||
env := append(os.Environ(), | |||
@@ -264,11 +271,21 @@ func (t *TemporaryUploadRepository) CommitTree(author, committer *models.User, t | |||
"GIT_COMMITTER_EMAIL="+committerSig.Email, | |||
"GIT_COMMITTER_DATE="+commitTimeStr, | |||
) | |||
commitHash, stderr, err := process.GetManager().ExecDirEnv(5*time.Minute, | |||
messageBytes := new(bytes.Buffer) | |||
_, _ = messageBytes.WriteString(message) | |||
_, _ = messageBytes.WriteString("\n") | |||
args := []string{"commit-tree", treeHash, "-p", "HEAD"} | |||
if version.Compare(binVersion, "2.0.0", ">=") { | |||
args = append(args, "--no-gpg-sign") | |||
} | |||
commitHash, stderr, err := process.GetManager().ExecDirEnvStdIn(5*time.Minute, | |||
t.basePath, | |||
fmt.Sprintf("commitTree (git commit-tree): %s", t.basePath), | |||
env, | |||
git.GitExecutable, "commit-tree", treeHash, "-p", "HEAD", "-m", message) | |||
messageBytes, | |||
git.GitExecutable, args...) | |||
if err != nil { | |||
return "", fmt.Errorf("git commit-tree: %s", stderr) | |||
} | |||
@@ -328,6 +345,12 @@ func (t *TemporaryUploadRepository) DiffIndex() (diff *gitdiff.Diff, err error) | |||
// CheckAttribute checks the given attribute of the provided files | |||
func (t *TemporaryUploadRepository) CheckAttribute(attribute string, args ...string) (map[string]map[string]string, error) { | |||
binVersion, err := git.BinVersion() | |||
if err != nil { | |||
log.Error("Error retrieving git version: %v", err) | |||
return nil, err | |||
} | |||
stdOut := new(bytes.Buffer) | |||
stdErr := new(bytes.Buffer) | |||
@@ -335,7 +358,14 @@ func (t *TemporaryUploadRepository) CheckAttribute(attribute string, args ...str | |||
ctx, cancel := context.WithTimeout(context.Background(), timeout) | |||
defer cancel() | |||
cmdArgs := []string{"check-attr", "-z", attribute, "--cached", "--"} | |||
cmdArgs := []string{"check-attr", "-z", attribute} | |||
// git check-attr --cached first appears in git 1.7.8 | |||
if version.Compare(binVersion, "1.7.8", ">=") { | |||
cmdArgs = append(cmdArgs, "--cached") | |||
} | |||
cmdArgs = append(cmdArgs, "--") | |||
for _, arg := range args { | |||
if arg != "" { | |||
cmdArgs = append(cmdArgs, arg) | |||
@@ -353,7 +383,7 @@ func (t *TemporaryUploadRepository) CheckAttribute(attribute string, args ...str | |||
} | |||
pid := process.GetManager().Add(desc, cmd) | |||
err := cmd.Wait() | |||
err = cmd.Wait() | |||
process.GetManager().Remove(pid) | |||
if err != nil { | |||
@@ -313,12 +313,6 @@ func CreateOrUpdateRepoFile(repo *models.Repository, doer *models.User, opts *Up | |||
} | |||
} | |||
// Check there is no way this can return multiple infos | |||
filename2attribute2info, err := t.CheckAttribute("filter", treePath) | |||
if err != nil { | |||
return nil, err | |||
} | |||
content := opts.Content | |||
if bom { | |||
content = string(charset.UTF8BOM) + content | |||
@@ -341,16 +335,23 @@ func CreateOrUpdateRepoFile(repo *models.Repository, doer *models.User, opts *Up | |||
opts.Content = content | |||
var lfsMetaObject *models.LFSMetaObject | |||
if setting.LFS.StartServer && filename2attribute2info[treePath] != nil && filename2attribute2info[treePath]["filter"] == "lfs" { | |||
// OK so we are supposed to LFS this data! | |||
oid, err := models.GenerateLFSOid(strings.NewReader(opts.Content)) | |||
if setting.LFS.StartServer { | |||
// Check there is no way this can return multiple infos | |||
filename2attribute2info, err := t.CheckAttribute("filter", treePath) | |||
if err != nil { | |||
return nil, err | |||
} | |||
lfsMetaObject = &models.LFSMetaObject{Oid: oid, Size: int64(len(opts.Content)), RepositoryID: repo.ID} | |||
content = lfsMetaObject.Pointer() | |||
} | |||
if filename2attribute2info[treePath] != nil && filename2attribute2info[treePath]["filter"] == "lfs" { | |||
// OK so we are supposed to LFS this data! | |||
oid, err := models.GenerateLFSOid(strings.NewReader(opts.Content)) | |||
if err != nil { | |||
return nil, err | |||
} | |||
lfsMetaObject = &models.LFSMetaObject{Oid: oid, Size: int64(len(opts.Content)), RepositoryID: repo.ID} | |||
content = lfsMetaObject.Pointer() | |||
} | |||
} | |||
// Add the object to the database | |||
objectHash, err := t.HashObject(strings.NewReader(content)) | |||
if err != nil { | |||
@@ -74,9 +74,12 @@ func UploadRepoFiles(repo *models.Repository, doer *models.User, opts *UploadRep | |||
infos[i] = uploadInfo{upload: upload} | |||
} | |||
filename2attribute2info, err := t.CheckAttribute("filter", names...) | |||
if err != nil { | |||
return err | |||
var filename2attribute2info map[string]map[string]string | |||
if setting.LFS.StartServer { | |||
filename2attribute2info, err = t.CheckAttribute("filter", names...) | |||
if err != nil { | |||
return err | |||
} | |||
} | |||
// Copy uploaded files into repository. | |||
@@ -88,7 +91,7 @@ func UploadRepoFiles(repo *models.Repository, doer *models.User, opts *UploadRep | |||
defer file.Close() | |||
var objectHash string | |||
if filename2attribute2info[uploadInfo.upload.Name] != nil && filename2attribute2info[uploadInfo.upload.Name]["filter"] == "lfs" { | |||
if setting.LFS.StartServer && filename2attribute2info[uploadInfo.upload.Name] != nil && filename2attribute2info[uploadInfo.upload.Name]["filter"] == "lfs" { | |||
// Handle LFS | |||
// FIXME: Inefficient! this should probably happen in models.Upload | |||
oid, err := models.GenerateLFSOid(file) | |||
@@ -91,7 +91,7 @@ func AddressNoCredentials(m *models.Mirror) string { | |||
func SaveAddress(m *models.Mirror, addr string) error { | |||
repoPath := m.Repo.RepoPath() | |||
// Remove old origin | |||
_, err := git.NewCommand("remote", "remove", "origin").RunInDir(repoPath) | |||
_, err := git.NewCommand("remote", "rm", "origin").RunInDir(repoPath) | |||
if err != nil && !strings.HasPrefix(err.Error(), "exit status 128 - fatal: No such remote ") { | |||
return err | |||
} | |||
@@ -11,7 +11,6 @@ import ( | |||
"fmt" | |||
"io/ioutil" | |||
"os" | |||
"path" | |||
"path/filepath" | |||
"strings" | |||
@@ -22,6 +21,8 @@ import ( | |||
"code.gitea.io/gitea/modules/setting" | |||
api "code.gitea.io/gitea/modules/structs" | |||
"code.gitea.io/gitea/modules/timeutil" | |||
"github.com/mcuadros/go-version" | |||
) | |||
// Merge merges pull request to base repository. | |||
@@ -66,20 +67,17 @@ func Merge(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repositor | |||
headRepoPath := models.RepoPath(pr.HeadUserName, pr.HeadRepo.Name) | |||
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) | |||
if err := git.InitRepository(tmpBasePath, false); err != nil { | |||
return fmt.Errorf("git init: %v", err) | |||
} | |||
remoteRepoName := "head_repo" | |||
baseBranch := "base" | |||
// Add head repo remote. | |||
addCacheRepo := func(staging, cache string) error { | |||
p := filepath.Join(staging, ".git", "objects", "info", "alternates") | |||
f, err := os.OpenFile(p, os.O_APPEND|os.O_WRONLY, 0600) | |||
f, err := os.OpenFile(p, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600) | |||
if err != nil { | |||
return err | |||
} | |||
@@ -91,25 +89,41 @@ func Merge(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repositor | |||
return nil | |||
} | |||
if err := addCacheRepo(tmpBasePath, headRepoPath); err != nil { | |||
if err := addCacheRepo(tmpBasePath, baseGitRepo.Path); err != nil { | |||
return fmt.Errorf("addCacheRepo [%s -> %s]: %v", headRepoPath, tmpBasePath, err) | |||
} | |||
var errbuf strings.Builder | |||
if err := git.NewCommand("remote", "add", "-t", pr.BaseBranch, "-m", pr.BaseBranch, "origin", baseGitRepo.Path).RunInDirPipeline(tmpBasePath, nil, &errbuf); err != nil { | |||
return fmt.Errorf("git remote add [%s -> %s]: %s", baseGitRepo.Path, tmpBasePath, errbuf.String()) | |||
} | |||
if err := git.NewCommand("fetch", "origin", "--no-tags", pr.BaseBranch+":"+baseBranch, pr.BaseBranch+":original_"+baseBranch).RunInDirPipeline(tmpBasePath, nil, &errbuf); err != nil { | |||
return fmt.Errorf("git fetch [%s -> %s]: %s", headRepoPath, tmpBasePath, errbuf.String()) | |||
} | |||
if err := git.NewCommand("symbolic-ref", "HEAD", git.BranchPrefix+baseBranch).RunInDirPipeline(tmpBasePath, nil, &errbuf); err != nil { | |||
return fmt.Errorf("git symbolic-ref HEAD base [%s]: %s", tmpBasePath, errbuf.String()) | |||
} | |||
if err := addCacheRepo(tmpBasePath, headRepoPath); err != nil { | |||
return fmt.Errorf("addCacheRepo [%s -> %s]: %v", headRepoPath, tmpBasePath, err) | |||
} | |||
if err := git.NewCommand("remote", "add", remoteRepoName, headRepoPath).RunInDirPipeline(tmpBasePath, nil, &errbuf); err != nil { | |||
return fmt.Errorf("git remote add [%s -> %s]: %s", headRepoPath, tmpBasePath, errbuf.String()) | |||
} | |||
trackingBranch := "tracking" | |||
// Fetch head branch | |||
if err := git.NewCommand("fetch", remoteRepoName, fmt.Sprintf("%s:refs/remotes/%s/%s", pr.HeadBranch, remoteRepoName, pr.HeadBranch)).RunInDirPipeline(tmpBasePath, nil, &errbuf); err != nil { | |||
if err := git.NewCommand("fetch", "--no-tags", remoteRepoName, pr.HeadBranch+":"+trackingBranch).RunInDirPipeline(tmpBasePath, nil, &errbuf); err != nil { | |||
return fmt.Errorf("git fetch [%s -> %s]: %s", headRepoPath, tmpBasePath, errbuf.String()) | |||
} | |||
trackingBranch := path.Join(remoteRepoName, pr.HeadBranch) | |||
stagingBranch := fmt.Sprintf("%s_%s", remoteRepoName, pr.HeadBranch) | |||
stagingBranch := "staging" | |||
// Enable sparse-checkout | |||
sparseCheckoutList, err := getDiffTree(tmpBasePath, pr.BaseBranch, trackingBranch) | |||
sparseCheckoutList, err := getDiffTree(tmpBasePath, baseBranch, trackingBranch) | |||
if err != nil { | |||
return fmt.Errorf("getDiffTree: %v", err) | |||
} | |||
@@ -123,21 +137,37 @@ func Merge(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repositor | |||
return fmt.Errorf("Writing sparse-checkout file to %s: %v", sparseCheckoutListPath, err) | |||
} | |||
gitConfigCommand := func() func() *git.Command { | |||
binVersion, err := git.BinVersion() | |||
if err != nil { | |||
log.Fatal("Error retrieving git version: %v", err) | |||
} | |||
if version.Compare(binVersion, "1.8.0", ">=") { | |||
return func() *git.Command { | |||
return git.NewCommand("config", "--local") | |||
} | |||
} | |||
return func() *git.Command { | |||
return git.NewCommand("config") | |||
} | |||
}() | |||
// Switch off LFS process (set required, clean and smudge here also) | |||
if err := git.NewCommand("config", "--local", "filter.lfs.process", "").RunInDirPipeline(tmpBasePath, nil, &errbuf); err != nil { | |||
if err := gitConfigCommand().AddArguments("filter.lfs.process", "").RunInDirPipeline(tmpBasePath, nil, &errbuf); err != nil { | |||
return fmt.Errorf("git config [filter.lfs.process -> <> ]: %v", errbuf.String()) | |||
} | |||
if err := git.NewCommand("config", "--local", "filter.lfs.required", "false").RunInDirPipeline(tmpBasePath, nil, &errbuf); err != nil { | |||
if err := gitConfigCommand().AddArguments("filter.lfs.required", "false").RunInDirPipeline(tmpBasePath, nil, &errbuf); err != nil { | |||
return fmt.Errorf("git config [filter.lfs.required -> <false> ]: %v", errbuf.String()) | |||
} | |||
if err := git.NewCommand("config", "--local", "filter.lfs.clean", "").RunInDirPipeline(tmpBasePath, nil, &errbuf); err != nil { | |||
if err := gitConfigCommand().AddArguments("filter.lfs.clean", "").RunInDirPipeline(tmpBasePath, nil, &errbuf); err != nil { | |||
return fmt.Errorf("git config [filter.lfs.clean -> <> ]: %v", errbuf.String()) | |||
} | |||
if err := git.NewCommand("config", "--local", "filter.lfs.smudge", "").RunInDirPipeline(tmpBasePath, nil, &errbuf); err != nil { | |||
if err := gitConfigCommand().AddArguments("filter.lfs.smudge", "").RunInDirPipeline(tmpBasePath, nil, &errbuf); err != nil { | |||
return fmt.Errorf("git config [filter.lfs.smudge -> <> ]: %v", errbuf.String()) | |||
} | |||
if err := git.NewCommand("config", "--local", "core.sparseCheckout", "true").RunInDirPipeline(tmpBasePath, nil, &errbuf); err != nil { | |||
if err := gitConfigCommand().AddArguments("core.sparseCheckout", "true").RunInDirPipeline(tmpBasePath, nil, &errbuf); err != nil { | |||
return fmt.Errorf("git config [core.sparsecheckout -> true]: %v", errbuf.String()) | |||
} | |||
@@ -163,11 +193,11 @@ func Merge(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repositor | |||
return fmt.Errorf("git checkout: %s", errbuf.String()) | |||
} | |||
// Rebase before merging | |||
if err := git.NewCommand("rebase", "-q", pr.BaseBranch).RunInDirPipeline(tmpBasePath, nil, &errbuf); err != nil { | |||
if err := git.NewCommand("rebase", "-q", baseBranch).RunInDirPipeline(tmpBasePath, nil, &errbuf); err != nil { | |||
return fmt.Errorf("git rebase [%s -> %s]: %s", headRepoPath, tmpBasePath, errbuf.String()) | |||
} | |||
// Checkout base branch again | |||
if err := git.NewCommand("checkout", pr.BaseBranch).RunInDirPipeline(tmpBasePath, nil, &errbuf); err != nil { | |||
if err := git.NewCommand("checkout", baseBranch).RunInDirPipeline(tmpBasePath, nil, &errbuf); err != nil { | |||
return fmt.Errorf("git checkout: %s", errbuf.String()) | |||
} | |||
// Merge fast forward | |||
@@ -180,11 +210,11 @@ func Merge(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repositor | |||
return fmt.Errorf("git checkout: %s", errbuf.String()) | |||
} | |||
// Rebase before merging | |||
if err := git.NewCommand("rebase", "-q", pr.BaseBranch).RunInDirPipeline(tmpBasePath, nil, &errbuf); err != nil { | |||
if err := git.NewCommand("rebase", "-q", baseBranch).RunInDirPipeline(tmpBasePath, nil, &errbuf); err != nil { | |||
return fmt.Errorf("git rebase [%s -> %s]: %s", headRepoPath, tmpBasePath, errbuf.String()) | |||
} | |||
// Checkout base branch again | |||
if err := git.NewCommand("checkout", pr.BaseBranch).RunInDirPipeline(tmpBasePath, nil, &errbuf); err != nil { | |||
if err := git.NewCommand("checkout", baseBranch).RunInDirPipeline(tmpBasePath, nil, &errbuf); err != nil { | |||
return fmt.Errorf("git checkout: %s", errbuf.String()) | |||
} | |||
// Prepare merge with commit | |||
@@ -216,7 +246,7 @@ func Merge(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repositor | |||
if err != nil { | |||
return fmt.Errorf("Failed to get full commit id for HEAD: %v", err) | |||
} | |||
mergeBaseSHA, err := git.GetFullCommitID(tmpBasePath, "origin/"+pr.BaseBranch) | |||
mergeBaseSHA, err := git.GetFullCommitID(tmpBasePath, "original_"+baseBranch) | |||
if err != nil { | |||
return fmt.Errorf("Failed to get full commit id for origin/%s: %v", pr.BaseBranch, err) | |||
} | |||
@@ -249,7 +279,7 @@ func Merge(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repositor | |||
) | |||
// Push back to upstream. | |||
if err := git.NewCommand("push", "origin", pr.BaseBranch).RunInDirTimeoutEnvPipeline(env, -1, tmpBasePath, nil, &errbuf); err != nil { | |||
if err := git.NewCommand("push", "origin", baseBranch+":"+pr.BaseBranch).RunInDirTimeoutEnvPipeline(env, -1, tmpBasePath, nil, &errbuf); err != nil { | |||
return fmt.Errorf("git push: %s", errbuf.String()) | |||
} | |||