git-lfs/commands/command_pre_push.go
rubyist ab5b35a8c0 Progress meter needs to be more dynamic for batches of n
This removes the `pb` based progress bar for the transfer queue and add
a simpler custom bar. pb really wants to know total counts and byte
sizes up front but when doing batches of n we would prefer to not know
that. This is due to the fact that the batcher will block until it is
drained instead of processing all pointers and storing them in memory
while transfer operations run. This also clarifies when files are
skipped because the server already has them. Progress output here
contains the number of finished transfers/bytes, the number of
transfers/bytes currently in the queue, and the number of files skipped,
if any.
2015-07-27 12:43:41 -04:00

140 lines
3.3 KiB
Go

package commands
import (
"fmt"
"io/ioutil"
"os"
"strings"
"github.com/github/git-lfs/lfs"
"github.com/github/git-lfs/vendor/_nuts/github.com/spf13/cobra"
)
var (
prePushCmd = &cobra.Command{
Use: "pre-push",
Short: "Implements the Git pre-push hook",
Run: prePushCommand,
}
prePushDryRun = false
prePushDeleteBranch = "(delete)"
)
// prePushCommand is run through Git's pre-push hook. The pre-push hook passes
// two arguments on the command line:
//
// 1. Name of the remote to which the push is being done
// 2. URL to which the push is being done
//
// The hook receives commit information on stdin in the form:
// <local ref> <local sha1> <remote ref> <remote sha1>
//
// In the typical case, prePushCommand will get a list of git objects being
// pushed by using the following:
//
// git rev-list --objects <local sha1> ^<remote sha1>
//
// If any of those git objects are associated with Git LFS objects, those
// objects will be pushed to the Git LFS API.
//
// In the case of pushing a new branch, the list of git objects will be all of
// the git objects in this branch.
//
// In the case of deleting a branch, no attempts to push Git LFS objects will be
// made.
func prePushCommand(cmd *cobra.Command, args []string) {
var left, right string
if len(args) == 0 {
Print("This should be run through Git's pre-push hook. Run `git lfs update` to install it.")
os.Exit(1)
}
lfs.Config.CurrentRemote = args[0]
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
}
// Just use scanner here
pointers, err := lfs.ScanRefs(left, right, nil)
if err != nil {
Panic(err, "Error scanning for Git LFS files")
}
if len(pointers) > 0 && !prePushDryRun {
fmt.Fprintf(os.Stdout, "Checking %d Git LFS files\n", len(pointers))
}
uploadQueue := lfs.NewUploadQueue(lfs.Config.ConcurrentTransfers(), len(pointers))
for _, pointer := range pointers {
if prePushDryRun {
Print("push %s", pointer.Name)
uploadQueue.SuppressProgress()
continue
}
u, wErr := lfs.NewUploadable(pointer.Oid, pointer.Name)
if wErr != nil {
if cleanPointerErr, ok := wErr.Err.(*lfs.CleanedPointerError); ok {
Exit("%s is an LFS pointer to %s, which does not exist in .git/lfs/objects.\n\nRun 'git lfs fsck' to verify Git LFS objects.",
pointer.Name, cleanPointerErr.Pointer.Oid)
} else if Debugging || wErr.Panic {
Panic(wErr.Err, wErr.Error())
} else {
Exit(wErr.Error())
}
}
uploadQueue.Add(u)
}
if !prePushDryRun {
uploadQueue.Wait()
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)
}
}
}
// decodeRefs pulls the sha1s out of the line read from the pre-push
// hook's stdin.
func decodeRefs(input string) (string, string) {
refs := strings.Split(strings.TrimSpace(input), " ")
var left, right string
if len(refs) > 1 {
left = refs[1]
}
if len(refs) > 3 {
right = "^" + refs[3]
}
return left, right
}
func init() {
prePushCmd.Flags().BoolVarP(&prePushDryRun, "dry-run", "d", false, "Do everything except actually send the updates")
RootCmd.AddCommand(prePushCmd)
}