@@ -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,24 @@ 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 = (
889+ m_remote_source_id .group (1 ) if m_remote_source_id else None
890+ )
891+ m_comment_id = RE_GHSTACK_COMMENT_ID .search (remote_summary .commit_msg )
892+ comment_id = int (m_comment_id .group (1 )) if m_comment_id else None
888893
889894 return DiffWithGitHubMetadata (
890895 diff = diff ,
@@ -917,6 +922,48 @@ def process_commit(
917922 if elab_diff is not None and elab_diff .closed :
918923 if self .direct :
919924 self ._raise_needs_rebase ()
925+ # If we're trying to submit a closed commit, check if it has been modified
926+ if elab_diff .remote_source_id is None :
927+ # The branch was deleted (e.g., after landing). Check if the commit has been
928+ # modified by comparing source_ids. If the commit is reachable from master with
929+ # the same source_id (tree hash), it means it was landed and we should skip it.
930+ # Otherwise, it's been modified and we should raise an error.
931+ try :
932+ # Check if there's a commit on master with the same tree (source_id)
933+ master_commits = self .sh .git (
934+ "log" ,
935+ "--format=%H %T" ,
936+ f"{ self .remote_name } /{ self .base } " ,
937+ "-n" ,
938+ "100" , # Check last 100 commits
939+ )
940+ for line in master_commits .split ("\n " ):
941+ if not line .strip ():
942+ continue
943+ commit_hash , tree_hash = line .split ()
944+ if tree_hash == diff .source_id :
945+ # Found a commit on master with the same tree, so this commit
946+ # was landed (just with a different commit message/hash)
947+ return None
948+ except Exception :
949+ pass
950+ # Didn't find a matching commit on master, so this is a modified closed commit
951+ raise RuntimeError (
952+ f"Cannot ghstack a stack with closed PR #{ elab_diff .number } whose branch was deleted. "
953+ "If you were just trying to update a later PR in the stack, `git rebase` and try again. "
954+ "Otherwise, you may have been trying to update a PR that was already closed. "
955+ "To disassociate your update from the old PR and open a new PR, "
956+ "run `ghstack unlink`, `git rebase` and then try again."
957+ )
958+ elif diff .source_id != elab_diff .remote_source_id :
959+ # The commit has been modified locally
960+ raise RuntimeError (
961+ f"Cannot ghstack a stack with closed PR #{ elab_diff .number } whose branch was deleted. "
962+ "If you were just trying to update a later PR in the stack, `git rebase` and try again. "
963+ "Otherwise, you may have been trying to update a PR that was already closed. "
964+ "To disassociate your update from the old PR and open a new PR, "
965+ "run `ghstack unlink`, `git rebase` and then try again."
966+ )
920967 return None
921968
922969 # Edge case: check if the commit is empty; if so skip submitting
0 commit comments