From 00ea6c7c1eb767eb7a579e97bfb64895c0e767e5 Mon Sep 17 00:00:00 2001
From: Tamer Tas <contact@tmrts.com>
Date: Sat, 28 May 2016 00:25:39 +0300
Subject: [PATCH] Use transactions when executing a template

---
 pkg/cmd/use.go           | 80 ++++++++++++++++++++++++++++++++++++----
 pkg/template/template.go |  6 ++-
 2 files changed, 77 insertions(+), 9 deletions(-)

diff --git a/pkg/cmd/use.go b/pkg/cmd/use.go
index a577eaa..e33ad12 100644
--- a/pkg/cmd/use.go
+++ b/pkg/cmd/use.go
@@ -2,6 +2,8 @@ package cmd
 
 import (
 	"fmt"
+	"io"
+	"io/ioutil"
 	"os"
 	"path/filepath"
 
@@ -29,7 +31,7 @@ func TemplateInRegistry(name string) (bool, error) {
 var Use = &cli.Command{
 	Use:   "use <template-tag> <target-dir>",
 	Short: "Execute a project template in the given directory",
-	Run: func(c *cli.Command, args []string) {
+	Run: func(cmd *cli.Command, args []string) {
 		MustValidateArgs(args, []validate.Argument{
 			{"template-tag", validate.UnixPath},
 			{"target-dir", validate.UnixPath},
@@ -43,9 +45,12 @@ var Use = &cli.Command{
 			exit.Fatal(fmt.Errorf("use: %s", err))
 		}
 
-		if ok, err := TemplateInRegistry(tmplName); err != nil {
+		templateFound, err := TemplateInRegistry(tmplName)
+		if err != nil {
 			exit.Fatal(fmt.Errorf("use: %s", err))
-		} else if !ok {
+		}
+
+		if !templateFound {
 			exit.Fatal(fmt.Errorf("Template %q couldn't be found in the template registry", tmplName))
 		}
 
@@ -59,14 +64,75 @@ var Use = &cli.Command{
 			exit.Fatal(fmt.Errorf("use: %s", err))
 		}
 
-		if shouldUseDefaults := GetBoolFlag(c, "use-defaults"); shouldUseDefaults {
+		if shouldUseDefaults := GetBoolFlag(cmd, "use-defaults"); shouldUseDefaults {
 			tmpl.UseDefaultValues()
 		}
 
-		if err := tmpl.Execute(targetDir); err != nil {
-			// Deletes the target dir if execute transaction fails
-			defer os.RemoveAll(targetDir)
+		tmpDir, err := ioutil.TempDir("", "boilr-use-template")
+		if err != nil {
+			exit.Fatal(fmt.Errorf("use: %s", err))
+		}
+		defer os.RemoveAll(tmpDir)
+
+		if err := os.Mkdir(targetDir, 0744); err != nil {
+			if os.IsNotExist(err) {
+				exit.Fatal(fmt.Errorf("use: directory %q doesn't exist", filepath.Dir(targetDir)))
+			}
+
+			if !os.IsExist(err) {
+				exit.Fatal(fmt.Errorf("use: %s", err))
+			}
+		}
+
+		if err := tmpl.Execute(tmpDir); err != nil {
+			exit.Fatal(fmt.Errorf("use: %s", err))
+		}
+
+		// Complete the template execution transaction by copying the temporary dir to
+		// the target directory.
+		if err := filepath.Walk(tmpDir, func(fname string, info os.FileInfo, err error) error {
+			if err != nil {
+				return err
+			}
+
+			relPath, err := filepath.Rel(tmpDir, fname)
+			if err != nil {
+				return err
+			}
+
+			mirrorPath := filepath.Join(targetDir, relPath)
+
+			if info.IsDir() {
+				if err := os.Mkdir(mirrorPath, 0744); err != nil {
+					if !os.IsExist(err) {
+						return err
+					}
+				}
+			} else {
+				fi, err := os.Lstat(fname)
+				if err != nil {
+					return err
+				}
+
+				tmpf, err := os.Open(fname)
+				if err != nil {
+					return err
+				}
+				defer tmpf.Close()
+
+				f, err := os.OpenFile(mirrorPath, os.O_CREATE|os.O_WRONLY, fi.Mode())
+				if err != nil {
+					return err
+				}
+				defer f.Close()
+
+				if _, err := io.Copy(f, tmpf); err != nil {
+					return err
+				}
+			}
 
+			return nil
+		}); err != nil {
 			exit.Fatal(fmt.Errorf("use: %s", err))
 		}
 
diff --git a/pkg/template/template.go b/pkg/template/template.go
index 1b93adc..4d884d5 100644
--- a/pkg/template/template.go
+++ b/pkg/template/template.go
@@ -198,8 +198,10 @@ func (t *dirTemplate) Execute(dirPrefix string) error {
 		target := filepath.Join(dirPrefix, newName)
 
 		if info.IsDir() {
-			if err := os.MkdirAll(target, 0744); err != nil {
-				return err
+			if err := os.Mkdir(target, 0744); err != nil {
+				if !os.IsExist(err) {
+					return err
+				}
 			}
 		} else {
 			fi, err := os.Lstat(filename)
-- 
GitLab