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 }