Skip to content

Commit 607ed84

Browse files
committed
Update
[ghstack-poisoned]
1 parent fef5b73 commit 607ed84

File tree

6 files changed

+143
-1
lines changed

6 files changed

+143
-1
lines changed

src/ghstack/checkout.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ def main(
1313
github: ghstack.github.GitHubEndpoint,
1414
sh: ghstack.shell.Shell,
1515
remote_name: str,
16+
same_base: bool = False,
1617
) -> None:
1718

1819
params = ghstack.github_utils.parse_pull_request(
@@ -27,5 +28,37 @@ def main(
2728

2829
# TODO: Handle remotes correctly too (so this subsumes hub)
2930

31+
# If --same-base is specified, check if checkout would change the merge-base
32+
if same_base:
33+
# Get the default branch name from the repo
34+
repo_info = ghstack.github_utils.get_github_repo_info(
35+
github=github,
36+
sh=sh,
37+
repo_owner=params["owner"],
38+
repo_name=params["name"],
39+
github_url=params["github_url"],
40+
remote_name=remote_name,
41+
)
42+
default_branch = repo_info["default_branch"]
43+
default_branch_ref = f"{remote_name}/{default_branch}"
44+
45+
# Get current merge-base with default branch
46+
current_base = sh.git("merge-base", default_branch_ref, "HEAD")
47+
else:
48+
current_base = None
49+
default_branch_ref = None
50+
3051
sh.git("fetch", "--prune", remote_name)
52+
53+
# If --same-base is specified, check what the new merge-base would be
54+
if same_base:
55+
target_ref = remote_name + "/" + orig_ref
56+
new_base = sh.git("merge-base", default_branch_ref, target_ref)
57+
58+
if current_base != new_base:
59+
raise RuntimeError(
60+
f"Checkout would change merge-base from {current_base[:8]} to {new_base[:8]}, "
61+
f"aborting due to --same-base flag"
62+
)
63+
3164
sh.git("checkout", remote_name + "/" + orig_ref)

src/ghstack/cli.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,13 @@ def action(close: bool, pull_request: str) -> None:
120120

121121

122122
@main.command("checkout")
123+
@click.option(
124+
"--same-base",
125+
is_flag=True,
126+
help="Only checkout if merge-base with main branch would remain the same",
127+
)
123128
@click.argument("pull_request", metavar="PR")
124-
def checkout(pull_request: str) -> None:
129+
def checkout(same_base: bool, pull_request: str) -> None:
125130
"""
126131
Checkout a PR
127132
"""
@@ -131,6 +136,7 @@ def checkout(pull_request: str) -> None:
131136
github=github,
132137
sh=shell,
133138
remote_name=config.remote_name,
139+
same_base=same_base,
134140
)
135141

136142

src/ghstack/test_prelude.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
from expecttest import assert_expected_inline
1414

15+
import ghstack.checkout
1516
import ghstack.cherry_pick
1617

1718
import ghstack.github
@@ -32,6 +33,7 @@
3233
"gh_land",
3334
"gh_unlink",
3435
"gh_cherry_pick",
36+
"gh_checkout",
3537
"GitCommitHash",
3638
"checkout",
3739
"amend",
@@ -251,6 +253,17 @@ def gh_cherry_pick(pull_request: str, stack: bool = False) -> None:
251253
)
252254

253255

256+
def gh_checkout(pull_request: str, same_base: bool = False) -> None:
257+
self = CTX
258+
return ghstack.checkout.main(
259+
pull_request=pull_request,
260+
github=self.github,
261+
sh=self.sh,
262+
remote_name="origin",
263+
same_base=same_base,
264+
)
265+
266+
254267
def write_file_and_add(filename: str, contents: str) -> None:
255268
self = CTX
256269
with self.sh.open(filename, "w") as f:

test/checkout/basic.py.test

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from ghstack.test_prelude import *
2+
3+
init_test()
4+
5+
# Create a PR to checkout
6+
commit("A")
7+
(A,) = gh_submit("Initial commit")
8+
9+
# Move to master and create another commit
10+
git("checkout", "master")
11+
commit("B")
12+
13+
# Verify we're on master with commit B
14+
current_log = git("log", "--oneline", "-n", "1")
15+
assert "Commit B" in current_log
16+
17+
# Checkout the PR
18+
gh_checkout(f"https://github.com/pytorch/pytorch/pull/{A.number}")
19+
20+
# After checkout, we should be on the PR commit
21+
current_log = git("log", "--oneline", "-n", "1")
22+
assert "Commit A" in current_log
23+
24+
ok()
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from ghstack.test_prelude import *
2+
3+
init_test()
4+
5+
# Create two PRs in a stack - they'll have the same base
6+
commit("A")
7+
commit("B")
8+
diffs = gh_submit("Stack of two commits")
9+
10+
# Should have two PRs
11+
assert len(diffs) == 2
12+
A = diffs[0] # First commit (A)
13+
B = diffs[1] # Second commit (B)
14+
15+
# Both PRs should have the same merge-base with master (initial commit)
16+
# Checkout PR A
17+
gh_checkout(f"https://github.com/pytorch/pytorch/pull/{A.number}")
18+
19+
# Verify we're on PR A
20+
current_log = git("log", "--oneline", "-n", "1")
21+
assert "Commit A" in current_log
22+
23+
# Now checkout PR B with --same-base
24+
# Since both have the same merge-base (initial commit), this should succeed
25+
gh_checkout(f"https://github.com/pytorch/pytorch/pull/{B.number}", same_base=True)
26+
27+
# Verify we successfully checked out PR B
28+
current_log = git("log", "--oneline", "-n", "1")
29+
assert "Commit B" in current_log
30+
31+
ok()
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import pytest
2+
from ghstack.test_prelude import *
3+
4+
init_test()
5+
6+
# Create first PR based on initial master
7+
commit("A")
8+
(A,) = gh_submit("First PR")
9+
10+
# Go back to master and advance it
11+
git("checkout", "master")
12+
commit("B")
13+
git("push", "origin", "master")
14+
15+
# Create second PR based on new master (different merge-base)
16+
commit("C")
17+
(C,) = gh_submit("Second PR")
18+
19+
# Checkout first PR
20+
gh_checkout(f"https://github.com/pytorch/pytorch/pull/{A.number}")
21+
22+
# Verify we're on PR A
23+
current_log = git("log", "--oneline", "-n", "1")
24+
assert "Commit A" in current_log
25+
26+
# Try to checkout second PR with --same-base
27+
# This should fail because merge-base would change from initial commit to commit B
28+
with pytest.raises(RuntimeError, match="would change merge-base"):
29+
gh_checkout(f"https://github.com/pytorch/pytorch/pull/{C.number}", same_base=True)
30+
31+
# Verify we're still on PR A (checkout was aborted)
32+
current_log = git("log", "--oneline", "-n", "1")
33+
assert "Commit A" in current_log
34+
35+
ok()

0 commit comments

Comments
 (0)