diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d3f2ee1 --- /dev/null +++ b/Makefile @@ -0,0 +1,4 @@ +test: + go test ./... +build: + go build \ No newline at end of file diff --git a/README.md b/README.md index e4dbe4e..835b470 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,12 @@ boilr template download boilr template download tmrts/boilr-license license ``` +An example for multi template, multi branch forced repo clone + +``` +boilr template download -p template2/templates/sample -b multi-branch -f https://github.com/jasimmk/boilr-test.git sample +``` + The downloaded template will be saved to local `boilr` registry. ## Save a Local Template diff --git a/pkg/boilr/configuration.go b/pkg/boilr/configuration.go index 8dde458..db85de0 100644 --- a/pkg/boilr/configuration.go +++ b/pkg/boilr/configuration.go @@ -27,6 +27,9 @@ const ( // TemplateDir is the directory that contains the template registry TemplateDir = "templates" + // TemplateTempDir is the directory that is used for temporary storage + TemplateTempDir = "../tmp" + // ContextFileName is the name of the file that contains the context values for the template ContextFileName = "project.json" @@ -57,6 +60,12 @@ func TemplatePath(name string) (string, error) { return filepath.Join(Configuration.TemplateDirPath, name), nil } +// TemplateTempPath returns absolute path of template temporary directory given name of template. +func TemplateTempPath(name string) (string, error) { + return filepath.Join(Configuration.TemplateDirPath, TemplateTempDir, name), nil +} + +// IsTemplateDirInitialized Checks if template directory is initialized func IsTemplateDirInitialized() (bool, error) { return osutil.DirExists(Configuration.TemplateDirPath) } diff --git a/pkg/cmd/download.go b/pkg/cmd/download.go index d642d73..e4cd358 100644 --- a/pkg/cmd/download.go +++ b/pkg/cmd/download.go @@ -4,6 +4,8 @@ import ( "fmt" "os" + "gopkg.in/src-d/go-git.v4/plumbing" + cli "github.com/spf13/cobra" "github.com/tmrts/boilr/pkg/boilr" @@ -27,9 +29,12 @@ var Download = &cli.Command{ MustValidateTemplateDir() + templateSubFolder := GetStringFlag(c, "sub-path") + templateRemoteBranch := GetStringFlag(c, "branch") templateURL, templateName := args[0], args[1] - targetDir, err := boilr.TemplatePath(templateName) + targetTmpDir := targetDir + if err != nil { exit.Error(fmt.Errorf("download: %s", err)) } @@ -47,14 +52,69 @@ var Download = &cli.Command{ exit.Error(fmt.Errorf("download: %s", err)) } } + // In case if we are copying template from repository sub-folder, clone repo to temp folder + if templateSubFolder != "" { + targetTmpDir, err = boilr.TemplateTempPath(templateName) + if err != nil { + exit.Error(fmt.Errorf("download: %s", err)) + } + exists, err := osutil.DirExists(targetTmpDir) + if exists || (!exists && err != nil) { + if err := os.RemoveAll(targetTmpDir); err != nil { + exit.Error(fmt.Errorf("download: %s", err)) + } + } + } // TODO(tmrts): allow fetching other branches than 'master' - if err := git.Clone(targetDir, git.CloneOptions{ + gitCloneOptions := git.CloneOptions{ URL: host.URL(templateURL), - }); err != nil { - exit.Error(fmt.Errorf("download: %s", err)) + } + if templateRemoteBranch != "" { + gitCloneOptions.ReferenceName = plumbing.NewBranchReferenceName(templateRemoteBranch) + gitCloneOptions.SingleBranch = true + } + if err := git.Clone(targetTmpDir, gitCloneOptions); err != nil { + exit.Error(fmt.Errorf("download: Cloning repo - %s", err)) } + // Copy content from sub-folder to target folder + if templateSubFolder != "" { + // Ensure sub-folder exists + templateTmpDir := osutil.JoinPaths(targetTmpDir, templateSubFolder) + exists, err := osutil.DirExists(templateTmpDir) + if err != nil { + exit.Error(fmt.Errorf("download: %s", err)) + } + if !exists { + exit.Error(fmt.Errorf("download: sub-folder doesn't exist")) + } + // Check target folder exists, and copy contents + if exists, err = osutil.DirExists(targetDir); err != nil { + exit.Error(fmt.Errorf("download: %s", err)) + } + if !exists { + if err = osutil.CreateDirs(targetDir); err != nil { + exit.Error(fmt.Errorf("download: %s", err)) + + } + } + if err = osutil.CopyRecursively(templateTmpDir, targetDir); err != nil { + exit.Error(fmt.Errorf("download: Error copying files from temp %s", err)) + } + // Delete all temp files + if err := os.RemoveAll(targetTmpDir); err != nil { + exit.Error(fmt.Errorf("download: Error deleting temp files %s", err)) + } + } + // Ensure that a 'template' folder exists inside the repo before registering template + exists, err := osutil.DirExists(osutil.JoinPaths(targetDir, boilr.TemplateDirName)) + if err != nil { + exit.Error(fmt.Errorf("download: Template error - %s", err)) + } + if !exists { + exit.Error(fmt.Errorf("download: Invalid template. Folder '%s' - doesn't exist at %s", boilr.TemplateDirName, targetDir)) + } // TODO(tmrts): use git-notes as metadata storage or boltdb if err := serializeMetadata(templateName, templateURL, targetDir); err != nil { exit.Error(fmt.Errorf("download: %s", err)) diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go index ed42596..fe3be09 100644 --- a/pkg/cmd/root.go +++ b/pkg/cmd/root.go @@ -23,6 +23,8 @@ func Run() { Download.PersistentFlags().BoolP("force", "f", false, "Overwrite existing template with the same name") Download.PersistentFlags().StringP("log-level", "l", "error", "log-level for output") + Download.PersistentFlags().StringP("branch", "b", "", "Branch, Commit Id, Tag or other reference to checkout") + Download.PersistentFlags().StringP("sub-path", "p", "", "Path inside the git repo where 'template' folder.") Template.AddCommand(Download) List.PersistentFlags().BoolP("dont-prettify", "", false, "Print only the template names without fancy formatting") diff --git a/pkg/prompt/prompt_test.go b/pkg/prompt/prompt_test.go index 3e3b93c..28deb18 100644 --- a/pkg/prompt/prompt_test.go +++ b/pkg/prompt/prompt_test.go @@ -49,7 +49,7 @@ func TestNewBooleanPromptFunc(t *testing.T) { expectedPromptMsg := "Please choose a value for \"fieldName\"" if msg != expectedPromptMsg { - t.Errorf("boolPrompt(%q).PromptMessage(%q) expected %q got %q", defval, name, expectedPromptMsg, msg) + t.Errorf("boolPrompt((%v).PromptMessage(%q) expected %q got %q", defval, name, expectedPromptMsg, msg) } choiceCases := []struct { @@ -73,7 +73,7 @@ func TestNewBooleanPromptFunc(t *testing.T) { for _, c := range choiceCases { val, err := boolPrompt.EvaluateChoice(c.choice) if err != nil { - t.Errorf("boolPrompt(%q).EvaluateChoice(%q) got error %q", defval, c.choice, err) + t.Errorf("boolPrompt(%v).EvaluateChoice(%q) got error %q", defval, c.choice, err) continue } diff --git a/pkg/util/osutil/fs.go b/pkg/util/osutil/fs.go index ca8a9e1..5f57e48 100644 --- a/pkg/util/osutil/fs.go +++ b/pkg/util/osutil/fs.go @@ -7,6 +7,11 @@ import ( "path/filepath" ) +// JoinPaths joins multiple paths together +func JoinPaths(paths ...string) string { + return filepath.Join(paths...) +} + // FileExists checks whether the given path exists and belongs to a file. func FileExists(path string) (bool, error) { info, err := os.Stat(path)