github.com/q2/git-lfs@v0.5.1-0.20150410234700-03a0d4cec40e/commands/command_push.go (about) 1 package commands 2 3 import ( 4 "fmt" 5 "github.com/github/git-lfs/git" 6 "github.com/github/git-lfs/lfs" 7 "github.com/github/git-lfs/pointer" 8 "github.com/github/git-lfs/scanner" 9 "github.com/rubyist/tracerx" 10 "github.com/spf13/cobra" 11 "io/ioutil" 12 "os" 13 "path/filepath" 14 "strings" 15 ) 16 17 var ( 18 pushCmd = &cobra.Command{ 19 Use: "push", 20 Short: "Push files to the Git LFS endpoint", 21 Run: pushCommand, 22 } 23 dryRun = false 24 useStdin = false 25 deleteBranch = "(delete)" 26 ) 27 28 // pushCommand is the command that's run via `git lfs push`. It has two modes 29 // of operation. The primary mode is run via the git pre-push hook. The pre-push 30 // hook passes two arguments on the command line: 31 // 1. Name of the remote to which the push is being done 32 // 2. URL to which the push is being done 33 // 34 // The hook receives commit information on stdin in the form: 35 // <local ref> <local sha1> <remote ref> <remote sha1> 36 // 37 // In the typical case, pushCommand will get a list of git objects being pushed 38 // by using the following: 39 // git rev-list --objects <local sha1> ^<remote sha1> 40 // 41 // If any of those git objects are associated with Git LFS objects, those 42 // objects will be pushed to the Git LFS API. 43 // 44 // In the case of pushing a new branch, the list of git objects will be all of 45 // the git objects in this branch. 46 // 47 // In the case of deleting a branch, no attempts to push Git LFS objects will be 48 // made. 49 // 50 // The other mode of operation is the dry run mode. In this mode, the repo 51 // and refspec are passed on the command line. pushCommand will calculate the 52 // git objects that would be pushed in a similar manner as above and will print 53 // out each file name. 54 func pushCommand(cmd *cobra.Command, args []string) { 55 var left, right string 56 57 if len(args) == 0 { 58 Print("The git lfs pre-push hook is out of date. Please run `git lfs update`") 59 os.Exit(1) 60 } 61 62 lfs.Config.CurrentRemote = args[0] 63 64 if useStdin { 65 refsData, err := ioutil.ReadAll(os.Stdin) 66 if err != nil { 67 Panic(err, "Error reading refs on stdin") 68 } 69 70 if len(refsData) == 0 { 71 return 72 } 73 74 left, right = decodeRefs(string(refsData)) 75 if left == deleteBranch { 76 return 77 } 78 } else { 79 var repo, refspec string 80 81 if len(args) < 1 { 82 Print("Usage: git lfs push --dry-run <repo> [refspec]") 83 return 84 } 85 86 repo = args[0] 87 if len(args) == 2 { 88 refspec = args[1] 89 } 90 91 localRef, err := git.CurrentRef() 92 if err != nil { 93 Panic(err, "Error getting local ref") 94 } 95 left = localRef 96 97 remoteRef, err := git.LsRemote(repo, refspec) 98 if err != nil { 99 Panic(err, "Error getting remote ref") 100 } 101 102 if remoteRef != "" { 103 right = "^" + strings.Split(remoteRef, "\t")[0] 104 } 105 } 106 107 // Just use scanner here 108 pointers, err := scanner.Scan(left, right) 109 if err != nil { 110 Panic(err, "Error scanning for Git LFS files") 111 } 112 113 for i, pointer := range pointers { 114 if dryRun { 115 Print("push %s", pointer.Name) 116 continue 117 } 118 if wErr := pushAsset(pointer.Oid, pointer.Name, i+1, len(pointers)); wErr != nil { 119 if Debugging || wErr.Panic { 120 Panic(wErr.Err, wErr.Error()) 121 } else { 122 Exit(wErr.Error()) 123 } 124 } 125 } 126 } 127 128 // pushAsset pushes the asset with the given oid to the Git LFS API. 129 func pushAsset(oid, filename string, index, totalFiles int) *lfs.WrappedError { 130 tracerx.Printf("checking_asset: %s %s %d/%d", oid, filename, index, totalFiles) 131 path, err := lfs.LocalMediaPath(oid) 132 if err != nil { 133 return lfs.Errorf(err, "Error uploading file %s (%s)", filename, oid) 134 } 135 136 if err := ensureFile(filename, path); err != nil { 137 return lfs.Errorf(err, "Error uploading file %s (%s)", filename, oid) 138 } 139 140 cb, file, cbErr := lfs.CopyCallbackFile("push", filename, index, totalFiles) 141 if cbErr != nil { 142 Error(cbErr.Error()) 143 } 144 145 if file != nil { 146 defer file.Close() 147 } 148 149 return lfs.Upload(path, filename, cb) 150 } 151 152 // ensureFile makes sure that the cleanPath exists before pushing it. If it 153 // does not exist, it attempts to clean it by reading the file at smudgePath. 154 func ensureFile(smudgePath, cleanPath string) error { 155 if _, err := os.Stat(cleanPath); err == nil { 156 return nil 157 } 158 159 expectedOid := filepath.Base(cleanPath) 160 localPath := filepath.Join(lfs.LocalWorkingDir, smudgePath) 161 file, err := os.Open(localPath) 162 if err != nil { 163 return err 164 } 165 166 defer file.Close() 167 168 stat, err := file.Stat() 169 if err != nil { 170 return err 171 } 172 173 cleaned, err := pointer.Clean(file, stat.Size(), nil) 174 if err != nil { 175 return err 176 } 177 178 cleaned.Close() 179 180 if expectedOid != cleaned.Oid { 181 return fmt.Errorf("Expected %s to have an OID of %s, got %s", smudgePath, expectedOid, cleaned.Oid) 182 } 183 184 return nil 185 } 186 187 // decodeRefs pulls the sha1s out of the line read from the pre-push 188 // hook's stdin. 189 func decodeRefs(input string) (string, string) { 190 refs := strings.Split(strings.TrimSpace(input), " ") 191 var left, right string 192 193 if len(refs) > 1 { 194 left = refs[1] 195 } 196 197 if len(refs) > 3 { 198 right = "^" + refs[3] 199 } 200 201 return left, right 202 } 203 204 func init() { 205 pushCmd.Flags().BoolVarP(&dryRun, "dry-run", "d", false, "Do everything except actually send the updates") 206 pushCmd.Flags().BoolVarP(&useStdin, "stdin", "s", false, "Take refs on stdin (for pre-push hook)") 207 RootCmd.AddCommand(pushCmd) 208 }