github.com/driusan/dgit@v0.0.0-20221118233547-f39f0c15edbb/git/sendpack.go (about)

     1  package git
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"log"
     7  	"os"
     8  	"strings"
     9  )
    10  
    11  type SendPackOptions struct {
    12  	All    bool
    13  	DryRun bool
    14  	Force  bool
    15  
    16  	Verbose bool
    17  	Thin    bool
    18  	Atomic  bool
    19  
    20  	Signed      bool
    21  	ReceivePack string
    22  }
    23  
    24  func SendPack(c *Client, opts SendPackOptions, r Remote, refs []Refname) error {
    25  	remoteConn, err := NewRemoteConn(c, r)
    26  	if err != nil {
    27  		return err
    28  	}
    29  	if opts.ReceivePack == "" {
    30  		opts.ReceivePack = "git-receive-pack"
    31  	}
    32  	remoteConn.SetService(opts.ReceivePack)
    33  	if err := remoteConn.OpenConn(ReceivePackService); err != nil {
    34  		return err
    35  	}
    36  	defer remoteConn.Close()
    37  
    38  	log.Printf("Send Pack Protocol Version %d Capabilities: %v", remoteConn.ProtocolVersion(), remoteConn.Capabilities())
    39  	remoterefs := make(map[Refname]Sha1)
    40  	localrefs := make(map[Refname]Sha1)
    41  	var refpatterns []string = make([]string, 0, len(refs))
    42  	for _, ref := range refs {
    43  		refpatterns = append(refpatterns, ref.RemoteName().String())
    44  
    45  		local := ref.LocalName()
    46  		if local == "" {
    47  			continue
    48  		}
    49  		localsha, err := ref.CommitID(c)
    50  		if err != nil {
    51  			return err
    52  		}
    53  		localrefs[local] = Sha1(localsha)
    54  	}
    55  
    56  	remotes, err := remoteConn.GetRefs(LsRemoteOptions{Heads: true, Tags: true, RefsOnly: true}, refpatterns)
    57  	if err != nil {
    58  		return err
    59  	}
    60  	for _, r := range remotes {
    61  		remoterefs[Refname(r.Name)] = r.Value
    62  	}
    63  
    64  	for _, ref := range refs {
    65  		log.Printf("Validating if %v is a fast-forward\n", ref)
    66  		remotesha := remoterefs[ref.RemoteName()]
    67  		local := ref.LocalName()
    68  		if local == "" {
    69  			log.Printf("%v is a delete, not a fast-forward\n", ref)
    70  			continue
    71  		}
    72  		if remotesha == (Sha1{}) {
    73  			log.Printf("%v is a new branch\n", ref)
    74  			continue
    75  		}
    76  		localsha := localrefs[ref.LocalName()]
    77  		if localsha == remotesha {
    78  			// Unmodified
    79  			continue
    80  		}
    81  
    82  		isancestor := CommitID(remotesha).IsAncestor(c, CommitID(localsha))
    83  		if !isancestor && !opts.Force {
    84  			return fmt.Errorf("Remote %v is not a fast-forward of %v", remotesha, localsha)
    85  		}
    86  		// FIXME: Check if any failed and honour opts.Atomic instead of bothering
    87  		// the server
    88  	}
    89  
    90  	if opts.DryRun {
    91  		return nil
    92  	}
    93  
    94  	remoteConn.SetWriteMode(PktLineMode)
    95  
    96  	// w := os.Stderr
    97  	w := remoteConn
    98  	// Send update lines
    99  	for i, ref := range refs {
   100  		remoteref := remoterefs[ref.RemoteName()]
   101  		localref := localrefs[ref.LocalName()]
   102  		// This handles all of update, delete, and create since the localrefs
   103  		// and remoterefs map will return Sha1{} if the key isn't set
   104  		if i == 0 {
   105  			var caps []string = []string{"ofs-delta", "report-status", "agent=dgit/0.0.2"}
   106  			if opts.Atomic {
   107  				caps = append(caps, "atomic")
   108  			}
   109  
   110  			fmt.Fprintf(w, "%v %v %v\000 %v\n", remoteref, localref, ref.RemoteName(), strings.Join(caps, " "))
   111  		} else {
   112  			fmt.Fprintf(w, "%v %v %v\n", remoteref, localref, ref.RemoteName())
   113  		}
   114  	}
   115  
   116  	// Figure out what should go in the pack
   117  	var revlistincludes []Commitish = make([]Commitish, 0, len(localrefs))
   118  	for _, sha := range localrefs {
   119  		revlistincludes = append(revlistincludes, CommitID(sha))
   120  	}
   121  	var revlistexcludes []Commitish = make([]Commitish, 0, len(remoterefs))
   122  	for _, sha := range remoterefs {
   123  		if have, _, err := c.HaveObject(sha); have && err == nil {
   124  			revlistexcludes = append(revlistexcludes, CommitID(sha))
   125  		}
   126  	}
   127  
   128  	objects, err := RevList(c, RevListOptions{Quiet: true, Objects: true}, nil, revlistincludes, revlistexcludes)
   129  	if err != nil {
   130  		return err
   131  	}
   132  
   133  	// Send the pack itself
   134  	remoteConn.SetWriteMode(DirectMode)
   135  	fmt.Fprintf(w, "0000")
   136  	rcaps := remoteConn.Capabilities()
   137  
   138  	// Our deltas aren't robust enough to reliably send in the wild yet,
   139  	// so for now don't use them in send-pack. (Our implementation is
   140  	// also slow enough that packing often takes longer than writing the
   141  	// raw data)
   142  	popts := PackObjectsOptions{Window: 0}
   143  	if _, ok := rcaps["ofs-delta"]; ok {
   144  		popts.DeltaBaseOffset = true
   145  	}
   146  
   147  	if _, err := PackObjects(c, popts, w, objects); err != nil {
   148  		return err
   149  	}
   150  
   151  	if !opts.DryRun {
   152  		// This causes the HTTP request to send when the transport mechanism
   153  		// is the Smart HTTP connection
   154  		if err := remoteConn.Flush(); err != nil {
   155  			return err
   156  		}
   157  		remoteConn.SetReadMode(PktLineMode)
   158  		io.Copy(os.Stderr, remoteConn)
   159  
   160  	}
   161  	return nil
   162  }