Skip to content

Commit 29e3f1d

Browse files
authored
Merge pull request #16 from manicminer/feature/skip-invalid-merge-requests
Optionally skip invalid merge requests
2 parents 0c6a5a8 + 4b65953 commit 29e3f1d

File tree

3 files changed

+77
-73
lines changed

3 files changed

+77
-73
lines changed

helper.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package main
22

33
import (
4+
"bytes"
45
"context"
6+
"encoding/json"
57
"fmt"
8+
"io"
9+
"net/http"
610
"time"
711

812
"github.com/google/go-github/v74/github"
@@ -122,3 +126,38 @@ func roundDuration(d, r time.Duration) time.Duration {
122126
}
123127
return d
124128
}
129+
130+
func sendErr(err error) {
131+
errCount++
132+
logger.Error(err.Error())
133+
}
134+
135+
func unmarshalResp(resp *http.Response, model interface{}) error {
136+
if resp == nil {
137+
return nil
138+
}
139+
140+
respBody, err := io.ReadAll(resp.Body)
141+
if err != nil {
142+
return fmt.Errorf("parsing response body: %+v", err)
143+
}
144+
_ = resp.Body.Close()
145+
146+
// Trim away a BOM if present
147+
respBody = bytes.TrimPrefix(respBody, []byte("\xef\xbb\xbf"))
148+
149+
// In some cases the respBody is empty, but not nil, so don't attempt to unmarshal this
150+
if len(respBody) == 0 {
151+
return nil
152+
}
153+
154+
// Unmarshal into provided model
155+
if err := json.Unmarshal(respBody, model); err != nil {
156+
return fmt.Errorf("unmarshaling response body: %+v", err)
157+
}
158+
159+
// Reassign the response body as downstream code may expect it
160+
resp.Body = io.NopCloser(bytes.NewBuffer(respBody))
161+
162+
return nil
163+
}

main.go

