Skip to content

Commit 380e619

Browse files
committed
init
1 parent 4b8798e commit 380e619

10 files changed

Lines changed: 115 additions & 47 deletions

src/ghstack/land.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,9 +145,33 @@ def main(
145145
stack_orig_refs.append((ref, pr_resolved))
146146

147147
# OK, actually do the land now
148-
for orig_ref, _ in stack_orig_refs:
148+
for orig_ref, pr_resolved in stack_orig_refs:
149149
try:
150150
sh.git("cherry-pick", f"{remote_name}/{orig_ref}")
151+
# Add PR number to commit message like GitHub does
152+
commit_msg = sh.git("log", "-1", "--pretty=%B")
153+
# Get the original author and committer dates to preserve the commit hash
154+
author_date = sh.git("log", "-1", "--pretty=%aD")
155+
committer_date = sh.git("log", "-1", "--pretty=%cD")
156+
lines = commit_msg.split("\n")
157+
if lines:
158+
# Add PR number to the subject line (first line)
159+
subject = lines[0].rstrip()
160+
# Only add if not already present
161+
pr_tag = f"(#{pr_resolved.number})"
162+
if pr_tag not in subject:
163+
subject = f"{subject} {pr_tag}"
164+
lines[0] = subject
165+
new_msg = "\n".join(lines)
166+
# Preserve dates to keep the commit hash consistent
167+
sh.sh(
168+
"git",
169+
"commit",
170+
"--amend",
171+
"-m",
172+
new_msg,
173+
env={"GIT_AUTHOR_DATE": author_date, "GIT_COMMITTER_DATE": committer_date},
174+
)
151175
except BaseException:
152176
sh.git("cherry-pick", "--abort")
153177
raise

src/ghstack/submit.py

Lines changed: 70 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -487,18 +487,20 @@ def run(self) -> List[DiffMeta]:
487487
d = ghstack.git.convert_header(h, self.github_url)
488488
if d.pull_request_resolved is not None:
489489
ed = self.elaborate_diff(d)
490-
pre_branch_state_index[h.commit_id] = PreBranchState(
491-
head_commit_id=GitCommitHash(
492-
self.sh.git(
493-
"rev-parse", f"{self.remote_name}/{ed.head_ref}"
494-
)
495-
),
496-
base_commit_id=GitCommitHash(
497-
self.sh.git(
498-
"rev-parse", f"{self.remote_name}/{ed.base_ref}"
499-
)
500-
),
501-
)
490+
# Skip closed PRs (e.g., after landing) where branches have been deleted
491+
if not ed.closed:
492+
pre_branch_state_index[h.commit_id] = PreBranchState(
493+
head_commit_id=GitCommitHash(
494+
self.sh.git(
495+
"rev-parse", f"{self.remote_name}/{ed.head_ref}"
496+
)
497+
),
498+
base_commit_id=GitCommitHash(
499+
self.sh.git(
500+
"rev-parse", f"{self.remote_name}/{ed.base_ref}"
501+
)
502+
),
503+
)
502504

503505
# NB: deduplicates
504506
commit_index = {
@@ -870,21 +872,22 @@ def elaborate_diff(
870872
"--header",
871873
self.remote_name + "/" + branch_orig(username, gh_number),
872874
)
873-
except RuntimeError as e:
875+
except RuntimeError:
874876
if r["closed"]:
875-
raise RuntimeError(
876-
f"Cannot ghstack a stack with closed PR #{number} whose branch was deleted. "
877-
"If you were just trying to update a later PR in the stack, `git rebase` and try again. "
878-
"Otherwise, you may have been trying to update a PR that was already closed. "
879-
"To disassociate your update from the old PR and open a new PR, "
880-
"run `ghstack unlink`, `git rebase` and then try again."
881-
) from e
882-
raise
883-
remote_summary = ghstack.git.split_header(rev_list)[0]
884-
m_remote_source_id = RE_GHSTACK_SOURCE_ID.search(remote_summary.commit_msg)
885-
remote_source_id = m_remote_source_id.group(1) if m_remote_source_id else None
886-
m_comment_id = RE_GHSTACK_COMMENT_ID.search(remote_summary.commit_msg)
887-
comment_id = int(m_comment_id.group(1)) if m_comment_id else None
877+
# If the PR is closed and the branch is deleted (e.g., after landing),
878+
# we can't get the remote source ID. Return None for it, which will
879+
# signal to process_commit that this commit has been landed and should
880+
# be skipped (not updated).
881+
remote_source_id = None
882+
comment_id = None
883+
else:
884+
raise
885+
else:
886+
remote_summary = ghstack.git.split_header(rev_list)[0]
887+
m_remote_source_id = RE_GHSTACK_SOURCE_ID.search(remote_summary.commit_msg)
888+
remote_source_id = m_remote_source_id.group(1) if m_remote_source_id else None
889+
m_comment_id = RE_GHSTACK_COMMENT_ID.search(remote_summary.commit_msg)
890+
comment_id = int(m_comment_id.group(1)) if m_comment_id else None
888891

889892
return DiffWithGitHubMetadata(
890893
diff=diff,
@@ -917,6 +920,47 @@ def process_commit(
917920
if elab_diff is not None and elab_diff.closed:
918921
if self.direct:
919922
self._raise_needs_rebase()
923+
# If we're trying to submit a closed commit, check if it has been modified
924+
if elab_diff.remote_source_id is None:
925+
# The branch was deleted (e.g., after landing). Check if the commit has been
926+
# modified by comparing source_ids. If the commit is reachable from master with
927+
# the same source_id (tree hash), it means it was landed and we should skip it.
928+
# Otherwise, it's been modified and we should raise an error.
929+
try:
930+
# Check if there's a commit on master with the same tree (source_id)
931+
master_commits = self.sh.git(
932+
"log",
933+
"--format=%H %T",
934+
f"{self.remote_name}/{self.base}",
935+
"-n", "100", # Check last 100 commits
936+
)
937+
for line in master_commits.split("\n"):
938+
if not line.strip():
939+
continue
940+
commit_hash, tree_hash = line.split()
941+
if tree_hash == diff.source_id:
942+
# Found a commit on master with the same tree, so this commit
943+
# was landed (just with a different commit message/hash)
944+
return None
945+
except Exception:
946+
pass
947+
# Didn't find a matching commit on master, so this is a modified closed commit
948+
raise RuntimeError(
949+
f"Cannot ghstack a stack with closed PR #{elab_diff.number} whose branch was deleted. "
950+
"If you were just trying to update a later PR in the stack, `git rebase` and try again. "
951+
"Otherwise, you may have been trying to update a PR that was already closed. "
952+
"To disassociate your update from the old PR and open a new PR, "
953+
"run `ghstack unlink`, `git rebase` and then try again."
954+
)
955+
elif diff.source_id != elab_diff.remote_source_id:
956+
# The commit has been modified locally
957+
raise RuntimeError(
958+
f"Cannot ghstack a stack with closed PR #{elab_diff.number} whose branch was deleted. "
959+
"If you were just trying to update a later PR in the stack, `git rebase` and try again. "
960+
"Otherwise, you may have been trying to update a PR that was already closed. "
961+
"To disassociate your update from the old PR and open a new PR, "
962+
"run `ghstack unlink`, `git rebase` and then try again."
963+
)
920964
return None
921965

922966
# Edge case: check if the commit is empty; if so skip submitting

test/land/default_branch_change.py.test

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,8 @@ gh_land(diff2.pr_url)
107107
assert_expected_inline(
108108
get_upstream_sh().git("log", "--oneline", "master"),
109109
"""\
110-
d779d84 Commit B
111-
542f79d Commit A
110+
838086c Commit B (#501)
111+
da05922 Commit A (#500)
112112
dc8bfe4 Initial commit""",
113113
)
114114
assert_expected_inline(

test/land/ff.py.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ gh_land(pr_url)
1111
assert_expected_inline(
1212
get_upstream_sh().git("log", "--oneline", "master"),
1313
"""\
14-
8927014 Commit A
14+
8b039fb Commit A (#500)
1515
dc8bfe4 Initial commit""",
1616
)
1717

test/land/ff_stack.py.test

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ gh_land(pr_url)
1616
assert_expected_inline(
1717
get_upstream_sh().git("log", "--oneline", "master"),
1818
"""\
19-
b6c40ad Commit B
20-
851cf96 Commit A
19+
34cfe12 Commit B (#501)
20+
049a42c Commit A (#500)
2121
dc8bfe4 Initial commit""",
2222
)

test/land/ff_stack_two_phase.py.test

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ gh_land(pr_url2)
1818
assert_expected_inline(
1919
get_upstream_sh().git("log", "--oneline", "master"),
2020
"""\
21-
b6c40ad Commit B
22-
851cf96 Commit A
21+
34cfe12 Commit B (#501)
22+
049a42c Commit A (#500)
2323
dc8bfe4 Initial commit""",
2424
)

test/land/invalid_resubmit.py.test

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,16 +44,16 @@ else:
4444

4545
This is commit A
4646

47-
* 98fcb03 New PR
47+
* d700a19 New PR
4848

4949
Repository state:
5050

51-
* 98fcb03 (gh/ezyang/1/head)
51+
* d700a19 (gh/ezyang/1/head)
5252
| New PR
53-
* 0d09e7d (gh/ezyang/1/base)
53+
* 239fc10 (gh/ezyang/1/base)
5454
| New PR (base update)
55-
* 8927014 (HEAD -> master)
56-
| Commit A
55+
* 8b039fb (HEAD -> master)
56+
| Commit A (#500)
5757
* dc8bfe4
5858
Initial commit
5959
"""

test/land/non_ff.py.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ gh_land(pr_url)
1717
assert_expected_inline(
1818
get_upstream_sh().git("log", "--oneline", "master"),
1919
"""\
20-
d43d06e Commit A
20+
0388cc8 Commit A (#500)
2121
38808c0 Commit U
2222
dc8bfe4 Initial commit""",
2323
)

test/land/non_ff_stack_two_phase.py.test

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ gh_land(pr_url2)
2222
assert_expected_inline(
2323
get_upstream_sh().git("log", "--oneline", "master"),
2424
"""\
25-
ec1d0de Commit B
26-
d8a6272 Commit A
25+
3657657 Commit B (#501)
26+
18a5a80 Commit A (#500)
2727
a8ca27f Commit C
2828
dc8bfe4 Initial commit""",
2929
)

test/land/update_after_land.py.test

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,17 +55,17 @@ else:
5555

5656
This is commit B
5757

58-
* c6c8f43 Run 3
58+
* 0b71837 Run 3
5959
* 16e1e12 Initial 1
6060

6161
Repository state:
6262

63-
* c6c8f43 (gh/ezyang/2/head)
63+
* 0b71837 (gh/ezyang/2/head)
6464
|\\ Run 3
65-
| * 36376f9 (gh/ezyang/2/base)
65+
| * 16c66a5 (gh/ezyang/2/base)
6666
| |\\ Run 3 (base update)
67-
| | * 9bf93f4 (HEAD -> master)
68-
| | | Commit A
67+
| | * 71f0c87 (HEAD -> master)
68+
| | | Commit A (#500)
6969
| | * 7f0288c
7070
| | | Commit U
7171
* | | 16e1e12

0 commit comments

Comments
 (0)