forked from tangled.org/core
Monorepo for Tangled

appview: fix and refactor resubmit endpoint

- ksClient returns strong types in its API
- ResubmitPull is broken down into 3 flows similar to NewPull

there is quite a bit of duplication going on here; but i imagine this is solved by introducing the xrpc layer down the line.

authored by oppi.li and committed by Tangled 8ca7ffdd 7d3d8fe7

Changed files
+396 -206
appview
db
pages
templates
fragments
repo
state
+13 -3
appview/db/pulls.go
··· 122 122 return len(p.Submissions) - 1 123 123 } 124 124 125 - func (p *Pull) IsSameRepoBranch() bool { 125 + func (p *Pull) IsPatchBased() bool { 126 + return p.PullSource == nil 127 + } 128 + 129 + func (p *Pull) IsBranchBased() bool { 126 130 if p.PullSource != nil { 127 131 if p.PullSource.RepoAt != nil { 128 132 return p.PullSource.RepoAt == &p.RepoAt ··· 134 138 return false 135 139 } 136 140 137 - func (p *Pull) IsPatch() bool { 138 - return p.PullSource == nil 141 + func (p *Pull) IsForkBased() bool { 142 + if p.PullSource != nil { 143 + if p.PullSource.RepoAt != nil { 144 + // make sure repos are different 145 + return p.PullSource.RepoAt != &p.RepoAt 146 + } 147 + } 148 + return false 139 149 } 140 150 141 151 func (s PullSubmission) AsNiceDiff(targetBranch string) types.NiceDiff {
+2 -2
appview/pages/templates/fragments/pullActions.html
··· 9 9 {{ $isConflicted := and .MergeCheck (or .MergeCheck.Error .MergeCheck.IsConflicted) }} 10 10 {{ $isPullAuthor := and .LoggedInUser (eq .LoggedInUser.Did .Pull.OwnerDid) }} 11 11 {{ $isLastRound := eq $roundNumber $lastIdx }} 12 - {{ $isSameRepoBranch := .Pull.IsSameRepoBranch }} 12 + {{ $isSameRepoBranch := .Pull.IsBranchBased }} 13 13 {{ $isUpToDate := .ResubmitCheck.No }} 14 14 <div class="relative w-fit"> 15 15 <div id="actions-{{$roundNumber}}" class="flex flex-wrap gap-2"> ··· 42 42 {{ $disabled = "disabled" }} 43 43 {{ end }} 44 44 <button id="resubmitBtn" 45 - {{ if not .Pull.IsPatch }} 45 + {{ if not .Pull.IsPatchBased }} 46 46 hx-post="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/resubmit" 47 47 {{ else }} 48 48 hx-get="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/resubmit"
+3 -3
appview/pages/templates/repo/pulls/pull.html
··· 45 45 <a href="/{{ .RepoInfo.FullName }}/tree/{{ .Pull.TargetBranch }}" class="no-underline hover:underline">{{ .Pull.TargetBranch }}</a> 46 46 </span> 47 47 </span> 48 - {{ if not .Pull.IsPatch }} 48 + {{ if not .Pull.IsPatchBased }} 49 49 <span>from 50 - {{ if not .Pull.IsSameRepoBranch }} 50 + {{ if not .Pull.IsBranchBased }} 51 51 <a href="/{{ $owner }}/{{ .PullSourceRepo.Name }}" class="no-underline hover:underline">{{ $owner }}/{{ .PullSourceRepo.Name }}</a> 52 52 {{ end }} 53 53 54 54 {{ $fullRepo := .RepoInfo.FullName }} 55 - {{ if not .Pull.IsSameRepoBranch }} 55 + {{ if not .Pull.IsBranchBased }} 56 56 {{ $fullRepo = printf "%s/%s" $owner .PullSourceRepo.Name }} 57 57 {{ end }} 58 58 <span class="text-xs rounded bg-gray-100 dark:bg-gray-700 text-black dark:text-white font-mono px-2 mx-1/2 inline-flex items-center">
+2 -2
appview/pages/templates/repo/pulls/pulls.html
··· 78 78 {{ .TargetBranch }} 79 79 </span> 80 80 </span> 81 - {{ if not .IsPatch }} 81 + {{ if not .IsPatchBased }} 82 82 <span>from 83 - {{ if not .IsSameRepoBranch }} 83 + {{ if not .IsBranchBased }} 84 84 <a href="/{{ $owner }}/{{ .PullSource.Repo.Name }}" class="no-underline hover:underline">{{ $owner }}/{{ .PullSource.Repo.Name }}</a> 85 85 {{ end }} 86 86
+344 -192
appview/state/pull.go
··· 634 634 return 635 635 } 636 636 637 - resp, err := ksClient.Compare(f.OwnerDid(), f.RepoName, targetBranch, sourceBranch) 638 - switch resp.StatusCode { 639 - case 404: 640 - case 400: 641 - s.pages.Notice(w, "pull", "Branch based pull requests are not supported on this knot.") 642 - return 643 - } 644 - 645 - respBody, err := io.ReadAll(resp.Body) 637 + diffTreeResponse, err := ksClient.Compare(f.OwnerDid(), f.RepoName, targetBranch, sourceBranch) 646 638 if err != nil { 647 - log.Println("failed to compare across branches") 648 - s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 649 - return 650 - } 651 - defer resp.Body.Close() 652 - 653 - var diffTreeResponse types.RepoDiffTreeResponse 654 - err = json.Unmarshal(respBody, &diffTreeResponse) 655 - if err != nil { 656 - log.Println("failed to unmarshal diff tree response", err) 657 - s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 639 + log.Println("failed to compare", err) 640 + s.pages.Notice(w, "pull", err.Error()) 658 641 return 659 642 } 660 643 ··· 730 713 // hiddenRef: hidden/feature-1/main (on repo-fork) 731 714 // targetBranch: main (on repo-1) 732 715 // sourceBranch: feature-1 (on repo-fork) 733 - diffResp, err := us.Compare(user.Did, fork.Name, hiddenRef, sourceBranch) 716 + diffTreeResponse, err := us.Compare(user.Did, fork.Name, hiddenRef, sourceBranch) 734 717 if err != nil { 735 718 log.Println("failed to compare across branches", err) 736 - s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 737 - return 738 - } 739 - 740 - respBody, err := io.ReadAll(diffResp.Body) 741 - if err != nil { 742 - log.Println("failed to read response body", err) 743 - s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 744 - return 745 - } 746 - 747 - defer resp.Body.Close() 748 - 749 - var diffTreeResponse types.RepoDiffTreeResponse 750 - err = json.Unmarshal(respBody, &diffTreeResponse) 751 - if err != nil { 752 - log.Println("failed to unmarshal diff tree response", err) 753 - s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 719 + s.pages.Notice(w, "pull", err.Error()) 754 720 return 755 721 } 756 722 ··· 1015 981 }) 1016 982 return 1017 983 case http.MethodPost: 1018 - patch := r.FormValue("patch") 1019 - var sourceRev string 1020 - var recordPullSource *tangled.RepoPull_Source 1021 - 1022 - var ownerDid, repoName, knotName string 1023 - var isSameRepo bool = pull.IsSameRepoBranch() 1024 - sourceBranch := pull.PullSource.Branch 1025 - targetBranch := pull.TargetBranch 1026 - recordPullSource = &tangled.RepoPull_Source{ 1027 - Branch: sourceBranch, 984 + if pull.IsPatchBased() { 985 + s.resubmitPatch(w, r) 986 + return 987 + } else if pull.IsBranchBased() { 988 + s.resubmitBranch(w, r) 989 + return 990 + } else if pull.IsForkBased() { 991 + s.resubmitFork(w, r) 992 + return 1028 993 } 994 + } 995 + } 1029 996 1030 - isPushAllowed := f.RepoInfo(s, user).Roles.IsPushAllowed() 1031 - if isSameRepo && isPushAllowed { 1032 - ownerDid = f.OwnerDid() 1033 - repoName = f.RepoName 1034 - knotName = f.Knot 1035 - } else if !isSameRepo { 1036 - sourceRepo, err := db.GetRepoByAtUri(s.db, pull.PullSource.RepoAt.String()) 1037 - if err != nil { 1038 - log.Println("failed to get source repo", err) 1039 - s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 1040 - return 1041 - } 1042 - ownerDid = sourceRepo.Did 1043 - repoName = sourceRepo.Name 1044 - knotName = sourceRepo.Knot 1045 - } 997 + func (s *State) resubmitPatch(w http.ResponseWriter, r *http.Request) { 998 + user := s.auth.GetUser(r) 1046 999 1047 - if sourceBranch != "" && knotName != "" { 1048 - // extract patch by performing compare 1049 - ksClient, err := NewUnsignedClient(knotName, s.config.Dev) 1050 - if err != nil { 1051 - log.Printf("failed to create client for %s: %s", knotName, err) 1052 - s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 1053 - return 1054 - } 1000 + pull, ok := r.Context().Value("pull").(*db.Pull) 1001 + if !ok { 1002 + log.Println("failed to get pull") 1003 + s.pages.Notice(w, "pull-error", "Failed to edit patch. Try again later.") 1004 + return 1005 + } 1055 1006 1056 - if !isSameRepo { 1057 - secret, err := db.GetRegistrationKey(s.db, knotName) 1058 - if err != nil { 1059 - log.Printf("failed to get registration key for %s: %s", knotName, err) 1060 - s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 1061 - return 1062 - } 1063 - // update the hidden tracking branch to latest 1064 - signedClient, err := NewSignedClient(knotName, secret, s.config.Dev) 1065 - if err != nil { 1066 - log.Printf("failed to create signed client for %s: %s", knotName, err) 1067 - s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 1068 - return 1069 - } 1070 - resp, err := signedClient.NewHiddenRef(ownerDid, repoName, sourceBranch, targetBranch) 1071 - if err != nil || resp.StatusCode != http.StatusNoContent { 1072 - log.Printf("failed to update tracking branch: %s", err) 1073 - s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 1074 - return 1075 - } 1076 - } 1007 + f, err := fullyResolvedRepo(r) 1008 + if err != nil { 1009 + log.Println("failed to get repo and knot", err) 1010 + return 1011 + } 1077 1012 1078 - var compareResp *http.Response 1079 - if !isSameRepo { 1080 - hiddenRef := url.QueryEscape(fmt.Sprintf("hidden/%s/%s", sourceBranch, targetBranch)) 1081 - compareResp, err = ksClient.Compare(ownerDid, repoName, hiddenRef, sourceBranch) 1082 - } else { 1083 - compareResp, err = ksClient.Compare(ownerDid, repoName, targetBranch, sourceBranch) 1084 - } 1085 - if err != nil { 1086 - log.Printf("failed to compare branches: %s", err) 1087 - s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 1088 - return 1089 - } 1090 - defer compareResp.Body.Close() 1013 + if user.Did != pull.OwnerDid { 1014 + log.Println("unauthorized user") 1015 + w.WriteHeader(http.StatusUnauthorized) 1016 + return 1017 + } 1091 1018 1092 - switch compareResp.StatusCode { 1093 - case 404: 1094 - case 400: 1095 - s.pages.Notice(w, "pull", "Branch based pull requests are not supported on this knot.") 1096 - return 1097 - } 1019 + patch := r.FormValue("patch") 1098 1020 1099 - respBody, err := io.ReadAll(compareResp.Body) 1100 - if err != nil { 1101 - log.Println("failed to compare across branches") 1102 - s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 1103 - return 1104 - } 1105 - defer compareResp.Body.Close() 1021 + if err = validateResubmittedPatch(pull, patch); err != nil { 1022 + s.pages.Notice(w, "resubmit-error", err.Error()) 1023 + } 1106 1024 1107 - var diffTreeResponse types.RepoDiffTreeResponse 1108 - err = json.Unmarshal(respBody, &diffTreeResponse) 1109 - if err != nil { 1110 - log.Println("failed to unmarshal diff tree response", err) 1111 - s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 1112 - return 1113 - } 1025 + tx, err := s.db.BeginTx(r.Context(), nil) 1026 + if err != nil { 1027 + log.Println("failed to start tx") 1028 + s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") 1029 + return 1030 + } 1031 + defer tx.Rollback() 1114 1032 1115 - sourceRev = diffTreeResponse.DiffTree.Rev2 1116 - patch = diffTreeResponse.DiffTree.Patch 1117 - } 1033 + err = db.ResubmitPull(tx, pull, patch, "") 1034 + if err != nil { 1035 + log.Println("failed to resubmit pull request", err) 1036 + s.pages.Notice(w, "resubmit-error", "Failed to resubmit pull request. Try again later.") 1037 + return 1038 + } 1039 + client, _ := s.auth.AuthorizedClient(r) 1118 1040 1119 - if patch == "" { 1120 - s.pages.Notice(w, "resubmit-error", "Patch is empty.") 1121 - return 1122 - } 1041 + ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoPullNSID, user.Did, pull.Rkey) 1042 + if err != nil { 1043 + // failed to get record 1044 + s.pages.Notice(w, "resubmit-error", "Failed to update pull, no record found on PDS.") 1045 + return 1046 + } 1123 1047 1124 - if patch == pull.LatestPatch() { 1125 - s.pages.Notice(w, "resubmit-error", "Patch is identical to previous submission.") 1126 - return 1127 - } 1048 + _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 1049 + Collection: tangled.RepoPullNSID, 1050 + Repo: user.Did, 1051 + Rkey: pull.Rkey, 1052 + SwapRecord: ex.Cid, 1053 + Record: &lexutil.LexiconTypeDecoder{ 1054 + Val: &tangled.RepoPull{ 1055 + Title: pull.Title, 1056 + PullId: int64(pull.PullId), 1057 + TargetRepo: string(f.RepoAt), 1058 + TargetBranch: pull.TargetBranch, 1059 + Patch: patch, // new patch 1060 + }, 1061 + }, 1062 + }) 1063 + if err != nil { 1064 + log.Println("failed to update record", err) 1065 + s.pages.Notice(w, "resubmit-error", "Failed to update pull request on the PDS. Try again later.") 1066 + return 1067 + } 1128 1068 1129 - if sourceRev == pull.Submissions[pull.LastRoundNumber()].SourceRev { 1130 - s.pages.Notice(w, "resubmit-error", "This branch has not changed since the last submission.") 1131 - return 1132 - } 1069 + if err = tx.Commit(); err != nil { 1070 + log.Println("failed to commit transaction", err) 1071 + s.pages.Notice(w, "resubmit-error", "Failed to resubmit pull.") 1072 + return 1073 + } 1133 1074 1134 - if !isPatchValid(patch) { 1135 - s.pages.Notice(w, "resubmit-error", "Invalid patch format. Please provide a valid diff.") 1136 - return 1137 - } 1075 + s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d", f.OwnerSlashRepo(), pull.PullId)) 1076 + return 1077 + } 1138 1078 1139 - tx, err := s.db.BeginTx(r.Context(), nil) 1140 - if err != nil { 1141 - log.Println("failed to start tx") 1142 - s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") 1143 - return 1144 - } 1145 - defer tx.Rollback() 1079 + func (s *State) resubmitBranch(w http.ResponseWriter, r *http.Request) { 1080 + user := s.auth.GetUser(r) 1146 1081 1147 - err = db.ResubmitPull(tx, pull, patch, sourceRev) 1148 - if err != nil { 1149 - log.Println("failed to create pull request", err) 1150 - s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") 1151 - return 1152 - } 1153 - client, _ := s.auth.AuthorizedClient(r) 1082 + pull, ok := r.Context().Value("pull").(*db.Pull) 1083 + if !ok { 1084 + log.Println("failed to get pull") 1085 + s.pages.Notice(w, "resubmit-error", "Failed to edit patch. Try again later.") 1086 + return 1087 + } 1154 1088 1155 - ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoPullNSID, user.Did, pull.Rkey) 1156 - if err != nil { 1157 - // failed to get record 1158 - s.pages.Notice(w, "resubmit-error", "Failed to update pull, no record found on PDS.") 1159 - return 1160 - } 1089 + f, err := fullyResolvedRepo(r) 1090 + if err != nil { 1091 + log.Println("failed to get repo and knot", err) 1092 + return 1093 + } 1161 1094 1162 - _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 1163 - Collection: tangled.RepoPullNSID, 1164 - Repo: user.Did, 1165 - Rkey: pull.Rkey, 1166 - SwapRecord: ex.Cid, 1167 - Record: &lexutil.LexiconTypeDecoder{ 1168 - Val: &tangled.RepoPull{ 1169 - Title: pull.Title, 1170 - PullId: int64(pull.PullId), 1171 - TargetRepo: string(f.RepoAt), 1172 - TargetBranch: pull.TargetBranch, 1173 - Patch: patch, // new patch 1174 - Source: recordPullSource, 1175 - }, 1095 + if user.Did != pull.OwnerDid { 1096 + log.Println("unauthorized user") 1097 + w.WriteHeader(http.StatusUnauthorized) 1098 + return 1099 + } 1100 + 1101 + if !f.RepoInfo(s, user).Roles.IsPushAllowed() { 1102 + log.Println("unauthorized user") 1103 + w.WriteHeader(http.StatusUnauthorized) 1104 + return 1105 + } 1106 + 1107 + ksClient, err := NewUnsignedClient(f.Knot, s.config.Dev) 1108 + if err != nil { 1109 + log.Printf("failed to create client for %s: %s", f.Knot, err) 1110 + s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") 1111 + return 1112 + } 1113 + 1114 + diffTreeResponse, err := ksClient.Compare(f.OwnerDid(), f.RepoName, pull.TargetBranch, pull.PullSource.Branch) 1115 + if err != nil { 1116 + log.Printf("compare request failed: %s", err) 1117 + s.pages.Notice(w, "resubmit-error", err.Error()) 1118 + return 1119 + } 1120 + 1121 + sourceRev := diffTreeResponse.DiffTree.Rev2 1122 + patch := diffTreeResponse.DiffTree.Patch 1123 + 1124 + if err = validateResubmittedPatch(pull, patch); err != nil { 1125 + s.pages.Notice(w, "resubmit-error", err.Error()) 1126 + } 1127 + 1128 + if sourceRev == pull.Submissions[pull.LastRoundNumber()].SourceRev { 1129 + s.pages.Notice(w, "resubmit-error", "This branch has not changed since the last submission.") 1130 + return 1131 + } 1132 + 1133 + tx, err := s.db.BeginTx(r.Context(), nil) 1134 + if err != nil { 1135 + log.Println("failed to start tx") 1136 + s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") 1137 + return 1138 + } 1139 + defer tx.Rollback() 1140 + 1141 + err = db.ResubmitPull(tx, pull, patch, sourceRev) 1142 + if err != nil { 1143 + log.Println("failed to create pull request", err) 1144 + s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") 1145 + return 1146 + } 1147 + client, _ := s.auth.AuthorizedClient(r) 1148 + 1149 + ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoPullNSID, user.Did, pull.Rkey) 1150 + if err != nil { 1151 + // failed to get record 1152 + s.pages.Notice(w, "resubmit-error", "Failed to update pull, no record found on PDS.") 1153 + return 1154 + } 1155 + 1156 + recordPullSource := &tangled.RepoPull_Source{ 1157 + Branch: pull.PullSource.Branch, 1158 + } 1159 + _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 1160 + Collection: tangled.RepoPullNSID, 1161 + Repo: user.Did, 1162 + Rkey: pull.Rkey, 1163 + SwapRecord: ex.Cid, 1164 + Record: &lexutil.LexiconTypeDecoder{ 1165 + Val: &tangled.RepoPull{ 1166 + Title: pull.Title, 1167 + PullId: int64(pull.PullId), 1168 + TargetRepo: string(f.RepoAt), 1169 + TargetBranch: pull.TargetBranch, 1170 + Patch: patch, // new patch 1171 + Source: recordPullSource, 1176 1172 }, 1177 - }) 1178 - if err != nil { 1179 - log.Println("failed to update record", err) 1180 - s.pages.Notice(w, "resubmit-error", "Failed to update pull request on the PDS. Try again later.") 1181 - return 1182 - } 1173 + }, 1174 + }) 1175 + if err != nil { 1176 + log.Println("failed to update record", err) 1177 + s.pages.Notice(w, "resubmit-error", "Failed to update pull request on the PDS. Try again later.") 1178 + return 1179 + } 1180 + 1181 + if err = tx.Commit(); err != nil { 1182 + log.Println("failed to commit transaction", err) 1183 + s.pages.Notice(w, "resubmit-error", "Failed to resubmit pull.") 1184 + return 1185 + } 1186 + 1187 + s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d", f.OwnerSlashRepo(), pull.PullId)) 1188 + return 1189 + } 1190 + 1191 + func (s *State) resubmitFork(w http.ResponseWriter, r *http.Request) { 1192 + user := s.auth.GetUser(r) 1183 1193 1184 - if err = tx.Commit(); err != nil { 1185 - log.Println("failed to commit transaction", err) 1186 - s.pages.Notice(w, "resubmit-error", "Failed to resubmit pull.") 1187 - return 1188 - } 1194 + pull, ok := r.Context().Value("pull").(*db.Pull) 1195 + if !ok { 1196 + log.Println("failed to get pull") 1197 + s.pages.Notice(w, "resubmit-error", "Failed to edit patch. Try again later.") 1198 + return 1199 + } 1189 1200 1190 - s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d", f.OwnerSlashRepo(), pull.PullId)) 1201 + f, err := fullyResolvedRepo(r) 1202 + if err != nil { 1203 + log.Println("failed to get repo and knot", err) 1191 1204 return 1192 1205 } 1206 + 1207 + if user.Did != pull.OwnerDid { 1208 + log.Println("unauthorized user") 1209 + w.WriteHeader(http.StatusUnauthorized) 1210 + return 1211 + } 1212 + 1213 + forkRepo, err := db.GetRepoByAtUri(s.db, pull.PullSource.RepoAt.String()) 1214 + if err != nil { 1215 + log.Println("failed to get source repo", err) 1216 + s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") 1217 + return 1218 + } 1219 + 1220 + // extract patch by performing compare 1221 + ksClient, err := NewUnsignedClient(forkRepo.Knot, s.config.Dev) 1222 + if err != nil { 1223 + log.Printf("failed to create client for %s: %s", forkRepo.Knot, err) 1224 + s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") 1225 + return 1226 + } 1227 + 1228 + secret, err := db.GetRegistrationKey(s.db, forkRepo.Knot) 1229 + if err != nil { 1230 + log.Printf("failed to get registration key for %s: %s", forkRepo.Knot, err) 1231 + s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") 1232 + return 1233 + } 1234 + 1235 + // update the hidden tracking branch to latest 1236 + signedClient, err := NewSignedClient(forkRepo.Knot, secret, s.config.Dev) 1237 + if err != nil { 1238 + log.Printf("failed to create signed client for %s: %s", forkRepo.Knot, err) 1239 + s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") 1240 + return 1241 + } 1242 + 1243 + resp, err := signedClient.NewHiddenRef(forkRepo.Did, forkRepo.Name, pull.PullSource.Branch, pull.TargetBranch) 1244 + if err != nil || resp.StatusCode != http.StatusNoContent { 1245 + log.Printf("failed to update tracking branch: %s", err) 1246 + s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") 1247 + return 1248 + } 1249 + 1250 + hiddenRef := url.QueryEscape(fmt.Sprintf("hidden/%s/%s", pull.PullSource.Branch, pull.TargetBranch)) 1251 + diffTreeResponse, err := ksClient.Compare(forkRepo.Did, forkRepo.Name, hiddenRef, pull.PullSource.Branch) 1252 + if err != nil { 1253 + log.Printf("failed to compare branches: %s", err) 1254 + s.pages.Notice(w, "resubmit-error", err.Error()) 1255 + return 1256 + } 1257 + 1258 + sourceRev := diffTreeResponse.DiffTree.Rev2 1259 + patch := diffTreeResponse.DiffTree.Patch 1260 + 1261 + if err = validateResubmittedPatch(pull, patch); err != nil { 1262 + s.pages.Notice(w, "resubmit-error", err.Error()) 1263 + } 1264 + 1265 + if sourceRev == pull.Submissions[pull.LastRoundNumber()].SourceRev { 1266 + s.pages.Notice(w, "resubmit-error", "This branch has not changed since the last submission.") 1267 + return 1268 + } 1269 + 1270 + tx, err := s.db.BeginTx(r.Context(), nil) 1271 + if err != nil { 1272 + log.Println("failed to start tx") 1273 + s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") 1274 + return 1275 + } 1276 + defer tx.Rollback() 1277 + 1278 + err = db.ResubmitPull(tx, pull, patch, sourceRev) 1279 + if err != nil { 1280 + log.Println("failed to create pull request", err) 1281 + s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") 1282 + return 1283 + } 1284 + client, _ := s.auth.AuthorizedClient(r) 1285 + 1286 + ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoPullNSID, user.Did, pull.Rkey) 1287 + if err != nil { 1288 + // failed to get record 1289 + s.pages.Notice(w, "resubmit-error", "Failed to update pull, no record found on PDS.") 1290 + return 1291 + } 1292 + 1293 + repoAt := pull.PullSource.RepoAt.String() 1294 + recordPullSource := &tangled.RepoPull_Source{ 1295 + Branch: pull.PullSource.Branch, 1296 + Repo: &repoAt, 1297 + } 1298 + _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 1299 + Collection: tangled.RepoPullNSID, 1300 + Repo: user.Did, 1301 + Rkey: pull.Rkey, 1302 + SwapRecord: ex.Cid, 1303 + Record: &lexutil.LexiconTypeDecoder{ 1304 + Val: &tangled.RepoPull{ 1305 + Title: pull.Title, 1306 + PullId: int64(pull.PullId), 1307 + TargetRepo: string(f.RepoAt), 1308 + TargetBranch: pull.TargetBranch, 1309 + Patch: patch, // new patch 1310 + Source: recordPullSource, 1311 + }, 1312 + }, 1313 + }) 1314 + if err != nil { 1315 + log.Println("failed to update record", err) 1316 + s.pages.Notice(w, "resubmit-error", "Failed to update pull request on the PDS. Try again later.") 1317 + return 1318 + } 1319 + 1320 + if err = tx.Commit(); err != nil { 1321 + log.Println("failed to commit transaction", err) 1322 + s.pages.Notice(w, "resubmit-error", "Failed to resubmit pull.") 1323 + return 1324 + } 1325 + 1326 + s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d", f.OwnerSlashRepo(), pull.PullId)) 1327 + return 1328 + } 1329 + 1330 + // validate a resubmission against a pull request 1331 + func validateResubmittedPatch(pull *db.Pull, patch string) error { 1332 + if patch == "" { 1333 + return fmt.Errorf("Patch is empty.") 1334 + } 1335 + 1336 + if patch == pull.LatestPatch() { 1337 + return fmt.Errorf("Patch is identical to previous submission.") 1338 + } 1339 + 1340 + if !isPatchValid(patch) { 1341 + return fmt.Errorf("Invalid patch format. Please provide a valid diff.") 1342 + } 1343 + 1344 + return nil 1193 1345 } 1194 1346 1195 1347 func (s *State) MergePull(w http.ResponseWriter, r *http.Request) {
+31 -3
appview/state/signer.go
··· 7 7 "encoding/hex" 8 8 "encoding/json" 9 9 "fmt" 10 + "io" 11 + "log" 10 12 "net/http" 11 13 "net/url" 12 14 "time" ··· 376 378 return &capabilities, nil 377 379 } 378 380 379 - func (us *UnsignedClient) Compare(ownerDid, repoName, rev1, rev2 string) (*http.Response, error) { 381 + func (us *UnsignedClient) Compare(ownerDid, repoName, rev1, rev2 string) (*types.RepoDiffTreeResponse, error) { 380 382 const ( 381 383 Method = "GET" 382 384 ) ··· 385 387 386 388 req, err := us.newRequest(Method, endpoint, nil) 387 389 if err != nil { 388 - return nil, err 390 + return nil, fmt.Errorf("Failed to create request.") 389 391 } 390 392 391 - return us.client.Do(req) 393 + compareResp, err := us.client.Do(req) 394 + if err != nil { 395 + return nil, fmt.Errorf("Failed to create request.") 396 + } 397 + defer compareResp.Body.Close() 398 + 399 + switch compareResp.StatusCode { 400 + case 404: 401 + case 400: 402 + return nil, fmt.Errorf("Branch comparisons not supported on this knot.") 403 + } 404 + 405 + respBody, err := io.ReadAll(compareResp.Body) 406 + if err != nil { 407 + log.Println("failed to compare across branches") 408 + return nil, fmt.Errorf("Failed to compare branches.") 409 + } 410 + defer compareResp.Body.Close() 411 + 412 + var diffTreeResponse types.RepoDiffTreeResponse 413 + err = json.Unmarshal(respBody, &diffTreeResponse) 414 + if err != nil { 415 + log.Println("failed to unmarshal diff tree response", err) 416 + return nil, fmt.Errorf("Failed to compare branches.") 417 + } 418 + 419 + return &diffTreeResponse, nil 392 420 }
+1 -1
flake.nix
··· 420 420 g = config.services.tangled-knotserver.gitUser; 421 421 in [ 422 422 "d /var/lib/knotserver 0770 ${u} ${g} - -" # Create the directory first 423 - "f+ /var/lib/knotserver/secret 0660 ${u} ${g} - KNOT_SERVER_SECRET=6995e040e80e2d593b5e5e9ca611a70140b9ef8044add0a28b48b1ee34aa3e85" 423 + "f+ /var/lib/knotserver/secret 0660 ${u} ${g} - KNOT_SERVER_SECRET=5b42390da4c6659f34c9a545adebd8af82c4a19960d735f651e3d582623ba9f2" 424 424 ]; 425 425 services.tangled-knotserver = { 426 426 enable = true;