File: pull_create.go

package info (click to toggle)
tea-cli 0.11.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,648 kB
  • sloc: makefile: 116; sh: 17
file content (155 lines) | stat: -rw-r--r-- 4,341 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package task

import (
	"fmt"
	"regexp"
	"strings"

	"code.gitea.io/sdk/gitea"
	"code.gitea.io/tea/modules/config"
	"code.gitea.io/tea/modules/context"
	local_git "code.gitea.io/tea/modules/git"
	"code.gitea.io/tea/modules/print"
	"code.gitea.io/tea/modules/utils"
)

var (
	spaceRegex  = regexp.MustCompile(`[\s_-]+`)
	noSpace     = regexp.MustCompile(`^[^a-zA-Z\s]*`)
	consecutive = regexp.MustCompile(`[\s]{2,}`)
)

// CreatePull creates a PR in the given repo and prints the result
func CreatePull(ctx *context.TeaContext, base, head string, allowMaintainerEdits *bool, opts *gitea.CreateIssueOption) (err error) {
	// default is default branch
	if len(base) == 0 {
		base, err = GetDefaultPRBase(ctx.Login, ctx.Owner, ctx.Repo)
		if err != nil {
			return err
		}
	}

	// default is current one
	if len(head) == 0 {
		if ctx.LocalRepo == nil {
			return fmt.Errorf("no local git repo detected, please specify head branch")
		}
		headOwner, headBranch, err := GetDefaultPRHead(ctx.LocalRepo)
		if err != nil {
			return err
		}

		head = GetHeadSpec(headOwner, headBranch, ctx.Owner)
	}

	// head & base may not be the same
	if head == base {
		return fmt.Errorf("can't create PR from %s to %s", head, base)
	}

	// default is head branch name
	if len(opts.Title) == 0 {
		opts.Title = GetDefaultPRTitle(head)
	}
	// title is required
	if len(opts.Title) == 0 {
		return fmt.Errorf("title is required")
	}

	client := ctx.Login.Client()

	pr, _, err := client.CreatePullRequest(ctx.Owner, ctx.Repo, gitea.CreatePullRequestOption{
		Head:      head,
		Base:      base,
		Title:     opts.Title,
		Body:      opts.Body,
		Assignees: opts.Assignees,
		Labels:    opts.Labels,
		Milestone: opts.Milestone,
		Deadline:  opts.Deadline,
	})
	if err != nil {
		return fmt.Errorf("could not create PR from %s to %s:%s: %s", head, ctx.Owner, base, err)
	}

	if allowMaintainerEdits != nil && pr.AllowMaintainerEdit != *allowMaintainerEdits {
		pr, _, err = client.EditPullRequest(ctx.Owner, ctx.Repo, pr.Index, gitea.EditPullRequestOption{
			AllowMaintainerEdit: allowMaintainerEdits,
		})
		if err != nil {
			return fmt.Errorf("could not enable maintainer edit on pull: %v", err)
		}
	}

	print.PullDetails(pr, nil, nil)

	fmt.Println(pr.HTMLURL)

	return err
}

// GetDefaultPRBase retrieves the default base branch for the given repo
func GetDefaultPRBase(login *config.Login, owner, repo string) (string, error) {
	meta, _, err := login.Client().GetRepo(owner, repo)
	if err != nil {
		return "", fmt.Errorf("could not fetch repo meta: %s", err)
	}
	return meta.DefaultBranch, nil
}

// GetDefaultPRHead uses the currently checked out branch, tries to find a remote
// that has a branch with the same name, and extracts the owner from its URL.
// If no remote matches, owner is empty, meaning same as head repo owner.
func GetDefaultPRHead(localRepo *local_git.TeaRepo) (owner, branch string, err error) {
	var sha string
	if branch, sha, err = localRepo.TeaGetCurrentBranchNameAndSHA(); err != nil {
		return
	}

	remote, err := localRepo.TeaFindBranchRemote(branch, sha)
	if err != nil {
		err = fmt.Errorf("could not determine remote for current branch: %s", err)
		return
	}

	if remote == nil {
		// if no remote branch is found for the local branch,
		// we leave owner empty, meaning "use same repo as head" to gitea.
		return
	}

	url, err := local_git.ParseURL(remote.Config().URLs[0])
	if err != nil {
		return
	}
	owner, _ = utils.GetOwnerAndRepo(url.Path, "")
	return
}

// GetHeadSpec creates a head string as expected by gitea API
func GetHeadSpec(owner, branch, baseOwner string) string {
	if len(owner) != 0 && owner != baseOwner {
		return fmt.Sprintf("%s:%s", owner, branch)
	}
	return branch
}

// GetDefaultPRTitle transforms a string like a branchname to a readable text
func GetDefaultPRTitle(header string) string {
	// Extract the part after the last colon in the input string
	colonIndex := strings.LastIndex(header, ":")
	if colonIndex != -1 {
		header = header[colonIndex+1:]
	}

	title := noSpace.ReplaceAllString(header, "")
	title = spaceRegex.ReplaceAllString(title, " ")
	title = strings.TrimSpace(title)
	title = strings.Title(strings.ToLower(title))
	title = consecutive.ReplaceAllString(title, " ")

	return title
}