Monorepo for Tangled tangled.org

lexicons,appview/pulls: upload patches as blobs

By using blobs we massively increase our maximum patch size. When stored
directly on the record patches can be at most 2 MiB. By moving it to a
blob, we get a minimum of 50 MiB (smaller if the pds has set it but 50
is the default).

legacy field name `patch` in `repo.pull` lexicon is preserved (but not
used) to support some level of backward compatibility

Signed-off-by: Samuel Shuert <me@thecoded.prof>
Signed-off-by: Seongmin Lee <git@boltless.me>

Changed files
+148 -68
api
appview
models
pulls
lexicons
pulls
+79 -20
api/tangled/cbor_gen.go
··· 7934 } 7935 7936 cw := cbg.NewCborWriter(w) 7937 - fieldCount := 9 7938 7939 if t.Body == nil { 7940 fieldCount-- 7941 } 7942 7943 if t.Mentions == nil { 7944 fieldCount-- 7945 } 7946 ··· 8008 } 8009 8010 // t.Patch (string) (string) 8011 - if len("patch") > 1000000 { 8012 - return xerrors.Errorf("Value in field \"patch\" was too long") 8013 - } 8014 8015 - if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("patch"))); err != nil { 8016 - return err 8017 - } 8018 - if _, err := cw.WriteString(string("patch")); err != nil { 8019 - return err 8020 - } 8021 8022 - if len(t.Patch) > 1000000 { 8023 - return xerrors.Errorf("Value in field t.Patch was too long") 8024 - } 8025 8026 - if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Patch))); err != nil { 8027 - return err 8028 - } 8029 - if _, err := cw.WriteString(string(t.Patch)); err != nil { 8030 - return err 8031 } 8032 8033 // t.Title (string) (string) ··· 8147 return err 8148 } 8149 8150 // t.References ([]string) (slice) 8151 if t.References != nil { 8152 ··· 8262 case "patch": 8263 8264 { 8265 - sval, err := cbg.ReadStringWithMax(cr, 1000000) 8266 if err != nil { 8267 return err 8268 } 8269 8270 - t.Patch = string(sval) 8271 } 8272 // t.Title (string) (string) 8273 case "title": ··· 8370 } 8371 8372 t.CreatedAt = string(sval) 8373 } 8374 // t.References ([]string) (slice) 8375 case "references":
··· 7934 } 7935 7936 cw := cbg.NewCborWriter(w) 7937 + fieldCount := 10 7938 7939 if t.Body == nil { 7940 fieldCount-- 7941 } 7942 7943 if t.Mentions == nil { 7944 + fieldCount-- 7945 + } 7946 + 7947 + if t.Patch == nil { 7948 fieldCount-- 7949 } 7950 ··· 8012 } 8013 8014 // t.Patch (string) (string) 8015 + if t.Patch != nil { 8016 8017 + if len("patch") > 1000000 { 8018 + return xerrors.Errorf("Value in field \"patch\" was too long") 8019 + } 8020 8021 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("patch"))); err != nil { 8022 + return err 8023 + } 8024 + if _, err := cw.WriteString(string("patch")); err != nil { 8025 + return err 8026 + } 8027 + 8028 + if t.Patch == nil { 8029 + if _, err := cw.Write(cbg.CborNull); err != nil { 8030 + return err 8031 + } 8032 + } else { 8033 + if len(*t.Patch) > 1000000 { 8034 + return xerrors.Errorf("Value in field t.Patch was too long") 8035 + } 8036 8037 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*t.Patch))); err != nil { 8038 + return err 8039 + } 8040 + if _, err := cw.WriteString(string(*t.Patch)); err != nil { 8041 + return err 8042 + } 8043 + } 8044 } 8045 8046 // t.Title (string) (string) ··· 8160 return err 8161 } 8162 8163 + // t.PatchBlob (util.LexBlob) (struct) 8164 + if len("patchBlob") > 1000000 { 8165 + return xerrors.Errorf("Value in field \"patchBlob\" was too long") 8166 + } 8167 + 8168 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("patchBlob"))); err != nil { 8169 + return err 8170 + } 8171 + if _, err := cw.WriteString(string("patchBlob")); err != nil { 8172 + return err 8173 + } 8174 + 8175 + if err := t.PatchBlob.MarshalCBOR(cw); err != nil { 8176 + return err 8177 + } 8178 + 8179 // t.References ([]string) (slice) 8180 if t.References != nil { 8181 ··· 8291 case "patch": 8292 8293 { 8294 + b, err := cr.ReadByte() 8295 if err != nil { 8296 return err 8297 } 8298 + if b != cbg.CborNull[0] { 8299 + if err := cr.UnreadByte(); err != nil { 8300 + return err 8301 + } 8302 8303 + sval, err := cbg.ReadStringWithMax(cr, 1000000) 8304 + if err != nil { 8305 + return err 8306 + } 8307 + 8308 + t.Patch = (*string)(&sval) 8309 + } 8310 } 8311 // t.Title (string) (string) 8312 case "title": ··· 8409 } 8410 8411 t.CreatedAt = string(sval) 8412 + } 8413 + // t.PatchBlob (util.LexBlob) (struct) 8414 + case "patchBlob": 8415 + 8416 + { 8417 + 8418 + b, err := cr.ReadByte() 8419 + if err != nil { 8420 + return err 8421 + } 8422 + if b != cbg.CborNull[0] { 8423 + if err := cr.UnreadByte(); err != nil { 8424 + return err 8425 + } 8426 + t.PatchBlob = new(util.LexBlob) 8427 + if err := t.PatchBlob.UnmarshalCBOR(cr); err != nil { 8428 + return xerrors.Errorf("unmarshaling t.PatchBlob pointer: %w", err) 8429 + } 8430 + } 8431 + 8432 } 8433 // t.References ([]string) (slice) 8434 case "references":
+12 -9
api/tangled/repopull.go
··· 17 } // 18 // RECORDTYPE: RepoPull 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 string `json:"patch" cborgen:"patch"` 25 - References []string `json:"references,omitempty" cborgen:"references,omitempty"` 26 - Source *RepoPull_Source `json:"source,omitempty" cborgen:"source,omitempty"` 27 - Target *RepoPull_Target `json:"target" cborgen:"target"` 28 - Title string `json:"title" cborgen:"title"` 29 } 30 31 // RepoPull_Source is a "source" in the sh.tangled.repo.pull schema.
··· 17 } // 18 // RECORDTYPE: RepoPull 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"` 32 } 33 34 // RepoPull_Source is a "source" in the sh.tangled.repo.pull schema.
+1 -1
appview/models/pull.go
··· 83 Repo *Repo 84 } 85 86 func (p Pull) AsRecord() tangled.RepoPull { 87 var source *tangled.RepoPull_Source 88 if p.PullSource != nil { ··· 113 Repo: p.RepoAt.String(), 114 Branch: p.TargetBranch, 115 }, 116 - Patch: p.LatestPatch(), 117 Source: source, 118 } 119 return record
··· 83 Repo *Repo 84 } 85 86 + // NOTE: This method does not include patch blob in returned atproto record 87 func (p Pull) AsRecord() tangled.RepoPull { 88 var source *tangled.RepoPull_Source 89 if p.PullSource != nil { ··· 114 Repo: p.RepoAt.String(), 115 Branch: p.TargetBranch, 116 }, 117 Source: source, 118 } 119 return record
+48 -36
appview/pulls/pulls.go
··· 1241 return 1242 } 1243 1244 _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 1245 Collection: tangled.RepoPullNSID, 1246 Repo: user.Did, ··· 1252 Repo: string(repo.RepoAt()), 1253 Branch: targetBranch, 1254 }, 1255 - Patch: patch, 1256 Source: recordPullSource, 1257 CreatedAt: time.Now().Format(time.RFC3339), 1258 }, ··· 1328 // apply all record creations at once 1329 var writes []*comatproto.RepoApplyWrites_Input_Writes_Elem 1330 for _, p := range stack { 1331 record := p.AsRecord() 1332 - write := comatproto.RepoApplyWrites_Input_Writes_Elem{ 1333 RepoApplyWrites_Create: &comatproto.RepoApplyWrites_Create{ 1334 Collection: tangled.RepoPullNSID, 1335 Rkey: &p.Rkey, ··· 1337 Val: &record, 1338 }, 1339 }, 1340 - } 1341 - writes = append(writes, &write) 1342 } 1343 _, err = comatproto.RepoApplyWrites(r.Context(), client, &comatproto.RepoApplyWrites_Input{ 1344 Repo: user.Did, ··· 1871 return 1872 } 1873 1874 - var recordPullSource *tangled.RepoPull_Source 1875 - if pull.IsBranchBased() { 1876 - recordPullSource = &tangled.RepoPull_Source{ 1877 - Branch: pull.PullSource.Branch, 1878 - Sha: sourceRev, 1879 - } 1880 } 1881 - if pull.IsForkBased() { 1882 - repoAt := pull.PullSource.RepoAt.String() 1883 - recordPullSource = &tangled.RepoPull_Source{ 1884 - Branch: pull.PullSource.Branch, 1885 - Repo: &repoAt, 1886 - Sha: sourceRev, 1887 - } 1888 - } 1889 1890 _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 1891 Collection: tangled.RepoPullNSID, ··· 1893 Rkey: pull.Rkey, 1894 SwapRecord: ex.Cid, 1895 Record: &lexutil.LexiconTypeDecoder{ 1896 - Val: &tangled.RepoPull{ 1897 - Title: pull.Title, 1898 - Target: &tangled.RepoPull_Target{ 1899 - Repo: string(repo.RepoAt()), 1900 - Branch: pull.TargetBranch, 1901 - }, 1902 - Patch: patch, // new patch 1903 - Source: recordPullSource, 1904 - CreatedAt: time.Now().Format(time.RFC3339), 1905 - }, 1906 }, 1907 }) 1908 if err != nil { ··· 1988 } 1989 defer tx.Rollback() 1990 1991 // pds updates to make 1992 var writes []*comatproto.RepoApplyWrites_Input_Writes_Elem 1993 ··· 2021 return 2022 } 2023 2024 record := p.AsRecord() 2025 writes = append(writes, &comatproto.RepoApplyWrites_Input_Writes_Elem{ 2026 RepoApplyWrites_Create: &comatproto.RepoApplyWrites_Create{ 2027 Collection: tangled.RepoPullNSID, ··· 2056 return 2057 } 2058 2059 record := np.AsRecord() 2060 - 2061 writes = append(writes, &comatproto.RepoApplyWrites_Input_Writes_Elem{ 2062 RepoApplyWrites_Update: &comatproto.RepoApplyWrites_Update{ 2063 Collection: tangled.RepoPullNSID, ··· 2091 if err != nil { 2092 log.Println("failed to resubmit pull", err) 2093 s.pages.Notice(w, "pull-resubmit-error", "Failed to resubmit pull request. Try again later.") 2094 - return 2095 - } 2096 - 2097 - client, err := s.oauth.AuthorizedClient(r) 2098 - if err != nil { 2099 - log.Println("failed to authorize client") 2100 - s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") 2101 return 2102 } 2103
··· 1241 return 1242 } 1243 1244 + blob, err := comatproto.RepoUploadBlob(r.Context(), client, strings.NewReader(patch)) 1245 + if err != nil { 1246 + log.Println("failed to upload patch", err) 1247 + s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 1248 + return 1249 + } 1250 + 1251 _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 1252 Collection: tangled.RepoPullNSID, 1253 Repo: user.Did, ··· 1259 Repo: string(repo.RepoAt()), 1260 Branch: targetBranch, 1261 }, 1262 + PatchBlob: blob.Blob, 1263 Source: recordPullSource, 1264 CreatedAt: time.Now().Format(time.RFC3339), 1265 }, ··· 1335 // apply all record creations at once 1336 var writes []*comatproto.RepoApplyWrites_Input_Writes_Elem 1337 for _, p := range stack { 1338 + blob, err := comatproto.RepoUploadBlob(r.Context(), client, strings.NewReader(p.LatestPatch())) 1339 + if err != nil { 1340 + log.Println("failed to upload patch blob", err) 1341 + s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 1342 + return 1343 + } 1344 + 1345 record := p.AsRecord() 1346 + record.PatchBlob = blob.Blob 1347 + writes = append(writes, &comatproto.RepoApplyWrites_Input_Writes_Elem{ 1348 RepoApplyWrites_Create: &comatproto.RepoApplyWrites_Create{ 1349 Collection: tangled.RepoPullNSID, 1350 Rkey: &p.Rkey, ··· 1352 Val: &record, 1353 }, 1354 }, 1355 + }) 1356 } 1357 _, err = comatproto.RepoApplyWrites(r.Context(), client, &comatproto.RepoApplyWrites_Input{ 1358 Repo: user.Did, ··· 1885 return 1886 } 1887 1888 + blob, err := comatproto.RepoUploadBlob(r.Context(), client, strings.NewReader(patch)) 1889 + if err != nil { 1890 + log.Println("failed to upload patch blob", err) 1891 + s.pages.Notice(w, "resubmit-error", "Failed to update pull request on the PDS. Try again later.") 1892 + return 1893 } 1894 + record := pull.AsRecord() 1895 + record.PatchBlob = blob.Blob 1896 + record.CreatedAt = time.Now().Format(time.RFC3339) 1897 1898 _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 1899 Collection: tangled.RepoPullNSID, ··· 1901 Rkey: pull.Rkey, 1902 SwapRecord: ex.Cid, 1903 Record: &lexutil.LexiconTypeDecoder{ 1904 + Val: &record, 1905 }, 1906 }) 1907 if err != nil { ··· 1987 } 1988 defer tx.Rollback() 1989 1990 + client, err := s.oauth.AuthorizedClient(r) 1991 + if err != nil { 1992 + log.Println("failed to authorize client") 1993 + s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") 1994 + return 1995 + } 1996 + 1997 // pds updates to make 1998 var writes []*comatproto.RepoApplyWrites_Input_Writes_Elem 1999 ··· 2027 return 2028 } 2029 2030 + blob, err := comatproto.RepoUploadBlob(r.Context(), client, strings.NewReader(patch)) 2031 + if err != nil { 2032 + log.Println("failed to upload patch blob", err) 2033 + s.pages.Notice(w, "resubmit-error", "Failed to update pull request on the PDS. Try again later.") 2034 + return 2035 + } 2036 record := p.AsRecord() 2037 + record.PatchBlob = blob.Blob 2038 writes = append(writes, &comatproto.RepoApplyWrites_Input_Writes_Elem{ 2039 RepoApplyWrites_Create: &comatproto.RepoApplyWrites_Create{ 2040 Collection: tangled.RepoPullNSID, ··· 2069 return 2070 } 2071 2072 + blob, err := comatproto.RepoUploadBlob(r.Context(), client, strings.NewReader(patch)) 2073 + if err != nil { 2074 + log.Println("failed to upload patch blob", err) 2075 + s.pages.Notice(w, "resubmit-error", "Failed to update pull request on the PDS. Try again later.") 2076 + return 2077 + } 2078 record := np.AsRecord() 2079 + record.PatchBlob = blob.Blob 2080 writes = append(writes, &comatproto.RepoApplyWrites_Input_Writes_Elem{ 2081 RepoApplyWrites_Update: &comatproto.RepoApplyWrites_Update{ 2082 Collection: tangled.RepoPullNSID, ··· 2110 if err != nil { 2111 log.Println("failed to resubmit pull", err) 2112 s.pages.Notice(w, "pull-resubmit-error", "Failed to resubmit pull request. Try again later.") 2113 return 2114 } 2115
+8 -2
lexicons/pulls/pull.json
··· 12 "required": [ 13 "target", 14 "title", 15 - "patch", 16 "createdAt" 17 ], 18 "properties": { ··· 27 "type": "string" 28 }, 29 "patch": { 30 - "type": "string" 31 }, 32 "source": { 33 "type": "ref",
··· 12 "required": [ 13 "target", 14 "title", 15 + "patchBlob", 16 "createdAt" 17 ], 18 "properties": { ··· 27 "type": "string" 28 }, 29 "patch": { 30 + "type": "string", 31 + "description": "(deprecated) use patchBlob instead" 32 + }, 33 + "patchBlob": { 34 + "type": "blob", 35 + "accept": "text/x-patch", 36 + "description": "patch content" 37 }, 38 "source": { 39 "type": "ref",