| 17 | |
| 18 | |
| 19 | def main( |
| 20 | *, |
| 21 | commits: Optional[List[str]] = None, |
| 22 | github: ghstack.github.GitHubEndpoint, |
| 23 | sh: Optional[ghstack.shell.Shell] = None, |
| 24 | repo_owner: Optional[str] = None, |
| 25 | repo_name: Optional[str] = None, |
| 26 | github_url: str, |
| 27 | remote_name: str, |
| 28 | ) -> GitCommitHash: |
| 29 | # If commits is empty, we unlink the entire stack |
| 30 | # |
| 31 | # For now, we only process commits on our current |
| 32 | # stack, because we have no way of knowing how to |
| 33 | # "restack" for other commits. |
| 34 | |
| 35 | if sh is None: |
| 36 | # Use CWD |
| 37 | sh = ghstack.shell.Shell() |
| 38 | |
| 39 | default_branch = ghstack.github_utils.get_github_repo_info( |
| 40 | github=github, |
| 41 | sh=sh, |
| 42 | repo_owner=repo_owner, |
| 43 | repo_name=repo_name, |
| 44 | github_url=github_url, |
| 45 | remote_name=remote_name, |
| 46 | )["default_branch"] |
| 47 | |
| 48 | # Parse the commits |
| 49 | parsed_commits: Optional[Set[GitCommitHash]] = None |
| 50 | if commits: |
| 51 | parsed_commits = set() |
| 52 | for c in commits: |
| 53 | parsed_commits.add(GitCommitHash(sh.git("rev-parse", c))) |
| 54 | |
| 55 | base = GitCommitHash( |
| 56 | sh.git("merge-base", f"{remote_name}/{default_branch}", "HEAD") |
| 57 | ) |
| 58 | |
| 59 | # compute the stack of commits in chronological order (does not |
| 60 | # include base) |
| 61 | stack = ghstack.git.split_header( |
| 62 | sh.git("rev-list", "--reverse", "--header", "^" + base, "HEAD") |
| 63 | ) |
| 64 | |
| 65 | # sanity check the parsed_commits |
| 66 | if parsed_commits is not None: |
| 67 | stack_commits = set() |
| 68 | for s in stack: |
| 69 | stack_commits.add(s.commit_id) |
| 70 | invalid_commits = parsed_commits - stack_commits |
| 71 | if invalid_commits: |
| 72 | raise RuntimeError( |
| 73 | "unlink can only process commits which are on the " |
| 74 | "current stack; these commits are not:\n{}".format( |
| 75 | "\n".join(invalid_commits) |
| 76 | ) |