Skip to content

Commit

Permalink
feat: add ability to configure repository clone directory (#366)
Browse files Browse the repository at this point in the history
  • Loading branch information
filipkrayem authored May 4, 2024
1 parent b556a44 commit 6345c6b
Show file tree
Hide file tree
Showing 10 changed files with 174 additions and 16 deletions.
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", "[email protected]",
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", "[email protected]",
"-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", "[email protected]",
"-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())
defer os.Remove(outFile.Name())

vc := test.vcCreate(t)

defer vc.Clean()

cmd.OverrideVersionController = vc
Expand Down

0 comments on commit 6345c6b

Please sign in to comment.