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

     1  package git
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"log"
     8  	"os"
     9  	"strconv"
    10  	"strings"
    11  )
    12  
    13  type Refname string
    14  
    15  // returns true if the reference name exists under the client's GitDir.
    16  func (rn Refname) Exists(c *Client) bool {
    17  	return c.GitDir.File(File(rn)).Exists()
    18  }
    19  
    20  func (rn Refname) String() string {
    21  	return string(rn)
    22  }
    23  
    24  // Returns the local part of a refname when given a ref like
    25  // refs/heads/foo:refs/heads/bar for send-pack
    26  func (rn Refname) LocalName() Refname {
    27  	if idx := strings.IndexByte(rn.String(), ':'); idx >= 0 {
    28  		return rn[:idx]
    29  	}
    30  	return rn
    31  }
    32  
    33  func (rn Refname) RemoteName() Refname {
    34  	if idx := strings.IndexByte(rn.String(), ':'); idx >= 0 {
    35  		return rn[idx+1:]
    36  	}
    37  	return rn
    38  }
    39  
    40  func (rn Refname) CommitID(c *Client) (CommitID, error) {
    41  	local := rn.LocalName()
    42  	cmt, err := RevParseCommitish(c, &RevParseOptions{}, local.String())
    43  	if err != nil {
    44  		return CommitID{}, err
    45  	}
    46  	return cmt.CommitID(c)
    47  }
    48  
    49  type FetchPackOptions struct {
    50  	All                            bool
    51  	Stdin                          io.Reader
    52  	StatelessRPC                   bool
    53  	Quiet                          bool
    54  	Keep                           bool
    55  	Thin                           bool
    56  	IncludeTag                     bool
    57  	UploadPack                     string
    58  	Depth                          int32
    59  	DeepenRelative                 bool
    60  	NoProgress                     bool
    61  	CheckSelfContainedAndConnected bool
    62  	Verbose                        bool
    63  }
    64  
    65  // FetchPack fetches a packfile from rmt. It uses wants to retrieve the refnames
    66  // from the remote, and haves to negotiate the missing objects. FetchPack
    67  // always makes a single request and declares "done" at the end.
    68  func FetchPack(c *Client, opts FetchPackOptions, rm Remote, wants []Refname) ([]Ref, error) {
    69  	// We just declare everything we have locally for this remote as a "have"
    70  	// and then declare done, we don't try and be intelligent about what we
    71  	// tell them we have. If we've gotten some objects from another remote,
    72  	// we'll just end up with them duplicated.
    73  	haves, err := rm.GetLocalRefs(c)
    74  	if err != nil {
    75  		return nil, err
    76  	}
    77  	// We put haves into a map to ensure that duplicates are excluded
    78  	havemap := make(map[Sha1]struct{})
    79  	for _, h := range haves {
    80  		havemap[h.Value] = struct{}{}
    81  	}
    82  
    83  	conn, err := NewRemoteConn(c, rm)
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  	if opts.UploadPack == "" {
    88  		opts.UploadPack = "git-upload-pack"
    89  	}
    90  
    91  	if err := conn.SetService(opts.UploadPack); err != nil {
    92  		return nil, err
    93  	}
    94  
    95  	if err := conn.OpenConn(UploadPackService); err != nil {
    96  		return nil, err
    97  	}
    98  	defer conn.Close()
    99  
   100  	return fetchPackDone(c, opts, conn, wants, havemap)
   101  }
   102  
   103  // fetchPackDone makes a single request over conn and declares it done. It returns
   104  // the refs from the connection that were fetched.
   105  func fetchPackDone(c *Client, opts FetchPackOptions, conn RemoteConn, wants []Refname, haves map[Sha1]struct{}) ([]Ref, error) {
   106  	if len(wants) == 0 && !opts.All {
   107  		// There is nothing to fetch, so don't bother doing anything.
   108  		return nil, nil
   109  	}
   110  
   111  	// FIXME: This should be configurable
   112  	conn.SetSideband(os.Stderr)
   113  
   114  	var refs []Ref
   115  
   116  	// Ref patterns as strings for GetRefs
   117  	var rs []string = make([]string, len(wants))
   118  	for i := range wants {
   119  		rs[i] = string(wants[i])
   120  	}
   121  
   122  	conn.SetWriteMode(PktLineMode)
   123  	switch v := conn.ProtocolVersion(); v {
   124  	case 2:
   125  		log.Println("Using protocol version 2 for fetch-pack")
   126  		capabilities := conn.Capabilities()
   127  		// Discard the extra capabilities advertised by the server
   128  		// because we don't support any of them yet.
   129  		_, ok := capabilities["fetch"]
   130  		if !ok {
   131  			return nil, fmt.Errorf("Server did not advertise fetch capability")
   132  		}
   133  		// First we use ls-refs to get a list of references that we
   134  		// want.
   135  		var rs []string = make([]string, len(wants))
   136  		for i := range wants {
   137  			rs[i] = string(wants[i])
   138  		}
   139  		rmtrefs, err := conn.GetRefs(LsRemoteOptions{Heads: true, Tags: opts.IncludeTag, RefsOnly: true}, rs)
   140  		if err != nil {
   141  			return nil, err
   142  		}
   143  
   144  		objects := make(map[Sha1]bool)
   145  		// Sometimes we are given commit ID's directly from the command-line in
   146  		//  which case we fetch them directly.
   147  		for _, refname := range wants {
   148  			if sha, err := Sha1FromString(string(refname)); err == nil {
   149  				objects[sha] = true
   150  			}
   151  		}
   152  		for _, ref := range rmtrefs {
   153  			objects[ref.Value] = true
   154  		}
   155  		log.Printf("Fetching these objects: %+v\n", objects)
   156  		refs = rmtrefs
   157  
   158  		// Now we perform the fetch itself.
   159  		fmt.Fprintf(conn, "command=fetch\n")
   160  		if err := conn.Delim(); err != nil {
   161  			return nil, err
   162  		}
   163  		fmt.Fprintf(conn, "ofs-delta\n")
   164  		if opts.NoProgress {
   165  			fmt.Fprintf(conn, "no-progress\n")
   166  		}
   167  		wanted := false
   168  		for object, _ := range objects {
   169  			have, _, err := c.HaveObject(object)
   170  			if err != nil {
   171  				return nil, err
   172  			}
   173  			if !have {
   174  				fmt.Fprintf(conn, "want %v\n", object)
   175  				wanted = true
   176  			}
   177  		}
   178  		if !wanted {
   179  			return nil, fmt.Errorf("Already up to date.")
   180  		}
   181  		for ref := range haves {
   182  			fmt.Fprintf(conn, "have %v\n", ref)
   183  		}
   184  		fmt.Fprintf(conn, "done\n")
   185  		if err := conn.Flush(); err != nil {
   186  			return nil, err
   187  		}
   188  		buf := make([]byte, 65536)
   189  		n, err := conn.Read(buf)
   190  		if err != nil {
   191  			return nil, err
   192  		}
   193  		if string(buf[:n]) != "packfile\n" {
   194  			// Panic because this is a bug in dgit. There are other
   195  			// valid values that a server can return, but we don't
   196  			// support them, so make sure people know it's a bug.
   197  			panic(fmt.Sprintf("Unexpected line returned: got %s want packfile", buf[:n]))
   198  		}
   199  
   200  		// V2 always uses side-band-64k
   201  		conn.SetReadMode(PktLineSidebandMode)
   202  	default:
   203  		// protocol v1
   204  		log.Printf("Using protocol was %d: using version 1 for fetch-pack\n", v)
   205  		sideband := false
   206  		rmtrefs, err := conn.GetRefs(LsRemoteOptions{Heads: true, Tags: true, RefsOnly: true}, rs)
   207  		if err != nil {
   208  			return nil, err
   209  		}
   210  
   211  		objects := make(map[Sha1]bool)
   212  		// Sometimes we are given commit ID's directly from the command-line in
   213  		//  which case we fetch them directly.
   214  		for _, refname := range wants {
   215  			if sha, err := Sha1FromString(string(refname)); err == nil {
   216  				objects[sha] = true
   217  			}
   218  		}
   219  		for _, ref := range rmtrefs {
   220  			objects[ref.Value] = true
   221  		}
   222  		log.Printf("Fetching these objects: %+v\n", objects)
   223  		refs = rmtrefs
   224  
   225  		if len(objects) == 0 {
   226  			return nil, nil
   227  		}
   228  		wanted := false
   229  		for object, _ := range objects {
   230  			found, _, err := c.HaveObject(object)
   231  			if err != nil {
   232  				return nil, err
   233  			}
   234  			if found {
   235  				haves[object] = struct{}{}
   236  				continue
   237  			}
   238  
   239  			if !wanted {
   240  				capabilities := conn.Capabilities()
   241  				log.Printf("Server Capabilities: %v\n", capabilities)
   242  				var caps string
   243  				// Add protocol capabilities on the first line
   244  				if _, ok := capabilities["ofs-delta"]; ok {
   245  					caps += " ofs-delta"
   246  				}
   247  				if opts.Quiet {
   248  					if _, ok := capabilities["quiet"]; ok {
   249  						caps += " quiet"
   250  					}
   251  				}
   252  				if opts.NoProgress {
   253  					if _, ok := capabilities["no-progress"]; ok {
   254  						caps += " no-progress"
   255  					}
   256  				}
   257  				if _, ok := capabilities["side-band-64k"]; ok {
   258  					caps += " side-band-64k"
   259  					sideband = true
   260  				} else if _, ok := capabilities["side-band"]; ok {
   261  					caps += " side-band"
   262  					sideband = true
   263  				}
   264  				if _, ok := capabilities["agent"]; ok {
   265  					caps += " agent=dgit/0.0.2"
   266  				}
   267  				caps = strings.TrimSpace(caps)
   268  				log.Printf("Sending capabilities: %v", caps)
   269  				log.Printf("want %v\n", object)
   270  				fmt.Fprintf(conn, "want %v %v\n", object, caps)
   271  				wanted = true
   272  			} else {
   273  				log.Printf("want %v\n", object)
   274  				fmt.Fprintf(conn, "want %v\n", object)
   275  			}
   276  		}
   277  		if !wanted {
   278  			// Nothing wanted, already up to date.
   279  			return refs, nil
   280  		}
   281  		if h, ok := conn.(*smartHTTPConn); ok {
   282  			// Hack so that the flush doesn't send a request.
   283  			h.almostdone = true
   284  		}
   285  		if err := conn.Flush(); err != nil {
   286  			return nil, err
   287  		}
   288  		for ref := range haves {
   289  			log.Printf("have %v\n", ref)
   290  			fmt.Fprintf(conn, "have %v\n", ref)
   291  		}
   292  
   293  		if _, err := fmt.Fprintf(conn, "done\n"); err != nil {
   294  			return nil, err
   295  		}
   296  
   297  		// Read the last ack/nack and discard it before
   298  		// reading the pack file.
   299  		buf := make([]byte, 65536)
   300  		if _, err := conn.Read(buf); err != nil {
   301  			return nil, err
   302  		}
   303  		if len(haves) > 1 {
   304  			// If there were have lines, read the extras to ensure
   305  			// they're all read before trying to read the packfile.
   306  			for i := 0; i < len(haves); i++ {
   307  				if _, err := conn.Read(buf); err != nil {
   308  					return nil, err
   309  				}
   310  			}
   311  		}
   312  		if sideband {
   313  			conn.SetReadMode(PktLineSidebandMode)
   314  		} else {
   315  			conn.SetReadMode(DirectMode)
   316  		}
   317  	}
   318  
   319  	// Whether we've used V1 or V2, the connection is now returning the
   320  	// packfile upon read, so we want to index it and copy it into the
   321  	// .git directory.
   322  	_, err := IndexAndCopyPack(
   323  		c,
   324  		IndexPackOptions{
   325  			Verbose: opts.Verbose,
   326  			FixThin: opts.Thin,
   327  		},
   328  		conn,
   329  	)
   330  	return refs, err
   331  }
   332  
   333  var flushPkt = errors.New("Git protocol flush packet")
   334  var delimPkt = errors.New("Git protocol delimiter packet")
   335  
   336  // A PackProtocolMode determines how calling Read on a RemoteConn
   337  // returns data to the caller.
   338  type PackProtocolMode uint8
   339  
   340  // Valid PackProtocolModes
   341  const (
   342  	// Directly pass through read/write requests (used when sending a
   343  	// packfile)
   344  	DirectMode = PackProtocolMode(iota)
   345  
   346  	// Decode PktLine format and send the decoded data to the caller.
   347  	// (used when negotiating pack files)
   348  	PktLineMode
   349  
   350  	// Like PktLineMode, but also read 1 extra byte for determining which
   351  	// sideband channel the data is on. Keeps reading from the connection
   352  	// until something comes in on the main channel, printing any sideband
   353  	// data to sideband.
   354  	PktLineSidebandMode
   355  )
   356  
   357  // A packProtocolReader reads from a connection using the git pack
   358  // protocol, decoding lines read from the connection as necessary.
   359  type packProtocolReader struct {
   360  	conn     io.Reader
   361  	state    PackProtocolMode
   362  	sideband io.Writer
   363  
   364  	// a buffer to hold the extra data from the connection when the buf
   365  	// passed to read isn't big enough to hold it.
   366  	underreadBuf []byte
   367  }
   368  
   369  const (
   370  	sidebandDataChannel = 1
   371  	sidebandChannel     = 2
   372  	sidebandErrChannel  = 3
   373  )
   374  
   375  // Reads a line from the underlying connection into buf in a decoded
   376  // format.
   377  func (p *packProtocolReader) Read(buf []byte) (int, error) {
   378  	// First check if there's still data left from the last read.
   379  	if len(p.underreadBuf) > 0 {
   380  		if len(buf) > len(p.underreadBuf) {
   381  			for i, b := range p.underreadBuf {
   382  				buf[i] = b
   383  			}
   384  			n := len(p.underreadBuf)
   385  			p.underreadBuf = nil
   386  			return n, nil
   387  		} else {
   388  			for i := range buf {
   389  				buf[i] = p.underreadBuf[i]
   390  			}
   391  			p.underreadBuf = p.underreadBuf[len(buf):]
   392  			return len(buf), nil
   393  		}
   394  	}
   395  	switch p.state {
   396  	case DirectMode:
   397  		return p.conn.Read(buf)
   398  	case PktLineMode:
   399  		n, err := io.ReadFull(p.conn, buf[0:4])
   400  		if err != nil {
   401  			return 0, err
   402  		}
   403  		if n != 4 {
   404  			return 0, fmt.Errorf("Bad read for git protocol")
   405  		}
   406  		switch string(buf[0:4]) {
   407  		case "0000":
   408  			// Denotes a boundary between client/server
   409  			// communication
   410  			return 0, flushPkt
   411  		case "0001":
   412  			// Delimits a command in protocol v2
   413  			return 0, delimPkt
   414  		default:
   415  			size, err := strconv.ParseUint(string(buf[0:4]), 16, 0)
   416  			if err != nil {
   417  				return 0, err
   418  			}
   419  			return io.ReadFull(p.conn, buf[:size-4])
   420  		}
   421  	case PktLineSidebandMode:
   422  	sidebandRead:
   423  		var sizebuf []byte
   424  		if len(buf) < 4 {
   425  			sizebuf = make([]byte, 4)
   426  		} else {
   427  			sizebuf = buf
   428  		}
   429  		n, err := io.ReadFull(p.conn, sizebuf[0:4])
   430  		if err != nil {
   431  			return 0, err
   432  		}
   433  
   434  		// Allow either flush packets or data with a sideband channel
   435  		if n != 4 {
   436  			return 0, fmt.Errorf("Bad read for git protocol: read %v (%s)", n, buf[:n])
   437  		}
   438  		switch string(sizebuf[0:4]) {
   439  		case "0000":
   440  			// Denotes a boundary between client/server
   441  			// communication
   442  			return 0, flushPkt
   443  		case "0001":
   444  			// Delimits a command in protocol v2
   445  			return 0, delimPkt
   446  		default:
   447  			size, err := strconv.ParseUint(string(sizebuf[0:4]), 16, 0)
   448  			if err != nil {
   449  				return 0, err
   450  			}
   451  			_, err = p.conn.Read(buf[0:1])
   452  			if err != nil {
   453  				return 0, err
   454  			}
   455  			switch buf[0] {
   456  			case sidebandDataChannel:
   457  				if len(buf) < int(size-5) {
   458  					tmp := make([]byte, size-5)
   459  					_, err := io.ReadFull(p.conn, tmp)
   460  					if err != nil {
   461  						return 0, err
   462  					}
   463  					p.underreadBuf = tmp[len(buf):]
   464  					for i := range buf {
   465  						buf[i] = tmp[i]
   466  					}
   467  					return len(buf), nil
   468  				}
   469  				return io.ReadFull(p.conn, buf[:size-5])
   470  			case sidebandChannel:
   471  				msgbuf := make([]byte, size-5)
   472  				n, err := io.ReadFull(p.conn, msgbuf)
   473  				if err != nil {
   474  					return n, err
   475  				}
   476  				if p.sideband != nil {
   477  					fmt.Fprintf(p.sideband, "remote: %s", msgbuf[:n])
   478  				}
   479  				goto sidebandRead
   480  			case sidebandErrChannel:
   481  				msgbuf := make([]byte, size-5)
   482  				n, err := io.ReadFull(p.conn, msgbuf)
   483  				if err != nil {
   484  					return 0, err
   485  				}
   486  				log.Printf("Remote error: %s\n", msgbuf[:n])
   487  				return 0, fmt.Errorf("remote err: %s", msgbuf[:n])
   488  			default:
   489  				return 0, fmt.Errorf("Invalid sideband channel: %d", buf[0])
   490  			}
   491  		}
   492  
   493  	default:
   494  		return 0, fmt.Errorf("Invalid read mode for pack protocol")
   495  	}
   496  
   497  }
   498  
   499  func (p *packProtocolReader) SetSideband(w io.Writer) {
   500  	p.sideband = w
   501  }
   502  
   503  func (p *packProtocolReader) SetReadMode(mode PackProtocolMode) {
   504  	p.state = mode
   505  }