github.com/2lambda123/git-lfs@v2.5.2+incompatible/commands/command_pre_push.go (about)

     1  package commands
     2  
     3  import (
     4  	"bufio"
     5  	"io"
     6  	"os"
     7  	"strings"
     8  
     9  	"github.com/git-lfs/git-lfs/git"
    10  	"github.com/rubyist/tracerx"
    11  	"github.com/spf13/cobra"
    12  )
    13  
    14  var (
    15  	prePushDryRun       = false
    16  	prePushDeleteBranch = strings.Repeat("0", 40)
    17  )
    18  
    19  // prePushCommand is run through Git's pre-push hook. The pre-push hook passes
    20  // two arguments on the command line:
    21  //
    22  //   1. Name of the remote to which the push is being done
    23  //   2. URL to which the push is being done
    24  //
    25  // The hook receives commit information on stdin in the form:
    26  //   <local ref> <local sha1> <remote ref> <remote sha1>
    27  //
    28  // In the typical case, prePushCommand will get a list of git objects being
    29  // pushed by using the following:
    30  //
    31  //    git rev-list --objects <local sha1> ^<remote sha1>
    32  //
    33  // If any of those git objects are associated with Git LFS objects, those
    34  // objects will be pushed to the Git LFS API.
    35  //
    36  // In the case of pushing a new branch, the list of git objects will be all of
    37  // the git objects in this branch.
    38  //
    39  // In the case of deleting a branch, no attempts to push Git LFS objects will be
    40  // made.
    41  func prePushCommand(cmd *cobra.Command, args []string) {
    42  	if len(args) == 0 {
    43  		Print("This should be run through Git's pre-push hook.  Run `git lfs update` to install it.")
    44  		os.Exit(1)
    45  	}
    46  
    47  	requireGitVersion()
    48  
    49  	// Remote is first arg
    50  	if err := cfg.SetValidRemote(args[0]); err != nil {
    51  		Exit("Invalid remote name %q: %s", args[0], err)
    52  	}
    53  
    54  	ctx := newUploadContext(prePushDryRun)
    55  	updates := prePushRefs(os.Stdin)
    56  	if err := uploadForRefUpdates(ctx, updates, false); err != nil {
    57  		ExitWithError(err)
    58  	}
    59  }
    60  
    61  // prePushRefs parses commit information that the pre-push git hook receives:
    62  //
    63  //   <local ref> <local sha1> <remote ref> <remote sha1>
    64  //
    65  // Each line describes a proposed update of the remote ref at the remote sha to
    66  // the local sha. Multiple updates can be received on multiple lines (such as
    67  // from 'git push --all'). These updates are typically received over STDIN.
    68  func prePushRefs(r io.Reader) []*git.RefUpdate {
    69  	scanner := bufio.NewScanner(r)
    70  	refs := make([]*git.RefUpdate, 0, 1)
    71  
    72  	// We can be passed multiple lines of refs
    73  	for scanner.Scan() {
    74  		line := strings.TrimSpace(scanner.Text())
    75  		if len(line) == 0 {
    76  			continue
    77  		}
    78  
    79  		tracerx.Printf("pre-push: %s", line)
    80  
    81  		left, right := decodeRefs(line)
    82  		if left.Sha == prePushDeleteBranch {
    83  			continue
    84  		}
    85  
    86  		refs = append(refs, git.NewRefUpdate(cfg.Git, cfg.PushRemote(), left, right))
    87  	}
    88  
    89  	return refs
    90  }
    91  
    92  // decodeRefs pulls the sha1s out of the line read from the pre-push
    93  // hook's stdin.
    94  func decodeRefs(input string) (*git.Ref, *git.Ref) {
    95  	refs := strings.Split(strings.TrimSpace(input), " ")
    96  	for len(refs) < 4 {
    97  		refs = append(refs, "")
    98  	}
    99  
   100  	leftRef := git.ParseRef(refs[0], refs[1])
   101  	rightRef := git.ParseRef(refs[2], refs[3])
   102  	return leftRef, rightRef
   103  }
   104  
   105  func init() {
   106  	RegisterCommand("pre-push", prePushCommand, func(cmd *cobra.Command) {
   107  		cmd.Flags().BoolVarP(&prePushDryRun, "dry-run", "d", false, "Do everything except actually send the updates")
   108  	})
   109  }