Skip to content

Commit 96613ed

Browse files
http: add support for HTTP 429 rate limit retries
Add retry logic for HTTP 429 (Too Many Requests) responses to handle server-side rate limiting gracefully. When Git's HTTP client receives a 429 response, it can now automatically retry the request after an appropriate delay, respecting the server's rate limits. The implementation supports the RFC-compliant Retry-After header in both delay-seconds (integer) and HTTP-date (RFC 2822) formats. If a past date is provided, Git retries immediately without waiting. Retry behavior is controlled by three new configuration options: * http.maxRetries: Maximum number of retry attempts (default: 0, meaning retries are disabled by default). Users must explicitly opt-in to retry behavior. * http.retryAfter: Default delay in seconds when the server doesn't provide a Retry-After header (default: -1, meaning fail if no header is provided). This serves as a fallback mechanism. * http.maxRetryTime: Maximum delay in seconds for a single retry (default: 300). If the server requests a delay exceeding this limit, Git fails immediately rather than waiting. This prevents indefinite blocking on unreasonable server requests. All three options can be overridden via environment variables: GIT_HTTP_MAX_RETRIES, GIT_HTTP_RETRY_AFTER, and GIT_HTTP_MAX_RETRY_TIME. The retry logic implements a fail-fast approach: if any delay (whether from server header or configuration) exceeds maxRetryTime, Git fails immediately with a clear error message rather than capping the delay. This provides better visibility into rate limiting issues. Trace2 logging has been added to track retry attempts, delays, and error conditions. This enables monitoring and debugging of rate limit scenarios in production environments. The implementation includes extensive test coverage for basic retry behavior, Retry-After header formats (integer and HTTP-date), configuration combinations, maxRetryTime limits, invalid header handling, environment variable overrides, and edge cases. Signed-off-by: Vaidas Pilkauskas <[email protected]>
1 parent 6ab38b7 commit 96613ed

File tree

8 files changed

+704
-83
lines changed

8 files changed

+704
-83
lines changed

Documentation/config/http.adoc

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,30 @@ http.keepAliveCount::
315315
unset, curl's default value is used. Can be overridden by the
316316
`GIT_HTTP_KEEPALIVE_COUNT` environment variable.
317317

318+
http.retryAfter::
319+
Default wait time in seconds before retrying when a server returns
320+
HTTP 429 (Too Many Requests) without a Retry-After header. If set
321+
to -1 (the default), Git will fail immediately when encountering
322+
a 429 response without a Retry-After header. When a Retry-After
323+
header is present, its value takes precedence over this setting.
324+
Can be overridden by the `GIT_HTTP_RETRY_AFTER` environment variable.
325+
See also `http.maxRetries` and `http.maxRetryTime`.
326+
327+
http.maxRetries::
328+
Maximum number of times to retry after receiving HTTP 429 (Too Many
329+
Requests) responses. Set to 0 (the default) to disable retries.
330+
Can be overridden by the `GIT_HTTP_MAX_RETRIES` environment variable.
331+
See also `http.retryAfter` and `http.maxRetryTime`.
332+
333+
http.maxRetryTime::
334+
Maximum time in seconds to wait for a single retry attempt when
335+
handling HTTP 429 (Too Many Requests) responses. If the server
336+
requests a delay (via Retry-After header) or if `http.retryAfter`
337+
is configured with a value that exceeds this maximum, Git will fail
338+
immediately rather than waiting. Default is 300 seconds (5 minutes).
339+
Can be overridden by the `GIT_HTTP_MAX_RETRY_TIME` environment
340+
variable. See also `http.retryAfter` and `http.maxRetries`.
341+
318342
http.noEPSV::
319343
A boolean which disables using of EPSV ftp command by curl.
320344
This can be helpful with some "poor" ftp servers which don't

http-push.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -716,6 +716,10 @@ static int fetch_indices(void)
716716
case HTTP_MISSING_TARGET:
717717
ret = 0;
718718
break;
719+
case HTTP_RATE_LIMITED:
720+
error("rate limited by '%s', please try again later", repo->url);
721+
ret = -1;
722+
break;
719723
default:
720724
ret = -1;
721725
}
@@ -1548,6 +1552,10 @@ static int remote_exists(const char *path)
15481552
case HTTP_MISSING_TARGET:
15491553
ret = 0;
15501554
break;
1555+
case HTTP_RATE_LIMITED:
1556+
error("rate limited by '%s', please try again later", url);
1557+
ret = -1;
1558+
break;
15511559
case HTTP_ERROR:
15521560
error("unable to access '%s': %s", url, curl_errorstr);
15531561
/* fallthrough */

http-walker.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,11 @@ static int fetch_indices(struct walker *walker, struct alt_base *repo)
414414
repo->got_indices = 1;
415415
ret = 0;
416416
break;
417+
case HTTP_RATE_LIMITED:
418+
error("rate limited by '%s', please try again later", repo->base);
419+
repo->got_indices = 0;
420+
ret = -1;
421+
break;
417422
default:
418423
repo->got_indices = 0;
419424
ret = -1;

0 commit comments

Comments
 (0)