func smudgeCommand(cmd *cobra.Command, args []string) { requireStdin("This command should be run by the Git 'smudge' filter") lfs.InstallHooks(false) // keeps the initial buffer from lfs.DecodePointer b := &bytes.Buffer{} r := io.TeeReader(os.Stdin, b) ptr, err := lfs.DecodePointer(r) if err != nil { mr := io.MultiReader(b, os.Stdin) _, err := io.Copy(os.Stdout, mr) if err != nil { Panic(err, "Error writing data to stdout:") } return } if smudgeInfo { localPath, err := lfs.LocalMediaPath(ptr.Oid) if err != nil { Exit(err.Error()) } stat, err := os.Stat(localPath) if err != nil { Print("%d --", ptr.Size) } else { Print("%d %s", stat.Size(), localPath) } return } filename := smudgeFilename(args, err) cb, file, err := lfs.CopyCallbackFile("smudge", filename, 1, 1) if err != nil { Error(err.Error()) } cfg := lfs.Config download := lfs.FilenamePassesIncludeExcludeFilter(filename, cfg.FetchIncludePaths(), cfg.FetchExcludePaths()) if smudgeSkip || lfs.Config.GetenvBool("GIT_LFS_SKIP_SMUDGE", false) { download = false } err = ptr.Smudge(os.Stdout, filename, download, cb) if file != nil { file.Close() } if err != nil { ptr.Encode(os.Stdout) // Download declined error is ok to skip if we weren't requesting download if !(lfs.IsDownloadDeclinedError(err) && !download) { LoggedError(err, "Error accessing media: %s (%s)", filename, ptr.Oid) os.Exit(2) } } }
func trackCommand(cmd *cobra.Command, args []string) { if lfs.LocalGitDir == "" { Print("Not a git repository.") os.Exit(128) } if lfs.LocalWorkingDir == "" { Print("This operation must be run in a work tree.") os.Exit(128) } lfs.InstallHooks(false) knownPaths := findPaths() if len(args) == 0 { Print("Listing tracked paths") for _, t := range knownPaths { Print(" %s (%s)", t.Path, t.Source) } return } addTrailingLinebreak := needsTrailingLinebreak(".gitattributes") attributesFile, err := os.OpenFile(".gitattributes", os.O_RDWR|os.O_APPEND|os.O_CREATE, 0660) if err != nil { Print("Error opening .gitattributes file") return } defer attributesFile.Close() if addTrailingLinebreak { if _, err := attributesFile.WriteString("\n"); err != nil { Print("Error writing to .gitattributes") } } wd, _ := os.Getwd() relpath, err := filepath.Rel(lfs.LocalWorkingDir, wd) if err != nil { Exit("Current directory %q outside of git working directory %q.", wd, lfs.LocalWorkingDir) } ArgsLoop: for _, pattern := range args { for _, known := range knownPaths { if known.Path == filepath.Join(relpath, pattern) { Print("%s already supported", pattern) continue ArgsLoop } } encodedArg := strings.Replace(pattern, " ", "[[:space:]]", -1) _, err := attributesFile.WriteString(fmt.Sprintf("%s filter=lfs diff=lfs merge=lfs -text\n", encodedArg)) if err != nil { Print("Error adding path %s", pattern) continue } Print("Tracking %s", pattern) } }
// updateCommand is used for updating parts of Git LFS that reside under // .git/lfs. func updateCommand(cmd *cobra.Command, args []string) { if err := lfs.InstallHooks(updateForce); err != nil { if lfs.IsInvalidRepoError(err) { Print(err.Error()) os.Exit(128) } else { Error(err.Error()) Print("Run `git lfs update --force` to overwrite this hook.") } } else { Print("Updated pre-push hook.") } lfsAccessRE := regexp.MustCompile(`\Alfs\.(.*)\.access\z`) for key, value := range lfs.Config.AllGitConfig() { matches := lfsAccessRE.FindStringSubmatch(key) if len(matches) < 2 { continue } switch value { case "basic": case "private": git.Config.SetLocal("", key, "basic") Print("Updated %s access from %s to %s.", matches[1], value, "basic") default: git.Config.UnsetLocalKey("", key) Print("Removed invalid %s access of %s.", matches[1], value) } } }
// updatePrePushHook will force an update of the pre-push hook. func updatePrePushHook() { if err := lfs.InstallHooks(updateForce); err != nil { Error(err.Error()) Print("Run `git lfs update --force` to overwrite this hook.") } else { Print("Updated pre-push hook") } }
// pushCommand pushes local objects to a Git LFS server. It takes two // arguments: // // `<remote> <remote ref>` // // Remote must be a remote name, not a URL // // pushCommand calculates the git objects to send by looking comparing the range // of commits between the local and remote git servers. func pushCommand(cmd *cobra.Command, args []string) { if len(args) == 0 { Print("Specify a remote and a remote branch name (`git lfs push origin master`)") os.Exit(1) } requireGitVersion() // Remote is first arg if err := git.ValidateRemote(args[0]); err != nil { Exit("Invalid remote name %q", args[0]) } cfg.CurrentRemote = args[0] ctx := newUploadContext(pushDryRun) if useStdin { requireStdin("Run this command from the Git pre-push hook, or leave the --stdin flag off.") // called from a pre-push hook! Update the existing pre-push hook if it's // one that git-lfs set. lfs.InstallHooks(false) refsData, err := ioutil.ReadAll(os.Stdin) if err != nil { Panic(err, "Error reading refs on stdin") } if len(refsData) == 0 { return } left, right := decodeRefs(string(refsData)) if left == prePushDeleteBranch { return } uploadsBetweenRefs(ctx, left, right) } else if pushObjectIDs { if len(args) < 2 { Print("Usage: git lfs push --object-id <remote> <lfs-object-id> [lfs-object-id] ...") return } uploadsWithObjectIDs(ctx, args[1:]) } else { if len(args) < 1 { Print("Usage: git lfs push --dry-run <remote> [ref]") return } uploadsBetweenRefAndRemote(ctx, args[1:]) } }
// TODO(zeroshirts): 'git fsck' reports status (percentage, current#/total) as // it checks... we should do the same, as we are rehashing potentially gigs and // gigs of content. // // NOTE(zeroshirts): Ideally git would have hooks for fsck such that we could // chain a lfs-fsck, but I don't think it does. func fsckCommand(cmd *cobra.Command, args []string) { lfs.InstallHooks(false) ok, err := doFsck() if err != nil { Panic(err, "Error checking Git LFS files") } if ok { Print("Git LFS fsck OK") } }
func smudgeCommand(cmd *cobra.Command, args []string) { requireStdin("This command should be run by the Git 'smudge' filter") lfs.InstallHooks(false) b := &bytes.Buffer{} r := io.TeeReader(os.Stdin, b) ptr, err := lfs.DecodePointer(r) if err != nil { mr := io.MultiReader(b, os.Stdin) _, err := io.Copy(os.Stdout, mr) if err != nil { Panic(err, "Error writing data to stdout:") } return } if smudgeInfo { localPath, err := lfs.LocalMediaPath(ptr.Oid) if err != nil { Exit(err.Error()) } stat, err := os.Stat(localPath) if err != nil { Print("%d --", ptr.Size) } else { Print("%d %s", stat.Size(), localPath) } return } filename := smudgeFilename(args, err) cb, file, err := lfs.CopyCallbackFile("smudge", filename, 1, 1) if err != nil { Error(err.Error()) } cfg := lfs.Config download := lfs.FilenamePassesIncludeExcludeFilter(filename, cfg.FetchIncludePaths(), cfg.FetchExcludePaths()) err = ptr.Smudge(os.Stdout, filename, download, cb) if file != nil { file.Close() } if err != nil { ptr.Encode(os.Stdout) LoggedError(err, "Error accessing media: %s (%s)", filename, ptr.Oid) } }
// untrackCommand takes a list of paths as an argument, and removes each path from the // default attributes file (.gitattributes), if it exists. func untrackCommand(cmd *cobra.Command, args []string) { if config.LocalGitDir == "" { Print("Not a git repository.") os.Exit(128) } if config.LocalWorkingDir == "" { Print("This operation must be run in a work tree.") os.Exit(128) } lfs.InstallHooks(false) if len(args) < 1 { Print("git lfs untrack <path> [path]*") return } data, err := ioutil.ReadFile(".gitattributes") if err != nil { return } attributes := strings.NewReader(string(data)) attributesFile, err := os.Create(".gitattributes") if err != nil { Print("Error opening .gitattributes for writing") return } defer attributesFile.Close() scanner := bufio.NewScanner(attributes) // Iterate through each line of the attributes file and rewrite it, // if the path was meant to be untracked, omit it, and print a message instead. for scanner.Scan() { line := scanner.Text() if !strings.Contains(line, "filter=lfs") { attributesFile.WriteString(line + "\n") continue } path := strings.Fields(line)[0] if removePath(path, args) { Print("Untracking %s", path) } else { attributesFile.WriteString(line + "\n") } } }
// untrackCommand takes a list of paths as an argument, and removes each path from the // default attribtues file (.gitattributes), if it exists. func untrackCommand(cmd *cobra.Command, args []string) { lfs.InstallHooks(false) if len(args) < 1 { Print("git lfs untrack <path> [path]*") return } data, err := ioutil.ReadFile(".gitattributes") if err != nil { return } attributes := strings.NewReader(string(data)) attributesFile, err := os.Create(".gitattributes") if err != nil { Print("Error opening .gitattributes for writing") return } scanner := bufio.NewScanner(attributes) // Iterate through each line of the attributes file and rewrite it, // if the path was meant to be untracked, omit it, and print a message instead. for scanner.Scan() { line := scanner.Text() if strings.Contains(line, "filter=lfs") { fields := strings.Fields(line) removeThisPath := false for _, t := range args { if t == fields[0] { removeThisPath = true } } if !removeThisPath { attributesFile.WriteString(line + "\n") } else { Print("Untracking %s", fields[0]) } } } attributesFile.Close() }
// updateCommand is used for updating parts of Git LFS that reside under // .git/lfs. func updateCommand(cmd *cobra.Command, args []string) { requireGitVersion() requireInRepo() lfsAccessRE := regexp.MustCompile(`\Alfs\.(.*)\.access\z`) for key, value := range cfg.AllGitConfig() { matches := lfsAccessRE.FindStringSubmatch(key) if len(matches) < 2 { continue } switch value { case "basic": case "private": git.Config.SetLocal("", key, "basic") Print("Updated %s access from %s to %s.", matches[1], value, "basic") default: git.Config.UnsetLocalKey("", key) Print("Removed invalid %s access of %s.", matches[1], value) } } if updateForce && updateManual { Exit("You cannot use --force and --manual options together") } if updateManual { Print(lfs.GetHookInstallSteps()) } else { if err := lfs.InstallHooks(updateForce); err != nil { Error(err.Error()) Exit("To resolve this, either:\n 1: run `git lfs update --manual` for instructions on how to merge hooks.\n 2: run `git lfs update --force` to overwrite your hook.") } else { Print("Updated pre-push hook.") } } }
// pushCommand pushes local objects to a Git LFS server. It takes two // arguments: // // `<remote> <remote ref>` // // Both a remote name ("origin") or a remote URL are accepted. // // pushCommand calculates the git objects to send by looking comparing the range // of commits between the local and remote git servers. func pushCommand(cmd *cobra.Command, args []string) { var uploadQueue *lfs.TransferQueue if len(args) == 0 { Print("Specify a remote and a remote branch name (`git lfs push origin master`)") os.Exit(1) } lfs.Config.CurrentRemote = args[0] if useStdin { requireStdin("Run this command from the Git pre-push hook, or leave the --stdin flag off.") // called from a pre-push hook! Update the existing pre-push hook if it's // one that git-lfs set. lfs.InstallHooks(false) refsData, err := ioutil.ReadAll(os.Stdin) if err != nil { Panic(err, "Error reading refs on stdin") } if len(refsData) == 0 { return } left, right := decodeRefs(string(refsData)) if left == pushDeleteBranch { return } uploadQueue = uploadsBetweenRefs(left, right) } else if pushObjectIDs { if len(args) < 2 { Print("Usage: git lfs push --object-id <remote> <lfs-object-id> [lfs-object-id] ...") return } uploadQueue = uploadsWithObjectIDs(args[1:]) } else { if len(args) < 1 { Print("Usage: git lfs push --dry-run <remote> [ref]") return } uploadQueue = uploadsBetweenRefAndRemote(args[0], args[1:]) } if !pushDryRun { uploadQueue.Wait() for _, err := range uploadQueue.Errors() { if Debugging || lfs.IsFatalError(err) { LoggedError(err, err.Error()) } else { Error(err.Error()) } } if len(uploadQueue.Errors()) > 0 { os.Exit(2) } } }
func trackCommand(cmd *cobra.Command, args []string) { if lfs.LocalGitDir == "" { Print("Not a git repository.") os.Exit(128) } if lfs.LocalWorkingDir == "" { Print("This operation must be run in a work tree.") os.Exit(128) } lfs.InstallHooks(false) knownPaths := findPaths() if len(args) == 0 { Print("Listing tracked paths") for _, t := range knownPaths { Print(" %s (%s)", t.Path, t.Source) } return } addTrailingLinebreak := needsTrailingLinebreak(".gitattributes") attributesFile, err := os.OpenFile(".gitattributes", os.O_RDWR|os.O_APPEND|os.O_CREATE, 0660) if err != nil { Print("Error opening .gitattributes file") return } defer attributesFile.Close() if addTrailingLinebreak { if _, err := attributesFile.WriteString("\n"); err != nil { Print("Error writing to .gitattributes") } } wd, _ := os.Getwd() ArgsLoop: for _, t := range args { absT, relT := absRelPath(t, wd) if !filepath.HasPrefix(absT, lfs.LocalWorkingDir) { Print("%s is outside repository", t) os.Exit(128) } for _, k := range knownPaths { absK, _ := absRelPath(k.Path, filepath.Join(wd, filepath.Dir(k.Source))) if absT == absK { Print("%s already supported", t) continue ArgsLoop } } encodedArg := strings.Replace(relT, " ", "[[:space:]]", -1) _, err := attributesFile.WriteString(fmt.Sprintf("%s filter=lfs diff=lfs merge=lfs -text\n", encodedArg)) if err != nil { Print("Error adding path %s", t) continue } Print("Tracking %s", t) } }
func trackCommand(cmd *cobra.Command, args []string) { requireGitVersion() if config.LocalGitDir == "" { Print("Not a git repository.") os.Exit(128) } if config.LocalWorkingDir == "" { Print("This operation must be run in a work tree.") os.Exit(128) } lfs.InstallHooks(false) knownPaths := findPaths() if len(args) == 0 { Print("Listing tracked paths") for _, t := range knownPaths { Print(" %s (%s)", t.Path, t.Source) } return } addTrailingLinebreak := needsTrailingLinebreak(".gitattributes") attributesFile, err := os.OpenFile(".gitattributes", os.O_RDWR|os.O_APPEND|os.O_CREATE, 0660) if err != nil { Print("Error opening .gitattributes file") return } defer attributesFile.Close() if addTrailingLinebreak { if _, err := attributesFile.WriteString("\n"); err != nil { Print("Error writing to .gitattributes") } } wd, _ := os.Getwd() relpath, err := filepath.Rel(config.LocalWorkingDir, wd) if err != nil { Exit("Current directory %q outside of git working directory %q.", wd, config.LocalWorkingDir) } ArgsLoop: for _, pattern := range args { for _, known := range knownPaths { if known.Path == filepath.Join(relpath, pattern) { Print("%s already supported", pattern) continue ArgsLoop } } // Make sure any existing git tracked files have their timestamp updated // so they will now show as modifed // note this is relative to current dir which is how we write .gitattributes // deliberately not done in parallel as a chan because we'll be marking modified // // NOTE: `git ls-files` does not do well with leading slashes. // Since all `git-lfs track` calls are relative to the root of // the repository, the leading slash is simply removed for its // implicit counterpart. if trackVerboseLoggingFlag { Print("Searching for files matching pattern: %s", pattern) } gittracked, err := git.GetTrackedFiles(pattern) if err != nil { LoggedError(err, "Error getting git tracked files") continue } if trackVerboseLoggingFlag { Print("Found %d files previously added to Git matching pattern: %s", len(gittracked), pattern) } now := time.Now() var matchedBlocklist bool for _, f := range gittracked { if forbidden := blocklistItem(f); forbidden != "" { Print("Pattern %s matches forbidden file %s. If you would like to track %s, modify .gitattributes manually.", pattern, f, f) matchedBlocklist = true } } if matchedBlocklist { continue } if !trackDryRunFlag { encodedArg := strings.Replace(pattern, " ", "[[:space:]]", -1) _, err := attributesFile.WriteString(fmt.Sprintf("%s filter=lfs diff=lfs merge=lfs -text\n", encodedArg)) if err != nil { Print("Error adding path %s", pattern) continue } } Print("Tracking %s", pattern) for _, f := range gittracked { if trackVerboseLoggingFlag || trackDryRunFlag { Print("Git LFS: touching %s", f) } if !trackDryRunFlag { err := os.Chtimes(f, now, now) if err != nil { LoggedError(err, "Error marking %q modified", f) continue } } } } }
func trackCommand(cmd *cobra.Command, args []string) { if lfs.LocalGitDir == "" { Print("Not a git repository.") os.Exit(128) } if lfs.LocalWorkingDir == "" { Print("This operation must be run in a work tree.") os.Exit(128) } lfs.InstallHooks(false) knownPaths := findPaths() if len(args) == 0 { Print("Listing tracked paths") for _, t := range knownPaths { Print(" %s (%s)", t.Path, t.Source) } return } addTrailingLinebreak := needsTrailingLinebreak(".gitattributes") attributesFile, err := os.OpenFile(".gitattributes", os.O_RDWR|os.O_APPEND|os.O_CREATE, 0660) if err != nil { Print("Error opening .gitattributes file") return } defer attributesFile.Close() if addTrailingLinebreak { if _, err := attributesFile.WriteString("\n"); err != nil { Print("Error writing to .gitattributes") } } wd, _ := os.Getwd() relpath, err := filepath.Rel(lfs.LocalWorkingDir, wd) if err != nil { Exit("Current directory %q outside of git working directory %q.", wd, lfs.LocalWorkingDir) } ArgsLoop: for _, pattern := range args { for _, known := range knownPaths { if known.Path == filepath.Join(relpath, pattern) { Print("%s already supported", pattern) continue ArgsLoop } } encodedArg := strings.Replace(pattern, " ", "[[:space:]]", -1) _, err := attributesFile.WriteString(fmt.Sprintf("%s filter=lfs diff=lfs merge=lfs -text\n", encodedArg)) if err != nil { Print("Error adding path %s", pattern) continue } Print("Tracking %s", pattern) // Make sure any existing git tracked files have their timestamp updated // so they will now show as modifed // note this is relative to current dir which is how we write .gitattributes // deliberately not done in parallel as a chan because we'll be marking modified gittracked, err := git.GetTrackedFiles(pattern) if err != nil { LoggedError(err, "Error getting git tracked files") continue } now := time.Now() for _, f := range gittracked { err := os.Chtimes(f, now, now) if err != nil { LoggedError(err, "Error marking %q modified", f) continue } } } }
func initHooksCommand(cmd *cobra.Command, args []string) { if err := lfs.InstallHooks(false); err != nil { Error(err.Error()) } }
func cleanCommand(cmd *cobra.Command, args []string) { requireStdin("This command should be run by the Git 'clean' filter") lfs.InstallHooks(false) var fileName string var cb progress.CopyCallback var file *os.File var fileSize int64 if len(args) > 0 { fileName = args[0] stat, err := os.Stat(fileName) if err == nil && stat != nil { fileSize = stat.Size() localCb, localFile, err := lfs.CopyCallbackFile("clean", fileName, 1, 1) if err != nil { Error(err.Error()) } else { cb = localCb file = localFile } } } cleaned, err := lfs.PointerClean(os.Stdin, fileName, fileSize, cb) if file != nil { file.Close() } if cleaned != nil { defer cleaned.Teardown() } if errors.IsCleanPointerError(err) { os.Stdout.Write(errors.GetContext(err, "bytes").([]byte)) return } if err != nil { Panic(err, "Error cleaning asset.") } tmpfile := cleaned.Filename mediafile, err := lfs.LocalMediaPath(cleaned.Oid) if err != nil { Panic(err, "Unable to get local media path.") } if stat, _ := os.Stat(mediafile); stat != nil { if stat.Size() != cleaned.Size && len(cleaned.Pointer.Extensions) == 0 { Exit("Files don't match:\n%s\n%s", mediafile, tmpfile) } Debug("%s exists", mediafile) } else { if err := os.Rename(tmpfile, mediafile); err != nil { Panic(err, "Unable to move %s to %s\n", tmpfile, mediafile) } Debug("Writing %s", mediafile) } lfs.EncodePointer(os.Stdout, cleaned.Pointer) }
// pushCommand pushes local objects to a Git LFS server. It takes two // arguments: // // `<remote> <remote ref>` // // Both a remote name ("origin") or a remote URL are accepted. // // pushCommand calculates the git objects to send by looking comparing the range // of commits between the local and remote git servers. func pushCommand(cmd *cobra.Command, args []string) { var left, right string if len(args) == 0 { Print("Specify a remote and a remote branch name (`git lfs push origin master`)") os.Exit(1) } lfs.Config.CurrentRemote = args[0] if useStdin { requireStdin("Run this command from the Git pre-push hook, or leave the --stdin flag off.") // called from a pre-push hook! Update the existing pre-push hook if it's // one that git-lfs set. lfs.InstallHooks(false) refsData, err := ioutil.ReadAll(os.Stdin) if err != nil { Panic(err, "Error reading refs on stdin") } if len(refsData) == 0 { return } left, right = decodeRefs(string(refsData)) if left == pushDeleteBranch { return } } else { var remoteArg, refArg string if len(args) < 1 { Print("Usage: git lfs push --dry-run <remote> [ref]") return } remoteArg = args[0] if len(args) == 2 { refArg = args[1] } localRef, err := git.CurrentRef() if err != nil { Panic(err, "Error getting local ref") } left = localRef remoteRef, err := git.LsRemote(remoteArg, refArg) if err != nil { Panic(err, "Error getting remote ref") } if remoteRef != "" { right = "^" + strings.Split(remoteRef, "\t")[0] } } // Just use scanner here pointers, err := lfs.ScanRefs(left, right) if err != nil { Panic(err, "Error scanning for Git LFS files") } uploadQueue := lfs.NewUploadQueue(lfs.Config.ConcurrentTransfers(), len(pointers)) for i, pointer := range pointers { if pushDryRun { Print("push %s", pointer.Name) continue } tracerx.Printf("checking_asset: %s %s %d/%d", pointer.Oid, pointer.Name, i+1, len(pointers)) u, wErr := lfs.NewUploadable(pointer.Oid, pointer.Name, i+1, len(pointers)) if wErr != nil { if Debugging || wErr.Panic { Panic(wErr.Err, wErr.Error()) } else { Exit(wErr.Error()) } } uploadQueue.Add(u) } if !pushDryRun { uploadQueue.Process() for _, err := range uploadQueue.Errors() { if Debugging || err.Panic { LoggedError(err.Err, err.Error()) } else { Error(err.Error()) } } if len(uploadQueue.Errors()) > 0 { os.Exit(2) } } }