ghstack 0.13.0__tar.gz → 0.14.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. {ghstack-0.13.0 → ghstack-0.14.0}/PKG-INFO +1 -1
  2. {ghstack-0.13.0 → ghstack-0.14.0}/pyproject.toml +1 -1
  3. ghstack-0.14.0/src/ghstack/checkout.py +64 -0
  4. {ghstack-0.13.0 → ghstack-0.14.0}/src/ghstack/cli.py +23 -1
  5. {ghstack-0.13.0 → ghstack-0.14.0}/src/ghstack/config.py +101 -1
  6. {ghstack-0.13.0 → ghstack-0.14.0}/src/ghstack/github_fake.py +21 -0
  7. {ghstack-0.13.0 → ghstack-0.14.0}/src/ghstack/github_utils.py +17 -3
  8. {ghstack-0.13.0 → ghstack-0.14.0}/src/ghstack/submit.py +32 -0
  9. {ghstack-0.13.0 → ghstack-0.14.0}/src/ghstack/test_prelude.py +39 -4
  10. ghstack-0.13.0/src/ghstack/checkout.py +0 -31
  11. {ghstack-0.13.0 → ghstack-0.14.0}/LICENSE +0 -0
  12. {ghstack-0.13.0 → ghstack-0.14.0}/README.md +0 -0
  13. {ghstack-0.13.0 → ghstack-0.14.0}/src/ghstack/__init__.py +0 -0
  14. {ghstack-0.13.0 → ghstack-0.14.0}/src/ghstack/__main__.py +0 -0
  15. {ghstack-0.13.0 → ghstack-0.14.0}/src/ghstack/action.py +0 -0
  16. {ghstack-0.13.0 → ghstack-0.14.0}/src/ghstack/cache.py +0 -0
  17. {ghstack-0.13.0 → ghstack-0.14.0}/src/ghstack/cherry_pick.py +0 -0
  18. {ghstack-0.13.0 → ghstack-0.14.0}/src/ghstack/circleci.py +0 -0
  19. {ghstack-0.13.0 → ghstack-0.14.0}/src/ghstack/circleci_real.py +0 -0
  20. {ghstack-0.13.0 → ghstack-0.14.0}/src/ghstack/diff.py +0 -0
  21. {ghstack-0.13.0 → ghstack-0.14.0}/src/ghstack/forensics.py +0 -0
  22. {ghstack-0.13.0 → ghstack-0.14.0}/src/ghstack/git.py +0 -0
  23. {ghstack-0.13.0 → ghstack-0.14.0}/src/ghstack/github.py +0 -0
  24. {ghstack-0.13.0 → ghstack-0.14.0}/src/ghstack/github_real.py +0 -0
  25. {ghstack-0.13.0 → ghstack-0.14.0}/src/ghstack/github_schema.graphql +0 -0
  26. {ghstack-0.13.0 → ghstack-0.14.0}/src/ghstack/gpg_sign.py +0 -0
  27. {ghstack-0.13.0 → ghstack-0.14.0}/src/ghstack/land.py +0 -0
  28. {ghstack-0.13.0 → ghstack-0.14.0}/src/ghstack/logs.py +0 -0
  29. {ghstack-0.13.0 → ghstack-0.14.0}/src/ghstack/py.typed +0 -0
  30. {ghstack-0.13.0 → ghstack-0.14.0}/src/ghstack/rage.py +0 -0
  31. {ghstack-0.13.0 → ghstack-0.14.0}/src/ghstack/shell.py +0 -0
  32. {ghstack-0.13.0 → ghstack-0.14.0}/src/ghstack/status.py +0 -0
  33. {ghstack-0.13.0 → ghstack-0.14.0}/src/ghstack/trailers.py +0 -0
  34. {ghstack-0.13.0 → ghstack-0.14.0}/src/ghstack/types.py +0 -0
  35. {ghstack-0.13.0 → ghstack-0.14.0}/src/ghstack/unlink.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ghstack
3
- Version: 0.13.0
3
+ Version: 0.14.0
4
4
  Summary: Stack diff support for GitHub
5
5
  Author: Edward Z. Yang
6
6
  Author-email: Edward Z. Yang <ezyang@mit.edu>
