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 }