Lines changed: 2 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,8 @@ import (
44
"bytes"
55
"context"
66
"encoding/csv"
7-
"encoding/json"
87
"flag"
98
"fmt"
10-
"io"
119
"math"
1210
"net/http"
1311
"os"
@@ -33,7 +31,7 @@ const (
3331
)
3432

3533
var loop, report bool
36-
var deleteExistingRepos, enablePullRequests, renameMasterToMain bool
34+
var deleteExistingRepos, enablePullRequests, renameMasterToMain, skipInvalidMergeRequests bool
3735
var githubDomain, githubRepo, githubToken, githubUser, gitlabDomain, gitlabProject, gitlabToken, projectsCsvPath, renameTrunkBranch string
3836

3937
var (
@@ -58,41 +56,6 @@ type GitHubError struct {
5856
DocumentationURL string `json:"documentation_url"`
5957
}
6058

61-
func sendErr(err error) {
62-
errCount++
63-
logger.Error(err.Error())
64-
}
65-
66-
func unmarshalResp(resp *http.Response, model interface{}) error {
67-
if resp == nil {
68-
return nil
69-
}
70-
71-
respBody, err := io.ReadAll(resp.Body)
72-
if err != nil {
73-
return fmt.Errorf("parsing response body: %+v", err)
74-
}
75-
_ = resp.Body.Close()
76-
77-
// Trim away a BOM if present
78-
respBody = bytes.TrimPrefix(respBody, []byte("\xef\xbb\xbf"))
79-
80-
// In some cases the respBody is empty, but not nil, so don't attempt to unmarshal this
81-
if len(respBody) == 0 {
82-
return nil
83-
}
84-
85-
// Unmarshal into provided model
86-
if err := json.Unmarshal(respBody, model); err != nil {
87-
return fmt.Errorf("unmarshaling response body: %+v", err)
88-
}
89-
90-
// Reassign the response body as downstream code may expect it
91-
resp.Body = io.NopCloser(bytes.NewBuffer(respBody))
92-
93-
return nil
94-
}
95-
9659
func main() {
9760
var err error
9861

@@ -141,6 +104,7 @@ func main() {
141104
flag.BoolVar(&deleteExistingRepos, "delete-existing-repos", false, "whether existing repositories should be deleted before migrating")
142105
flag.BoolVar(&enablePullRequests, "migrate-pull-requests", false, "whether pull requests should be migrated")
143106
flag.BoolVar(&renameMasterToMain, "rename-master-to-main", false, "rename master branch to main and update pull requests (incompatible with -rename-trunk-branch)")
107+
flag.BoolVar(&skipInvalidMergeRequests, "skip-invalid-merge-requests", false, "when true, will log and skip invalid merge requests instead of raising an error")
144108

145109
flag.StringVar(&githubDomain, "github-domain", defaultGithubDomain, "specifies the GitHub domain to use")
146110
flag.StringVar(&githubRepo, "github-repo", "", "the GitHub repository to migrate to")

project.go

Lines changed: 36 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -279,10 +279,10 @@ func (p *project) migrateMergeRequests(ctx context.Context) {
279279
continue
280280
}
281281

282-
if err := p.migrateMergeRequest(ctx, mergeRequest); err != nil {
282+
if ok, err := p.migrateMergeRequest(ctx, mergeRequest); err != nil {
283283
sendErr(err)
284284
failureCount++
285-
} else {
285+
} else if ok {
286286
successCount++
287287
}
288288
}
@@ -292,10 +292,10 @@ func (p *project) migrateMergeRequests(ctx context.Context) {
292292
logger.Info("migrated merge requests from GitLab to GitHub", "name", p.gitlabPath[1], "group", p.gitlabPath[0], "successful", successCount, "failed", failureCount, "skipped", skippedCount)
293293
}
294294

295-
func (p *project) migrateMergeRequest(ctx context.Context, mergeRequest *gitlab.MergeRequest) error {
295+
func (p *project) migrateMergeRequest(ctx context.Context, mergeRequest *gitlab.MergeRequest) (bool, error) {
296296
// Check for context cancellation
297297
if err := ctx.Err(); err != nil {
298-
return fmt.Errorf("preparing to list pull requests: %v", err)
298+
return false, fmt.Errorf("preparing to list pull requests: %v", err)
299299
}
300300

301301
sourceBranchForClosedMergeRequest := fmt.Sprintf("migration-source-%d/%s", mergeRequest.IID, mergeRequest.SourceBranch)
@@ -309,7 +309,7 @@ func (p *project) migrateMergeRequest(ctx context.Context, mergeRequest *gitlab.
309309
query := fmt.Sprintf("repo:%s/%s AND is:pr AND (%s)", p.githubPath[0], p.githubPath[1], branchQuery)
310310
searchResult, err := getGithubSearchResults(ctx, query)
311311
if err != nil {
312-
return fmt.Errorf("listing pull requests: %v", err)
312+
return false, fmt.Errorf("listing pull requests: %v", err)
313313
}
314314

315315
// Look for an existing GitHub pull request
@@ -320,21 +320,21 @@ func (p *project) migrateMergeRequest(ctx context.Context, mergeRequest *gitlab.
320320

321321
// Check for context cancellation
322322
if err := ctx.Err(); err != nil {
323-
return fmt.Errorf("preparing to retrieve pull request: %v", err)
323+
return false, fmt.Errorf("preparing to retrieve pull request: %v", err)
324324
}
325325

326326
if issue.IsPullRequest() {
327327
// Extract the PR number from the URL
328328
prUrl, err := url.Parse(*issue.PullRequestLinks.URL)
329329
if err != nil {
330-
return fmt.Errorf("parsing pull request url: %v", err)
330+
return false, fmt.Errorf("parsing pull request url: %v", err)
331331
}
332332

333333
if m := regexp.MustCompile(".+/([0-9]+)$").FindStringSubmatch(prUrl.Path); len(m) == 2 {
334334
prNumber, _ := strconv.Atoi(m[1])
335335
pr, err := getGithubPullRequest(ctx, p.githubPath[0], p.githubPath[1], prNumber)
336336
if err != nil {
337-
return fmt.Errorf("retrieving pull request: %v", err)
337+
return false, fmt.Errorf("retrieving pull request: %v", err)
338338
}
339339

340340
if strings.Contains(pr.GetBody(), fmt.Sprintf("**GitLab MR Number** | %d", mergeRequest.IID)) ||
@@ -354,7 +354,7 @@ func (p *project) migrateMergeRequest(ctx context.Context, mergeRequest *gitlab.
354354
// Create a worktree
355355
worktree, err := p.repo.Worktree()
356356
if err != nil {
357-
return fmt.Errorf("creating worktree: %v", err)
357+
return false, fmt.Errorf("creating worktree: %v", err)
358358
}
359359

360360
// Generate temporary branch names
@@ -364,12 +364,12 @@ func (p *project) migrateMergeRequest(ctx context.Context, mergeRequest *gitlab.
364364
logger.Trace("retrieving commits for merge request", "name", p.gitlabPath[1], "group", p.gitlabPath[0], "project_id", p.project.ID, "merge_request_id", mergeRequest.IID)
365365
mergeRequestCommits, _, err := gl.MergeRequests.GetMergeRequestCommits(p.project.ID, mergeRequest.IID, &gitlab.GetMergeRequestCommitsOptions{OrderBy: "created_at", Sort: "asc"})
366366
if err != nil {
367-
return fmt.Errorf("retrieving merge request commits: %v", err)
367+
return false, fmt.Errorf("retrieving merge request commits: %v", err)
368368
}
369369

370370
// Some merge requests have no commits, disregard these
371371
if len(mergeRequestCommits) == 0 {
372-
return nil
372+
return false, nil
373373
}
374374

375375
// API is buggy, ordering is not respected, so we'll reorder by commit datestamp
@@ -378,25 +378,26 @@ func (p *project) migrateMergeRequest(ctx context.Context, mergeRequest *gitlab.
378378
})
379379

380380
if mergeRequestCommits[0] == nil {
381-
return fmt.Errorf("start commit for merge request %d is nil", mergeRequest.IID)
381+
return false, fmt.Errorf("start commit for merge request %d is nil", mergeRequest.IID)
382382
}
383383
if mergeRequestCommits[len(mergeRequestCommits)-1] == nil {
384-
return fmt.Errorf("end commit for merge request %d is nil", mergeRequest.IID)
384+
return false, fmt.Errorf("end commit for merge request %d is nil", mergeRequest.IID)
385385
}
386386

387387
logger.Trace("inspecting start commit", "name", p.gitlabPath[1], "group", p.gitlabPath[0], "project_id", p.project.ID, "merge_request_id", mergeRequest.IID, "sha", mergeRequestCommits[0].ShortID)
388388
startCommit, err := object.GetCommit(p.repo.Storer, plumbing.NewHash(mergeRequestCommits[0].ID))
389389
if err != nil {
390-
return fmt.Errorf("loading start commit: %v", err)
390+
return false, fmt.Errorf("loading start commit: %v", err)
391391
}
392392

393393
if startCommit.NumParents() == 0 {
394-
// Orphaned commit, start with an empty branch
395-
// TODO: this isn't working as hoped, try to figure this out. in the meantime, we'll skip MRs from orphaned branches
396-
//if err = repo.Storer.SetReference(plumbing.NewSymbolicReference("HEAD", plumbing.ReferenceName(fmt.Sprintf("refs/heads/%s", mergeRequest.TargetBranch)))); err != nil {
397-
// return fmt.Errorf("creating empty branch: %s", err)
398-
//}
399-
return fmt.Errorf("start commit %s for merge request %d has no parents", mergeRequestCommits[0].ShortID, mergeRequest.IID)
394+
// Orphaned commit, we cannot open a pull request as GitHub rejects it
395+
if skipInvalidMergeRequests {
396+
logger.Info("skipping invalid merge request as start commit has no parents", "name", p.gitlabPath[1], "group", p.gitlabPath[0], "project_id", p.project.ID, "merge_request_id", mergeRequest.IID, "sha", startCommit.Hash)
397+
return false, nil
398+
}
399+
400+
return false, fmt.Errorf("start commit %s for merge request %d has no parents", mergeRequestCommits[0].ShortID, mergeRequest.IID)
400401
} else {
401402
// Sometimes we will be starting from a merge commit, so look for a suitable parent commit to branch out from
402403
var startCommitParent *object.Commit
@@ -412,7 +413,7 @@ func (p *project) migrateMergeRequest(ctx context.Context, mergeRequest *gitlab.
412413
}
413414

414415
if startCommitParent == nil {
415-
return fmt.Errorf("identifying suitable parent of start commit %s for merge request %d", mergeRequestCommits[0].ShortID, mergeRequest.IID)
416+
return false, fmt.Errorf("identifying suitable parent of start commit %s for merge request %d", mergeRequestCommits[0].ShortID, mergeRequest.IID)
416417
}
417418

418419
logger.Trace("creating target branch for merged/closed merge request", "name", p.gitlabPath[1], "group", p.gitlabPath[0], "project_id", p.project.ID, "merge_request_id", mergeRequest.IID, "branch", mergeRequest.TargetBranch, "sha", startCommitParent.Hash)
@@ -422,7 +423,7 @@ func (p *project) migrateMergeRequest(ctx context.Context, mergeRequest *gitlab.
422423
Branch: plumbing.NewBranchReferenceName(mergeRequest.TargetBranch),
423424
Hash: startCommitParent.Hash,
424425
}); err != nil {
425-
return fmt.Errorf("checking out temporary target branch: %v", err)
426+
return false, fmt.Errorf("checking out temporary target branch: %v", err)
426427
}
427428
}
428429

@@ -434,7 +435,7 @@ func (p *project) migrateMergeRequest(ctx context.Context, mergeRequest *gitlab.
434435
Branch: plumbing.NewBranchReferenceName(mergeRequest.SourceBranch),
435436
Hash: endHash,
436437
}); err != nil {
437-
return fmt.Errorf("checking out temporary source branch: %v", err)
438+
return false, fmt.Errorf("checking out temporary source branch: %v", err)
438439
}
439440

440441
logger.Debug("pushing branches for merged/closed merge request", "owner", p.githubPath[0], "repo", p.githubPath[1], "source_branch", mergeRequest.SourceBranch, "target_branch", mergeRequest.TargetBranch)
@@ -449,7 +450,7 @@ func (p *project) migrateMergeRequest(ctx context.Context, mergeRequest *gitlab.
449450
if errors.Is(err, git.NoErrAlreadyUpToDate) {
450451
logger.Trace("branch already exists and is up-to-date on GitHub", "owner", p.githubPath[0], "repo", p.githubPath[1], "source_branch", mergeRequest.SourceBranch, "target_branch", mergeRequest.TargetBranch)
451452
} else {
452-
return fmt.Errorf("pushing temporary branches to github: %v", err)
453+
return false, fmt.Errorf("pushing temporary branches to github: %v", err)
453454
}
454455
}
455456

@@ -483,7 +484,7 @@ func (p *project) migrateMergeRequest(ctx context.Context, mergeRequest *gitlab.
483484

484485
author, err := getGitlabUser(mergeRequest.Author.Username)
485486
if err != nil {
486-
return fmt.Errorf("retrieving gitlab user: %v", err)
487+
return false, fmt.Errorf("retrieving gitlab user: %v", err)
487488
}
488489
if author.WebsiteURL != "" {
489490
githubAuthorName = "@" + strings.TrimPrefix(strings.ToLower(author.WebsiteURL), "https://github.com/")
@@ -573,17 +574,17 @@ func (p *project) migrateMergeRequest(ctx context.Context, mergeRequest *gitlab.
573574
if pullRequest, _, err = gh.PullRequests.Create(ctx, p.githubPath[0], p.githubPath[1], &newPullRequest); err != nil {
574575
if mergeRequest.State == "closed" && strings.Contains(err.Error(), "No commits between") {
575576
logger.Debug("skipping closed merge request as the change is already present in trunk branch", "owner", p.githubPath[0], "repo", p.githubPath[1], "merge_request_id", mergeRequest.IID)
576-
return nil
577+
return false, nil
577578
}
578-
return fmt.Errorf("creating pull request: %v", err)
579+
return false, fmt.Errorf("creating pull request: %v", err)
579580
}
580581

581582
if mergeRequest.State == "closed" || mergeRequest.State == "merged" {
582583
logger.Debug("closing pull request", "owner", p.githubPath[0], "repo", p.githubPath[1], "pr_number", pullRequest.GetNumber())
583584

584585
pullRequest.State = pointer("closed")
585586
if pullRequest, _, err = gh.PullRequests.Edit(ctx, p.githubPath[0], p.githubPath[1], pullRequest.GetNumber(), pullRequest); err != nil {
586-
return fmt.Errorf("updating pull request: %v", err)
587+
return false, fmt.Errorf("updating pull request: %v", err)
587588
}
588589
}
589590

@@ -603,7 +604,7 @@ func (p *project) migrateMergeRequest(ctx context.Context, mergeRequest *gitlab.
603604
}
604605

605606
if pullRequest, _, err = gh.PullRequests.Edit(ctx, p.githubPath[0], p.githubPath[1], pullRequestState.GetNumber(), pullRequestState); err != nil {
606-
return fmt.Errorf("updating pull request state: %v", err)
607+
return false, fmt.Errorf("updating pull request state: %v", err)
607608
}
608609
}
609610

@@ -618,7 +619,7 @@ func (p *project) migrateMergeRequest(ctx context.Context, mergeRequest *gitlab.
618619
pullRequest.Draft = &mergeRequest.Draft
619620
pullRequest.MaintainerCanModify = nil
620621
if pullRequest, _, err = gh.PullRequests.Edit(ctx, p.githubPath[0], p.githubPath[1], pullRequest.GetNumber(), pullRequest); err != nil {
621-
return fmt.Errorf("updating pull request: %v", err)
622+
return false, fmt.Errorf("updating pull request: %v", err)
622623
}
623624
} else {
624625
logger.Trace("existing pull request is up-to-date", "owner", p.githubPath[0], "repo", p.githubPath[1], "pr_number", pullRequest.GetNumber())
@@ -635,7 +636,7 @@ func (p *project) migrateMergeRequest(ctx context.Context, mergeRequest *gitlab.
635636
for {
636637
result, resp, err := gl.Notes.ListMergeRequestNotes(p.project.ID, mergeRequest.IID, opts)
637638
if err != nil {
638-
return fmt.Errorf("listing merge request notes: %v", err)
639+
return false, fmt.Errorf("listing merge request notes: %v", err)
639640
}
640641

641642
comments = append(comments, result...)
@@ -663,7 +664,7 @@ func (p *project) migrateMergeRequest(ctx context.Context, mergeRequest *gitlab.
663664

664665
commentAuthor, err := getGitlabUser(comment.Author.Username)
665666
if err != nil {
666-
return fmt.Errorf("retrieving gitlab user: %v", err)
667+
return false, fmt.Errorf("retrieving gitlab user: %v", err)
667668
}
668669
if commentAuthor.WebsiteURL != "" {
669670
githubCommentAuthorName = "@" + strings.TrimPrefix(strings.ToLower(commentAuthor.WebsiteURL), "https://github.com/")
@@ -697,7 +698,7 @@ func (p *project) migrateMergeRequest(ctx context.Context, mergeRequest *gitlab.
697698
logger.Debug("updating pull request comment", "owner", p.githubPath[0], "repo", p.githubPath[1], "pr_number", pullRequest.GetNumber(), "comment_id", prComment.GetID())
698699
prComment.Body = &commentBody
699700
if _, _, err = gh.Issues.EditComment(ctx, p.githubPath[0], p.githubPath[1], prComment.GetID(), prComment); err != nil {
700-
return fmt.Errorf("updating pull request comments: %v", err)
701+
return false, fmt.Errorf("updating pull request comments: %v", err)
701702
}
702703
}
703704
} else {
@@ -711,11 +712,11 @@ func (p *project) migrateMergeRequest(ctx context.Context, mergeRequest *gitlab.
711712
Body: &commentBody,
712713
}
713714
if _, _, err = gh.Issues.CreateComment(ctx, p.githubPath[0], p.githubPath[1], pullRequest.GetNumber(), &newComment); err != nil {
714-
return fmt.Errorf("creating pull request comment: %v", err)
715+
return false, fmt.Errorf("creating pull request comment: %v", err)
715716
}
716717
}
717718
}
718719
}
719720

720-
return nil
721+
return true, nil
721722
}

0 commit comments

Comments
 (0)