@@ -16,7 +16,7 @@ dependencies = [
16
16
  "flake8<8.0.0,>=7.0.0",
17
17
  ]
18
18
  name = "ghstack"
19
- version = "0.13.0"
19
+ version = "0.14.0"
20
20
  description = "Stack diff support for GitHub"
21
21
  readme = "README.md"
22
22
  classifiers = [
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import logging
4
+ import re
5
+
6
+ import ghstack.github
7
+ import ghstack.github_utils
8
+ import ghstack.shell
9
+
10
+
11
+ def main(
12
+ pull_request: str,
13
+ github: ghstack.github.GitHubEndpoint,
14
+ sh: ghstack.shell.Shell,
15
+ remote_name: str,
16
+ same_base: bool = False,
17
+ ) -> None:
18
+
19
+ params = ghstack.github_utils.parse_pull_request(
20
+ pull_request, sh=sh, remote_name=remote_name
21
+ )
22
+ head_ref = github.get_head_ref(**params)
23
+ orig_ref = re.sub(r"/head$", "/orig", head_ref)
24
+ if orig_ref == head_ref:
25
+ logging.warning(
26
+ "The ref {} doesn't look like a ghstack reference".format(head_ref)
27
+ )
28
+
29
+ # TODO: Handle remotes correctly too (so this subsumes hub)
30
+
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
+
51
+ 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
+
64
+ sh.git("checkout", remote_name + "/" + orig_ref)
@@ -120,8 +120,13 @@ def action(close: bool, pull_request: str) -> None:
120
120
 
121
121
 
122
122
  @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
+ )
123
128
  @click.argument("pull_request", metavar="PR")
124
- def checkout(pull_request: str) -> None:
129
+ def checkout(same_base: bool, pull_request: str) -> None:
125
130
  """
126
131
  Checkout a PR
127
132
  """
@@ -131,6 +136,7 @@ def checkout(pull_request: str) -> None:
131
136
  github=github,
132
137
  sh=shell,
133
138
  remote_name=config.remote_name,
139
+ same_base=same_base,
134
140
  )
135
141
 
136
142
 
@@ -257,6 +263,18 @@ def status(pull_request: str) -> None:
257
263
  "With --no-stack, we support only non-range identifiers, and will submit each commit "
258
264
  "listed in the command line.",
259
265
  )
