Skip to content

Commit fe276db

Browse files
committed
Export of internal change
1 parent ae02df7 commit fe276db

File tree

5 files changed

+148
-23
lines changed

5 files changed

+148
-23
lines changed

docs/reference.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2131,7 +2131,7 @@ version_selector | `latestVersionSelector`<br><p>Select a custom version (tag)to
21312131

21322132
Creates changes in a new pull request in the destination.
21332133

2134-
`gitHubPrDestination git.github_pr_destination(url, destination_ref="master", pr_branch=None, title=None, body=None, integrates=None, api_checker=None, update_description=False)`
2134+
`gitHubPrDestination git.github_pr_destination(url, destination_ref="master", pr_destination_url=None, pr_branch=None, title=None, body=None, integrates=None, api_checker=None, update_description=False)`
21352135

21362136

21372137
#### Parameters:
@@ -2140,6 +2140,7 @@ Parameter | Description
21402140
--------- | -----------
21412141
url | `string`<br><p>Url of the GitHub project. For example "https://github.com/google/copybara'"</p>
21422142
destination_ref | `string`<br><p>Destination reference for the change. By default 'master'</p>
2143+
pr_destination_url | `string`<br><p>Url of the GitHub project to create the PullRequest on. Set this if you want to push to a personal fork and create the PullRequest on the upstream project (e.g. because you don't have write access to the upstream repo).By default, `pr_destination_url` is the same as `url`.</p>
21432144
pr_branch | `string`<br><p>Customize the pull request branch. Any variable present in the message in the form of ${CONTEXT_REFERENCE} will be replaced by the corresponding stable reference (head, PR number, Gerrit change number, etc.).</p>
21442145
title | `string`<br><p>When creating (or updating if `update_description` is set) a pull request, use this title. By default it uses the change first line. This field accepts a template with labels. For example: `"Change ${CONTEXT_REFERENCE}"`</p>
21452146
body | `string`<br><p>When creating (or updating if `update_description` is set) a pull request, use this body. By default it uses the change summary. This field accepts a template with labels. For example: `"Change ${CONTEXT_REFERENCE}"`</p>

java/com/google/copybara/git/GitHubPrDestination.java

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
import com.google.copybara.util.Identity;
5555
import com.google.copybara.util.console.Console;
5656
import java.io.IOException;
57+
import java.util.Optional;
5758
import java.util.UUID;
5859
import javax.annotation.Nullable;
5960

@@ -64,6 +65,7 @@ public class GitHubPrDestination implements Destination<GitRevision> {
6465

6566
private final String url;
6667
private final String destinationRef;
68+
private final Optional<String> prDestinationUrl;
6769
private final String prBranch;
6870
private final GeneralOptions generalOptions;
6971
private final GitHubOptions gitHubOptions;
@@ -82,6 +84,7 @@ public class GitHubPrDestination implements Destination<GitRevision> {
8284
GitHubPrDestination(
8385
String url,
8486
String destinationRef,
87+
Optional<String> prDestinationUrl,
8588
@Nullable String prBranch,
8689
GeneralOptions generalOptions,
8790
GitHubOptions gitHubOptions,
@@ -97,6 +100,7 @@ public class GitHubPrDestination implements Destination<GitRevision> {
97100
boolean updateDescription) {
98101
this.url = Preconditions.checkNotNull(url);
99102
this.destinationRef = Preconditions.checkNotNull(destinationRef);
103+
this.prDestinationUrl = Preconditions.checkNotNull(prDestinationUrl);
100104
this.prBranch = prBranch;
101105
this.generalOptions = Preconditions.checkNotNull(generalOptions);
102106
this.gitHubOptions = Preconditions.checkNotNull(gitHubOptions);
@@ -176,6 +180,12 @@ public ImmutableList<DestinationEffect> write(
176180
return result.build();
177181
}
178182

183+
String prBranch =
184+
getQualifiedPullRequestBranchName(
185+
writerContext.getOriginalRevision(),
186+
writerContext.getWorkflowName(),
187+
writerContext.getWorkflowIdentityUser());
188+
179189
if (!gitHubDestinationOptions.createPullRequest) {
180190
console.infoFmt(
181191
"Please create a PR manually following this link: %s/compare/%s...%s"
@@ -188,9 +198,7 @@ public ImmutableList<DestinationEffect> write(
188198
GitHubApi api = gitHubOptions.newGitHubApi(getProjectName());
189199

190200
ImmutableList<PullRequest> pullRequests = api.getPullRequests(
191-
getProjectName(),
192-
PullRequestListParams.DEFAULT
193-
.withHead(String.format("%s:%s", getUserNameFromUrl(url), prBranch)));
201+
getProjectName(), PullRequestListParams.DEFAULT.withHead(prBranch));
194202

195203
ChangeMessage msg = ChangeMessage.parseMessage(transformResult.getSummary().trim());
196204

@@ -275,7 +283,7 @@ private String asHttpsUrl() throws ValidationException {
275283

276284
@VisibleForTesting
277285
String getProjectName() throws ValidationException {
278-
return GitHubUtil.getProjectNameFromUrl(url);
286+
return GitHubUtil.getProjectNameFromUrl(prDestinationUrl.orElse(url));
279287
}
280288

281289
@VisibleForTesting
@@ -288,6 +296,16 @@ public Iterable<GitIntegrateChanges> getIntegrates() {
288296
return integrates;
289297
}
290298

299+
private String getQualifiedPullRequestBranchName(
300+
@Nullable Revision changeRevision, String workflowName, String workflowIdentityUser)
301+
throws ValidationException {
302+
String branch = getPullRequestBranchName(changeRevision, workflowName, workflowIdentityUser);
303+
if (prDestinationUrl.isPresent()) {
304+
return String.format("%s:%s", getUserNameFromUrl(url), branch);
305+
}
306+
return branch;
307+
}
308+
291309
private String getPullRequestBranchName(
292310
@Nullable Revision changeRevision, String workflowName, String workflowIdentityUser)
293311
throws ValidationException {

java/com/google/copybara/git/GitModule.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
import java.util.LinkedHashSet;
9090
import java.util.List;
9191
import java.util.Map;
92+
import java.util.Optional;
9293
import java.util.TreeMap;
9394
import javax.annotation.CheckReturnValue;
9495
import javax.annotation.Nullable;
@@ -1199,6 +1200,18 @@ public GitDestination gitHubDestination(
11991200
named = true,
12001201
doc = "Destination reference for the change. By default 'master'",
12011202
defaultValue = "\"master\""),
1203+
@Param(
1204+
name = "pr_destination_url",
1205+
type = String.class,
1206+
defaultValue = "None",
1207+
noneable = true,
1208+
named = true,
1209+
positional = false,
1210+
doc =
1211+
"Url of the GitHub project to create the PullRequest on. Set this if you want to "
1212+
+ "push to a personal fork and create the PullRequest on the upstream project "
1213+
+ "(e.g. because you don't have write access to the upstream repo)."
1214+
+ "By default, `pr_destination_url` is the same as `url`."),
12021215
@Param(
12031216
name = "pr_branch",
12041217
type = String.class,
@@ -1297,6 +1310,7 @@ public GitDestination gitHubDestination(
12971310
public GitHubPrDestination githubPrDestination(
12981311
String url,
12991312
String destinationRef,
1313+
Object prDestinationUrl,
13001314
Object prBranch,
13011315
Object title,
13021316
Object body,
@@ -1309,12 +1323,20 @@ public GitHubPrDestination githubPrDestination(
13091323
// This restricts to github.com, we will have to revisit this to support setups like GitHub
13101324
// Enterprise.
13111325
check(GitHubUtil.isGitHubUrl(url), "'%s' is not a valid GitHub url", url);
1326+
check(prDestinationUrl == Starlark.NONE || GitHubUtil.isGitHubUrl((String)prDestinationUrl),
1327+
"'%s' is not a valid GitHub url", prDestinationUrl);
13121328
GitDestinationOptions destinationOptions = options.get(GitDestinationOptions.class);
13131329
return new GitHubPrDestination(
13141330
fixHttp(
13151331
checkNotEmpty(firstNotNull(destinationOptions.url, url), "url"),
13161332
thread.getCallerLocation()),
13171333
destinationRef,
1334+
Optional.ofNullable(
1335+
prDestinationUrl == Starlark.NONE ?
1336+
null :
1337+
fixHttp(
1338+
checkNotEmpty((String)prDestinationUrl, "pr_destination_url"),
1339+
thread.getCallerLocation())),
13181340
convertFromNoneable(prBranch, null),
13191341
generalOptions,
13201342
options.get(GitHubOptions.class),

javatests/com/google/copybara/git/GitHubPrDestinationTest.java

Lines changed: 95 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,45 @@ public void testFindProject() throws ValidationException {
438438
.onceInLog(MessageType.ERROR, ".*'https://github.com' is not a valid GitHub url.*");
439439
}
440440

441+
private void checkFindProject(String url, String project) throws ValidationException {
442+
GitHubPrDestination d = skylark.eval("r", "r = git.github_pr_destination("
443+
+ " url = '" + url + "',"
444+
+ " destination_ref = 'other',"
445+
+ ")");
446+
447+
assertThat(d.getProjectName()).isEqualTo(project);
448+
}
449+
450+
@Test
451+
public void testFindUpstreamProject() throws ValidationException {
452+
checkFindProjectFromUpstreamUrl("https://github.com/foo", "foo");
453+
checkFindProjectFromUpstreamUrl("https://github.com/foo/bar", "foo/bar");
454+
checkFindProjectFromUpstreamUrl("https://github.com/foo.git", "foo");
455+
checkFindProjectFromUpstreamUrl("https://github.com/foo/", "foo");
456+
checkFindProjectFromUpstreamUrl("git+https://github.com/foo", "foo");
457+
checkFindProjectFromUpstreamUrl("[email protected]/foo", "foo");
458+
checkFindProjectFromUpstreamUrl(
459+
"[email protected]:org/internal_repo_name.git", "org/internal_repo_name");
460+
ValidationException e =
461+
assertThrows(
462+
ValidationException.class,
463+
() -> checkFindProjectFromUpstreamUrl("https://github.com", "foo"));
464+
console
465+
.assertThat()
466+
.onceInLog(MessageType.ERROR, ".*'https://github.com' is not a valid GitHub url.*");
467+
}
468+
469+
private void checkFindProjectFromUpstreamUrl(
470+
String url, String project) throws ValidationException {
471+
GitHubPrDestination d = skylark.eval("r", "r = git.github_pr_destination("
472+
+ " url = 'https://github.com/google/copybara',"
473+
+ " pr_destination_url = '" + url + "',"
474+
+ " destination_ref = 'other',"
475+
+ ")");
476+
477+
assertThat(d.getProjectName()).isEqualTo(project);
478+
}
479+
441480
@Test
442481
public void testIntegratesCanBeRemoved() throws Exception {
443482
GitHubPrDestination d = skylark.eval("r", "r = git.github_pr_destination("
@@ -464,15 +503,6 @@ public void testIntegratesCanBeRemoved() throws Exception {
464503
assertThat(d.getIntegrates()).isEmpty();
465504
}
466505

467-
private void checkFindProject(String url, String project) throws ValidationException {
468-
GitHubPrDestination d = skylark.eval("r", "r = git.github_pr_destination("
469-
+ " url = '" + url + "',"
470-
+ " destination_ref = 'other',"
471-
+ ")");
472-
473-
assertThat(d.getProjectName()).isEqualTo(project);
474-
}
475-
476506
@Test
477507
public void testWriteNoMaster() throws ValidationException, IOException, RepoException {
478508
GitHubPrDestination d = skylark.eval("r", "r = git.github_pr_destination("
@@ -530,6 +560,62 @@ public void testWriteNoMaster() throws ValidationException, IOException, RepoExc
530560
+ "DummyOrigin-RevId: one\n");
531561
}
532562

563+
@Test
564+
public void testPrDestinationUrl() throws ValidationException, IOException, RepoException {
565+
GitHubPrDestination d = skylark.eval("r", "r = git.github_pr_destination("
566+
+ " url = 'https://github.com/foo',"
567+
+ " pr_destination_url = 'https://github.com/bar',"
568+
+ ")");
569+
DummyRevision dummyRevision = new DummyRevision("dummyReference", "feature");
570+
WriterContext writerContext =
571+
new WriterContext("piper_to_github_pr", "TEST", false, dummyRevision,
572+
Glob.ALL_FILES.roots());
573+
String branchName =
574+
Identity.computeIdentity(
575+
"OriginGroupIdentity",
576+
dummyRevision.contextReference(),
577+
writerContext.getWorkflowName(),
578+
"copy.bara.sky",
579+
writerContext.getWorkflowIdentityUser());
580+
581+
gitUtil.mockApi(
582+
"GET",
583+
"https://api.github.com/repos/bar/pulls?per_page=100&head=foo:" + branchName,
584+
mockResponse("[]"));
585+
gitUtil.mockApi(
586+
"POST",
587+
"https://api.github.com/repos/bar/pulls",
588+
mockResponseAndValidateRequest(
589+
"{\n"
590+
+ " \"id\": 1,\n"
591+
+ " \"number\": 12345,\n"
592+
+ " \"state\": \"open\",\n"
593+
+ " \"title\": \"test summary\",\n"
594+
+ " \"body\": \"test summary\""
595+
+ "}",
596+
req ->
597+
req.equals(
598+
"{\"base\":\"master\",\"body\":\"test summary\\n\",\"head\":\""
599+
+ branchName
600+
+ "\",\"title\":\"test summary\"}")));
601+
602+
Writer<GitRevision> writer = d.newWriter(writerContext);
603+
GitRepository remote = gitUtil.mockRemoteRepo("github.com/foo");
604+
addFiles(remote, "master", "first change", ImmutableMap.<String, String>builder()
605+
.put("foo.txt", "").build());
606+
607+
writeFile(this.workdir, "test.txt", "some content");
608+
writer.write(
609+
TransformResults.of(this.workdir, new DummyRevision("one")), Glob.ALL_FILES, console);
610+
611+
assertThat(remote.refExists(branchName)).isTrue();
612+
assertThat(Iterables.transform(remote.log(branchName).run(), GitLogEntry::getBody))
613+
.containsExactly("first change\n",
614+
"test summary\n"
615+
+ "\n"
616+
+ "DummyOrigin-RevId: one\n");
617+
}
618+
533619
@Test
534620
public void testBranchNameFromUserWithLabel() throws ValidationException, IOException, RepoException {
535621
testBranchNameFromUser("test${CONTEXT_REFERENCE}", "test_feature", "&feature");

third_party/BUILD

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ package(
1515
java_library(
1616
name = "guava",
1717
exports = [
18-
"@maven//:com_google_guava_failureaccess",
18+
"@maven//:com_google_guava_failureaccess",
1919
"@maven//:com_google_guava_guava",
2020
],
2121
)
@@ -95,18 +95,18 @@ java_library(
9595
name = "truth",
9696
testonly = 1,
9797
exports = [
98-
"@maven//:com_googlecode_java_diff_utils_diffutils",
9998
"@maven//:com_google_truth_truth",
99+
"@maven//:com_googlecode_java_diff_utils_diffutils",
100100
],
101101
)
102102

103103
java_library(
104104
name = "google_http_client",
105105
exports = [
106+
"@maven//:com_google_code_gson_gson",
106107
"@maven//:com_google_http_client_google_http_client",
107108
"@maven//:com_google_http_client_google_http_client_gson",
108-
"@maven//:com_google_code_gson_gson",
109-
"@maven//:commons_codec_commons_codec",
109+
"@maven//:commons_codec_commons_codec",
110110
],
111111
)
112112

@@ -152,20 +152,18 @@ java_library(
152152
"@maven//:com_google_flogger_flogger",
153153
],
154154
runtime_deps = [
155-
"@maven//:com_google_flogger_flogger_system_backend"
156-
]
155+
"@maven//:com_google_flogger_flogger_system_backend",
156+
],
157157
)
158158

159159
java_library(
160160
name = "hg_testing",
161161
testonly = 1,
162162
exports = [
163163
"//java/com/google/copybara/hg/testing",
164-
]
164+
],
165165
)
166166

167167
# Required temporarily until @io_bazel//src/main/java/com/google/devtools/build/lib/syntax/...
168168
# is fixed
169169
exports_files(["bazel.patch"])
170-
171-

0 commit comments

Comments
 (0)