Monorepo for Tangled
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

wip: atprotate pulls

Signed-off-by: oppiliappan <me@oppi.li>

+1396 -499
+276 -128
api/tangled/cbor_gen.go
··· 7983 7983 fieldCount-- 7984 7984 } 7985 7985 7986 - if t.Mentions == nil { 7986 + if t.DependentOn == nil { 7987 7987 fieldCount-- 7988 7988 } 7989 7989 7990 - if t.Patch == nil { 7990 + if t.Mentions == nil { 7991 7991 fieldCount-- 7992 7992 } 7993 7993 7994 7994 if t.References == nil { 7995 + fieldCount-- 7996 + } 7997 + 7998 + if t.Rounds == nil { 7995 7999 fieldCount-- 7996 8000 } 7997 8001 ··· 8054 8058 return err 8055 8059 } 8056 8060 8057 - // t.Patch (string) (string) 8058 - if t.Patch != nil { 8059 - 8060 - if len("patch") > 1000000 { 8061 - return xerrors.Errorf("Value in field \"patch\" was too long") 8062 - } 8063 - 8064 - if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("patch"))); err != nil { 8065 - return err 8066 - } 8067 - if _, err := cw.WriteString(string("patch")); err != nil { 8068 - return err 8069 - } 8070 - 8071 - if t.Patch == nil { 8072 - if _, err := cw.Write(cbg.CborNull); err != nil { 8073 - return err 8074 - } 8075 - } else { 8076 - if len(*t.Patch) > 1000000 { 8077 - return xerrors.Errorf("Value in field t.Patch was too long") 8078 - } 8079 - 8080 - if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*t.Patch))); err != nil { 8081 - return err 8082 - } 8083 - if _, err := cw.WriteString(string(*t.Patch)); err != nil { 8084 - return err 8085 - } 8086 - } 8087 - } 8088 - 8089 8061 // t.Title (string) (string) 8090 8062 if len("title") > 1000000 { 8091 8063 return xerrors.Errorf("Value in field \"title\" was too long") ··· 8109 8081 return err 8110 8082 } 8111 8083 8084 + // t.Rounds ([]*tangled.RepoPull_Round) (slice) 8085 + if t.Rounds != nil { 8086 + 8087 + if len("rounds") > 1000000 { 8088 + return xerrors.Errorf("Value in field \"rounds\" was too long") 8089 + } 8090 + 8091 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("rounds"))); err != nil { 8092 + return err 8093 + } 8094 + if _, err := cw.WriteString(string("rounds")); err != nil { 8095 + return err 8096 + } 8097 + 8098 + if len(t.Rounds) > 8192 { 8099 + return xerrors.Errorf("Slice value in field t.Rounds was too long") 8100 + } 8101 + 8102 + if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.Rounds))); err != nil { 8103 + return err 8104 + } 8105 + for _, v := range t.Rounds { 8106 + if err := v.MarshalCBOR(cw); err != nil { 8107 + return err 8108 + } 8109 + 8110 + } 8111 + } 8112 + 8112 8113 // t.Source (tangled.RepoPull_Source) (struct) 8113 8114 if t.Source != nil { 8114 8115 ··· 8203 8204 return err 8204 8205 } 8205 8206 8206 - // t.PatchBlob (util.LexBlob) (struct) 8207 - if len("patchBlob") > 1000000 { 8208 - return xerrors.Errorf("Value in field \"patchBlob\" was too long") 8209 - } 8210 - 8211 - if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("patchBlob"))); err != nil { 8212 - return err 8213 - } 8214 - if _, err := cw.WriteString(string("patchBlob")); err != nil { 8215 - return err 8216 - } 8217 - 8218 - if err := t.PatchBlob.MarshalCBOR(cw); err != nil { 8219 - return err 8220 - } 8221 - 8222 8207 // t.References ([]string) (slice) 8223 8208 if t.References != nil { 8224 8209 ··· 8254 8239 8255 8240 } 8256 8241 } 8242 + 8243 + // t.DependentOn (string) (string) 8244 + if t.DependentOn != nil { 8245 + 8246 + if len("dependentOn") > 1000000 { 8247 + return xerrors.Errorf("Value in field \"dependentOn\" was too long") 8248 + } 8249 + 8250 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("dependentOn"))); err != nil { 8251 + return err 8252 + } 8253 + if _, err := cw.WriteString(string("dependentOn")); err != nil { 8254 + return err 8255 + } 8256 + 8257 + if t.DependentOn == nil { 8258 + if _, err := cw.Write(cbg.CborNull); err != nil { 8259 + return err 8260 + } 8261 + } else { 8262 + if len(*t.DependentOn) > 1000000 { 8263 + return xerrors.Errorf("Value in field t.DependentOn was too long") 8264 + } 8265 + 8266 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*t.DependentOn))); err != nil { 8267 + return err 8268 + } 8269 + if _, err := cw.WriteString(string(*t.DependentOn)); err != nil { 8270 + return err 8271 + } 8272 + } 8273 + } 8257 8274 return nil 8258 8275 } 8259 8276 ··· 8282 8299 8283 8300 n := extra 8284 8301 8285 - nameBuf := make([]byte, 10) 8302 + nameBuf := make([]byte, 11) 8286 8303 for i := uint64(0); i < n; i++ { 8287 8304 nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000) 8288 8305 if err != nil { ··· 8330 8347 8331 8348 t.LexiconTypeID = string(sval) 8332 8349 } 8333 - // t.Patch (string) (string) 8334 - case "patch": 8350 + // t.Title (string) (string) 8351 + case "title": 8335 8352 8336 8353 { 8337 - b, err := cr.ReadByte() 8354 + sval, err := cbg.ReadStringWithMax(cr, 1000000) 8338 8355 if err != nil { 8339 8356 return err 8340 8357 } 8341 - if b != cbg.CborNull[0] { 8342 - if err := cr.UnreadByte(); err != nil { 8343 - return err 8344 - } 8358 + 8359 + t.Title = string(sval) 8360 + } 8361 + // t.Rounds ([]*tangled.RepoPull_Round) (slice) 8362 + case "rounds": 8363 + 8364 + maj, extra, err = cr.ReadHeader() 8365 + if err != nil { 8366 + return err 8367 + } 8368 + 8369 + if extra > 8192 { 8370 + return fmt.Errorf("t.Rounds: array too large (%d)", extra) 8371 + } 8345 8372 8346 - sval, err := cbg.ReadStringWithMax(cr, 1000000) 8347 - if err != nil { 8348 - return err 8349 - } 8373 + if maj != cbg.MajArray { 8374 + return fmt.Errorf("expected cbor array") 8375 + } 8350 8376 8351 - t.Patch = (*string)(&sval) 8352 - } 8377 + if extra > 0 { 8378 + t.Rounds = make([]*RepoPull_Round, extra) 8353 8379 } 8354 - // t.Title (string) (string) 8355 - case "title": 8380 + 8381 + for i := 0; i < int(extra); i++ { 8382 + { 8383 + var maj byte 8384 + var extra uint64 8385 + var err error 8386 + _ = maj 8387 + _ = extra 8388 + _ = err 8389 + 8390 + { 8391 + 8392 + b, err := cr.ReadByte() 8393 + if err != nil { 8394 + return err 8395 + } 8396 + if b != cbg.CborNull[0] { 8397 + if err := cr.UnreadByte(); err != nil { 8398 + return err 8399 + } 8400 + t.Rounds[i] = new(RepoPull_Round) 8401 + if err := t.Rounds[i].UnmarshalCBOR(cr); err != nil { 8402 + return xerrors.Errorf("unmarshaling t.Rounds[i] pointer: %w", err) 8403 + } 8404 + } 8405 + 8406 + } 8356 8407 8357 - { 8358 - sval, err := cbg.ReadStringWithMax(cr, 1000000) 8359 - if err != nil { 8360 - return err 8361 8408 } 8362 - 8363 - t.Title = string(sval) 8364 8409 } 8365 8410 // t.Source (tangled.RepoPull_Source) (struct) 8366 8411 case "source": ··· 8453 8498 8454 8499 t.CreatedAt = string(sval) 8455 8500 } 8456 - // t.PatchBlob (util.LexBlob) (struct) 8457 - case "patchBlob": 8458 - 8459 - { 8460 - 8461 - b, err := cr.ReadByte() 8462 - if err != nil { 8463 - return err 8464 - } 8465 - if b != cbg.CborNull[0] { 8466 - if err := cr.UnreadByte(); err != nil { 8467 - return err 8468 - } 8469 - t.PatchBlob = new(util.LexBlob) 8470 - if err := t.PatchBlob.UnmarshalCBOR(cr); err != nil { 8471 - return xerrors.Errorf("unmarshaling t.PatchBlob pointer: %w", err) 8472 - } 8473 - } 8474 - 8475 - } 8476 8501 // t.References ([]string) (slice) 8477 8502 case "references": 8478 8503 ··· 8511 8536 t.References[i] = string(sval) 8512 8537 } 8513 8538 8539 + } 8540 + } 8541 + // t.DependentOn (string) (string) 8542 + case "dependentOn": 8543 + 8544 + { 8545 + b, err := cr.ReadByte() 8546 + if err != nil { 8547 + return err 8548 + } 8549 + if b != cbg.CborNull[0] { 8550 + if err := cr.UnreadByte(); err != nil { 8551 + return err 8552 + } 8553 + 8554 + sval, err := cbg.ReadStringWithMax(cr, 1000000) 8555 + if err != nil { 8556 + return err 8557 + } 8558 + 8559 + t.DependentOn = (*string)(&sval) 8514 8560 } 8515 8561 } 8516 8562 ··· 8890 8936 } 8891 8937 8892 8938 cw := cbg.NewCborWriter(w) 8893 - fieldCount := 3 8939 + fieldCount := 2 8894 8940 8895 8941 if t.Repo == nil { 8896 8942 fieldCount-- 8897 8943 } 8898 8944 8899 8945 if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil { 8900 - return err 8901 - } 8902 - 8903 - // t.Sha (string) (string) 8904 - if len("sha") > 1000000 { 8905 - return xerrors.Errorf("Value in field \"sha\" was too long") 8906 - } 8907 - 8908 - if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("sha"))); err != nil { 8909 - return err 8910 - } 8911 - if _, err := cw.WriteString(string("sha")); err != nil { 8912 - return err 8913 - } 8914 - 8915 - if len(t.Sha) > 1000000 { 8916 - return xerrors.Errorf("Value in field t.Sha was too long") 8917 - } 8918 - 8919 - if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Sha))); err != nil { 8920 - return err 8921 - } 8922 - if _, err := cw.WriteString(string(t.Sha)); err != nil { 8923 8946 return err 8924 8947 } 8925 8948 ··· 9021 9044 } 9022 9045 9023 9046 switch string(nameBuf[:nameLen]) { 9024 - // t.Sha (string) (string) 9025 - case "sha": 9026 - 9027 - { 9028 - sval, err := cbg.ReadStringWithMax(cr, 1000000) 9029 - if err != nil { 9030 - return err 9031 - } 9032 - 9033 - t.Sha = string(sval) 9034 - } 9035 - // t.Repo (string) (string) 9047 + // t.Repo (string) (string) 9036 9048 case "repo": 9037 9049 9038 9050 { ··· 9063 9075 } 9064 9076 9065 9077 t.Branch = string(sval) 9078 + } 9079 + 9080 + default: 9081 + // Field doesn't exist on this type, so ignore it 9082 + if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil { 9083 + return err 9084 + } 9085 + } 9086 + } 9087 + 9088 + return nil 9089 + } 9090 + func (t *RepoPull_Round) MarshalCBOR(w io.Writer) error { 9091 + if t == nil { 9092 + _, err := w.Write(cbg.CborNull) 9093 + return err 9094 + } 9095 + 9096 + cw := cbg.NewCborWriter(w) 9097 + 9098 + if _, err := cw.Write([]byte{162}); err != nil { 9099 + return err 9100 + } 9101 + 9102 + // t.CreatedAt (string) (string) 9103 + if len("createdAt") > 1000000 { 9104 + return xerrors.Errorf("Value in field \"createdAt\" was too long") 9105 + } 9106 + 9107 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("createdAt"))); err != nil { 9108 + return err 9109 + } 9110 + if _, err := cw.WriteString(string("createdAt")); err != nil { 9111 + return err 9112 + } 9113 + 9114 + if len(t.CreatedAt) > 1000000 { 9115 + return xerrors.Errorf("Value in field t.CreatedAt was too long") 9116 + } 9117 + 9118 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.CreatedAt))); err != nil { 9119 + return err 9120 + } 9121 + if _, err := cw.WriteString(string(t.CreatedAt)); err != nil { 9122 + return err 9123 + } 9124 + 9125 + // t.PatchBlob (util.LexBlob) (struct) 9126 + if len("patchBlob") > 1000000 { 9127 + return xerrors.Errorf("Value in field \"patchBlob\" was too long") 9128 + } 9129 + 9130 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("patchBlob"))); err != nil { 9131 + return err 9132 + } 9133 + if _, err := cw.WriteString(string("patchBlob")); err != nil { 9134 + return err 9135 + } 9136 + 9137 + if err := t.PatchBlob.MarshalCBOR(cw); err != nil { 9138 + return err 9139 + } 9140 + return nil 9141 + } 9142 + 9143 + func (t *RepoPull_Round) UnmarshalCBOR(r io.Reader) (err error) { 9144 + *t = RepoPull_Round{} 9145 + 9146 + cr := cbg.NewCborReader(r) 9147 + 9148 + maj, extra, err := cr.ReadHeader() 9149 + if err != nil { 9150 + return err 9151 + } 9152 + defer func() { 9153 + if err == io.EOF { 9154 + err = io.ErrUnexpectedEOF 9155 + } 9156 + }() 9157 + 9158 + if maj != cbg.MajMap { 9159 + return fmt.Errorf("cbor input should be of type map") 9160 + } 9161 + 9162 + if extra > cbg.MaxLength { 9163 + return fmt.Errorf("RepoPull_Round: map struct too large (%d)", extra) 9164 + } 9165 + 9166 + n := extra 9167 + 9168 + nameBuf := make([]byte, 9) 9169 + for i := uint64(0); i < n; i++ { 9170 + nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000) 9171 + if err != nil { 9172 + return err 9173 + } 9174 + 9175 + if !ok { 9176 + // Field doesn't exist on this type, so ignore it 9177 + if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil { 9178 + return err 9179 + } 9180 + continue 9181 + } 9182 + 9183 + switch string(nameBuf[:nameLen]) { 9184 + // t.CreatedAt (string) (string) 9185 + case "createdAt": 9186 + 9187 + { 9188 + sval, err := cbg.ReadStringWithMax(cr, 1000000) 9189 + if err != nil { 9190 + return err 9191 + } 9192 + 9193 + t.CreatedAt = string(sval) 9194 + } 9195 + // t.PatchBlob (util.LexBlob) (struct) 9196 + case "patchBlob": 9197 + 9198 + { 9199 + 9200 + b, err := cr.ReadByte() 9201 + if err != nil { 9202 + return err 9203 + } 9204 + if b != cbg.CborNull[0] { 9205 + if err := cr.UnreadByte(); err != nil { 9206 + return err 9207 + } 9208 + t.PatchBlob = new(util.LexBlob) 9209 + if err := t.PatchBlob.UnmarshalCBOR(cr); err != nil { 9210 + return xerrors.Errorf("unmarshaling t.PatchBlob pointer: %w", err) 9211 + } 9212 + } 9213 + 9066 9214 } 9067 9215 9068 9216 default:
+18 -13
api/tangled/repopull.go
··· 17 17 } // 18 18 // RECORDTYPE: RepoPull 19 19 type RepoPull struct { 20 - LexiconTypeID string `json:"$type,const=sh.tangled.repo.pull" cborgen:"$type,const=sh.tangled.repo.pull"` 21 - Body *string `json:"body,omitempty" cborgen:"body,omitempty"` 22 - CreatedAt string `json:"createdAt" cborgen:"createdAt"` 23 - Mentions []string `json:"mentions,omitempty" cborgen:"mentions,omitempty"` 24 - // patch: (deprecated) use patchBlob instead 25 - Patch *string `json:"patch,omitempty" cborgen:"patch,omitempty"` 26 - // patchBlob: patch content 27 - PatchBlob *util.LexBlob `json:"patchBlob" cborgen:"patchBlob"` 28 - References []string `json:"references,omitempty" cborgen:"references,omitempty"` 29 - Source *RepoPull_Source `json:"source,omitempty" cborgen:"source,omitempty"` 30 - Target *RepoPull_Target `json:"target" cborgen:"target"` 31 - Title string `json:"title" cborgen:"title"` 20 + LexiconTypeID string `json:"$type,const=sh.tangled.repo.pull" cborgen:"$type,const=sh.tangled.repo.pull"` 21 + Body *string `json:"body,omitempty" cborgen:"body,omitempty"` 22 + CreatedAt string `json:"createdAt" cborgen:"createdAt"` 23 + DependentOn *string `json:"dependentOn,omitempty" cborgen:"dependentOn,omitempty"` 24 + Mentions []string `json:"mentions,omitempty" cborgen:"mentions,omitempty"` 25 + References []string `json:"references,omitempty" cborgen:"references,omitempty"` 26 + Rounds []*RepoPull_Round `json:"rounds,omitempty" cborgen:"rounds,omitempty"` 27 + Source *RepoPull_Source `json:"source,omitempty" cborgen:"source,omitempty"` 28 + Target *RepoPull_Target `json:"target" cborgen:"target"` 29 + Title string `json:"title" cborgen:"title"` 30 + } 31 + 32 + // RepoPull_Round is a "round" in the sh.tangled.repo.pull schema. 33 + // 34 + // revisions of this pull request, newer rounds are appended to this array. appviews may reject records do not treat this field as append-only. the blob format is gzipped text-based git-format-patches. 35 + type RepoPull_Round struct { 36 + CreatedAt string `json:"createdAt" cborgen:"createdAt"` 37 + PatchBlob *util.LexBlob `json:"patchBlob" cborgen:"patchBlob"` 32 38 } 33 39 34 40 // RepoPull_Source is a "source" in the sh.tangled.repo.pull schema. 35 41 type RepoPull_Source struct { 36 42 Branch string `json:"branch" cborgen:"branch"` 37 43 Repo *string `json:"repo,omitempty" cborgen:"repo,omitempty"` 38 - Sha string `json:"sha" cborgen:"sha"` 39 44 } 40 45 41 46 // RepoPull_Target is a "target" in the sh.tangled.repo.pull schema.
+47
appview/db/db.go
··· 1235 1235 return err 1236 1236 }) 1237 1237 1238 + orm.RunMigration(conn, logger, "add-blob-data-to-pull-submissions", func(tx *sql.Tx) error { 1239 + _, err := tx.Exec(` 1240 + alter table pull_submissions add column patch_blob_ref text; 1241 + alter table pull_submissions add column patch_blob_mime text; 1242 + alter table pull_submissions add column patch_blob_size integer; 1243 + `) 1244 + return err 1245 + }) 1246 + 1247 + orm.RunMigration(conn, logger, "remove-stack-id-from-pull-submissions", func(tx *sql.Tx) error { 1248 + _, err := tx.Exec(` 1249 + alter table pulls drop column stack_id; 1250 + `) 1251 + return err 1252 + }) 1253 + 1254 + orm.RunMigration(conn, logger, "replace-parent-change-id-with-aturi", func(tx *sql.Tx) error { 1255 + // add new column 1256 + _, err := tx.Exec(` 1257 + alter table pulls add column dependent_on text; 1258 + `) 1259 + if err != nil { 1260 + return err 1261 + } 1262 + 1263 + // populate dependent_on with at_uri of the parent 1264 + _, err = tx.Exec(` 1265 + update pulls 1266 + set dependent_on = ( 1267 + select at_uri 1268 + from pulls as parent 1269 + where parent.change_id = pulls.parent_change_id 1270 + ) 1271 + where parent_change_id is not null; 1272 + `) 1273 + if err != nil { 1274 + return err 1275 + } 1276 + 1277 + // drop old column 1278 + _, err = tx.Exec(` 1279 + alter table pulls drop column parent_change_id; 1280 + `) 1281 + 1282 + return err 1283 + }) 1284 + 1238 1285 return &DB{ 1239 1286 db, 1240 1287 logger,
+497 -118
appview/db/pulls.go
··· 12 12 "time" 13 13 14 14 "github.com/bluesky-social/indigo/atproto/syntax" 15 + lexutil "github.com/bluesky-social/indigo/lex/util" 16 + "github.com/ipfs/go-cid" 15 17 "tangled.org/core/appview/models" 16 18 "tangled.org/core/appview/pagination" 17 19 "tangled.org/core/orm" 20 + "tangled.org/core/sets" 18 21 ) 19 22 20 - func NewPull(tx *sql.Tx, pull *models.Pull) error { 23 + func comparePullSource(existing, new *models.PullSource) bool { 24 + if existing == nil && new == nil { 25 + return true 26 + } 27 + if existing == nil || new == nil { 28 + return false 29 + } 30 + if existing.Branch != new.Branch { 31 + return false 32 + } 33 + if existing.RepoAt == nil && new.RepoAt == nil { 34 + return true 35 + } 36 + if existing.RepoAt == nil || new.RepoAt == nil { 37 + return false 38 + } 39 + return *existing.RepoAt == *new.RepoAt 40 + } 41 + 42 + func compareSubmissions(existing, new []*models.PullSubmission) bool { 43 + if len(existing) != len(new) { 44 + return false 45 + } 46 + for i := range existing { 47 + if existing[i].Blob.Ref.String() != new[i].Blob.Ref.String() { 48 + return false 49 + } 50 + if existing[i].Blob.MimeType != new[i].Blob.MimeType { 51 + return false 52 + } 53 + if existing[i].Blob.Size != new[i].Blob.Size { 54 + return false 55 + } 56 + } 57 + return true 58 + } 59 + 60 + func PutPull(tx *sql.Tx, pull *models.Pull) error { 61 + // ensure sequence exists 62 + _, err := tx.Exec(` 63 + insert or ignore into repo_pull_seqs (repo_at, next_pull_id) 64 + values (?, 1) 65 + `, pull.RepoAt) 66 + if err != nil { 67 + return err 68 + } 69 + 70 + pulls, err := GetPulls( 71 + tx, 72 + orm.FilterEq("owner_did", pull.OwnerDid), 73 + orm.FilterEq("rkey", pull.Rkey), 74 + ) 75 + switch { 76 + case err != nil: 77 + return err 78 + case len(pulls) == 0: 79 + return createNewPull(tx, pull) 80 + case len(pulls) != 1: // should be unreachable 81 + return fmt.Errorf("invalid number of pulls returned: %d", len(pulls)) 82 + default: 83 + existingPull := pulls[0] 84 + if existingPull.State == models.PullMerged { 85 + return nil 86 + } 87 + 88 + dependentOnEqual := (existingPull.DependentOn == nil && pull.DependentOn == nil) || 89 + (existingPull.DependentOn != nil && pull.DependentOn != nil && *existingPull.DependentOn == *pull.DependentOn) 90 + 91 + pullSourceEqual := comparePullSource(existingPull.PullSource, pull.PullSource) 92 + submissionsEqual := compareSubmissions(existingPull.Submissions, pull.Submissions) 93 + 94 + if existingPull.Title == pull.Title && 95 + existingPull.Body == pull.Body && 96 + existingPull.TargetBranch == pull.TargetBranch && 97 + existingPull.RepoAt == pull.RepoAt && 98 + dependentOnEqual && 99 + pullSourceEqual && 100 + submissionsEqual { 101 + return nil 102 + } 103 + 104 + isLonger := len(existingPull.Submissions) < len(pull.Submissions) 105 + if isLonger { 106 + isAppendOnly := compareSubmissions(existingPull.Submissions, pull.Submissions[:len(existingPull.Submissions)]) 107 + if !isAppendOnly { 108 + return fmt.Errorf("the new pull does not treat submissions as append-only") 109 + } 110 + } else if !submissionsEqual { 111 + return fmt.Errorf("the new pull does not treat submissions as append-only") 112 + } 113 + 114 + pull.ID = existingPull.ID 115 + pull.PullId = existingPull.PullId 116 + return updatePull(tx, pull, existingPull) 117 + } 118 + } 119 + 120 + func createNewPull(tx *sql.Tx, pull *models.Pull) error { 21 121 _, err := tx.Exec(` 22 122 insert or ignore into repo_pull_seqs (repo_at, next_pull_id) 23 123 values (?, 1) ··· 49 149 } 50 150 } 51 151 52 - var stackId, changeId, parentChangeId *string 53 - if pull.StackId != "" { 54 - stackId = &pull.StackId 55 - } 56 - if pull.ChangeId != "" { 57 - changeId = &pull.ChangeId 58 - } 59 - if pull.ParentChangeId != "" { 60 - parentChangeId = &pull.ParentChangeId 61 - } 152 + // var stackId, changeId, parentChangeId *string 153 + // if pull.StackId != "" { 154 + // stackId = &pull.StackId 155 + // } 156 + // if pull.ChangeId != "" { 157 + // changeId = &pull.ChangeId 158 + // } 159 + // if pull.ParentChangeId != "" { 160 + // parentChangeId = &pull.ParentChangeId 161 + // } 62 162 63 163 result, err := tx.Exec( 64 164 ` 65 165 insert into pulls ( 66 - repo_at, owner_did, pull_id, title, target_branch, body, rkey, state, source_branch, source_repo_at, stack_id, change_id, parent_change_id 166 + repo_at, 167 + owner_did, 168 + pull_id, 169 + title, 170 + target_branch, 171 + body, 172 + rkey, 173 + state, 174 + dependent_on, 175 + source_branch, 176 + source_repo_at 67 177 ) 68 - values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, 178 + values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, 69 179 pull.RepoAt, 70 180 pull.OwnerDid, 71 181 pull.PullId, ··· 74 184 pull.Body, 75 185 pull.Rkey, 76 186 pull.State, 187 + pull.DependentOn, 77 188 sourceBranch, 78 189 sourceRepoAt, 79 - stackId, 80 - changeId, 81 - parentChangeId, 82 190 ) 83 191 if err != nil { 84 192 return err ··· 91 199 } 92 200 pull.ID = int(id) 93 201 94 - _, err = tx.Exec(` 95 - insert into pull_submissions (pull_at, round_number, patch, combined, source_rev) 96 - values (?, ?, ?, ?, ?) 97 - `, pull.AtUri(), 0, pull.Submissions[0].Patch, pull.Submissions[0].Combined, pull.Submissions[0].SourceRev) 98 - if err != nil { 99 - return err 202 + for i, s := range pull.Submissions { 203 + _, err = tx.Exec(` 204 + insert into pull_submissions ( 205 + pull_at, 206 + round_number, 207 + patch, 208 + combined, 209 + source_rev, 210 + patch_blob_ref, 211 + patch_blob_mime, 212 + patch_blob_size 213 + ) 214 + values (?, ?, ?, ?, ?, ?, ?, ?) 215 + `, 216 + pull.AtUri(), 217 + i, 218 + s.Patch, 219 + s.Combined, 220 + s.SourceRev, 221 + s.Blob.Ref.String(), 222 + s.Blob.MimeType, 223 + s.Blob.Size, 224 + ) 225 + if err != nil { 226 + return err 227 + } 100 228 } 101 229 102 230 if err := putReferences(tx, pull.AtUri(), pull.References); err != nil { ··· 106 234 return nil 107 235 } 108 236 109 - func GetPullAt(e Execer, repoAt syntax.ATURI, pullId int) (syntax.ATURI, error) { 110 - pull, err := GetPull(e, repoAt, pullId) 237 + func updatePull(tx *sql.Tx, pull *models.Pull, existingPull *models.Pull) error { 238 + var sourceBranch, sourceRepoAt *string 239 + if pull.PullSource != nil { 240 + sourceBranch = &pull.PullSource.Branch 241 + if pull.PullSource.RepoAt != nil { 242 + x := pull.PullSource.RepoAt.String() 243 + sourceRepoAt = &x 244 + } 245 + } 246 + 247 + _, err := tx.Exec(` 248 + update pulls set 249 + title = ?, 250 + body = ?, 251 + target_branch = ?, 252 + dependent_on = ?, 253 + source_branch = ?, 254 + source_repo_at = ? 255 + where owner_did = ? and rkey = ? 256 + `, pull.Title, pull.Body, pull.TargetBranch, pull.DependentOn, sourceBranch, sourceRepoAt, pull.OwnerDid, pull.Rkey) 111 257 if err != nil { 112 - return "", err 258 + return err 113 259 } 114 - return pull.AtUri(), err 260 + 261 + // insert new submissions (append-only) 262 + for i := len(existingPull.Submissions); i < len(pull.Submissions); i++ { 263 + s := pull.Submissions[i] 264 + _, err = tx.Exec(` 265 + insert into pull_submissions ( 266 + pull_at, 267 + round_number, 268 + patch, 269 + combined, 270 + source_rev, 271 + patch_blob_ref, 272 + patch_blob_mime, 273 + patch_blob_size 274 + ) 275 + values (?, ?, ?, ?, ?, ?, ?, ?) 276 + `, 277 + pull.AtUri(), 278 + i, 279 + s.Patch, 280 + s.Combined, 281 + s.SourceRev, 282 + s.Blob.Ref.String(), 283 + s.Blob.MimeType, 284 + s.Blob.Size, 285 + ) 286 + if err != nil { 287 + return err 288 + } 289 + } 290 + 291 + if err := putReferences(tx, pull.AtUri(), pull.References); err != nil { 292 + return fmt.Errorf("put reference_links: %w", err) 293 + } 294 + return nil 115 295 } 116 296 297 + // func NewPull(tx *sql.Tx, pull *models.Pull) error { 298 + // _, err := tx.Exec(` 299 + // insert or ignore into repo_pull_seqs (repo_at, next_pull_id) 300 + // values (?, 1) 301 + // `, pull.RepoAt) 302 + // if err != nil { 303 + // return err 304 + // } 305 + // 306 + // var nextId int 307 + // err = tx.QueryRow(` 308 + // update repo_pull_seqs 309 + // set next_pull_id = next_pull_id + 1 310 + // where repo_at = ? 311 + // returning next_pull_id - 1 312 + // `, pull.RepoAt).Scan(&nextId) 313 + // if err != nil { 314 + // return err 315 + // } 316 + // 317 + // pull.PullId = nextId 318 + // pull.State = models.PullOpen 319 + // 320 + // var sourceBranch, sourceRepoAt *string 321 + // if pull.PullSource != nil { 322 + // sourceBranch = &pull.PullSource.Branch 323 + // if pull.PullSource.RepoAt != nil { 324 + // x := pull.PullSource.RepoAt.String() 325 + // sourceRepoAt = &x 326 + // } 327 + // } 328 + // 329 + // // var stackId, changeId, parentChangeId *string 330 + // // if pull.StackId != "" { 331 + // // stackId = &pull.StackId 332 + // // } 333 + // // if pull.ChangeId != "" { 334 + // // changeId = &pull.ChangeId 335 + // // } 336 + // // if pull.ParentChangeId != "" { 337 + // // parentChangeId = &pull.ParentChangeId 338 + // // } 339 + // 340 + // result, err := tx.Exec( 341 + // ` 342 + // insert into pulls ( 343 + // repo_at, 344 + // owner_did, 345 + // pull_id, 346 + // title, 347 + // target_branch, 348 + // body, 349 + // rkey, 350 + // state, 351 + // dependent_on, 352 + // source_branch, 353 + // source_repo_at 354 + // ) 355 + // values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, 356 + // pull.RepoAt, 357 + // pull.OwnerDid, 358 + // pull.PullId, 359 + // pull.Title, 360 + // pull.TargetBranch, 361 + // pull.Body, 362 + // pull.Rkey, 363 + // pull.State, 364 + // pull.DependentOn, 365 + // sourceBranch, 366 + // sourceRepoAt, 367 + // ) 368 + // if err != nil { 369 + // return err 370 + // } 371 + // 372 + // // Set the database primary key ID 373 + // id, err := result.LastInsertId() 374 + // if err != nil { 375 + // return err 376 + // } 377 + // pull.ID = int(id) 378 + // 379 + // _, err = tx.Exec(` 380 + // insert into pull_submissions ( 381 + // pull_at, 382 + // round_number, 383 + // patch, 384 + // combined, 385 + // source_rev, 386 + // patch_blob_ref, 387 + // patch_blob_mime, 388 + // patch_blob_size 389 + // ) 390 + // values (?, ?, ?, ?, ?, ?, ?, ?) 391 + // `, 392 + // pull.AtUri(), 393 + // 0, 394 + // pull.Submissions[0].Patch, 395 + // pull.Submissions[0].Combined, 396 + // pull.Submissions[0].SourceRev, 397 + // pull.Submissions[0].Blob.Ref.String(), 398 + // pull.Submissions[0].Blob.MimeType, 399 + // pull.Submissions[0].Blob.Size, 400 + // ) 401 + // if err != nil { 402 + // return err 403 + // } 404 + // 405 + // if err := putReferences(tx, pull.AtUri(), pull.References); err != nil { 406 + // return fmt.Errorf("put reference_links: %w", err) 407 + // } 408 + // 409 + // return nil 410 + // } 411 + 117 412 func NextPullId(e Execer, repoAt syntax.ATURI) (int, error) { 118 413 var pullId int 119 414 err := e.QueryRow(`select next_pull_id from repo_pull_seqs where repo_at = ?`, repoAt).Scan(&pullId) ··· 157 452 rkey, 158 453 source_branch, 159 454 source_repo_at, 160 - stack_id, 161 - change_id, 162 - parent_change_id 455 + dependent_on 163 456 from 164 457 pulls 165 458 %s ··· 177 470 for rows.Next() { 178 471 var pull models.Pull 179 472 var createdAt string 180 - var sourceBranch, sourceRepoAt, stackId, changeId, parentChangeId sql.NullString 473 + var sourceBranch, sourceRepoAt, dependentOn sql.NullString 181 474 err := rows.Scan( 182 475 &pull.ID, 183 476 &pull.OwnerDid, ··· 191 484 &pull.Rkey, 192 485 &sourceBranch, 193 486 &sourceRepoAt, 194 - &stackId, 195 - &changeId, 196 - &parentChangeId, 487 + &dependentOn, 197 488 ) 198 489 if err != nil { 199 490 return nil, err ··· 218 509 } 219 510 } 220 511 221 - if stackId.Valid { 222 - pull.StackId = stackId.String 223 - } 224 - if changeId.Valid { 225 - pull.ChangeId = changeId.String 226 - } 227 - if parentChangeId.Valid { 228 - pull.ParentChangeId = parentChangeId.String 512 + if dependentOn.Valid { 513 + x := syntax.ATURI(dependentOn.String) 514 + pull.DependentOn = &x 229 515 } 230 516 231 517 pulls[pull.AtUri()] = &pull ··· 305 591 return GetPullsPaginated(e, pagination.Page{}, filters...) 306 592 } 307 593 308 - func GetPull(e Execer, repoAt syntax.ATURI, pullId int) (*models.Pull, error) { 309 - pulls, err := GetPullsPaginated(e, pagination.Page{Limit: 1}, orm.FilterEq("repo_at", repoAt), orm.FilterEq("pull_id", pullId)) 594 + func GetPull(e Execer, filters ...orm.Filter) (*models.Pull, error) { 595 + pulls, err := GetPullsPaginated(e, pagination.Page{Limit: 1}, filters...) 310 596 if err != nil { 311 597 return nil, err 312 598 } ··· 339 625 patch, 340 626 combined, 341 627 created, 342 - source_rev 628 + source_rev, 629 + patch_blob_ref, 630 + patch_blob_mime, 631 + patch_blob_size 343 632 from 344 633 pull_submissions 345 634 %s ··· 358 647 for rows.Next() { 359 648 var submission models.PullSubmission 360 649 var submissionCreatedStr string 361 - var submissionSourceRev, submissionCombined sql.NullString 650 + var submissionSourceRev, submissionCombined sql.Null[string] 651 + var patchBlobRef, patchBlobMime sql.Null[string] 652 + var patchBlobSize sql.Null[int64] 362 653 err := rows.Scan( 363 654 &submission.ID, 364 655 &submission.PullAt, ··· 367 658 &submissionCombined, 368 659 &submissionCreatedStr, 369 660 &submissionSourceRev, 661 + &patchBlobRef, 662 + &patchBlobMime, 663 + &patchBlobSize, 370 664 ) 371 665 if err != nil { 372 666 return nil, err ··· 377 671 } 378 672 379 673 if submissionSourceRev.Valid { 380 - submission.SourceRev = submissionSourceRev.String 674 + submission.SourceRev = submissionSourceRev.V 381 675 } 382 676 383 677 if submissionCombined.Valid { 384 - submission.Combined = submissionCombined.String 678 + submission.Combined = submissionCombined.V 679 + } 680 + 681 + if patchBlobRef.Valid { 682 + submission.Blob.Ref = lexutil.LexLink(cid.MustParse(patchBlobRef.V)) 683 + } 684 + 685 + if patchBlobMime.Valid { 686 + submission.Blob.MimeType = patchBlobMime.V 687 + } 688 + 689 + if patchBlobSize.Valid { 690 + submission.Blob.Size = patchBlobSize.V 385 691 } 386 692 387 693 submissionMap[submission.ID] = &submission ··· 618 924 pullState, 619 925 repoAt, 620 926 pullId, 621 - models.PullDeleted, // only update state of non-deleted pulls 622 - models.PullMerged, // only update state of non-merged pulls 927 + models.PullAbandoned, // only update state of non-deleted pulls 928 + models.PullMerged, // only update state of non-merged pulls 623 929 ) 624 930 return err 625 931 } 626 932 627 - func ClosePull(e Execer, repoAt syntax.ATURI, pullId int) error { 628 - err := SetPullState(e, repoAt, pullId, models.PullClosed) 933 + // use with transaction 934 + func SetPullsState(e Execer, pullState models.PullState, filters ...orm.Filter) error { 935 + var conditions []string 936 + var args []any 937 + 938 + args = append(args, pullState) 939 + for _, filter := range filters { 940 + conditions = append(conditions, filter.Condition()) 941 + args = append(args, filter.Arg()...) 942 + } 943 + args = append(args, models.PullAbandoned) // only update state of non-deleted pulls 944 + args = append(args, models.PullMerged) // only update state of non-merged pulls 945 + 946 + whereClause := "" 947 + if conditions != nil { 948 + whereClause = " where " + strings.Join(conditions, " and ") 949 + } 950 + 951 + query := fmt.Sprintf("update pulls set state = ? %s and (state <> ? or state <> ?)", whereClause) 952 + 953 + _, err := e.Exec(query, args...) 629 954 return err 630 955 } 631 956 632 - func ReopenPull(e Execer, repoAt syntax.ATURI, pullId int) error { 633 - err := SetPullState(e, repoAt, pullId, models.PullOpen) 634 - return err 957 + func ClosePull(e Execer, repoAt syntax.ATURI, pullId int) error { 958 + return SetPullState(e, repoAt, pullId, models.PullClosed) 635 959 } 636 960 637 - func MergePull(e Execer, repoAt syntax.ATURI, pullId int) error { 638 - err := SetPullState(e, repoAt, pullId, models.PullMerged) 639 - return err 961 + func ClosePulls(e Execer, filters ...orm.Filter) error { 962 + return SetPullsState(e, models.PullClosed, filters...) 640 963 } 641 964 642 - func DeletePull(e Execer, repoAt syntax.ATURI, pullId int) error { 643 - err := SetPullState(e, repoAt, pullId, models.PullDeleted) 644 - return err 965 + func ReopenPull(e Execer, repoAt syntax.ATURI, pullId int) error { 966 + return SetPullState(e, repoAt, pullId, models.PullOpen) 645 967 } 646 968 647 - func ResubmitPull(e Execer, pullAt syntax.ATURI, newRoundNumber int, newPatch string, combinedPatch string, newSourceRev string) error { 648 - _, err := e.Exec(` 649 - insert into pull_submissions (pull_at, round_number, patch, combined, source_rev) 650 - values (?, ?, ?, ?, ?) 651 - `, pullAt, newRoundNumber, newPatch, combinedPatch, newSourceRev) 969 + func ReopenPulls(e Execer, filters ...orm.Filter) error { 970 + return SetPullsState(e, models.PullOpen, filters...) 971 + } 652 972 653 - return err 973 + func MergePull(e Execer, repoAt syntax.ATURI, pullId int) error { 974 + return SetPullState(e, repoAt, pullId, models.PullMerged) 654 975 } 655 976 656 - func SetPullParentChangeId(e Execer, parentChangeId string, filters ...orm.Filter) error { 657 - var conditions []string 658 - var args []any 977 + func MergePulls(e Execer, filters ...orm.Filter) error { 978 + return SetPullsState(e, models.PullMerged, filters...) 979 + } 659 980 660 - args = append(args, parentChangeId) 981 + func AbandonPull(e Execer, repoAt syntax.ATURI, pullId int) error { 982 + return SetPullState(e, repoAt, pullId, models.PullAbandoned) 983 + } 661 984 662 - for _, filter := range filters { 663 - conditions = append(conditions, filter.Condition()) 664 - args = append(args, filter.Arg()...) 665 - } 985 + func AbandonPulls(e Execer, filters ...orm.Filter) error { 986 + return SetPullsState(e, models.PullAbandoned, filters...) 987 + } 666 988 667 - whereClause := "" 668 - if conditions != nil { 669 - whereClause = " where " + strings.Join(conditions, " and ") 670 - } 671 - 672 - query := fmt.Sprintf("update pulls set parent_change_id = ? %s", whereClause) 673 - _, err := e.Exec(query, args...) 989 + func ResubmitPull( 990 + e Execer, 991 + pullAt syntax.ATURI, 992 + newRoundNumber int, 993 + newPatch string, 994 + combinedPatch string, 995 + newSourceRev string, 996 + blob *lexutil.LexBlob, 997 + ) error { 998 + _, err := e.Exec(` 999 + insert into pull_submissions ( 1000 + pull_at, 1001 + round_number, 1002 + patch, 1003 + combined, 1004 + source_rev, 1005 + patch_blob_ref, 1006 + patch_blob_mime, 1007 + patch_blob_size 1008 + ) 1009 + values (?, ?, ?, ?, ?, ?, ?, ?) 1010 + `, pullAt, newRoundNumber, newPatch, combinedPatch, newSourceRev, blob.Ref.String(), blob.MimeType, blob.Size) 674 1011 675 1012 return err 676 1013 } 677 1014 678 - // Only used when stacking to update contents in the event of a rebase (the interdiff should be empty). 679 - // otherwise submissions are immutable 680 - func UpdatePull(e Execer, newPatch, sourceRev string, filters ...orm.Filter) error { 1015 + func SetDependentOn(e Execer, dependentOn syntax.ATURI, filters ...orm.Filter) error { 681 1016 var conditions []string 682 1017 var args []any 683 1018 684 - args = append(args, sourceRev) 685 - args = append(args, newPatch) 1019 + args = append(args, dependentOn) 686 1020 687 1021 for _, filter := range filters { 688 1022 conditions = append(conditions, filter.Condition()) ··· 694 1028 whereClause = " where " + strings.Join(conditions, " and ") 695 1029 } 696 1030 697 - query := fmt.Sprintf("update pull_submissions set source_rev = ?, patch = ? %s", whereClause) 1031 + query := fmt.Sprintf("update pulls set dependent_on = ? %s", whereClause) 698 1032 _, err := e.Exec(query, args...) 699 1033 700 1034 return err ··· 712 1046 models.PullOpen, 713 1047 models.PullMerged, 714 1048 models.PullClosed, 715 - models.PullDeleted, 1049 + models.PullAbandoned, 716 1050 repoAt, 717 1051 ) 718 1052 ··· 724 1058 return count, nil 725 1059 } 726 1060 727 - // change-id parent-change-id 1061 + // change-id dependent_on 728 1062 // 729 - // 4 w ,-------- z (TOP) 730 - // 3 z <----',------- y 731 - // 2 y <-----',------ x 1063 + // 4 w ,-------- at_uri(z) (TOP) 1064 + // 3 z <----',------- at_uri(y) 1065 + // 2 y <-----',------ at_uri(x) 732 1066 // 1 x <------' nil (BOT) 733 1067 // 734 - // `w` is parent of none, so it is the top of the stack 735 - func GetStack(e Execer, stackId string) (models.Stack, error) { 736 - unorderedPulls, err := GetPulls( 737 - e, 738 - orm.FilterEq("stack_id", stackId), 739 - orm.FilterNotEq("state", models.PullDeleted), 740 - ) 1068 + // `w` has no dependents, so it is the top of the stack 1069 + // 1070 + // this unfortunately does a db query for *each* pull of the stack, 1071 + // ideally this would be a recursive query, but in the interest of implementation simplicity, 1072 + // we took the less performant route 1073 + // 1074 + // TODO: make this less bad 1075 + func GetStack(e Execer, atUri syntax.ATURI) (models.Stack, error) { 1076 + // first get the pull for the given at-uri 1077 + pull, err := GetPull(e, orm.FilterEq("at_uri", atUri)) 741 1078 if err != nil { 742 1079 return nil, err 743 1080 } 744 - // map of parent-change-id to pull 745 - changeIdMap := make(map[string]*models.Pull, len(unorderedPulls)) 746 - parentMap := make(map[string]*models.Pull, len(unorderedPulls)) 747 - for _, p := range unorderedPulls { 748 - changeIdMap[p.ChangeId] = p 749 - if p.ParentChangeId != "" { 750 - parentMap[p.ParentChangeId] = p 1081 + 1082 + // Collect all pulls in the stack by traversing up and down 1083 + allPulls := []*models.Pull{pull} 1084 + visited := sets.New[syntax.ATURI]() 1085 + 1086 + // Traverse up to find all dependents 1087 + current := pull 1088 + for { 1089 + dependent, err := GetPull(e, 1090 + orm.FilterEq("dependent_on", current.AtUri), 1091 + orm.FilterNotEq("state", models.PullAbandoned), 1092 + ) 1093 + if err != nil || dependent == nil { 1094 + break 751 1095 } 1096 + if visited.Contains(dependent.AtUri()) { 1097 + return nil, fmt.Errorf("circular dependency detected in stack") 1098 + } 1099 + allPulls = append(allPulls, dependent) 1100 + visited.Insert(dependent.AtUri()) 1101 + current = dependent 752 1102 } 753 1103 754 - // the top of the stack is the pull that is not a parent of any pull 1104 + // Traverse down to find all dependencies 1105 + current = pull 1106 + for current.DependentOn != nil { 1107 + dependency, err := GetPull(e, orm.FilterEq("at_uri", current.DependentOn)) 1108 + if err != nil { 1109 + return nil, fmt.Errorf("failed to find parent pull request, stack is malformed") 1110 + } 1111 + if visited.Contains(dependency.AtUri()) { 1112 + return nil, fmt.Errorf("circular dependency detected in stack") 1113 + } 1114 + allPulls = append(allPulls, dependency) 1115 + visited.Insert(dependency.AtUri()) 1116 + current = dependency 1117 + } 1118 + 1119 + // sort the list: find the top and build ordered list 1120 + atUriMap := make(map[syntax.ATURI]*models.Pull, len(allPulls)) 1121 + dependentMap := make(map[syntax.ATURI]*models.Pull, len(allPulls)) 1122 + 1123 + for _, p := range allPulls { 1124 + atUriMap[p.AtUri()] = p 1125 + if p.DependentOn != nil { 1126 + dependentMap[*p.DependentOn] = p 1127 + } 1128 + } 1129 + 1130 + // the top of the stack is the pull that no other pull depends on 755 1131 var topPull *models.Pull 756 - for _, maybeTop := range unorderedPulls { 757 - if _, ok := parentMap[maybeTop.ChangeId]; !ok { 1132 + for _, maybeTop := range allPulls { 1133 + if _, ok := dependentMap[maybeTop.AtUri()]; !ok { 758 1134 topPull = maybeTop 759 1135 break 760 1136 } ··· 763 1139 pulls := []*models.Pull{} 764 1140 for { 765 1141 pulls = append(pulls, topPull) 766 - if topPull.ParentChangeId != "" { 767 - if next, ok := changeIdMap[topPull.ParentChangeId]; ok { 1142 + if topPull.DependentOn != nil { 1143 + if next, ok := atUriMap[*topPull.DependentOn]; ok { 768 1144 topPull = next 769 1145 } else { 770 1146 return nil, fmt.Errorf("failed to find parent pull request, stack is malformed") ··· 777 1153 return pulls, nil 778 1154 } 779 1155 780 - func GetAbandonedPulls(e Execer, stackId string) ([]*models.Pull, error) { 781 - pulls, err := GetPulls( 782 - e, 783 - orm.FilterEq("stack_id", stackId), 784 - orm.FilterEq("state", models.PullDeleted), 785 - ) 1156 + func GetAbandonedPulls(e Execer, atUri syntax.ATURI) ([]*models.Pull, error) { 1157 + stack, err := GetStack(e, atUri) 786 1158 if err != nil { 787 1159 return nil, err 788 1160 } 789 1161 790 - return pulls, nil 1162 + var abandoned []*models.Pull 1163 + for _, p := range stack { 1164 + if p.State == models.PullAbandoned { 1165 + abandoned = append(abandoned, p) 1166 + } 1167 + } 1168 + 1169 + return abandoned, nil 791 1170 }
+1 -1
appview/db/repos.go
··· 261 261 models.PullOpen, 262 262 models.PullMerged, 263 263 models.PullClosed, 264 - models.PullDeleted, 264 + models.PullAbandoned, 265 265 }, args...) 266 266 rows, err = e.Query( 267 267 pullCountQuery,
+147
appview/ingester.go
··· 4 4 "context" 5 5 "encoding/json" 6 6 "fmt" 7 + "io" 7 8 "log/slog" 8 9 "maps" 10 + "net/http" 11 + "net/url" 9 12 "slices" 13 + "sync" 10 14 11 15 "time" 12 16 ··· 14 18 jmodels "github.com/bluesky-social/jetstream/pkg/models" 15 19 "github.com/go-git/go-git/v5/plumbing" 16 20 "github.com/ipfs/go-cid" 21 + "golang.org/x/sync/errgroup" 17 22 "tangled.org/core/api/tangled" 18 23 "tangled.org/core/appview/config" 19 24 "tangled.org/core/appview/db" ··· 79 84 err = i.ingestString(e) 80 85 case tangled.RepoIssueNSID: 81 86 err = i.ingestIssue(ctx, e) 87 + case tangled.RepoPullNSID: 88 + err = i.ingestPull(ctx, e) 82 89 case tangled.RepoIssueCommentNSID: 83 90 err = i.ingestIssueComment(e) 84 91 case tangled.LabelDefinitionNSID: ··· 862 869 ); err != nil { 863 870 l.Error("failed to delete", "err", err) 864 871 return fmt.Errorf("failed to delete issue record: %w", err) 872 + } 873 + if err := tx.Commit(); err != nil { 874 + l.Error("failed to commit txn", "err", err) 875 + return err 876 + } 877 + 878 + return nil 879 + } 880 + 881 + return nil 882 + } 883 + 884 + func (i *Ingester) ingestPull(ctx context.Context, e *jmodels.Event) error { 885 + did := e.Did 886 + rkey := e.Commit.RKey 887 + 888 + var err error 889 + 890 + l := i.Logger.With("handler", "ingestPull", "nsid", e.Commit.Collection, "did", did, "rkey", rkey) 891 + l.Info("ingesting record") 892 + 893 + ddb, ok := i.Db.Execer.(*db.DB) 894 + if !ok { 895 + return fmt.Errorf("failed to index pull record, invalid db cast") 896 + } 897 + 898 + switch e.Commit.Operation { 899 + case jmodels.CommitOperationCreate, jmodels.CommitOperationUpdate: 900 + raw := json.RawMessage(e.Commit.Record) 901 + record := tangled.RepoPull{} 902 + err = json.Unmarshal(raw, &record) 903 + if err != nil { 904 + l.Error("invalid record", "err", err) 905 + return err 906 + } 907 + 908 + ownerId, err := i.IdResolver.ResolveIdent(ctx, did) 909 + if err != nil { 910 + l.Error("failed to resolve did") 911 + return err 912 + } 913 + 914 + // go through and fetch all blobs in parallel 915 + readers := make([]*io.ReadCloser, len(record.Rounds)) 916 + var mu sync.Mutex 917 + 918 + g, gctx := errgroup.WithContext(ctx) 919 + 920 + for idx, b := range record.Rounds { 921 + g.Go(func() error { 922 + ownerPds := ownerId.PDSEndpoint() 923 + url, _ := url.Parse(fmt.Sprintf("%s/xrpc/com.atproto.sync.getBlob", ownerPds)) 924 + q := url.Query() 925 + q.Set("cid", b.PatchBlob.Ref.String()) 926 + q.Set("did", did) 927 + url.RawQuery = q.Encode() 928 + 929 + req, err := http.NewRequestWithContext(gctx, http.MethodGet, url.String(), nil) 930 + if err != nil { 931 + l.Error("failed to create request") 932 + return err 933 + } 934 + req.Header.Set("Content-Type", "application/json") 935 + 936 + resp, err := http.DefaultClient.Do(req) 937 + if err != nil { 938 + l.Error("failed to make request") 939 + return err 940 + } 941 + 942 + mu.Lock() 943 + readers[idx] = &resp.Body 944 + mu.Unlock() 945 + 946 + return nil 947 + }) 948 + } 949 + 950 + if err := g.Wait(); err != nil { 951 + for _, r := range readers { 952 + if r != nil && *r != nil { 953 + (*r).Close() 954 + } 955 + } 956 + return err 957 + } 958 + 959 + defer func() { 960 + for _, r := range readers { 961 + if r != nil && *r != nil { 962 + (*r).Close() 963 + } 964 + } 965 + }() 966 + 967 + pull := models.PullFromRecord(did, rkey, record, readers) 968 + l.Debug("pull data ingested", "data", pull) 969 + for _, s := range pull.Submissions { 970 + l.Debug("round", "data", s) 971 + } 972 + // if err := i.Validator.ValidateIssue(&issue); err != nil { 973 + // return fmt.Errorf("failed to validate issue: %w", err) 974 + // } 975 + 976 + tx, err := ddb.BeginTx(ctx, nil) 977 + if err != nil { 978 + l.Error("failed to begin transaction", "err", err) 979 + return err 980 + } 981 + defer tx.Rollback() 982 + 983 + err = db.PutPull(tx, &pull) 984 + if err != nil { 985 + l.Error("failed to create pull", "err", err) 986 + return err 987 + } 988 + 989 + err = tx.Commit() 990 + if err != nil { 991 + l.Error("failed to commit txn", "err", err) 992 + return err 993 + } 994 + 995 + return nil 996 + 997 + case jmodels.CommitOperationDelete: 998 + tx, err := ddb.BeginTx(ctx, nil) 999 + if err != nil { 1000 + l.Error("failed to begin transaction", "err", err) 1001 + return err 1002 + } 1003 + defer tx.Rollback() 1004 + 1005 + if err := db.AbandonPulls( 1006 + tx, 1007 + orm.FilterEq("owner_did", did), 1008 + orm.FilterEq("rkey", rkey), 1009 + ); err != nil { 1010 + l.Error("failed to abandon", "err", err) 1011 + return fmt.Errorf("failed to abandon pull record: %w", err) 865 1012 } 866 1013 if err := tx.Commit(); err != nil { 867 1014 l.Error("failed to commit txn", "err", err)
+3 -3
appview/middleware/middleware.go
··· 252 252 return 253 253 } 254 254 255 - pr, err := db.GetPull(mw.db, f.RepoAt(), prIdInt) 255 + pr, err := db.GetPull(mw.db, orm.FilterEq("repo_at", f.RepoAt()), orm.FilterEq("pull_id", prIdInt)) 256 256 if err != nil { 257 257 log.Println("failed to get pull and comments", err) 258 258 mw.pages.Error404(w) ··· 262 262 ctx := context.WithValue(r.Context(), "pull", pr) 263 263 264 264 if pr.IsStacked() { 265 - stack, err := db.GetStack(mw.db, pr.StackId) 265 + stack, err := db.GetStack(mw.db, pr.AtUri()) 266 266 if err != nil { 267 267 log.Println("failed to get stack", err) 268 268 return 269 269 } 270 - abandonedPulls, err := db.GetAbandonedPulls(mw.db, pr.StackId) 270 + abandonedPulls, err := db.GetAbandonedPulls(mw.db, pr.AtUri()) 271 271 if err != nil { 272 272 log.Println("failed to get abandoned pulls", err) 273 273 return
+151 -14
appview/models/pull.go
··· 1 1 package models 2 2 3 3 import ( 4 + "bytes" 5 + "compress/gzip" 4 6 "fmt" 7 + "io" 5 8 "log" 6 9 "slices" 7 10 "strings" 8 11 "time" 9 12 10 - "github.com/bluesky-social/indigo/atproto/syntax" 11 13 "tangled.org/core/api/tangled" 12 14 "tangled.org/core/patchutil" 13 15 "tangled.org/core/types" 16 + 17 + "github.com/bluesky-social/indigo/atproto/syntax" 18 + lexutil "github.com/bluesky-social/indigo/lex/util" 14 19 ) 15 20 16 21 type PullState int ··· 19 24 PullClosed PullState = iota 20 25 PullOpen 21 26 PullMerged 22 - PullDeleted 27 + PullAbandoned 23 28 ) 24 29 25 30 func (p PullState) String() string { ··· 30 35 return "merged" 31 36 case PullClosed: 32 37 return "closed" 33 - case PullDeleted: 34 - return "deleted" 38 + case PullAbandoned: 39 + return "abandoned" 35 40 default: 36 41 return "closed" 37 42 } ··· 46 51 func (p PullState) IsClosed() bool { 47 52 return p == PullClosed 48 53 } 49 - func (p PullState) IsDeleted() bool { 50 - return p == PullDeleted 54 + func (p PullState) IsAbandoned() bool { 55 + return p == PullAbandoned 51 56 } 52 57 53 58 type Pull struct { ··· 70 75 References []syntax.ATURI 71 76 72 77 // stacking 73 - StackId string // nullable string 74 - ChangeId string // nullable string 75 - ParentChangeId string // nullable string 78 + DependentOn *syntax.ATURI 79 + // StackId string // nullable string 80 + // ChangeId string // nullable string 81 + // ParentChangeId string // nullable string 76 82 77 83 // meta 78 84 Created time.Time ··· 89 95 if p.PullSource != nil { 90 96 source = &tangled.RepoPull_Source{} 91 97 source.Branch = p.PullSource.Branch 92 - source.Sha = p.LatestSha() 93 98 if p.PullSource.RepoAt != nil { 94 99 s := p.PullSource.RepoAt.String() 95 100 source.Repo = &s ··· 104 109 references[i] = string(uri) 105 110 } 106 111 112 + rounds := make([]*tangled.RepoPull_Round, len(p.Submissions)) 113 + for i, submission := range p.Submissions { 114 + rounds[i] = submission.AsRecord() 115 + } 116 + 107 117 record := tangled.RepoPull{ 108 118 Title: p.Title, 109 119 Body: &p.Body, ··· 114 124 Repo: p.RepoAt.String(), 115 125 Branch: p.TargetBranch, 116 126 }, 127 + Rounds: rounds, 117 128 Source: source, 118 129 } 119 130 return record 120 131 } 121 132 133 + func PullFromRecord(did, rkey string, record tangled.RepoPull, blobs []*io.ReadCloser) Pull { 134 + created, err := time.Parse(time.RFC3339, record.CreatedAt) 135 + if err != nil { 136 + created = time.Now() 137 + } 138 + 139 + body := "" 140 + if record.Body != nil { 141 + body = *record.Body 142 + } 143 + 144 + var mentions []syntax.DID 145 + for _, m := range record.Mentions { 146 + if did, err := syntax.ParseDID(m); err == nil { 147 + mentions = append(mentions, did) 148 + } 149 + } 150 + 151 + var repoAt syntax.ATURI 152 + var branch string 153 + if record.Target != nil { 154 + if uri, err := syntax.ParseATURI(record.Target.Repo); err == nil { 155 + repoAt = uri 156 + } 157 + branch = record.Target.Branch 158 + } 159 + 160 + var submissions []*PullSubmission 161 + for i, s := range record.Rounds { 162 + created, err := time.Parse(time.RFC3339, s.CreatedAt) 163 + if err != nil { 164 + created = time.Now() 165 + } 166 + 167 + var patch, sourceRev string 168 + if blobs[i] != nil { 169 + p, err := extractGzip(*blobs[i]) 170 + if err != nil { 171 + continue 172 + } 173 + patch = p 174 + if patchutil.IsFormatPatch(p) { 175 + patches, err := patchutil.ExtractPatches(p) 176 + if err != nil { 177 + continue 178 + } 179 + 180 + for _, part := range patches { 181 + sourceRev = part.SHA 182 + } 183 + } 184 + } 185 + 186 + submissions = append(submissions, &PullSubmission{ 187 + PullAt: syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", did, tangled.RepoPullNSID, rkey)), 188 + RoundNumber: i, 189 + Blob: *s.PatchBlob, 190 + Created: created, 191 + Patch: patch, 192 + SourceRev: sourceRev, 193 + }) 194 + } 195 + 196 + return Pull{ 197 + RepoAt: repoAt, 198 + OwnerDid: did, 199 + Rkey: rkey, 200 + Title: record.Title, 201 + Body: body, 202 + TargetBranch: branch, 203 + State: PullOpen, 204 + Submissions: submissions, 205 + Created: created, 206 + } 207 + } 208 + 122 209 type PullSource struct { 123 210 Branch string 124 211 RepoAt *syntax.ATURI ··· 136 223 137 224 // content 138 225 RoundNumber int 226 + Blob lexutil.LexBlob 139 227 Patch string 140 228 Combined string 141 229 Comments []PullComment ··· 226 314 } 227 315 228 316 func (p *Pull) IsStacked() bool { 229 - return p.StackId != "" 317 + return p.DependentOn != nil 230 318 } 231 319 232 320 func (p *Pull) Participants() []string { ··· 265 353 return patches 266 354 } 267 355 356 + // empty if invalid, not otherwise 357 + func (s PullSubmission) ChangeId() string { 358 + patches := s.AsFormatPatch() 359 + if len(patches) != 1 { 360 + return "" 361 + } 362 + 363 + c, err := patches[0].ChangeId() 364 + if err != nil { 365 + return "" 366 + } 367 + 368 + return c 369 + } 370 + 268 371 func (s *PullSubmission) Participants() []string { 269 372 participantSet := make(map[string]struct{}) 270 373 participants := []string{} ··· 293 396 return s.Combined 294 397 } 295 398 399 + func (s *PullSubmission) GetBlob() *lexutil.LexBlob { 400 + if !s.Blob.Ref.Defined() { 401 + return nil 402 + } 403 + 404 + return &s.Blob 405 + } 406 + 407 + func (s *PullSubmission) AsRecord() *tangled.RepoPull_Round { 408 + return &tangled.RepoPull_Round{ 409 + CreatedAt: s.Created.Format(time.RFC3339), 410 + PatchBlob: s.GetBlob(), 411 + } 412 + } 413 + 296 414 type Stack []*Pull 297 415 298 416 // position of this pull in the stack 299 417 func (stack Stack) Position(pull *Pull) int { 300 418 return slices.IndexFunc(stack, func(p *Pull) bool { 301 - return p.ChangeId == pull.ChangeId 419 + return p.AtUri() == pull.AtUri() 302 420 }) 303 421 } 304 422 ··· 372 490 break 373 491 } 374 492 375 - // skip over deleted PRs 376 - if p.State != PullDeleted { 493 + // skip over abandoned PRs 494 + if p.State != PullAbandoned { 377 495 mergeable = append(mergeable, p) 378 496 } 379 497 } ··· 385 503 Repo *Repo 386 504 Branch string 387 505 } 506 + 507 + func extractGzip(blob io.Reader) (string, error) { 508 + var b bytes.Buffer 509 + r, err := gzip.NewReader(blob) 510 + if err != nil { 511 + return "", err 512 + } 513 + defer r.Close() 514 + 515 + const maxSize = 15 * 1024 * 1024 516 + limitedReader := io.LimitReader(r, maxSize) 517 + 518 + _, err = io.Copy(&b, limitedReader) 519 + if err != nil { 520 + return "", err 521 + } 522 + 523 + return b.String(), nil 524 + }
+2 -2
appview/notify/db/db.go
··· 282 282 l := log.FromContext(ctx) 283 283 284 284 pull, err := db.GetPull(n.db, 285 - syntax.ATURI(comment.RepoAt), 286 - comment.PullId, 285 + orm.FilterEq("repo_at", syntax.ATURI(comment.RepoAt)), 286 + orm.FilterEq("pull_id", comment.PullId), 287 287 ) 288 288 if err != nil { 289 289 l.Error("failed to get pulls", "err", err)
+1 -1
appview/pages/templates/repo/pulls/fragments/summarizedPullState.html
··· 8 8 {{ else if .IsMerged }} 9 9 {{ $fgColor = "text-purple-600 dark:text-purple-500" }} 10 10 {{ $icon = "git-merge" }} 11 - {{ else if .IsDeleted }} 11 + {{ else if .IsAbandoned }} 12 12 {{ $fgColor = "text-red-600 dark:text-red-500" }} 13 13 {{ $icon = "git-pull-request-closed" }} 14 14 {{ end }}
+1 -1
appview/pages/templates/repo/pulls/pull.html
··· 461 461 > 462 462 </div> 463 463 </div> 464 - {{ else if .Pull.State.IsDeleted }} 464 + {{ else if .Pull.State.IsAbandoned }} 465 465 <div class="bg-red-50 dark:bg-red-900 border border-red-500 rounded drop-shadow-sm px-6 py-2 relative"> 466 466 <div class="flex items-center gap-2 text-red-500 dark:text-red-300"> 467 467 {{ i "git-pull-request-closed" "w-4 h-4" }}
-20
appview/pages/templates/repo/pulls/pulls.html
··· 132 132 {{ end }} 133 133 </div> 134 134 </div> 135 - {{ if .StackId }} 136 - {{ $otherPulls := index $.Stacks .StackId }} 137 - {{ if gt (len $otherPulls) 0 }} 138 - <details class="bg-white dark:bg-gray-800 group"> 139 - <summary class="pb-4 px-6 text-xs list-none cursor-pointer hover:text-gray-500 hover:dark:text-gray-400"> 140 - {{ $s := "s" }} 141 - {{ if eq (len $otherPulls) 1 }} 142 - {{ $s = "" }} 143 - {{ end }} 144 - <div class="group-open:hidden flex items-center gap-2"> 145 - {{ i "chevrons-up-down" "w-4 h-4" }} expand {{ len $otherPulls }} pull{{$s}} in this stack 146 - </div> 147 - <div class="hidden group-open:flex items-center gap-2"> 148 - {{ i "chevrons-down-up" "w-4 h-4" }} hide {{ len $otherPulls }} pull{{$s}} in this stack 149 - </div> 150 - </summary> 151 - {{ block "stackedPullList" (list $otherPulls $) }} {{ end }} 152 - </details> 153 - {{ end }} 154 - {{ end }} 155 135 </div> 156 136 {{ end }} 157 137 </div>
+204 -166
appview/pulls/pulls.go
··· 47 47 lexutil "github.com/bluesky-social/indigo/lex/util" 48 48 indigoxrpc "github.com/bluesky-social/indigo/xrpc" 49 49 "github.com/go-chi/chi/v5" 50 - "github.com/google/uuid" 51 50 ) 52 51 53 52 const ApplicationGzip = "application/gzip" ··· 277 276 diff = patchutil.Interdiff(previousPatch, currentPatch) 278 277 } 279 278 280 - s.pages.RepoSinglePull(w, pages.RepoSinglePullParams{ 279 + fmt.Println(s.pages.RepoSinglePull(w, pages.RepoSinglePullParams{ 281 280 LoggedInUser: user, 282 281 RepoInfo: s.repoResolver.GetRepoInfo(r, user), 283 282 Pull: pull, ··· 297 296 UserReacted: userReactions, 298 297 299 298 LabelDefs: defs, 300 - }) 299 + })) 301 300 } 302 301 303 302 func (s *Pulls) RepoSinglePull(w http.ResponseWriter, r *http.Request) { ··· 432 431 } 433 432 434 433 func (s *Pulls) resubmitCheck(r *http.Request, repo *models.Repo, pull *models.Pull, stack models.Stack) pages.ResubmitResult { 435 - if pull.State == models.PullMerged || pull.State == models.PullDeleted || pull.PullSource == nil { 434 + if pull.State == models.PullMerged || pull.State == models.PullAbandoned || pull.PullSource == nil { 436 435 return pages.Unknown 437 436 } 438 437 ··· 711 710 // we want to group all stacked PRs into just one list 712 711 stacks := make(map[string]models.Stack) 713 712 var shas []string 714 - n := 0 713 + // n := 0 715 714 for _, p := range pulls { 716 715 // store the sha for later 717 716 shas = append(shas, p.LatestSha()) 718 717 // this PR is stacked 719 - if p.StackId != "" { 720 - // we have already seen this PR stack 721 - if _, seen := stacks[p.StackId]; seen { 722 - stacks[p.StackId] = append(stacks[p.StackId], p) 723 - // skip this PR 724 - } else { 725 - stacks[p.StackId] = nil 726 - pulls[n] = p 727 - n++ 728 - } 729 - } else { 730 - pulls[n] = p 731 - n++ 732 - } 718 + // if p.StackId != "" { 719 + // // we have already seen this PR stack 720 + // if _, seen := stacks[p.StackId]; seen { 721 + // stacks[p.StackId] = append(stacks[p.StackId], p) 722 + // // skip this PR 723 + // } else { 724 + // stacks[p.StackId] = nil 725 + // pulls[n] = p 726 + // n++ 727 + // } 728 + // } else { 729 + // pulls[n] = p 730 + // n++ 731 + // } 733 732 } 734 - pulls = pulls[:n] 733 + // pulls = pulls[:n] 735 734 736 735 ps, err := db.GetPipelineStatuses( 737 736 s.db, ··· 771 770 filterState = state.String() 772 771 } 773 772 774 - s.pages.RepoPulls(w, pages.RepoPullsParams{ 773 + fmt.Println(s.pages.RepoPulls(w, pages.RepoPullsParams{ 775 774 LoggedInUser: s.oauth.GetMultiAccountUser(r), 776 775 RepoInfo: repoInfo, 777 776 Pulls: pulls, ··· 782 781 Pipelines: m, 783 782 Page: page, 784 783 PullCount: totalPulls, 785 - }) 784 + })) 786 785 } 787 786 788 787 func (s *Pulls) PullComment(w http.ResponseWriter, r *http.Request) { ··· 1115 1114 } 1116 1115 recordPullSource := &tangled.RepoPull_Source{ 1117 1116 Branch: sourceBranch, 1118 - Sha: comparison.Rev2, 1119 1117 } 1120 1118 1121 1119 s.createPullRequest(w, r, repo, user, title, body, targetBranch, patch, combined, sourceRev, pullSource, recordPullSource, isStacked) ··· 1230 1228 recordPullSource := &tangled.RepoPull_Source{ 1231 1229 Branch: sourceBranch, 1232 1230 Repo: &forkAtUriStr, 1233 - Sha: sourceRev, 1234 1231 } 1235 1232 1236 1233 s.createPullRequest(w, r, repo, user, title, body, targetBranch, patch, combined, sourceRev, pullSource, recordPullSource, isStacked) ··· 1303 1300 mentions, references := s.mentionsResolver.Resolve(r.Context(), body) 1304 1301 1305 1302 rkey := tid.TID() 1303 + 1304 + blob, err := xrpc.RepoUploadBlob(r.Context(), client, gz(patch), ApplicationGzip) 1305 + if err != nil { 1306 + log.Println("failed to upload patch", err) 1307 + s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 1308 + return 1309 + } 1310 + 1311 + record := tangled.RepoPull{ 1312 + Title: title, 1313 + Body: &body, 1314 + Target: &tangled.RepoPull_Target{ 1315 + Repo: string(repo.RepoAt()), 1316 + Branch: targetBranch, 1317 + }, 1318 + Source: recordPullSource, 1319 + CreatedAt: time.Now().Format(time.RFC3339), 1320 + Rounds: []*tangled.RepoPull_Round{ 1321 + { 1322 + CreatedAt: time.Now().Format(time.RFC3339), 1323 + PatchBlob: blob.Blob, 1324 + }, 1325 + }, 1326 + } 1306 1327 initialSubmission := models.PullSubmission{ 1307 1328 Patch: patch, 1308 1329 Combined: combined, 1309 1330 SourceRev: sourceRev, 1331 + Blob: *blob.Blob, 1310 1332 } 1311 1333 pull := &models.Pull{ 1312 1334 Title: title, ··· 1321 1343 &initialSubmission, 1322 1344 }, 1323 1345 PullSource: pullSource, 1346 + State: models.PullOpen, 1324 1347 } 1325 - err = db.NewPull(tx, pull) 1348 + 1349 + _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 1350 + Collection: tangled.RepoPullNSID, 1351 + Repo: user.Active.Did, 1352 + Rkey: rkey, 1353 + Record: &lexutil.LexiconTypeDecoder{ 1354 + Val: &record, 1355 + }, 1356 + }) 1326 1357 if err != nil { 1327 1358 log.Println("failed to create pull request", err) 1328 1359 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 1329 1360 return 1330 1361 } 1331 - pullId, err := db.NextPullId(tx, repo.RepoAt()) 1332 - if err != nil { 1333 - log.Println("failed to get pull id", err) 1334 - s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 1335 - return 1336 - } 1337 1362 1338 - blob, err := xrpc.RepoUploadBlob(r.Context(), client, gz(patch), ApplicationGzip) 1363 + err = db.PutPull(tx, pull) 1339 1364 if err != nil { 1340 - log.Println("failed to upload patch", err) 1365 + log.Println("failed to create pull request", err) 1341 1366 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 1342 1367 return 1343 1368 } 1344 - 1345 - _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 1346 - Collection: tangled.RepoPullNSID, 1347 - Repo: user.Active.Did, 1348 - Rkey: rkey, 1349 - Record: &lexutil.LexiconTypeDecoder{ 1350 - Val: &tangled.RepoPull{ 1351 - Title: title, 1352 - Target: &tangled.RepoPull_Target{ 1353 - Repo: string(repo.RepoAt()), 1354 - Branch: targetBranch, 1355 - }, 1356 - PatchBlob: blob.Blob, 1357 - Source: recordPullSource, 1358 - CreatedAt: time.Now().Format(time.RFC3339), 1359 - }, 1360 - }, 1361 - }) 1369 + pullId, err := db.NextPullId(tx, repo.RepoAt()) 1362 1370 if err != nil { 1363 - log.Println("failed to create pull request", err) 1371 + log.Println("failed to get pull id", err) 1364 1372 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 1365 1373 return 1366 1374 } ··· 1391 1399 1392 1400 // must be branch or fork based 1393 1401 if sourceRev == "" { 1394 - log.Println("stacked PR from patch-based pull") 1402 + s.logger.Error("stacked PR from patch-based pull") 1395 1403 s.pages.Notice(w, "pull", "Stacking is only supported on branch and fork based pull-requests.") 1396 1404 return 1397 1405 } 1398 1406 1399 1407 formatPatches, err := patchutil.ExtractPatches(patch) 1400 1408 if err != nil { 1401 - log.Println("failed to extract patches", err) 1409 + s.logger.Error("failed to extract patches", "err", err) 1402 1410 s.pages.Notice(w, "pull", fmt.Sprintf("Failed to extract patches: %v", err)) 1403 1411 return 1404 1412 } 1405 1413 1406 1414 // must have atleast 1 patch to begin with 1407 1415 if len(formatPatches) == 0 { 1408 - log.Println("empty patches") 1416 + s.logger.Error("empty patches") 1409 1417 s.pages.Notice(w, "pull", "No patches found in the generated format-patch.") 1410 1418 return 1411 1419 } 1412 1420 1413 - // build a stack out of this patch 1414 - stackId := uuid.New() 1415 - stack, err := s.newStack(r.Context(), repo, user, targetBranch, patch, pullSource, stackId.String()) 1421 + client, err := s.oauth.AuthorizedClient(r) 1416 1422 if err != nil { 1417 - log.Println("failed to create stack", err) 1418 - s.pages.Notice(w, "pull", fmt.Sprintf("Failed to create stack: %v", err)) 1423 + s.logger.Error("failed to get authorized client", "err", err) 1424 + s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 1419 1425 return 1420 1426 } 1421 1427 1422 - client, err := s.oauth.AuthorizedClient(r) 1428 + // first upload all blobs 1429 + blobs := make([]*lexutil.LexBlob, len(formatPatches)) 1430 + for i, p := range formatPatches { 1431 + blob, err := xrpc.RepoUploadBlob(r.Context(), client, gz(p.Raw), ApplicationGzip) 1432 + if err != nil { 1433 + s.logger.Error("failed to upload patch blob", "err", err) 1434 + s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 1435 + return 1436 + } 1437 + s.logger.Info("uploaded blob", "idx", i+1, "total", len(formatPatches)) 1438 + blobs[i] = blob.Blob 1439 + } 1440 + 1441 + // build a stack out of this patch 1442 + stack, err := s.newStack(r.Context(), repo, user, targetBranch, pullSource, formatPatches, blobs) 1423 1443 if err != nil { 1424 - log.Println("failed to get authorized client", err) 1425 - s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 1444 + s.logger.Error("failed to create stack", "err", err) 1445 + s.pages.Notice(w, "pull", fmt.Sprintf("Failed to create stack: %v", err)) 1426 1446 return 1427 1447 } 1428 1448 1429 1449 // apply all record creations at once 1430 1450 var writes []*comatproto.RepoApplyWrites_Input_Writes_Elem 1431 1451 for _, p := range stack { 1432 - blob, err := xrpc.RepoUploadBlob(r.Context(), client, gz(p.LatestPatch()), ApplicationGzip) 1433 - if err != nil { 1434 - log.Println("failed to upload patch blob", err) 1435 - s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 1436 - return 1437 - } 1438 - 1439 1452 record := p.AsRecord() 1440 - record.PatchBlob = blob.Blob 1441 1453 writes = append(writes, &comatproto.RepoApplyWrites_Input_Writes_Elem{ 1442 1454 RepoApplyWrites_Create: &comatproto.RepoApplyWrites_Create{ 1443 1455 Collection: tangled.RepoPullNSID, ··· 1453 1465 Writes: writes, 1454 1466 }) 1455 1467 if err != nil { 1456 - log.Println("failed to create stacked pull request", err) 1468 + s.logger.Error("failed to create stacked pull request", "err", err) 1457 1469 s.pages.Notice(w, "pull", "Failed to create stacked pull request. Try again later.") 1458 1470 return 1459 1471 } ··· 1461 1473 // create all pulls at once 1462 1474 tx, err := s.db.BeginTx(r.Context(), nil) 1463 1475 if err != nil { 1464 - log.Println("failed to start tx") 1476 + s.logger.Error("failed to start tx") 1465 1477 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 1466 1478 return 1467 1479 } 1468 1480 defer tx.Rollback() 1469 1481 1470 1482 for _, p := range stack { 1471 - err = db.NewPull(tx, p) 1483 + err = db.PutPull(tx, p) 1472 1484 if err != nil { 1473 - log.Println("failed to create pull request", err) 1485 + s.logger.Error("failed to create pull request", "err", err) 1474 1486 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 1475 1487 return 1476 1488 } ··· 1478 1490 } 1479 1491 1480 1492 if err = tx.Commit(); err != nil { 1481 - log.Println("failed to create pull request", err) 1493 + s.logger.Error("failed to create pull request", "err", err) 1482 1494 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 1483 1495 return 1484 1496 } ··· 1497 1509 func (s *Pulls) ValidatePatch(w http.ResponseWriter, r *http.Request) { 1498 1510 _, err := s.repoResolver.Resolve(r) 1499 1511 if err != nil { 1500 - log.Println("failed to get repo and knot", err) 1512 + s.logger.Error("failed to get repo and knot", "err", err) 1501 1513 return 1502 1514 } 1503 1515 ··· 1508 1520 } 1509 1521 1510 1522 if err := s.validator.ValidatePatch(&patch); err != nil { 1511 - s.logger.Error("faield to validate patch", "err", err) 1523 + s.logger.Error("faield to validate patch", "err", "err", err) 1512 1524 s.pages.Notice(w, "patch-error", "Invalid patch format. Please provide a valid git diff or format-patch.") 1513 1525 return 1514 1526 } ··· 1924 1936 ) { 1925 1937 if pull.IsStacked() { 1926 1938 log.Println("resubmitting stacked PR") 1927 - s.resubmitStackedPullHelper(w, r, repo, user, pull, patch, pull.StackId) 1939 + s.resubmitStackedPullHelper(w, r, repo, user, pull, patch) 1928 1940 return 1929 1941 } 1930 1942 ··· 1946 1958 } 1947 1959 } 1948 1960 1949 - tx, err := s.db.BeginTx(r.Context(), nil) 1950 - if err != nil { 1951 - log.Println("failed to start tx") 1952 - s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") 1953 - return 1954 - } 1955 - defer tx.Rollback() 1956 - 1957 1961 pullAt := pull.AtUri() 1958 1962 newRoundNumber := len(pull.Submissions) 1959 1963 newPatch := patch 1960 1964 newSourceRev := sourceRev 1961 1965 combinedPatch := combined 1962 - err = db.ResubmitPull(tx, pullAt, newRoundNumber, newPatch, combinedPatch, newSourceRev) 1963 - if err != nil { 1964 - log.Println("failed to create pull request", err) 1965 - s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") 1966 - return 1967 - } 1966 + 1968 1967 client, err := s.oauth.AuthorizedClient(r) 1969 1968 if err != nil { 1970 1969 log.Println("failed to authorize client") ··· 1986 1985 return 1987 1986 } 1988 1987 record := pull.AsRecord() 1989 - record.PatchBlob = blob.Blob 1988 + record.Rounds = append(record.Rounds, &tangled.RepoPull_Round{ 1989 + CreatedAt: time.Now().Format(time.RFC3339), 1990 + PatchBlob: blob.Blob, 1991 + }) 1990 1992 record.CreatedAt = time.Now().Format(time.RFC3339) 1991 - 1992 - if record.Source != nil { 1993 - record.Source.Sha = newSourceRev 1994 - } 1995 1993 1996 1994 _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 1997 1995 Collection: tangled.RepoPullNSID, ··· 2008 2006 return 2009 2007 } 2010 2008 2011 - if err = tx.Commit(); err != nil { 2012 - log.Println("failed to commit transaction", err) 2013 - s.pages.Notice(w, "resubmit-error", "Failed to resubmit pull.") 2009 + err = db.ResubmitPull(s.db, pullAt, newRoundNumber, newPatch, combinedPatch, newSourceRev, blob.Blob) 2010 + if err != nil { 2011 + log.Println("failed to create pull request", err) 2012 + s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") 2014 2013 return 2015 2014 } 2016 2015 ··· 2025 2024 user *oauth.MultiAccountUser, 2026 2025 pull *models.Pull, 2027 2026 patch string, 2028 - stackId string, 2029 2027 ) { 2030 2028 targetBranch := pull.TargetBranch 2031 2029 2032 2030 origStack, _ := r.Context().Value("stack").(models.Stack) 2033 - newStack, err := s.newStack(r.Context(), repo, user, targetBranch, patch, pull.PullSource, stackId) 2031 + 2032 + formatPatches, err := patchutil.ExtractPatches(patch) 2033 + if err != nil { 2034 + s.logger.Error("Failed to extract patches", "err", err) 2035 + s.pages.Notice(w, "pull-resubmit-error", "Failed to resubmit pull request. Failed to parse patches.") 2036 + return 2037 + } 2038 + 2039 + // must have atleast 1 patch to begin with 2040 + if len(formatPatches) == 0 { 2041 + s.logger.Error("No patches found in the generated format-patch.") 2042 + s.pages.Notice(w, "pull-resubmit-error", "Failed to resubmit pull request: No patches found in the generated patch.") 2043 + return 2044 + } 2045 + 2046 + client, err := s.oauth.AuthorizedClient(r) 2047 + if err != nil { 2048 + s.logger.Error("failed to get authorized client", "err", err) 2049 + s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 2050 + return 2051 + } 2052 + 2053 + // first upload all blobs 2054 + blobs := make([]*lexutil.LexBlob, len(formatPatches)) 2055 + for i, p := range formatPatches { 2056 + blob, err := xrpc.RepoUploadBlob(r.Context(), client, gz(p.Raw), ApplicationGzip) 2057 + if err != nil { 2058 + s.logger.Error("failed to upload patch blob", "err", err) 2059 + s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 2060 + return 2061 + } 2062 + s.logger.Info("uploaded blob", "idx", i+1, "total", len(formatPatches)) 2063 + blobs[i] = blob.Blob 2064 + } 2065 + 2066 + newStack, err := s.newStack(r.Context(), repo, user, targetBranch, pull.PullSource, formatPatches, blobs) 2034 2067 if err != nil { 2035 2068 log.Println("failed to create resubmitted stack", err) 2036 2069 s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.") ··· 2041 2074 origById := make(map[string]*models.Pull) 2042 2075 newById := make(map[string]*models.Pull) 2043 2076 for _, p := range origStack { 2044 - origById[p.ChangeId] = p 2077 + origById[p.LatestSubmission().ChangeId()] = p 2045 2078 } 2046 2079 for _, p := range newStack { 2047 - newById[p.ChangeId] = p 2080 + newById[p.LatestSubmission().ChangeId()] = p 2048 2081 } 2049 2082 2050 2083 // commits that got deleted: corresponding pull is closed ··· 2056 2089 2057 2090 // pulls in orignal stack but not in new one 2058 2091 for _, op := range origStack { 2059 - if _, ok := newById[op.ChangeId]; !ok { 2060 - deletions[op.ChangeId] = op 2092 + if _, ok := newById[op.LatestSubmission().ChangeId()]; !ok { 2093 + deletions[op.LatestSubmission().ChangeId()] = op 2061 2094 } 2062 2095 } 2063 2096 2064 2097 // pulls in new stack but not in original one 2065 2098 for _, np := range newStack { 2066 - if _, ok := origById[np.ChangeId]; !ok { 2067 - additions[np.ChangeId] = np 2099 + if _, ok := origById[np.LatestSubmission().ChangeId()]; !ok { 2100 + additions[np.LatestSubmission().ChangeId()] = np 2068 2101 } 2069 2102 } 2070 2103 2071 2104 // NOTE: this loop can be written in any of above blocks, 2072 2105 // but is written separately in the interest of simpler code 2073 2106 for _, np := range newStack { 2074 - if op, ok := origById[np.ChangeId]; ok { 2107 + if op, ok := origById[np.LatestSubmission().ChangeId()]; ok { 2075 2108 // pull exists in both stacks 2076 - updated[op.ChangeId] = struct{}{} 2109 + updated[op.LatestSubmission().ChangeId()] = struct{}{} 2077 2110 } 2078 2111 } 2079 2112 ··· 2085 2118 } 2086 2119 defer tx.Rollback() 2087 2120 2088 - client, err := s.oauth.AuthorizedClient(r) 2089 - if err != nil { 2090 - log.Println("failed to authorize client") 2091 - s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") 2092 - return 2093 - } 2094 - 2095 2121 // pds updates to make 2096 2122 var writes []*comatproto.RepoApplyWrites_Input_Writes_Elem 2097 2123 ··· 2102 2128 continue 2103 2129 } 2104 2130 2105 - err := db.DeletePull(tx, p.RepoAt, p.PullId) 2131 + err := db.AbandonPull(tx, p.RepoAt, p.PullId) 2106 2132 if err != nil { 2107 2133 log.Println("failed to delete pull", err, p.PullId) 2108 2134 s.pages.Notice(w, "pull-resubmit-error", "Failed to resubmit pull request. Try again later.") ··· 2118 2144 2119 2145 // new pulls are created 2120 2146 for _, p := range additions { 2121 - err := db.NewPull(tx, p) 2147 + blob, err := xrpc.RepoUploadBlob(r.Context(), client, gz(patch), ApplicationGzip) 2122 2148 if err != nil { 2149 + log.Println("failed to upload patch blob", err) 2150 + s.pages.Notice(w, "resubmit-error", "Failed to update pull request on the PDS. Try again later.") 2151 + return 2152 + } 2153 + p.Submissions[0].Blob = *blob.Blob 2154 + 2155 + if err = db.PutPull(tx, p); err != nil { 2123 2156 log.Println("failed to create pull", err, p.PullId) 2124 2157 s.pages.Notice(w, "pull-resubmit-error", "Failed to resubmit pull request. Try again later.") 2125 2158 return 2126 2159 } 2127 2160 2128 - blob, err := xrpc.RepoUploadBlob(r.Context(), client, gz(patch), ApplicationGzip) 2129 - if err != nil { 2130 - log.Println("failed to upload patch blob", err) 2131 - s.pages.Notice(w, "resubmit-error", "Failed to update pull request on the PDS. Try again later.") 2132 - return 2161 + record := p.AsRecord() 2162 + record.Rounds = []*tangled.RepoPull_Round{ 2163 + { 2164 + CreatedAt: time.Now().Format(time.RFC3339), 2165 + PatchBlob: blob.Blob, 2166 + }, 2133 2167 } 2134 - record := p.AsRecord() 2135 - record.PatchBlob = blob.Blob 2136 2168 writes = append(writes, &comatproto.RepoApplyWrites_Input_Writes_Elem{ 2137 2169 RepoApplyWrites_Create: &comatproto.RepoApplyWrites_Create{ 2138 2170 Collection: tangled.RepoPullNSID, ··· 2160 2192 newPatch := np.LatestPatch() 2161 2193 combinedPatch := np.LatestSubmission().Combined 2162 2194 newSourceRev := np.LatestSha() 2163 - err := db.ResubmitPull(tx, pullAt, newRoundNumber, newPatch, combinedPatch, newSourceRev) 2164 - if err != nil { 2165 - log.Println("failed to update pull", err, op.PullId) 2166 - s.pages.Notice(w, "pull-resubmit-error", "Failed to resubmit pull request. Try again later.") 2167 - return 2168 - } 2169 2195 2170 2196 blob, err := xrpc.RepoUploadBlob(r.Context(), client, gz(patch), ApplicationGzip) 2171 2197 if err != nil { ··· 2173 2199 s.pages.Notice(w, "resubmit-error", "Failed to update pull request on the PDS. Try again later.") 2174 2200 return 2175 2201 } 2202 + 2203 + err = db.ResubmitPull(tx, pullAt, newRoundNumber, newPatch, combinedPatch, newSourceRev, blob.Blob) 2204 + if err != nil { 2205 + log.Println("failed to update pull", err, op.PullId) 2206 + s.pages.Notice(w, "pull-resubmit-error", "Failed to resubmit pull request. Try again later.") 2207 + return 2208 + } 2209 + 2176 2210 record := np.AsRecord() 2177 - record.PatchBlob = blob.Blob 2211 + record.Rounds = op.AsRecord().Rounds 2212 + record.Rounds = append(record.Rounds, &tangled.RepoPull_Round{ 2213 + CreatedAt: time.Now().Format(time.RFC3339), 2214 + PatchBlob: blob.Blob, 2215 + }) 2178 2216 writes = append(writes, &comatproto.RepoApplyWrites_Input_Writes_Elem{ 2179 2217 RepoApplyWrites_Update: &comatproto.RepoApplyWrites_Update{ 2180 2218 Collection: tangled.RepoPullNSID, ··· 2186 2224 }) 2187 2225 } 2188 2226 2189 - // update parent-change-id relations for the entire stack 2227 + // TODO: aturi is wrong here when newstack is produced 2228 + // update dependentOn relations for the entire stack 2190 2229 for _, p := range newStack { 2191 - err := db.SetPullParentChangeId( 2192 - tx, 2193 - p.ParentChangeId, 2194 - // these should be enough filters to be unique per-stack 2195 - orm.FilterEq("repo_at", p.RepoAt.String()), 2196 - orm.FilterEq("owner_did", p.OwnerDid), 2197 - orm.FilterEq("change_id", p.ChangeId), 2198 - ) 2230 + if p.DependentOn != nil { 2231 + err := db.SetDependentOn( 2232 + tx, 2233 + *p.DependentOn, 2234 + // these should be enough filters to be unique per-stack 2235 + orm.FilterEq("repo_at", p.RepoAt.String()), 2236 + orm.FilterEq("at_uri", p.AtUri), 2237 + ) 2199 2238 2200 - if err != nil { 2201 - log.Println("failed to update pull", err, p.PullId) 2202 - s.pages.Notice(w, "pull-resubmit-error", "Failed to resubmit pull request. Try again later.") 2203 - return 2239 + if err != nil { 2240 + log.Println("failed to update pull", err, p.PullId) 2241 + s.pages.Notice(w, "pull-resubmit-error", "Failed to resubmit pull request. Try again later.") 2242 + return 2243 + } 2204 2244 } 2205 2245 } 2206 2246 ··· 2491 2531 s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d", ownerSlashRepo, pull.PullId)) 2492 2532 } 2493 2533 2494 - func (s *Pulls) newStack(ctx context.Context, repo *models.Repo, user *oauth.MultiAccountUser, targetBranch, patch string, pullSource *models.PullSource, stackId string) (models.Stack, error) { 2495 - formatPatches, err := patchutil.ExtractPatches(patch) 2496 - if err != nil { 2497 - return nil, fmt.Errorf("Failed to extract patches: %v", err) 2498 - } 2499 - 2500 - // must have atleast 1 patch to begin with 2501 - if len(formatPatches) == 0 { 2502 - return nil, fmt.Errorf("No patches found in the generated format-patch.") 2503 - } 2504 - 2505 - // the stack is identified by a UUID 2534 + func (s *Pulls) newStack( 2535 + ctx context.Context, 2536 + repo *models.Repo, 2537 + user *oauth.MultiAccountUser, 2538 + targetBranch string, 2539 + pullSource *models.PullSource, 2540 + formatPatches []types.FormatPatch, 2541 + blobs []*lexutil.LexBlob, 2542 + ) (models.Stack, error) { 2506 2543 var stack models.Stack 2507 - parentChangeId := "" 2508 - for _, fp := range formatPatches { 2544 + var parentAtUri *syntax.ATURI 2545 + for i, fp := range formatPatches { 2509 2546 // all patches must have a jj change-id 2510 - changeId, err := fp.ChangeId() 2547 + _, err := fp.ChangeId() 2511 2548 if err != nil { 2512 2549 return nil, fmt.Errorf("Stacking is only supported if all patches contain a change-id commit header.") 2513 2550 } ··· 2522 2559 Patch: fp.Raw, 2523 2560 SourceRev: fp.SHA, 2524 2561 Combined: fp.Raw, 2562 + Blob: *blobs[i], 2525 2563 } 2526 2564 pull := models.Pull{ 2527 2565 Title: title, ··· 2537 2575 }, 2538 2576 PullSource: pullSource, 2539 2577 Created: time.Now(), 2578 + State: models.PullOpen, 2540 2579 2541 - StackId: stackId, 2542 - ChangeId: changeId, 2543 - ParentChangeId: parentChangeId, 2580 + DependentOn: parentAtUri, 2544 2581 } 2545 2582 2546 2583 stack = append(stack, &pull) 2547 2584 2548 - parentChangeId = changeId 2585 + parent := pull.AtUri() 2586 + parentAtUri = &parent 2549 2587 } 2550 2588 2551 2589 return stack, nil
+1
appview/state/state.go
··· 116 116 tangled.SpindleMemberNSID, 117 117 tangled.SpindleNSID, 118 118 tangled.StringNSID, 119 + tangled.RepoPullNSID, 119 120 tangled.RepoIssueNSID, 120 121 tangled.RepoIssueCommentNSID, 121 122 tangled.LabelDefinitionNSID,
+1
cmd/cborgen/cborgen.go
··· 50 50 tangled.RepoPull{}, 51 51 tangled.RepoPullComment{}, 52 52 tangled.RepoPull_Source{}, 53 + tangled.RepoPull_Round{}, 53 54 tangled.RepoPullStatus{}, 54 55 tangled.RepoPull_Target{}, 55 56 tangled.Spindle{},
+4 -2
knotserver/ingester.go
··· 149 149 return fmt.Errorf("failed to construct absolute repo path: %w", err) 150 150 } 151 151 152 - gr, err := git.Open(repoPath, record.Source.Sha) 152 + // TODO: fix this 153 + sha := "0000000000000000000000000000000000000000" 154 + gr, err := git.Open(repoPath, sha) 153 155 if err != nil { 154 156 return fmt.Errorf("failed to open git repository: %w", err) 155 157 } ··· 180 182 trigger := tangled.Pipeline_PullRequestTriggerData{ 181 183 Action: "create", 182 184 SourceBranch: record.Source.Branch, 183 - SourceSha: record.Source.Sha, 185 + SourceSha: sha, 184 186 TargetBranch: record.Target.Branch, 185 187 } 186 188
+35 -22
lexicons/pulls/pull.json
··· 12 12 "required": [ 13 13 "target", 14 14 "title", 15 - "patchBlob", 16 15 "createdAt" 17 16 ], 18 17 "properties": { 19 - "target": { 20 - "type": "ref", 21 - "ref": "#target" 22 - }, 23 18 "title": { 24 19 "type": "string" 25 20 }, 26 21 "body": { 27 22 "type": "string" 28 23 }, 29 - "patch": { 30 - "type": "string", 31 - "description": "(deprecated) use patchBlob instead" 32 - }, 33 - "patchBlob": { 34 - "type": "blob", 35 - "accept": [ 36 - "text/x-patch" 37 - ], 38 - "description": "patch content" 24 + "rounds": { 25 + "type": "array", 26 + "items": { 27 + "type": "ref", 28 + "ref": "#round" 29 + } 39 30 }, 40 31 "source": { 41 32 "type": "ref", 42 33 "ref": "#source" 34 + }, 35 + "target": { 36 + "type": "ref", 37 + "ref": "#target" 43 38 }, 44 39 "createdAt": { 45 40 "type": "string", ··· 58 53 "type": "string", 59 54 "format": "at-uri" 60 55 } 56 + }, 57 + "dependentOn": { 58 + "type": "string", 59 + "format": "at-uri" 61 60 } 62 61 } 63 62 } ··· 81 80 "source": { 82 81 "type": "object", 83 82 "required": [ 84 - "branch", 85 - "sha" 83 + "branch" 86 84 ], 87 85 "properties": { 88 86 "branch": { 89 87 "type": "string" 90 88 }, 91 - "sha": { 92 - "type": "string", 93 - "minLength": 40, 94 - "maxLength": 40 95 - }, 96 89 "repo": { 97 90 "type": "string", 98 91 "format": "at-uri" 92 + } 93 + } 94 + }, 95 + "round": { 96 + "type": "object", 97 + "required": [ 98 + "patchBlob", 99 + "createdAt" 100 + ], 101 + "description": "revisions of this pull request, newer rounds are appended to this array. appviews may reject records do not treat this field as append-only. the blob format is gzipped text-based git-format-patches.", 102 + "properties": { 103 + "createdAt": { 104 + "type": "string", 105 + "format": "datetime" 106 + }, 107 + "patchBlob": { 108 + "type": "blob", 109 + "accept": [ 110 + "application/gzip" 111 + ] 99 112 } 100 113 } 101 114 }
+7 -8
patchutil/patchutil.go
··· 17 17 func ExtractPatches(formatPatch string) ([]types.FormatPatch, error) { 18 18 patches := splitFormatPatch(formatPatch) 19 19 20 - result := []types.FormatPatch{} 21 - 22 - for _, patch := range patches { 20 + result := make([]types.FormatPatch, len(patches)) 21 + for i, patch := range patches { 23 22 files, headerStr, err := gitdiff.Parse(strings.NewReader(patch)) 24 23 if err != nil { 25 24 return nil, fmt.Errorf("failed to parse patch: %w", err) ··· 30 29 return nil, fmt.Errorf("failed to parse patch header: %w", err) 31 30 } 32 31 33 - result = append(result, types.FormatPatch{ 32 + result[i] = types.FormatPatch{ 34 33 Files: files, 35 34 PatchHeader: header, 36 35 Raw: patch, 37 - }) 36 + } 38 37 } 39 38 40 39 return result, nil ··· 114 113 return headerCount >= 2 115 114 } 116 115 117 - func splitFormatPatch(patchText string) []string { 118 - re := regexp.MustCompile(`(?m)^From [0-9a-f]{40} .*$`) 116 + var formatPatchRegex = regexp.MustCompile(`(?m)^From [0-9a-f]{40} .*$`) 119 117 120 - indexes := re.FindAllStringIndex(patchText, -1) 118 + func splitFormatPatch(patchText string) []string { 119 + indexes := formatPatchRegex.FindAllStringIndex(patchText, -1) 121 120 122 121 if len(indexes) == 0 { 123 122 return []string{}