···
"tangled.sh/tangled.sh/core/api/tangled"
···
"tangled.sh/tangled.sh/core/patchutil"
"tangled.sh/tangled.sh/core/types"
comatproto "github.com/bluesky-social/indigo/api/atproto"
"github.com/bluesky-social/indigo/atproto/syntax"
lexutil "github.com/bluesky-social/indigo/lex/util"
···
// can be nil if this pull is not stacked
-
stack := r.Context().Value("stack").(db.Stack)
roundNumberStr := chi.URLParam(r, "round")
roundNumber, err := strconv.Atoi(roundNumberStr)
···
mergeCheckResponse := s.mergeCheck(f, pull, stack)
resubmitResult := pages.Unknown
if user.Did == pull.OwnerDid {
-
resubmitResult = s.resubmitCheck(f, pull)
s.pages.PullActionsFragment(w, pages.PullActionsParams{
···
// can be nil if this pull is not stacked
-
stack := r.Context().Value("stack").(db.Stack)
for _, submission := range pull.Submissions {
···
mergeCheckResponse := s.mergeCheck(f, pull, stack)
resubmitResult := pages.Unknown
if user != nil && user.Did == pull.OwnerDid {
-
resubmitResult = s.resubmitCheck(f, pull)
s.pages.RepoSinglePull(w, pages.RepoSinglePullParams{
···
for _, p := range subStack {
// stop at the first merged PR
-
if p.State == db.PullMerged {
-
// skip over closed PRs
-
// we will close PRs that are "removed" from a stack
-
if p.State != db.PullClosed {
mergeable = append(mergeable, p)
···
return mergeCheckResponse
-
func (s *State) resubmitCheck(f *FullyResolvedRepo, pull *db.Pull) pages.ResubmitResult {
if pull.State == db.PullMerged || pull.PullSource == nil {
···
-
latestSubmission := pull.Submissions[pull.LastRoundNumber()]
-
if latestSubmission.SourceRev != result.Branch.Hash {
-
fmt.Println(latestSubmission.SourceRev, result.Branch.Hash)
return pages.ShouldResubmit
···
RepoInfo: f.RepoInfo(s, user),
Branches: result.Branches,
title := r.FormValue("title")
body := r.FormValue("body")
···
-
title, body, targetBranch,
···
-
title, body, targetBranch string,
pullSource *db.PullSource,
-
recordPullSource *tangled.RepoPull_Source,
// run some necessary checks for stacked-prs first
···
formatPatches, err := patchutil.ExtractPatches(patch)
s.pages.Notice(w, "pull", fmt.Sprintf("Failed to extract patches: %v", err))
// must have atleast 1 patch to begin with
if len(formatPatches) == 0 {
s.pages.Notice(w, "pull", "No patches found in the generated format-patch.")
-
tx, err := s.db.BeginTx(r.Context(), nil)
-
log.Println("failed to start tx")
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
-
// create a series of pull requests, and write records from them at once
var writes []*comatproto.RepoApplyWrites_Input_Writes_Elem
-
// the stack is identified by a UUID
-
for _, fp := range formatPatches {
-
// all patches must have a jj change-id
-
changeId, err := fp.ChangeId()
-
s.pages.Notice(w, "pull", "Stacking is only supported if all patches contain a change-id commit header.")
-
// TODO: can we just use a format-patch string here?
-
initialSubmission := db.PullSubmission{
-
err = db.NewPull(tx, &db.Pull{
-
TargetBranch: targetBranch,
-
Submissions: []*db.PullSubmission{
-
PullSource: pullSource,
-
StackId: stackId.String(),
-
ParentChangeId: parentChangeId,
-
log.Println("failed to create pull request", err)
-
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
-
record := tangled.RepoPull{
-
TargetRepo: string(f.RepoAt),
-
TargetBranch: targetBranch,
-
Source: recordPullSource,
-
writes = append(writes, &comatproto.RepoApplyWrites_Input_Writes_Elem{
RepoApplyWrites_Create: &comatproto.RepoApplyWrites_Create{
Collection: tangled.RepoPullNSID,
Value: &lexutil.LexiconTypeDecoder{
-
parentChangeId = changeId
-
client, err := s.oauth.AuthorizedClient(r)
-
log.Println("failed to get authorized client", err)
-
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
-
// apply all record creations at once
_, err = client.RepoApplyWrites(r.Context(), &comatproto.RepoApplyWrites_Input{
···
// create all pulls at once
if err = tx.Commit(); err != nil {
log.Println("failed to create pull request", err)
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
···
patch := r.FormValue("patch")
-
if err = validateResubmittedPatch(pull, patch); err != nil {
-
s.pages.Notice(w, "resubmit-error", err.Error())
-
tx, err := s.db.BeginTx(r.Context(), nil)
-
log.Println("failed to start tx")
-
s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.")
-
err = db.ResubmitPull(tx, pull, patch, "")
-
log.Println("failed to resubmit pull request", err)
-
s.pages.Notice(w, "resubmit-error", "Failed to resubmit pull request. Try again later.")
-
client, err := s.oauth.AuthorizedClient(r)
-
log.Println("failed to get authorized client", err)
-
s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.")
-
ex, err := client.RepoGetRecord(r.Context(), "", tangled.RepoPullNSID, user.Did, pull.Rkey)
-
// failed to get record
-
s.pages.Notice(w, "resubmit-error", "Failed to update pull, no record found on PDS.")
-
_, err = client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{
-
Collection: tangled.RepoPullNSID,
-
Record: &lexutil.LexiconTypeDecoder{
-
Val: &tangled.RepoPull{
-
PullId: int64(pull.PullId),
-
TargetRepo: string(f.RepoAt),
-
TargetBranch: pull.TargetBranch,
-
Patch: patch, // new patch
-
log.Println("failed to update record", err)
-
s.pages.Notice(w, "resubmit-error", "Failed to update pull request on the PDS. Try again later.")
-
if err = tx.Commit(); err != nil {
-
log.Println("failed to commit transaction", err)
-
s.pages.Notice(w, "resubmit-error", "Failed to resubmit pull.")
-
s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d", f.OwnerSlashRepo(), pull.PullId))
func (s *State) resubmitBranch(w http.ResponseWriter, r *http.Request) {
···
sourceRev := comparison.Rev2
patch := comparison.Patch
-
if err = validateResubmittedPatch(pull, patch); err != nil {
-
s.pages.Notice(w, "resubmit-error", err.Error())
-
if sourceRev == pull.Submissions[pull.LastRoundNumber()].SourceRev {
-
s.pages.Notice(w, "resubmit-error", "This branch has not changed since the last submission.")
-
tx, err := s.db.BeginTx(r.Context(), nil)
-
log.Println("failed to start tx")
-
s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.")
-
err = db.ResubmitPull(tx, pull, patch, sourceRev)
-
log.Println("failed to create pull request", err)
-
s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.")
-
client, err := s.oauth.AuthorizedClient(r)
-
log.Println("failed to authorize client")
-
s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.")
-
ex, err := client.RepoGetRecord(r.Context(), "", tangled.RepoPullNSID, user.Did, pull.Rkey)
-
// failed to get record
-
s.pages.Notice(w, "resubmit-error", "Failed to update pull, no record found on PDS.")
-
recordPullSource := &tangled.RepoPull_Source{
-
Branch: pull.PullSource.Branch,
-
_, err = client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{
-
Collection: tangled.RepoPullNSID,
-
Record: &lexutil.LexiconTypeDecoder{
-
Val: &tangled.RepoPull{
-
PullId: int64(pull.PullId),
-
TargetRepo: string(f.RepoAt),
-
TargetBranch: pull.TargetBranch,
-
Patch: patch, // new patch
-
Source: recordPullSource,
-
log.Println("failed to update record", err)
-
s.pages.Notice(w, "resubmit-error", "Failed to update pull request on the PDS. Try again later.")
-
if err = tx.Commit(); err != nil {
-
log.Println("failed to commit transaction", err)
-
s.pages.Notice(w, "resubmit-error", "Failed to resubmit pull.")
-
s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d", f.OwnerSlashRepo(), pull.PullId))
func (s *State) resubmitFork(w http.ResponseWriter, r *http.Request) {
···
sourceRev := comparison.Rev2
patch := comparison.Patch
-
if err = validateResubmittedPatch(pull, patch); err != nil {
-
s.pages.Notice(w, "resubmit-error", err.Error())
-
if sourceRev == pull.Submissions[pull.LastRoundNumber()].SourceRev {
-
s.pages.Notice(w, "resubmit-error", "This branch has not changed since the last submission.")
tx, err := s.db.BeginTx(r.Context(), nil)
log.Println("failed to start tx")
···
client, err := s.oauth.AuthorizedClient(r)
-
log.Println("failed to get client")
s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.")
···
-
repoAt := pull.PullSource.RepoAt.String()
-
recordPullSource := &tangled.RepoPull_Source{
-
Branch: pull.PullSource.Branch,
_, err = client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{
Collection: tangled.RepoPullNSID,
···
-
// validate a resubmission against a pull request
-
func validateResubmittedPatch(pull *db.Pull, patch string) error {
-
return fmt.Errorf("Patch is empty.")
-
if patch == pull.LatestPatch() {
-
return fmt.Errorf("Patch is identical to previous submission.")
-
if !patchutil.IsPatchValid(patch) {
-
return fmt.Errorf("Invalid patch format. Please provide a valid diff.")
func (s *State) MergePull(w http.ResponseWriter, r *http.Request) {
···
// collect the portion of the stack that is mergeable
for _, p := range subStack {
-
// stop at the first merged PR
-
if p.State == db.PullMerged {
-
// skip over closed PRs
-
// TODO: we need a "deleted" state for such PRs, but without losing discussions
-
// we will close PRs that are "removed" from a stack
-
if p.State == db.PullClosed {
···
-
log.Printf("failed to start transcation", err)
s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.")
for _, p := range pullsToMerge {
err := db.MergePull(tx, f.RepoAt, p.PullId)
···
s.pages.Notice(w, "pull-close", "Failed to close pull.")
var pullsToClose []*db.Pull
pullsToClose = append(pullsToClose, pull)
···
s.pages.Notice(w, "pull-reopen", "Failed to reopen pull.")
var pullsToReopen []*db.Pull
pullsToReopen = append(pullsToReopen, pull)
···
s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d", f.OwnerSlashRepo(), pull.PullId))
···
"tangled.sh/tangled.sh/core/api/tangled"
···
"tangled.sh/tangled.sh/core/patchutil"
"tangled.sh/tangled.sh/core/types"
+
"github.com/bluekeyes/go-gitdiff/gitdiff"
comatproto "github.com/bluesky-social/indigo/api/atproto"
"github.com/bluesky-social/indigo/atproto/syntax"
lexutil "github.com/bluesky-social/indigo/lex/util"
···
// can be nil if this pull is not stacked
+
stack, _ := r.Context().Value("stack").(db.Stack)
roundNumberStr := chi.URLParam(r, "round")
roundNumber, err := strconv.Atoi(roundNumberStr)
···
mergeCheckResponse := s.mergeCheck(f, pull, stack)
resubmitResult := pages.Unknown
if user.Did == pull.OwnerDid {
+
resubmitResult = s.resubmitCheck(f, pull, stack)
s.pages.PullActionsFragment(w, pages.PullActionsParams{
···
// can be nil if this pull is not stacked
+
stack, _ := r.Context().Value("stack").(db.Stack)
for _, submission := range pull.Submissions {
···
mergeCheckResponse := s.mergeCheck(f, pull, stack)
resubmitResult := pages.Unknown
if user != nil && user.Did == pull.OwnerDid {
+
resubmitResult = s.resubmitCheck(f, pull, stack)
s.pages.RepoSinglePull(w, pages.RepoSinglePullParams{
···
for _, p := range subStack {
// stop at the first merged PR
+
if p.State == db.PullMerged || p.State == db.PullClosed {
+
// skip over deleted PRs
+
if p.State != db.PullDeleted {
mergeable = append(mergeable, p)
···
return mergeCheckResponse
+
func (s *State) resubmitCheck(f *FullyResolvedRepo, pull *db.Pull, stack db.Stack) pages.ResubmitResult {
if pull.State == db.PullMerged || pull.PullSource == nil {
···
+
latestSourceRev := pull.Submissions[pull.LastRoundNumber()].SourceRev
+
if pull.IsStacked() && stack != nil {
+
latestSourceRev = top.Submissions[top.LastRoundNumber()].SourceRev
+
if latestSourceRev != result.Branch.Hash {
+
log.Println(latestSourceRev, result.Branch.Hash)
return pages.ShouldResubmit
···
RepoInfo: f.RepoInfo(s, user),
Branches: result.Branches,
title := r.FormValue("title")
body := r.FormValue("body")
···
···
pullSource *db.PullSource,
// run some necessary checks for stacked-prs first
···
formatPatches, err := patchutil.ExtractPatches(patch)
+
log.Println("failed to extract patches", err)
s.pages.Notice(w, "pull", fmt.Sprintf("Failed to extract patches: %v", err))
// must have atleast 1 patch to begin with
if len(formatPatches) == 0 {
+
log.Println("empty patches")
s.pages.Notice(w, "pull", "No patches found in the generated format-patch.")
+
// build a stack out of this patch
+
stack, err := newStack(f, user, targetBranch, patch, pullSource, stackId.String())
+
log.Println("failed to create stack", err)
+
s.pages.Notice(w, "pull", fmt.Sprintf("Failed to create stack: %v", err))
+
client, err := s.oauth.AuthorizedClient(r)
+
log.Println("failed to get authorized client", err)
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
+
// apply all record creations at once
var writes []*comatproto.RepoApplyWrites_Input_Writes_Elem
+
for _, p := range stack {
+
write := comatproto.RepoApplyWrites_Input_Writes_Elem{
RepoApplyWrites_Create: &comatproto.RepoApplyWrites_Create{
Collection: tangled.RepoPullNSID,
Value: &lexutil.LexiconTypeDecoder{
+
writes = append(writes, &write)
_, err = client.RepoApplyWrites(r.Context(), &comatproto.RepoApplyWrites_Input{
···
// create all pulls at once
+
tx, err := s.db.BeginTx(r.Context(), nil)
+
log.Println("failed to start tx")
+
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
+
for _, p := range stack {
+
err = db.NewPull(tx, p)
+
log.Println("failed to create pull request", err)
+
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
if err = tx.Commit(); err != nil {
log.Println("failed to create pull request", err)
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
···
patch := r.FormValue("patch")
+
s.resubmitPullHelper(w, r, f, user, pull, patch, "")
func (s *State) resubmitBranch(w http.ResponseWriter, r *http.Request) {
···
sourceRev := comparison.Rev2
patch := comparison.Patch
+
s.resubmitPullHelper(w, r, f, user, pull, patch, sourceRev)
func (s *State) resubmitFork(w http.ResponseWriter, r *http.Request) {
···
sourceRev := comparison.Rev2
patch := comparison.Patch
+
s.resubmitPullHelper(w, r, f, user, pull, patch, sourceRev)
+
// validate a resubmission against a pull request
+
func validateResubmittedPatch(pull *db.Pull, patch string) error {
+
return fmt.Errorf("Patch is empty.")
+
if patch == pull.LatestPatch() {
+
return fmt.Errorf("Patch is identical to previous submission.")
+
if !patchutil.IsPatchValid(patch) {
+
return fmt.Errorf("Invalid patch format. Please provide a valid diff.")
+
func (s *State) resubmitPullHelper(
+
log.Println("resubmitting stacked PR")
+
s.resubmitStackedPullHelper(w, r, f, user, pull, patch, pull.StackId)
+
if err := validateResubmittedPatch(pull, patch); err != nil {
+
s.pages.Notice(w, "resubmit-error", err.Error())
+
// validate sourceRev if branch/fork based
+
if pull.IsBranchBased() || pull.IsForkBased() {
+
if sourceRev == pull.Submissions[pull.LastRoundNumber()].SourceRev {
+
s.pages.Notice(w, "resubmit-error", "This branch has not changed since the last submission.")
tx, err := s.db.BeginTx(r.Context(), nil)
log.Println("failed to start tx")
···
client, err := s.oauth.AuthorizedClient(r)
+
log.Println("failed to authorize client")
s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.")
···
+
var recordPullSource *tangled.RepoPull_Source
+
if pull.IsBranchBased() {
+
recordPullSource = &tangled.RepoPull_Source{
+
Branch: pull.PullSource.Branch,
+
if pull.IsForkBased() {
+
repoAt := pull.PullSource.RepoAt.String()
+
recordPullSource = &tangled.RepoPull_Source{
+
Branch: pull.PullSource.Branch,
_, err = client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{
Collection: tangled.RepoPullNSID,
···
+
func (s *State) resubmitStackedPullHelper(
+
targetBranch := pull.TargetBranch
+
origStack, _ := r.Context().Value("stack").(db.Stack)
+
newStack, err := newStack(f, user, targetBranch, patch, pull.PullSource, stackId)
+
log.Println("failed to create resubmitted stack", err)
+
s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.")
+
// find the diff between the stacks, first, map them by changeId
+
origById := make(map[string]*db.Pull)
+
newById := make(map[string]*db.Pull)
+
for _, p := range origStack {
+
origById[p.ChangeId] = p
+
for _, p := range newStack {
+
newById[p.ChangeId] = p
+
// commits that got deleted: corresponding pull is closed
+
// commits that got added: new pull is created
+
// commits that got updated: corresponding pull is resubmitted & new round begins
+
// for commits that were unchanged: no changes, parent-change-id is updated as necessary
+
additions := make(map[string]*db.Pull)
+
deletions := make(map[string]*db.Pull)
+
unchanged := make(map[string]struct{})
+
updated := make(map[string]struct{})
+
// pulls in orignal stack but not in new one
+
for _, op := range origStack {
+
if _, ok := newById[op.ChangeId]; !ok {
+
deletions[op.ChangeId] = op
+
// pulls in new stack but not in original one
+
for _, np := range newStack {
+
if _, ok := origById[np.ChangeId]; !ok {
+
additions[np.ChangeId] = np
+
// NOTE: this loop can be written in any of above blocks,
+
// but is written separately in the interest of simpler code
+
for _, np := range newStack {
+
if op, ok := origById[np.ChangeId]; ok {
+
// pull exists in both stacks
+
// TODO: can we avoid reparse?
+
origFiles, _, _ := gitdiff.Parse(strings.NewReader(op.LatestPatch()))
+
newFiles, _, _ := gitdiff.Parse(strings.NewReader(np.LatestPatch()))
+
patchutil.SortPatch(newFiles)
+
patchutil.SortPatch(origFiles)
+
if patchutil.Equal(newFiles, origFiles) {
+
unchanged[op.ChangeId] = struct{}{}
+
updated[op.ChangeId] = struct{}{}
+
tx, err := s.db.Begin()
+
log.Println("failed to start transaction", err)
+
s.pages.Notice(w, "pull-resubmit-error", "Failed to resubmit pull request. Try again later.")
+
var writes []*comatproto.RepoApplyWrites_Input_Writes_Elem
+
// deleted pulls are marked as deleted in the DB
+
for _, p := range deletions {
+
err := db.DeletePull(tx, p.RepoAt, p.PullId)
+
log.Println("failed to delete pull", err, p.PullId)
+
s.pages.Notice(w, "pull-resubmit-error", "Failed to resubmit pull request. Try again later.")
+
writes = append(writes, &comatproto.RepoApplyWrites_Input_Writes_Elem{
+
RepoApplyWrites_Delete: &comatproto.RepoApplyWrites_Delete{
+
Collection: tangled.RepoPullNSID,
+
// new pulls are created
+
for _, p := range additions {
+
err := db.NewPull(tx, p)
+
log.Println("failed to create pull", err, p.PullId)
+
s.pages.Notice(w, "pull-resubmit-error", "Failed to resubmit pull request. Try again later.")
+
writes = append(writes, &comatproto.RepoApplyWrites_Input_Writes_Elem{
+
RepoApplyWrites_Create: &comatproto.RepoApplyWrites_Create{
+
Collection: tangled.RepoPullNSID,
+
Value: &lexutil.LexiconTypeDecoder{
+
// updated pulls are, well, updated; to start a new round
+
for id := range updated {
+
submission := np.Submissions[np.LastRoundNumber()]
+
// resubmit the old pull
+
err := db.ResubmitPull(tx, op, submission.Patch, submission.SourceRev)
+
log.Println("failed to update pull", err, op.PullId)
+
s.pages.Notice(w, "pull-resubmit-error", "Failed to resubmit pull request. Try again later.")
+
record := op.AsRecord()
+
record.Patch = submission.Patch
+
writes = append(writes, &comatproto.RepoApplyWrites_Input_Writes_Elem{
+
RepoApplyWrites_Update: &comatproto.RepoApplyWrites_Update{
+
Collection: tangled.RepoPullNSID,
+
Value: &lexutil.LexiconTypeDecoder{
+
// update parent-change-id relations for the entire stack
+
for _, p := range newStack {
+
err := db.SetPullParentChangeId(
+
// these should be enough filters to be unique per-stack
+
db.Filter("repo_at", p.RepoAt.String()),
+
db.Filter("owner_did", p.OwnerDid),
+
db.Filter("change_id", p.ChangeId),
+
log.Println("failed to update pull", err, p.PullId)
+
s.pages.Notice(w, "pull-resubmit-error", "Failed to resubmit pull request. Try again later.")
+
log.Println("failed to resubmit pull", err)
+
s.pages.Notice(w, "pull-resubmit-error", "Failed to resubmit pull request. Try again later.")
+
client, err := s.oauth.AuthorizedClient(r)
+
log.Println("failed to authorize client")
+
s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.")
+
_, err = client.RepoApplyWrites(r.Context(), &comatproto.RepoApplyWrites_Input{
+
log.Println("failed to create stacked pull request", err)
+
s.pages.Notice(w, "pull", "Failed to create stacked pull request. Try again later.")
+
s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d", f.OwnerSlashRepo(), pull.PullId))
func (s *State) MergePull(w http.ResponseWriter, r *http.Request) {
···
// collect the portion of the stack that is mergeable
for _, p := range subStack {
+
// stop at the first merged/closed PR
+
if p.State == db.PullMerged || p.State == db.PullClosed {
+
// skip over deleted PRs
+
if p.State == db.PullDeleted {
···
+
log.Println("failed to start transcation", err)
s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.")
for _, p := range pullsToMerge {
err := db.MergePull(tx, f.RepoAt, p.PullId)
···
s.pages.Notice(w, "pull-close", "Failed to close pull.")
var pullsToClose []*db.Pull
pullsToClose = append(pullsToClose, pull)
···
s.pages.Notice(w, "pull-reopen", "Failed to reopen pull.")
var pullsToReopen []*db.Pull
pullsToReopen = append(pullsToReopen, pull)
···
s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d", f.OwnerSlashRepo(), pull.PullId))
+
func newStack(f *FullyResolvedRepo, user *oauth.User, targetBranch, patch string, pullSource *db.PullSource, stackId string) (db.Stack, error) {
+
formatPatches, err := patchutil.ExtractPatches(patch)
+
return nil, fmt.Errorf("Failed to extract patches: %v", err)
+
// must have atleast 1 patch to begin with
+
if len(formatPatches) == 0 {
+
return nil, fmt.Errorf("No patches found in the generated format-patch.")
+
// the stack is identified by a UUID
+
for _, fp := range formatPatches {
+
// all patches must have a jj change-id
+
changeId, err := fp.ChangeId()
+
return nil, fmt.Errorf("Stacking is only supported if all patches contain a change-id commit header.")
+
initialSubmission := db.PullSubmission{
+
TargetBranch: targetBranch,
+
Submissions: []*db.PullSubmission{
+
PullSource: pullSource,
+
ParentChangeId: parentChangeId,
+
stack = append(stack, &pull)
+
parentChangeId = changeId