@@ -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
0 commit comments