X Tutup
Skip to content

Commit de9971d

Browse files
authored
Merge pull request #9208 from Frederick888/find-pr-by-rev-parse-push
Find PRs using `@{push}`
2 parents 15783a6 + e0f624b commit de9971d

13 files changed

+1298
-542
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Setup environment variables used for testscript
2+
env REPO=${SCRIPT_NAME}-${RANDOM_STRING}
3+
env FORK=${REPO}-fork
4+
5+
# Use gh as a credential helper
6+
exec gh auth setup-git
7+
8+
# Create a repository to act as upstream with a file so it has a default branch
9+
exec gh repo create ${ORG}/${REPO} --add-readme --private
10+
11+
# Defer repo cleanup of upstream
12+
defer gh repo delete --yes ${ORG}/${REPO}
13+
exec gh repo view ${ORG}/${REPO} --json id --jq '.id'
14+
stdout2env REPO_ID
15+
16+
# Create a user fork of repository as opposed to private organization fork
17+
exec gh repo fork ${ORG}/${REPO} --org ${ORG} --fork-name ${FORK}
18+
19+
# Defer repo cleanup of fork
20+
defer gh repo delete --yes ${ORG}/${FORK}
21+
sleep 5
22+
exec gh repo view ${ORG}/${FORK} --json id --jq '.id'
23+
stdout2env FORK_ID
24+
25+
# Clone the repo
26+
exec gh repo clone ${ORG}/${FORK}
27+
cd ${FORK}
28+
29+
# Prepare a branch where changes are pulled from the upstream default branch but pushed to fork
30+
exec git checkout -b feature-branch upstream/main
31+
exec git config branch.feature-branch.pushRemote origin
32+
exec git commit --allow-empty -m 'Empty Commit'
33+
exec git push
34+
35+
# Create the PR spanning upstream and fork repositories, gh pr create does not support headRepositoryId needed for private forks
36+
exec gh api graphql -F repositoryId="${REPO_ID}" -F headRepositoryId="${FORK_ID}" -F query='mutation CreatePullRequest($headRepositoryId: ID!, $repositoryId: ID!) { createPullRequest(input:{ baseRefName: "main", body: "Feature Body", draft: false, headRefName: "feature-branch", headRepositoryId: $headRepositoryId, repositoryId: $repositoryId, title:"Feature Title" }){ pullRequest{ id url } } }'
37+
38+
# View the PR
39+
exec gh pr view
40+
stdout 'Feature Title'
41+
42+
# Check the PR status
43+
env PR_STATUS_BRANCH=#1 Feature Title [${ORG}:feature-branch]
44+
exec gh pr status
45+
stdout $PR_STATUS_BRANCH
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Setup environment variables used for testscript
2+
env REPO=${SCRIPT_NAME}-${RANDOM_STRING}
3+
4+
# Use gh as a credential helper
5+
exec gh auth setup-git
6+
7+
# Create a repository with a file so it has a default branch
8+
exec gh repo create ${ORG}/${REPO} --add-readme --private
9+
10+
# Defer repo cleanup
11+
defer gh repo delete --yes ${ORG}/${REPO}
12+
13+
# Clone the repo
14+
exec gh repo clone ${ORG}/${REPO}
15+
cd ${REPO}
16+
17+
# Configure default push behavior so local and remote branches will be the same
18+
exec git config push.default current
19+
20+
# Prepare a branch where changes are pulled from the default branch instead of remote branch of same name
21+
exec git checkout -b feature-branch
22+
exec git branch --set-upstream-to origin/main
23+
exec git rev-parse --abbrev-ref feature-branch@{upstream}
24+
stdout origin/main
25+
26+
# Create the PR
27+
exec git commit --allow-empty -m 'Empty Commit'
28+
exec git push
29+
exec gh pr create -B main -H feature-branch --title 'Feature Title' --body 'Feature Body'
30+
31+
# View the PR
32+
exec gh pr view
33+
stdout 'Feature Title'
34+
35+
# Check the PR status
36+
env PR_STATUS_BRANCH=#1 Feature Title [feature-branch]
37+
exec gh pr status
38+
stdout $PR_STATUS_BRANCH
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Setup environment variables used for testscript
2+
env REPO=${SCRIPT_NAME}-${RANDOM_STRING}
3+
env FORK=${REPO}-fork
4+
5+
# Use gh as a credential helper
6+
exec gh auth setup-git
7+
8+
# Create a repository to act as upstream with a file so it has a default branch
9+
exec gh repo create ${ORG}/${REPO} --add-readme --private
10+
11+
# Defer repo cleanup of upstream
12+
defer gh repo delete --yes ${ORG}/${REPO}
13+
exec gh repo view ${ORG}/${REPO} --json id --jq '.id'
14+
stdout2env REPO_ID
15+
16+
# Create a user fork of repository as opposed to private organization fork
17+
exec gh repo fork ${ORG}/${REPO} --org ${ORG} --fork-name ${FORK}
18+
19+
# Defer repo cleanup of fork
20+
defer gh repo delete --yes ${ORG}/${FORK}
21+
sleep 5
22+
exec gh repo view ${ORG}/${FORK} --json id --jq '.id'
23+
stdout2env FORK_ID
24+
25+
# Clone the repo
26+
exec gh repo clone ${ORG}/${FORK}
27+
cd ${FORK}
28+
29+
# Prepare a branch where changes are pulled from the upstream default branch but pushed to fork
30+
exec git checkout -b feature-branch upstream/main
31+
exec git config remote.pushDefault origin
32+
exec git commit --allow-empty -m 'Empty Commit'
33+
exec git push
34+
35+
# Create the PR spanning upstream and fork repositories, gh pr create does not support headRepositoryId needed for private forks
36+
exec gh api graphql -F repositoryId="${REPO_ID}" -F headRepositoryId="${FORK_ID}" -F query='mutation CreatePullRequest($headRepositoryId: ID!, $repositoryId: ID!) { createPullRequest(input:{ baseRefName: "main", body: "Feature Body", draft: false, headRefName: "feature-branch", headRepositoryId: $headRepositoryId, repositoryId: $repositoryId, title:"Feature Title" }){ pullRequest{ id url } } }'
37+
38+
# View the PR
39+
exec gh pr view
40+
stdout 'Feature Title'
41+
42+
# Check the PR status
43+
env PR_STATUS_BRANCH=#1 Feature Title [${ORG}:feature-branch]
44+
exec gh pr status
45+
stdout $PR_STATUS_BRANCH
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Setup environment variables used for testscript
2+
env REPO=${SCRIPT_NAME}-${RANDOM_STRING}
3+
4+
# Use gh as a credential helper
5+
exec gh auth setup-git
6+
7+
# Create a repository with a file so it has a default branch
8+
exec gh repo create ${ORG}/${REPO} --add-readme --private
9+
10+
# Defer repo cleanup
11+
defer gh repo delete --yes ${ORG}/${REPO}
12+
13+
# Clone the repo
14+
exec gh repo clone ${ORG}/${REPO}
15+
cd ${REPO}
16+
17+
# Configure default push behavior so local and remote branches have to be the same
18+
exec git config push.default simple
19+
20+
# Prepare a branch where changes are pulled from the default branch instead of remote branch of same name
21+
exec git checkout -b feature-branch origin/main
22+
23+
# Create the PR
24+
exec git commit --allow-empty -m 'Empty Commit'
25+
exec git push origin feature-branch
26+
exec gh pr create -H feature-branch --title 'Feature Title' --body 'Feature Body'
27+
28+
# View the PR
29+
exec gh pr view
30+
stdout 'Feature Title'
31+
32+
# Check the PR status
33+
env PR_STATUS_BRANCH=#1 Feature Title [feature-branch]
34+
exec gh pr status
35+
stdout $PR_STATUS_BRANCH

