Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add ability to configure repository clone directory #366

Merged
merged 10 commits into from
May 4, 2024
3 changes: 3 additions & 0 deletions cmd/cmd-print.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ func PrintCmd() *cobra.Command {

cmd.Flags().IntP("concurrent", "C", 1, "The maximum number of concurrent runs.")
cmd.Flags().StringP("error-output", "E", "-", `The file that the output of the script should be outputted to. "-" means stderr.`)
cmd.Flags().StringP("clone-dir", "", "", "The temporary directory where the repositories will be cloned. If not set, the default os temporary directory will be used.")
configureGit(cmd)
configurePlatform(cmd)
configureLogging(cmd, "")
Expand All @@ -49,6 +50,7 @@ func printCMD(cmd *cobra.Command, _ []string) error {
concurrent, _ := flag.GetInt("concurrent")
strOutput, _ := flag.GetString("output")
strErrOutput, _ := flag.GetString("error-output")
cloneDir, _ := flag.GetString("clone-dir")

if concurrent < 1 {
return errors.New("concurrent runs can't be less than one")
Expand Down Expand Up @@ -101,6 +103,7 @@ func printCMD(cmd *cobra.Command, _ []string) error {
Stderr: errOutput,

Concurrent: concurrent,
CloneDir: cloneDir,

CreateGit: gitCreator,
}
Expand Down
3 changes: 3 additions & 0 deletions cmd/cmd-run.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ Available values:
cmd.Flags().StringSliceP("labels", "", nil, "Labels to be added to any created pull request.")
cmd.Flags().StringP("author-name", "", "", "Name of the committer. If not set, the global git config setting will be used.")
cmd.Flags().StringP("author-email", "", "", "Email of the committer. If not set, the global git config setting will be used.")
cmd.Flags().StringP("clone-dir", "", "", "The temporary directory where the repositories will be cloned. If not set, the default os temporary directory will be used.")
cmd.Flags().StringP("repo-include", "", "", "Include repositories that match with a given Regular Expression")
cmd.Flags().StringP("repo-exclude", "", "", "Exclude repositories that match with a given Regular Expression")
configureGit(cmd)
Expand Down Expand Up @@ -102,6 +103,7 @@ func run(cmd *cobra.Command, _ []string) error {
strOutput, _ := flag.GetString("output")
assignees, _ := stringSlice(flag, "assignees")
draft, _ := flag.GetBool("draft")
cloneDir, _ := flag.GetString("clone-dir")
labels, _ := stringSlice(flag, "labels")
repoInclude, _ := flag.GetString("repo-include")
repoExclude, _ := flag.GetString("repo-exclude")
Expand Down Expand Up @@ -246,6 +248,7 @@ func run(cmd *cobra.Command, _ []string) error {
ConflictStrategy: conflictStrategy,
Draft: draft,
Labels: labels,
CloneDir: cloneDir,

Concurrent: concurrent,

Expand Down
5 changes: 3 additions & 2 deletions internal/multigitter/print.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type Printer struct {
Stderr io.Writer

Concurrent int
CloneDir string

CreateGit func(dir string) Git
}
Expand Down Expand Up @@ -66,12 +67,12 @@ func (r Printer) runSingleRepo(ctx context.Context, repo scm.Repository) error {

log := log.WithField("repo", repo.FullName())
log.Info("Cloning and running script")
tmpDir, err := createTempDir(r.CloneDir)

tmpDir, err := os.MkdirTemp(os.TempDir(), "multi-git-changer-")
defer os.RemoveAll(tmpDir)
if err != nil {
return err
}
defer os.RemoveAll(tmpDir)

sourceController := r.CreateGit(tmpDir)

Expand Down
7 changes: 4 additions & 3 deletions internal/multigitter/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ type Runner struct {

Draft bool // If set, creates Pull Requests as draft

Labels []string // Labels to be added to the pull request
Labels []string // Labels to be added to the pull request
CloneDir string // Directory to clone repositories to

Interactive bool // If set, interactive mode is activated and the user will be asked to verify every change

Expand Down Expand Up @@ -229,12 +230,12 @@ func (r *Runner) runSingleRepo(ctx context.Context, repo scm.Repository) (scm.Pu

log := log.WithField("repo", repo.FullName())
log.Info("Cloning and running script")
tmpDir, err := createTempDir(r.CloneDir)

tmpDir, err := os.MkdirTemp(os.TempDir(), "multi-git-changer-")
defer os.RemoveAll(tmpDir)
if err != nil {
return nil, err
}
defer os.RemoveAll(tmpDir)

sourceController := r.CreateGit(tmpDir)

Expand Down
56 changes: 56 additions & 0 deletions internal/multigitter/shared.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package multigitter
import (
"context"
"fmt"
"os"
"path/filepath"
"syscall"

"github.com/lindell/multi-gitter/internal/git"
Expand Down Expand Up @@ -70,3 +72,57 @@ func ParseConflictStrategy(str string) (ConflictStrategy, error) {
return ConflictStrategyReplace, nil
}
}

// createTempDir creates a temporary directory in the given directory.
// If the given directory is an empty string, it will use the os.TempDir()
func createTempDir(cloneDir string) (string, error) {
if cloneDir == "" {
cloneDir = os.TempDir()
}

absDir, err := makeAbsolutePath(cloneDir)
if err != nil {
return "", err
}

err = createDirectoryIfDoesntExist(absDir)
if err != nil {
return "", err
}

tmpDir, err := os.MkdirTemp(absDir, "multi-git-changer-")
if err != nil {
return "", err
}

return tmpDir, nil
}

func createDirectoryIfDoesntExist(directoryPath string) error {
// Check if the directory exists
if _, err := os.Stat(directoryPath); !os.IsNotExist(err) {
return nil
}

// Create the directory
err := os.MkdirAll(directoryPath, 0700)
if err != nil {
return err
}

return nil
}

// makeAbsolutePath creates an absolute path from a relative path
func makeAbsolutePath(path string) (string, error) {
workingDir, err := os.Getwd()
if err != nil {
return "", errors.Wrap(err, "could not get the working directory")
}

if !filepath.IsAbs(path) {
return filepath.Join(workingDir, path), nil
}

return path, nil
}
3 changes: 2 additions & 1 deletion tests/print_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ func TestPrint(t *testing.T) {
errOutFile := filepath.Join(tmpDir, "err-out.txt")

command := cmd.RootCmd()
command.SetArgs([]string{"print",
command.SetArgs([]string{
"print",
"--log-file", filepath.ToSlash(runLogFile),
"--output", filepath.ToSlash(outFile),
"--error-output", filepath.ToSlash(errOutFile),
Expand Down
6 changes: 3 additions & 3 deletions tests/repo_helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (
const fileName = "test.txt"

func createRepo(t *testing.T, ownerName string, repoName string, dataInFile string) vcmock.Repository {
tmpDir, err := createDummyRepo(dataInFile)
tmpDir, err := createDummyRepo(dataInFile, os.TempDir())
require.NoError(t, err)

return vcmock.Repository{
Expand All @@ -29,8 +29,8 @@ func createRepo(t *testing.T, ownerName string, repoName string, dataInFile stri
}
}

func createDummyRepo(dataInFile string) (string, error) {
tmpDir, err := os.MkdirTemp(os.TempDir(), "multi-git-test-*.git")
func createDummyRepo(dataInFile string, dir string) (string, error) {
tmpDir, err := os.MkdirTemp(dir, "multi-git-test-*.git")
if err != nil {
return "", err
}
Expand Down
17 changes: 17 additions & 0 deletions tests/scripts/pwd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package main

import (
"fmt"
"os"
)

func main() {
path, _ := os.Getwd()
fmt.Println("Current path:", path)
err := os.WriteFile("pwd.txt", []byte(path), 0600)
if err != nil {
fmt.Println("Could not write to pwd.txt:", err)
return
}
fmt.Println("Wrote to pwd.txt")
}
18 changes: 12 additions & 6 deletions tests/story_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ func TestStory(t *testing.T) {
runOutFile := filepath.Join(tmpDir, "run-log.txt")

command := cmd.RootCmd()
command.SetArgs([]string{"run",
command.SetArgs([]string{
"run",
"--output", runOutFile,
"--author-name", "Test Author",
"--author-email", "test@example.com",
Expand Down Expand Up @@ -75,7 +76,8 @@ Repositories with a successful run:
statusOutFile := filepath.Join(tmpDir, "status-log.txt")

command = cmd.RootCmd()
command.SetArgs([]string{"status",
command.SetArgs([]string{
"status",
"--output", statusOutFile,
"-B", "custom-branch-name",
})
Expand All @@ -96,7 +98,8 @@ Repositories with a successful run:
mergeLogFile := filepath.Join(tmpDir, "merge-log.txt")

command = cmd.RootCmd()
command.SetArgs([]string{"merge",
command.SetArgs([]string{
"merge",
"--log-file", mergeLogFile,
"-B", "custom-branch-name",
})
Expand All @@ -115,7 +118,8 @@ Repositories with a successful run:
afterMergeStatusOutFile := filepath.Join(tmpDir, "after-merge-status-log.txt")

command = cmd.RootCmd()
command.SetArgs([]string{"status",
command.SetArgs([]string{
"status",
"--output", afterMergeStatusOutFile,
"-B", "custom-branch-name",
})
Expand All @@ -133,7 +137,8 @@ Repositories with a successful run:
closeLogFile := filepath.Join(tmpDir, "close-log.txt")

command = cmd.RootCmd()
command.SetArgs([]string{"close",
command.SetArgs([]string{
"close",
"--log-file", closeLogFile,
"-B", "custom-branch-name",
})
Expand All @@ -152,7 +157,8 @@ Repositories with a successful run:
afterCloseStatusOutFile := filepath.Join(tmpDir, "after-close-status-log.txt")

command = cmd.RootCmd()
command.SetArgs([]string{"status",
command.SetArgs([]string{
"status",
"--output", afterCloseStatusOutFile,
"-B", "custom-branch-name",
})
Expand Down
72 changes: 71 additions & 1 deletion tests/table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"io"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -1187,6 +1188,74 @@ Repositories with a successful run:
},
},

{
name: "custom clone dir with relative path",
vcCreate: func(t *testing.T) *vcmock.VersionController {
return &vcmock.VersionController{
Repositories: []vcmock.Repository{
createRepo(t, "owner", "should-change", "i like apples"),
},
}
},
args: []string{
"run",
"--author-name", "Test Author",
"--author-email", "test@example.com",
"-B", "clone-dir-branch-name",
"-m", "clone dir message",
"--clone-dir", "./tmp-test",
fmt.Sprintf("go run %s", normalizePath(filepath.Join(workingDir, "scripts/pwd/main.go"))),
},
verify: func(t *testing.T, vcMock *vcmock.VersionController, runData runData) {
require.Len(t, vcMock.PullRequests, 1)
expectedPath := filepath.Join(workingDir, "tmp-test")

// Check the path in the logs
assert.Contains(t, runData.logOut, "Current path: "+strings.ReplaceAll(expectedPath, `\`, `\\`))

changeBranch(t, vcMock.Repositories[0].Path, "clone-dir-branch-name", false)
pathInFile := readFile(t, vcMock.Repositories[0].Path, "pwd.txt")
assert.True(t, strings.HasPrefix(pathInFile, expectedPath))
},
},

{
name: "custom clone dir with absolute path",
vcCreate: func(t *testing.T) *vcmock.VersionController {
return &vcmock.VersionController{
Repositories: []vcmock.Repository{
createRepo(t, "owner", "should-change", "i like apples"),
},
}
},
args: []string{
"run",
"--author-name", "Test Author",
"--author-email", "test@example.com",
"-B", "clone-dir-branch-name",
"-m", "clone dir message",
"--clone-dir", filepath.Join(os.TempDir(), "tmp-test"),
fmt.Sprintf("go run %s", normalizePath(filepath.Join(workingDir, "scripts/pwd/main.go"))),
},
verify: func(t *testing.T, vcMock *vcmock.VersionController, runData runData) {
require.Len(t, vcMock.PullRequests, 1)

tmpDir := os.TempDir()
// Fix for MacOS (darwin) where the tmp directory is aliased under two different directories
if runtime.GOOS == "darwin" {
tmpDir = filepath.Join("/private", tmpDir)
}

expectedPath := filepath.Join(tmpDir, "tmp-test")

assert.Contains(t, runData.logOut, "Current path: "+strings.ReplaceAll(expectedPath, `\`, `\\`))

changeBranch(t, vcMock.Repositories[0].Path, "clone-dir-branch-name", false)
pathInFile := readFile(t, vcMock.Repositories[0].Path, "pwd.txt")
assert.True(t, strings.HasPrefix(pathInFile, expectedPath))
},
},

{
name: "fork conflicts with pushOnly",
vcCreate: func(t *testing.T) *vcmock.VersionController {
Expand Down Expand Up @@ -1277,9 +1346,10 @@ Repositories with a successful run:

outFile, err := os.CreateTemp(os.TempDir(), "multi-gitter-test-output")
require.NoError(t, err)
// defer os.Remove(outFile.Name())
filipkrayem marked this conversation as resolved.
Show resolved Hide resolved
defer os.Remove(outFile.Name())

vc := test.vcCreate(t)

defer vc.Clean()

cmd.OverrideVersionController = vc
Expand Down
Loading