github.com/atlassian/git-lob@v0.0.0-20150806085256-2386a5ed291a/core/checkout.go (about) 1 package core 2 3 import ( 4 "errors" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 10 "github.com/atlassian/git-lob/util" 11 ) 12 13 // Callback can report skip,transfer (on complete), error 14 type CheckoutCallback func(t util.ProgressCallbackType, filelob *FileLOB, err error) 15 16 // Populate local placeholders with real content, if available. Do entire working copy unless limited to pathspecs 17 func Checkout(pathspecs []string, dryRun bool, callback CheckoutCallback) error { 18 // We're going to scan for missing git-lob content not just by checking the working copy, but 19 // getting the expected content from git first. This is in case the working copy has had files 20 // deleted for example. We still check the content of the working copy if the file IS there 21 // in order to not overwrite modified files. 22 23 util.LogDebug("Checking for missing binary files in working copy") 24 25 // firstly convert any pathspecs to the root of the repo, in case this is being executed in a sub-folder 26 reporoot, _, err := util.GetRepoRoot() 27 if err != nil { 28 return err 29 } 30 curdir, err := os.Getwd() 31 if err != nil { 32 return err 33 } 34 var rootedpathspecs []string 35 for _, p := range pathspecs { 36 var abs string 37 if filepath.IsAbs(p) { 38 abs = p 39 } else { 40 abs = filepath.Join(curdir, p) 41 } 42 reltoroot, err := filepath.Rel(reporoot, abs) 43 if err != nil { 44 return errors.New(fmt.Sprintf("Unable to make %v relative to repo root %v", p, reporoot)) 45 } 46 rootedpathspecs = append(rootedpathspecs, reltoroot) 47 } 48 49 // Get what git thinks we should have 50 filelobs, err := GetGitAllFilesAndLOBsToCheckoutAtCommit("HEAD", rootedpathspecs, nil) 51 if err != nil { 52 return err 53 } 54 var modifiedfiles []string 55 for _, filelob := range filelobs { 56 // Check each file, and if it's missing or contains the placeholder text, replace it with content 57 // Otherwise, assume it's been locally modified and leave it alone (user can override this with git reset/checkout if they want) 58 absfile := filepath.Join(reporoot, filelob.Filename) 59 stat, err := os.Stat(absfile) 60 replaceContent := false 61 if err == nil { 62 // File existed, check content (smoke test on size) 63 if stat.Size() == int64(SHALineLen) { 64 // File existed and is right size for placeholder, so check contents 65 placeholderContent := getLOBPlaceholderContent(filelob.SHA) 66 filebytes, err := ioutil.ReadFile(absfile) 67 if err == nil && string(filebytes) == placeholderContent { 68 // File content is placeholder, so replace 69 replaceContent = true 70 } 71 } 72 } else { 73 // File did not exist 74 replaceContent = true 75 } 76 77 if replaceContent { 78 if !dryRun { 79 err = checkoutFile(absfile, filelob.SHA) 80 if err != nil { 81 if IsNotFoundError(err) { 82 // most common issue, log nicely 83 callback(util.ProgressNotFound, filelob, 84 NewNotFoundError(fmt.Sprintf("%v: content not available, placeholder used [%v]", filelob.Filename, filelob.SHA[:7]), 85 filelob.Filename)) 86 } else { 87 // Still not fatal but log full detail 88 callback(util.ProgressError, filelob, 89 errors.New(fmt.Sprintf("Can't retrieve content for %v: %v", filelob.Filename, err.Error()))) 90 } 91 } else { 92 // Success 93 callback(util.ProgressTransferBytes, filelob, nil) 94 } 95 // In all cases, we've changed the content of the file. It's important we note this for later 96 modifiedfiles = append(modifiedfiles, filelob.Filename) 97 } else { 98 // Dry run, still call back as if we did it 99 callback(util.ProgressTransferBytes, filelob, nil) 100 } 101 102 } else { 103 callback(util.ProgressSkip, filelob, nil) 104 } 105 106 } 107 108 var retErr error 109 if len(modifiedfiles) > 0 { 110 // Modifying files, even to a state that would show as unmodified in 'git diff' (because our filters 111 // make sure that it is so) confuses git because the cached stat() info it stores no longer agrees with the file 112 // So 'git status' would report the files modified even though 'git diff' wouldn't. Confusing for the user! 113 // Cause git to refresh its index 114 retErr = GitRefreshIndexForFiles(modifiedfiles) 115 } 116 117 if retErr == nil { 118 util.LogDebug("Successfully checked the working copy") 119 } 120 121 return retErr 122 123 } 124 125 // Checkout a single file to a specific path 126 func checkoutFile(path, sha string) error { 127 err := os.MkdirAll(filepath.Dir(path), 0755) 128 if err != nil { 129 return errors.New(fmt.Sprintf("Can't create parent directory of %v: %v\n", path, err.Error())) 130 } 131 f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) 132 if err != nil { 133 return errors.New(fmt.Sprintf("Can't open %v for writing: %v", path, err.Error())) 134 } 135 defer f.Close() 136 _, err = RetrieveLOB(sha, f) 137 if err != nil { 138 // We already truncated the file so we need to re-write the placeholder contents 139 ioutil.WriteFile(path, []byte(getLOBPlaceholderContent(sha)), 0644) 140 return err 141 } 142 143 return nil 144 145 }