git/client.go

Lines changed: 63 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -376,20 +376,20 @@ func (c *Client) lookupCommit(ctx context.Context, sha, format string) ([]byte,
376376
return out, nil
377377
}
378378

379-
// ReadBranchConfig parses the `branch.BRANCH.(remote|merge|gh-merge-base)` part of git config.
379+
// ReadBranchConfig parses the `branch.BRANCH.(remote|merge|pushremote|gh-merge-base)` part of git config.
380380
// If no branch config is found or there is an error in the command, it returns an empty BranchConfig.
381381
// Downstream consumers of ReadBranchConfig should consider the behavior they desire if this errors,
382382
// as an empty config is not necessarily breaking.
383383
func (c *Client) ReadBranchConfig(ctx context.Context, branch string) (BranchConfig, error) {
384384

385385
prefix := regexp.QuoteMeta(fmt.Sprintf("branch.%s.", branch))
386-
args := []string{"config", "--get-regexp", fmt.Sprintf("^%s(remote|merge|%s)$", prefix, MergeBaseConfig)}
386+
args := []string{"config", "--get-regexp", fmt.Sprintf("^%s(remote|merge|pushremote|%s)$", prefix, MergeBaseConfig)}
387387
cmd, err := c.Command(ctx, args...)
388388
if err != nil {
389389
return BranchConfig{}, err
390390
}
391391

392-
out, err := cmd.Output()
392+
branchCfgOut, err := cmd.Output()
393393
if err != nil {
394394
// This is the error we expect if the git command does not run successfully.
395395
// If the ExitCode is 1, then we just didn't find any config for the branch.
@@ -400,35 +400,31 @@ func (c *Client) ReadBranchConfig(ctx context.Context, branch string) (BranchCon
400400
return BranchConfig{}, nil
401401
}
402402

403-
return parseBranchConfig(outputLines(out)), nil
403+
return parseBranchConfig(outputLines(branchCfgOut)), nil
404404
}
405405

406-
func parseBranchConfig(configLines []string) BranchConfig {
406+
func parseBranchConfig(branchConfigLines []string) BranchConfig {
407407
var cfg BranchConfig
408408

409-
for _, line := range configLines {
409+
// Read the config lines for the specific branch
410+
for _, line := range branchConfigLines {
410411
parts := strings.SplitN(line, " ", 2)
411412
if len(parts) < 2 {
412413
continue
413414
}
414415
keys := strings.Split(parts[0], ".")
415416
switch keys[len(keys)-1] {
416417
case "remote":
417-
if strings.Contains(parts[1], ":") {
418-
u, err := ParseURL(parts[1])
419-
if err != nil {
420-
continue
421-
}
422-
cfg.RemoteURL = u
423-
} else if !isFilesystemPath(parts[1]) {
424-
cfg.RemoteName = parts[1]
425-
}
418+
cfg.RemoteURL, cfg.RemoteName = parseRemoteURLOrName(parts[1])
419+
case "pushremote":
420+
cfg.PushRemoteURL, cfg.PushRemoteName = parseRemoteURLOrName(parts[1])
426421
case "merge":
427422
cfg.MergeRef = parts[1]
428423
case MergeBaseConfig:
429424
cfg.MergeBase = parts[1]
430425
}
431426
}
427+
432428
return cfg
433429
}
434430

@@ -445,6 +441,47 @@ func (c *Client) SetBranchConfig(ctx context.Context, branch, name, value string
445441
return err
446442
}
447443

444+
// PushDefault returns the value of push.default in the config. If the value
445+
// is not set, it returns "simple" (the default git value). See
446+
// https://git-scm.com/docs/git-config#Documentation/git-config.txt-pushdefault
447+
func (c *Client) PushDefault(ctx context.Context) (string, error) {
448+
pushDefault, err := c.Config(ctx, "push.default")
449+
if err == nil {
450+
return pushDefault, nil
451+
}
452+
453+
var gitError *GitError
454+
if ok := errors.As(err, &gitError); ok && gitError.ExitCode == 1 {
455+
return "simple", nil
456+
}
457+
return "", err
458+
}
459+
460+
// RemotePushDefault returns the value of remote.pushDefault in the config. If
461+
// the value is not set, it returns an empty string.
462+
func (c *Client) RemotePushDefault(ctx context.Context) (string, error) {
463+
remotePushDefault, err := c.Config(ctx, "remote.pushDefault")
464+
if err == nil {
465+
return remotePushDefault, nil
466+
}
467+
468+
var gitError *GitError
469+
if ok := errors.As(err, &gitError); ok && gitError.ExitCode == 1 {
470+
return "", nil
471+
}
472+
473+
return "", err
474+
}
475+
476+
// ParsePushRevision gets the value of the @{push} revision syntax
477+
// An error here doesn't necessarily mean something is broken, but may mean that the @{push}
478+
// revision syntax couldn't be resolved, such as in non-centralized workflows with
479+
// push.default = simple. Downstream consumers should consider how to handle this error.
480+
func (c *Client) ParsePushRevision(ctx context.Context, branch string) (string, error) {
481+
revParseOut, err := c.revParse(ctx, "--abbrev-ref", branch+"@{push}")
482+
return firstLine(revParseOut), err
483+
}
484+
448485
func (c *Client) DeleteLocalTag(ctx context.Context, tag string) error {
449486
args := []string{"tag", "-d", tag}
450487
cmd, err := c.Command(ctx, args...)
@@ -790,6 +827,17 @@ func parseRemotes(remotesStr []string) RemoteSet {
790827
return remotes
791828
}
792829

830+
func parseRemoteURLOrName(value string) (*url.URL, string) {
831+
if strings.Contains(value, ":") {
832+
if u, err := ParseURL(value); err == nil {
833+
return u, ""
834+
}
835+
} else if !isFilesystemPath(value) {
836+
return nil, value
837+
}
838+
return nil, ""
839+
}
840+
793841
func populateResolvedRemotes(remotes RemoteSet, resolved []string) {
794842
for _, l := range resolved {
795843
parts := strings.SplitN(l, " ", 2)

0 commit comments

Comments
 (0)
X Tutup