266
+ @click.option(
267
+ "--reviewer",
268
+ default=None,
269
+ help="Comma-separated list of GitHub usernames to add as reviewers to new PRs "
270
+ "(overrides .ghstackrc setting)",
271
+ )
272
+ @click.option(
273
+ "--label",
274
+ default=None,
275
+ help="Comma-separated list of labels to add to new PRs "
276
+ "(overrides .ghstackrc setting)",
277
+ )
260
278
  @click.option(
261
279
  "--direct/--no-direct",
262
280
  "direct_opt",
@@ -280,6 +298,8 @@ def submit(
280
298
  base: Optional[str],
281
299
  revs: Tuple[str, ...],
282
300
  stack: bool,
301
+ reviewer: Optional[str],
302
+ label: Optional[str],
283
303
  ) -> None:
284
304
  """
285
305
  Submit or update a PR stack
@@ -301,6 +321,8 @@ def submit(
301
321
  revs=revs,
302
322
  stack=stack,
303
323
  direct_opt=direct_opt,
324
+ reviewer=reviewer if reviewer is not None else config.reviewer,
325
+ label=label if label is not None else config.label,
304
326
  )
305
327
 
306
328
 
@@ -5,8 +5,10 @@ import getpass
5
5
  import logging
6
6
  import os
7
7
  import re
8
+ import shutil
9
+ import subprocess
8
10
  from pathlib import Path
9
- from typing import NamedTuple, Optional
11
+ from typing import NamedTuple, Optional, Tuple
10
12
 
11
13
  import requests
12
14
 
@@ -15,6 +17,71 @@ import ghstack.logs
15
17
  DEFAULT_GHSTACKRC_PATH = Path.home() / ".ghstackrc"
16
18
  GHSTACKRC_PATH_VAR = "GHSTACKRC_PATH"
17
19
 
20
+
21
+ def is_gh_cli_available() -> bool:
22
+ """Check if the GitHub CLI (gh) is available in PATH."""
23
+ return shutil.which("gh") is not None
24
+
25
+
26
+ def get_gh_cli_credentials(
27
+ github_url: str = "github.com",
28
+ ) -> Tuple[Optional[str], Optional[str], Optional[str]]:
29
+ """
30
+ Extract credentials from the GitHub CLI if available and authenticated.
31
+
32
+ Args:
33
+ github_url: The GitHub host to get credentials for.
34
+
35
+ Returns:
36
+ A tuple of (token, username, url) or (None, None, None) if unavailable.
37
+ """
38
+ if not is_gh_cli_available():
39
+ return None, None, None
40
+
41
+ try:
42
+ # Check if gh is authenticated for this host
43
+ auth_status = subprocess.run(
44
+ ["gh", "auth", "status", "-h", github_url],
45
+ capture_output=True,
46
+ text=True,
47
+ )
48
+ if auth_status.returncode != 0:
49
+ logging.debug(f"gh CLI not authenticated for {github_url}")
50
+ return None, None, None
51
+
52
+ # Get the token
53
+ token_result = subprocess.run(
54
+ ["gh", "auth", "token", "-h", github_url],
55
+ capture_output=True,
56
+ text=True,
57
+ )
58
+ if token_result.returncode != 0:
59
+ logging.debug("Failed to get token from gh CLI")
60
+ return None, None, None
61
+ token = token_result.stdout.strip()
62
+ if not token:
63
+ return None, None, None
64
+
65
+ # Get the username using gh api
66
+ username_result = subprocess.run(
67
+ ["gh", "api", "user", "-q", ".login", "--hostname", github_url],
68
+ capture_output=True,
69
+ text=True,
70
+ )
71
+ username = None
72
+ if username_result.returncode == 0:
73
+ username = username_result.stdout.strip()
74
+
75
+ logging.debug(
76
+ f"Successfully retrieved credentials from gh CLI for {github_url}"
77
+ )
78
+ return token, username, github_url
79
+
80
+ except Exception as e:
81
+ logging.debug(f"Error getting credentials from gh CLI: {e}")
82
+ return None, None, None
83
+
84
+
18
85
  Config = NamedTuple(
19
86
  "Config",
20
87
  [
@@ -40,6 +107,10 @@ Config = NamedTuple(
40
107
  ("github_url", str),
41
108
  # Name of the upstream remote
42
109
  ("remote_name", str),
110
+ # Default reviewers to add to new pull requests (comma-separated usernames)
111
+ ("reviewer", Optional[str]),
112
+ # Default labels to add to new pull requests (comma-separated labels)
113
+ ("label", Optional[str]),
43
114
  ],
44
115
  )
45
116
 
@@ -97,6 +168,7 @@ def read_config(
97
168
  # Environment variable overrides config file
98
169
  # This envvar is legacy from ghexport days
99
170
  github_oauth = os.getenv("OAUTH_TOKEN")
171
+ gh_cli_username = None # Track username from gh CLI
100
172
  if github_oauth is not None:
101
173
  logging.warning(
102
174
  "Deprecated OAUTH_TOKEN environment variable used to populate github_oauth--"
@@ -105,6 +177,17 @@ def read_config(
105
177
  )
106
178
  if github_oauth is None and config.has_option("ghstack", "github_oauth"):
107
179
  github_oauth = config.get("ghstack", "github_oauth")
180
+
181
+ # Try GitHub CLI if available and no token found yet
182
+ if github_oauth is None and request_github_token:
183
+ gh_token, gh_username, _ = get_gh_cli_credentials(github_url)
184
+ if gh_token is not None:
185
+ print(f"Using GitHub credentials from gh CLI for {github_url}")
186
+ github_oauth = gh_token
187
+ gh_cli_username = gh_username
188
+ # Don't save gh CLI credentials to config - they may change/expire
189
+
190
+ # Fall back to device flow if still no token
108
191
  if github_oauth is None and request_github_token:
109
192
  print("Generating GitHub access token...")
110
193
  CLIENT_ID = "89cc88ca50efbe86907a"
@@ -150,6 +233,11 @@ def read_config(
150
233
  github_username = None
151
234
  if config.has_option("ghstack", "github_username"):
152
235
  github_username = config.get("ghstack", "github_username")
236
+ # Use username from gh CLI if we got it
237
+ if github_username is None and gh_cli_username is not None:
238
+ github_username = gh_cli_username
239
+ # Don't save gh CLI username to config - it comes from gh CLI
240
+ # Fall back to API lookup if we have a token but no username yet
153
241
  if github_username is None and github_oauth is not None:
154
242
  request_url: str
155
243
  if github_url == "github.com":
@@ -203,6 +291,16 @@ def read_config(
203
291
  else:
204
292
  remote_name = "origin"
205
293
 
294
+ if config.has_option("ghstack", "reviewer"):
295
+ reviewer = config.get("ghstack", "reviewer")
296
+ else:
297
+ reviewer = None
298
+
299
+ if config.has_option("ghstack", "label"):
300
+ label = config.get("ghstack", "label")
301
+ else:
302
+ label = None
303
+
206
304
  if write_back:
207
305
  with open(config_path, "w") as f:
208
306
  config.write(f)
@@ -218,6 +316,8 @@ def read_config(
218
316
  default_project_dir=default_project_dir,
219
317
  github_url=github_url,
220
318
  remote_name=remote_name,
319
+ reviewer=reviewer,
320
+ label=label,
221
321
  )
222
322
  logging.debug(f"conf = {conf}")
223
323
  return conf
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
 
3
+ import dataclasses
3
4
  import os.path
4
5
  import re
5
6
  from dataclasses import dataclass
@@ -271,6 +272,8 @@ class PullRequest(Node):
271
272
  # state: PullRequestState
272
273
  title: str
273
274
  url: str
275
+ reviewers: List[str] = dataclasses.field(default_factory=list)
276
+ labels: List[str] = dataclasses.field(default_factory=list)
274
277
 
275
278
  def repository(self, info: GraphQLResolveInfo) -> Repository:
276
279
  return github_state(info).repositories[self._repository]
@@ -473,6 +476,24 @@ class FakeGitHubEndpoint(ghstack.github.GitHubEndpoint):
473
476
  GitHubNumber(int(m.group(3))),
474
477
  cast(CreateIssueCommentInput, kwargs),
475
478
  )
479
+ if m := re.match(
480
+ r"^repos/([^/]+)/([^/]+)/pulls/([^/]+)/requested_reviewers", path
481
+ ):
482
+ # Handle adding reviewers
483
+ state = self.state
484
+ repo = state.repository(m.group(1), m.group(2))
485
+ pr = state.pull_request(repo, GitHubNumber(int(m.group(3))))
486
+ reviewers = kwargs.get("reviewers", [])
487
+ pr.reviewers.extend(reviewers)
488
+ return {}
489
+ if m := re.match(r"^repos/([^/]+)/([^/]+)/issues/([^/]+)/labels", path):
490
+ # Handle adding labels
491
+ state = self.state
492
+ repo = state.repository(m.group(1), m.group(2))
493
+ pr = state.pull_request(repo, GitHubNumber(int(m.group(3))))
494
+ labels = kwargs.get("labels", [])
495
+ pr.labels.extend(labels)
496
+ return {}
476
497
  elif method == "patch":
477
498
  if m := re.match(r"^repos/([^/]+)/([^/]+)(?:/pulls/([^/]+))?$", path):
478
499
  owner, name, number = m.groups()
@@ -25,7 +25,9 @@ def get_github_repo_name_with_owner(
25
25
  remote_name: str,
26
26
  ) -> GitHubRepoNameWithOwner:
27
27
  # Grovel in remotes to figure it out
28
- remote_url = sh.git("remote", "get-url", remote_name)
28
+ # Use --push to get the push URL, which is what matters for determining
29
+ # where commits will actually be pushed to
30
+ remote_url = sh.git("remote", "get-url", "--push", remote_name)
29
31
  while True:
30
32
  match = r"^git@{github_url}:/?([^/]+)/(.+?)(?:\.git)?$".format(
31
33
  github_url=github_url
@@ -138,20 +140,32 @@ GitHubPullRequestParams = TypedDict(
138
140
  )
139
141
 
140
142
 
143
+ def _normalize_remote_url(remote_url: str) -> str:
144
+ """Convert SSH remote URL to HTTPS format, strip .git suffix."""
145
+ # git@github.com:owner/repo.git -> https://github.com/owner/repo
146
+ m = re.match(r"^git@([^:]+):/?(.+?)(?:\.git)?$", remote_url)
147
+ if m:
148
+ return f"https://{m.group(1)}/{m.group(2)}"
149
+ return re.sub(r"\.git$", "", remote_url)
150
+
151
+
141
152
  def parse_pull_request(
142
153
  pull_request: str,
143
154
  *,
144
155
  sh: Optional[ghstack.shell.Shell] = None,
145
156
  remote_name: Optional[str] = None,
146
157
  ) -> GitHubPullRequestParams:
158
+ pull_request = pull_request.lstrip("#")
147
159
  m = RE_PR_URL.match(pull_request)
148
160
  if not m:
149
161
  # We can reconstruct the URL if just a PR number is passed
150
162
  if sh is not None and remote_name is not None:
151
- remote_url = sh.git("remote", "get-url", remote_name)
163
+ remote_url = sh.git("remote", "get-url", "--push", remote_name)
152
164
  # Do not pass the shell to avoid infinite loop
153
165
  try:
154
- return parse_pull_request(remote_url + "/pull/" + pull_request)
166
+ return parse_pull_request(
167
+ _normalize_remote_url(remote_url) + "/pull/" + pull_request
168
+ )
155
169
  except RuntimeError:
156
170
  # Fall back on original error message
157
171
  pass
@@ -343,6 +343,12 @@ class Submitter:
343
343
  # merged. If None, infer whether or not the PR should be direct or not.
344
344
  direct_opt: Optional[bool] = None
345
345
 
346
+ # Default reviewers to add to new pull requests (comma-separated usernames)
347
+ reviewer: Optional[str] = None
348
+
349
+ # Default labels to add to new pull requests (comma-separated labels)
350
+ label: Optional[str] = None
351
+
346
352
  # ~~~~~~~~~~~~~~~~~~~~~~~~
347
353
  # Computed in post init
348
354
 
@@ -1434,6 +1440,32 @@ is closed (likely due to being merged). Please rebase to upstream and try again
1434
1440
  )
1435
1441
  comment_id = rc["id"]
1436
1442
 
1443
+ # Add reviewers if specified
1444
+ if self.reviewer:
1445
+ reviewers = [r.strip() for r in self.reviewer.split(",") if r.strip()]
1446
+ if reviewers:
1447
+ try:
1448
+ self.github.post(
1449
+ f"repos/{self.repo_owner}/{self.repo_name}/pulls/{number}/requested_reviewers",
1450
+ reviewers=reviewers,
1451
+ )
1452
+ logging.info(f"Added reviewers: {', '.join(reviewers)}")
1453
+ except Exception as e:
1454
+ logging.warning(f"Failed to add reviewers: {e}")
1455
+
1456
+ # Add labels if specified
1457
+ if self.label:
1458
+ labels = [label.strip() for label in self.label.split(",") if label.strip()]
1459
+ if labels:
1460
+ try:
1461
+ self.github.post(
1462
+ f"repos/{self.repo_owner}/{self.repo_name}/issues/{number}/labels",
1463
+ labels=labels,
1464
+ )
1465
+ logging.info(f"Added labels: {', '.join(labels)}")
1466
+ except Exception as e:
1467
+ logging.warning(f"Failed to add labels: {e}")
1468
+
1437
1469
  logging.info("Opened PR #{}".format(number))
1438
1470
 
1439
1471
  pull_request_resolved = ghstack.diff.PullRequestResolved(
@@ -12,6 +12,7 @@ from typing import Any, Callable, Iterator, List, Optional, Sequence, Tuple, Uni
12
12
 
13
13
  from expecttest import assert_expected_inline
14
14
 
15
+ import ghstack.checkout
15
16
  import ghstack.cherry_pick
16
17
 
17
18
  import ghstack.github
@@ -32,6 +33,7 @@ __all__ = [
32
33
  "gh_land",
33
34
  "gh_unlink",
34
35
  "gh_cherry_pick",
36
+ "gh_checkout",
35
37
  "GitCommitHash",
36
38
  "checkout",
37
39
  "amend",
@@ -49,6 +51,8 @@ __all__ = [
49
51
  "get_sh",
50
52
  "get_upstream_sh",
51
53
  "get_github",
54
+ "get_pr_reviewers",
55
+ "get_pr_labels",
52
56
  "tick",
53
57
  "captured_output",
54
58
  ]
@@ -192,6 +196,8 @@ def gh_submit(
192
196
  base: Optional[str] = None,
193
197
  revs: Sequence[str] = (),
194
198
  stack: bool = True,
199
+ reviewer: Optional[str] = None,
200
+ label: Optional[str] = None,
195
201
  ) -> List[ghstack.submit.DiffMeta]:
196
202
  self = CTX
197
203
  r = ghstack.submit.main(
@@ -212,6 +218,8 @@ def gh_submit(
212
218
  revs=revs,
213
219
  stack=stack,
214
220
  check_invariants=True,
221
+ reviewer=reviewer,
222
+ label=label,
215
223
  )
216
224
  self.check_global_github_invariants(self.direct)
217
225
  return r
@@ -251,6 +259,17 @@ def gh_cherry_pick(pull_request: str, stack: bool = False) -> None:
251
259
  )
252
260
 
253
261
 
262
+ def gh_checkout(pull_request: str, same_base: bool = False) -> None:
263
+ self = CTX
264
+ return ghstack.checkout.main(
265
+ pull_request=pull_request,
266
+ github=self.github,
267
+ sh=self.sh,
268
+ remote_name="origin",
269
+ same_base=same_base,
270
+ )
271
+
272
+
254
273
  def write_file_and_add(filename: str, contents: str) -> None:
255
274
  self = CTX
256
275
  with self.sh.open(filename, "w") as f:
@@ -373,6 +392,26 @@ def is_direct() -> bool:
373
392
  return CTX.direct
374
393
 
375
394
 
395
+ def get_github() -> ghstack.github_fake.FakeGitHubEndpoint:
396
+ return CTX.github
397
+
398
+
399
+ def get_pr_reviewers(pr_number: int) -> List[str]:
400
+ """Get the reviewers for a PR number."""
401
+ github = get_github()
402
+ repo = github.state.repository("pytorch", "pytorch")
403
+ pr = github.state.pull_request(repo, ghstack.github_fake.GitHubNumber(pr_number))
404
+ return pr.reviewers
405
+
406
+
407
+ def get_pr_labels(pr_number: int) -> List[str]:
408
+ """Get the labels for a PR number."""
409
+ github = get_github()
410
+ repo = github.state.repository("pytorch", "pytorch")
411
+ pr = github.state.pull_request(repo, ghstack.github_fake.GitHubNumber(pr_number))
412
+ return pr.labels
413
+
414
+
376
415
  def assert_eq(a: Any, b: Any) -> None:
377
416
  assert a == b, f"{a} != {b}"
378
417
 
@@ -406,9 +445,5 @@ def get_upstream_sh() -> ghstack.shell.Shell:
406
445
  return CTX.upstream_sh
407
446
 
408
447
 
409
- def get_github() -> ghstack.github.GitHubEndpoint:
410
- return CTX.github
411
-
412
-
413
448
  def tick() -> None:
414
449
  CTX.sh.test_tick()
@@ -1,31 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
- import logging
4
- import re
5
-
6
- import ghstack.github
7
- import ghstack.github_utils
8
- import ghstack.shell
9
-
10
-
11
- def main(
12
- pull_request: str,
13
- github: ghstack.github.GitHubEndpoint,
14
- sh: ghstack.shell.Shell,
15
- remote_name: str,
16
- ) -> None:
17
-
18
- params = ghstack.github_utils.parse_pull_request(
19
- pull_request, sh=sh, remote_name=remote_name
20
- )
21
- head_ref = github.get_head_ref(**params)
22
- orig_ref = re.sub(r"/head$", "/orig", head_ref)
23
- if orig_ref == head_ref:
24
- logging.warning(
25
- "The ref {} doesn't look like a ghstack reference".format(head_ref)
26
- )
27
-
28
- # TODO: Handle remotes correctly too (so this subsumes hub)
29
-
30
- sh.git("fetch", "--prune", remote_name)
31
- sh.git("checkout", remote_name + "/" + orig_ref)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes