ghstack 0.10.0__tar.gz → 0.11.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 (32) hide show
  1. {ghstack-0.10.0 → ghstack-0.11.0}/PKG-INFO +5 -5
  2. {ghstack-0.10.0 → ghstack-0.11.0}/README.md +4 -4
  3. {ghstack-0.10.0 → ghstack-0.11.0}/pyproject.toml +1 -1
  4. {ghstack-0.10.0 → ghstack-0.11.0}/src/ghstack/diff.py +2 -2
  5. {ghstack-0.10.0 → ghstack-0.11.0}/src/ghstack/github_real.py +66 -29
  6. {ghstack-0.10.0 → ghstack-0.11.0}/src/ghstack/land.py +18 -13
  7. {ghstack-0.10.0 → ghstack-0.11.0}/src/ghstack/submit.py +4 -12
  8. {ghstack-0.10.0 → ghstack-0.11.0}/src/ghstack/__init__.py +0 -0
  9. {ghstack-0.10.0 → ghstack-0.11.0}/src/ghstack/__main__.py +0 -0
  10. {ghstack-0.10.0 → ghstack-0.11.0}/src/ghstack/action.py +0 -0
  11. {ghstack-0.10.0 → ghstack-0.11.0}/src/ghstack/cache.py +0 -0
  12. {ghstack-0.10.0 → ghstack-0.11.0}/src/ghstack/checkout.py +0 -0
  13. {ghstack-0.10.0 → ghstack-0.11.0}/src/ghstack/circleci.py +0 -0
  14. {ghstack-0.10.0 → ghstack-0.11.0}/src/ghstack/circleci_real.py +0 -0
  15. {ghstack-0.10.0 → ghstack-0.11.0}/src/ghstack/cli.py +0 -0
  16. {ghstack-0.10.0 → ghstack-0.11.0}/src/ghstack/config.py +0 -0
  17. {ghstack-0.10.0 → ghstack-0.11.0}/src/ghstack/forensics.py +0 -0
  18. {ghstack-0.10.0 → ghstack-0.11.0}/src/ghstack/git.py +0 -0
  19. {ghstack-0.10.0 → ghstack-0.11.0}/src/ghstack/github.py +0 -0
  20. {ghstack-0.10.0 → ghstack-0.11.0}/src/ghstack/github_fake.py +0 -0
  21. {ghstack-0.10.0 → ghstack-0.11.0}/src/ghstack/github_schema.graphql +0 -0
  22. {ghstack-0.10.0 → ghstack-0.11.0}/src/ghstack/github_utils.py +0 -0
  23. {ghstack-0.10.0 → ghstack-0.11.0}/src/ghstack/gpg_sign.py +0 -0
  24. {ghstack-0.10.0 → ghstack-0.11.0}/src/ghstack/logs.py +0 -0
  25. {ghstack-0.10.0 → ghstack-0.11.0}/src/ghstack/py.typed +0 -0
  26. {ghstack-0.10.0 → ghstack-0.11.0}/src/ghstack/rage.py +0 -0
  27. {ghstack-0.10.0 → ghstack-0.11.0}/src/ghstack/shell.py +0 -0
  28. {ghstack-0.10.0 → ghstack-0.11.0}/src/ghstack/status.py +0 -0
  29. {ghstack-0.10.0 → ghstack-0.11.0}/src/ghstack/test_prelude.py +0 -0
  30. {ghstack-0.10.0 → ghstack-0.11.0}/src/ghstack/trailers.py +0 -0
  31. {ghstack-0.10.0 → ghstack-0.11.0}/src/ghstack/types.py +0 -0
  32. {ghstack-0.10.0 → ghstack-0.11.0}/src/ghstack/unlink.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: ghstack
3
- Version: 0.10.0
3
+ Version: 0.11.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>
@@ -69,13 +69,13 @@ resolve conflicts in *each* PR, not just for the entire stack.)
69
69
  **How do I start a new feature?** Just checkout master on a new
70
70
  branch, and start working on a fresh branch.
71
71
 
72
- **WARNING.** You will NOT be able to merge these commits using the
73
- normal GitHub UI, as their branch bases won't be master. Use
74
- `ghstack land $PR_URL` (or alternatively `ghstack land #PR_NUM`) to land
72
+ **How do I merge my changes?** Warning: You will NOT be able to merge these
73
+ commits using the normal GitHub UI, as their branch bases won't be master.
74
+ Use `ghstack land $PR_URL` (or alternatively `ghstack land #PR_NUM`) to land
75
75
  a ghstack'ed pull request.
76
76
 
77
77
  You can also setup a GitHub action to allow a bot land.
78
- [ghstack_land_example](https://github.com/Chillee/ghstack_land_example/blob/main/.github/workflows/issue_comment.yml)
78
+ [ghstack_land_example](https://github.com/Chillee/ghstack_land_example/blob/main/.github/workflows/ghstack_land.yml)
79
79
  is an end-to-end example of how to do this.
80
80
 
81
81
  ## Structure of submitted pull requests
@@ -49,13 +49,13 @@ resolve conflicts in *each* PR, not just for the entire stack.)
49
49
  **How do I start a new feature?** Just checkout master on a new
50
50
  branch, and start working on a fresh branch.
51
51
 
52
- **WARNING.** You will NOT be able to merge these commits using the
53
- normal GitHub UI, as their branch bases won't be master. Use
54
- `ghstack land $PR_URL` (or alternatively `ghstack land #PR_NUM`) to land
52
+ **How do I merge my changes?** Warning: You will NOT be able to merge these
53
+ commits using the normal GitHub UI, as their branch bases won't be master.
54
+ Use `ghstack land $PR_URL` (or alternatively `ghstack land #PR_NUM`) to land
55
55
  a ghstack'ed pull request.
56
56
 
57
57
  You can also setup a GitHub action to allow a bot land.
58
- [ghstack_land_example](https://github.com/Chillee/ghstack_land_example/blob/main/.github/workflows/issue_comment.yml)
58
+ [ghstack_land_example](https://github.com/Chillee/ghstack_land_example/blob/main/.github/workflows/ghstack_land.yml)
59
59
  is an end-to-end example of how to do this.
60
60
 
61
61
  ## Structure of submitted pull requests
@@ -13,7 +13,7 @@ dependencies = [
13
13
  "flake8<8.0.0,>=7.0.0",
14
14
  ]
15
15
  name = "ghstack"
16
- version = "0.10.0"
16
+ version = "0.11.0"
17
17
  description = "Stack diff support for GitHub"
18
18
  readme = "README.md"
19
19
  classifiers = [
@@ -14,7 +14,7 @@ RE_GH_METADATA = re.compile(
14
14
 
15
15
 
16
16
  RAW_PULL_REQUEST_RESOLVED = (
17
- r"(Pull Request resolved|Pull-Request-resolved): "
17
+ r"(Pull Request resolved|Pull-Request-resolved|Pull-Request): "
18
18
  r"https://{github_url}/(?P<owner>[^/]+)/(?P<repo>[^/]+)/pull/(?P<number>[0-9]+)"
19
19
  )
20
20
 
@@ -93,7 +93,7 @@ class Diff:
93
93
  # be the same.
94
94
  source_id: str
95
95
 
96
- # The contents of 'Pull-Request-resolved'. This is None for
96
+ # The contents of 'Pull-Request'. This is None for
97
97
  # diffs that haven't been submitted by ghstack. For BC reasons,
98
98
  # this also accepts gh-metadata.
99
99
  pull_request_resolved: Optional[PullRequestResolved]
@@ -3,12 +3,16 @@
3
3
  import json
4
4
  import logging
5
5
  import re
6
+ import time
6
7
  from typing import Any, Dict, Optional, Sequence, Tuple, Union
7
8
 
8
9
  import requests
9
10
 
10
11
  import ghstack.github
11
12
 
13
+ MAX_RETRIES = 5
14
+ INITIAL_BACKOFF_SECONDS = 60
15
+
12
16
 
13
17
  class RealGitHubEndpoint(ghstack.github.GitHubEndpoint):
14
18
  """
@@ -155,32 +159,63 @@ class RealGitHubEndpoint(ghstack.github.GitHubEndpoint):
155
159
  }
156
160
 
157
161
  url = self.rest_endpoint.format(github_url=self.github_url) + "/" + path
158
- logging.debug("# {} {}".format(method, url))
159
- logging.debug("Request body:\n{}".format(json.dumps(kwargs, indent=1)))
160
162
 
161
- resp: requests.Response = getattr(requests, method)(
162
- url,
163
- json=kwargs,
164
- headers=headers,
165
- proxies=self._proxies(),
166
- verify=self.verify,
167
- cert=self.cert,
168
- )
163
+ backoff_seconds = INITIAL_BACKOFF_SECONDS
164
+ for attempt in range(0, MAX_RETRIES):
165
+ logging.debug("# {} {}".format(method, url))
166
+ logging.debug("Request body:\n{}".format(json.dumps(kwargs, indent=1)))
169
167
 
170
- logging.debug("Response status: {}".format(resp.status_code))
168
+ resp: requests.Response = getattr(requests, method)(
169
+ url,
170
+ json=kwargs,
171
+ headers=headers,
172
+ proxies=self._proxies(),
173
+ verify=self.verify,
174
+ cert=self.cert,
175
+ )
171
176
 
172
- try:
173
- r = resp.json()
174
- except ValueError:
175
- logging.debug("Response body:\n{}".format(r.text))
176
- raise
177
- else:
178
- pretty_json = json.dumps(r, indent=1)
179
- logging.debug("Response JSON:\n{}".format(pretty_json))
177
+ logging.debug("Response status: {}".format(resp.status_code))
180
178
 
181
- if resp.status_code == 404:
182
- raise ghstack.github.NotFoundError(
183
- """\
179
+ try:
180
+ r = resp.json()
181
+ except ValueError:
182
+ logging.debug("Response body:\n{}".format(r.text))
183
+ raise
184
+ else:
185
+ pretty_json = json.dumps(r, indent=1)
186
+ logging.debug("Response JSON:\n{}".format(pretty_json))
187
+
188
+ # Per Github rate limiting: https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api?apiVersion=2022-11-28#exceeding-the-rate-limit
189
+ if resp.status_code in (403, 429):
190
+ remaining_count = resp.headers.get("x-ratelimit-remaining")
191
+ reset_time = resp.headers.get("x-ratelimit-reset")
192
+
193
+ if remaining_count == "0" and reset_time:
194
+ sleep_time = int(reset_time) - int(time.time())
195
+ logging.warning(
196
+ f"Rate limit exceeded. Sleeping until reset in {sleep_time} seconds."
197
+ )
198
+ time.sleep(sleep_time)
199
+ continue
200
+ else:
201
+ retry_after_seconds = resp.headers.get("retry-after")
202
+ if retry_after_seconds:
203
+ sleep_time = int(retry_after_seconds)
204
+ logging.warning(
205
+ f"Secondary rate limit hit. Sleeping for {sleep_time} seconds."
206
+ )
207
+ else:
208
+ sleep_time = backoff_seconds
209
+ logging.warning(
210
+ f"Secondary rate limit hit. Sleeping for {sleep_time} seconds (exponential backoff)."
211
+ )
212
+ backoff_seconds *= 2
213
+ time.sleep(sleep_time)
214
+ continue
215
+
216
+ if resp.status_code == 404:
217
+ raise ghstack.github.NotFoundError(
218
+ """\
184
219
  GitHub raised a 404 error on the request for
185
220
  {url}.
186
221
  Usually, this doesn't actually mean the page doesn't exist; instead, it
@@ -190,13 +225,15 @@ https://{github_url}/settings/tokens and DOUBLE CHECK that you checked
190
225
  "public_repo" for permissions, and update ~/.ghstackrc with your new
191
226
  value.
192
227
  """.format(
193
- url=url, github_url=self.github_url
228
+ url=url, github_url=self.github_url
229
+ )
194
230
  )
195
- )
196
231
 
197
- try:
198
- resp.raise_for_status()
199
- except requests.HTTPError:
200
- raise RuntimeError(pretty_json)
232
+ try:
233
+ resp.raise_for_status()
234
+ except requests.HTTPError:
235
+ raise RuntimeError(pretty_json)
201
236
 
202
- return r
237
+ return r
238
+
239
+ raise RuntimeError("Exceeded maximum retries due to GitHub rate limiting")
@@ -152,11 +152,18 @@ to complain to the ghstack authors."""
152
152
  sh.git("cherry-pick", "--abort")
153
153
  raise
154
154
 
155
+ # All good! Push!
156
+ maybe_force_arg = []
157
+ if needs_force:
158
+ maybe_force_arg = ["--force-with-lease"]
159
+ sh.git(
160
+ "push", *maybe_force_arg, remote_name, f"HEAD:refs/heads/{default_branch}"
161
+ )
162
+
155
163
  # Advance base to head to "close" the PR for all PRs.
156
- # This has to happen before the push because the push
157
- # will trigger a closure, but we want a *merge*. This should
158
- # happen after the cherry-pick, because the cherry-picks can
159
- # fail
164
+ # This happens after the cherry-pick and push, because the cherry-picks
165
+ # can fail (merge conflict) and the push can also fail (race condition)
166
+
160
167
  # TODO: It might be helpful to advance orig to reflect the true
161
168
  # state of upstream at the time we are doing the land, and then
162
169
  # directly *merge* head into base, so that the PR accurately
@@ -173,21 +180,19 @@ to complain to the ghstack authors."""
173
180
  )
174
181
  github.notify_merged(pr_resolved)
175
182
 
176
- # All good! Push!
177
- maybe_force_arg = []
178
- if needs_force:
179
- maybe_force_arg = ["--force-with-lease"]
180
- sh.git(
181
- "push", *maybe_force_arg, remote_name, f"HEAD:refs/heads/{default_branch}"
182
- )
183
-
184
183
  # Delete the branches
185
184
  for orig_ref, _ in stack_orig_refs:
186
185
  # TODO: regex here so janky
187
186
  base_ref = re.sub(r"/orig$", "/base", orig_ref)
188
187
  head_ref = re.sub(r"/orig$", "/head", orig_ref)
189
188
  try:
190
- sh.git("push", remote_name, "--delete", orig_ref, base_ref, head_ref)
189
+ sh.git("push", remote_name, "--delete", orig_ref, base_ref)
190
+ except RuntimeError:
191
+ # Whatever, keep going
192
+ logging.warning("Failed to delete branch, continuing", exc_info=True)
193
+ # Try deleting head_ref separately since often after it's merged it doesn't exist anymore
194
+ try:
195
+ sh.git("push", remote_name, "--delete", head_ref)
191
196
  except RuntimeError:
192
197
  # Whatever, keep going
193
198
  logging.warning("Failed to delete branch, continuing", exc_info=True)
@@ -468,12 +468,6 @@ class Submitter:
468
468
  raise RuntimeError(
469
469
  "There appears to be no commits to process, based on the revs you passed me."
470
470
  )
471
- elif commit_count > 8 and not self.force:
472
- raise RuntimeError(
473
- "Cowardly refusing to handle a stack with more than eight PRs. "
474
- "You are likely to get rate limited by GitHub if you try to create or "
475
- "manipulate this many PRs. You can bypass this throttle using --force"
476
- )
477
471
 
478
472
  # This is not really accurate if you're doing a fancy pattern;
479
473
  # if this is a problem file us a bug.
@@ -816,7 +810,7 @@ as it supports bidirectional syncing. However, there is no way to
816
810
  convert a pre-existing PR in the old style to the new format which
817
811
  supports bidirectional syncing. If you would like to blow away the old
818
812
  PR and start anew, edit the Summary in the Phabricator diff to delete
819
- the line 'Pull-Request-resolved' and then run ghexport again.
813
+ the line 'Pull-Request' and then run ghexport again.
820
814
  """
821
815
  )
822
816
 
@@ -833,7 +827,7 @@ the Phabricator diff page? If so, please continue to use that button
833
827
  to export your diff.
834
828
 
835
829
  If you think this is in error, edit the Summary in the Phabricator diff
836
- to delete the line 'Pull-Request-resolved' and then run ghexport again.
830
+ to delete the line 'Pull-Request' and then run ghexport again.
837
831
  """
838
832
  )
839
833
  else:
@@ -970,9 +964,7 @@ to disassociate the commit with the pull request, and then try again.
970
964
  if self.direct:
971
965
  trailers_to_add.append(f"ghstack-comment-id: {elab_diff.comment_id}")
972
966
 
973
- trailers_to_add.append(
974
- f"Pull-Request-resolved: {pull_request_resolved.url()}"
975
- )
967
+ trailers_to_add.append(f"Pull-Request: {pull_request_resolved.url()}")
976
968
 
977
969
  commit_msg = ghstack.trailers.interpret_trailers(
978
970
  strip_mentions(diff.summary.rstrip()), trailers_to_add
@@ -1779,7 +1771,7 @@ is closed (likely due to being merged). Please rebase to upstream and try again
1779
1771
  commit_body = RE_GHSTACK_SOURCE_ID.sub("", commit_body)
1780
1772
  # Comment ID is not necessary; source of truth is orig commit
1781
1773
  commit_body = RE_GHSTACK_COMMENT_ID.sub("", commit_body)
1782
- # Don't store Pull request resolved in the PR body; it's
1774
+ # Don't store Pull request in the PR body; it's
1783
1775
  # unnecessary
1784
1776
  commit_body = ghstack.diff.re_pull_request_resolved_w_sp(self.github_url).sub(
1785
1777
  "", commit_body
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