-
Notifications
You must be signed in to change notification settings - Fork 0
Add feature to fork only the default branch #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
It takes the default_branch_only parameter into account. Tests still fail.
WalkthroughThe changes in this pull request involve the Changes
Assessment against linked issues
📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (3)
🔇 Additional comments (6)src/main/java/org/kohsuke/github/GHMeta.java (4)
The Map import is correctly added and properly ordered.
The API documentation link is updated to reference the correct endpoint with proper versioning.
The new fields are properly declared with appropriate types and JsonProperty annotations, following the existing pattern in the class.
The getter methods are well-implemented with:
src/test/java/org/kohsuke/github/GitHubTest.java (1)
The new assertions properly verify the expected sizes of SSH-related collections, matching the test data. src/test/resources/org/kohsuke/github/GitHubTest/wiremock/getMeta/__files/1-meta.json (1)
The test data is well-structured with:
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Outside diff range and nitpick comments (3)
src/main/java/org/kohsuke/github/GHRepository.java (2)
1462-1464
: Add@deprecated
Javadoc tag to deprecated methodThe deprecated method
fork()
should include an@deprecated
tag in its Javadoc comment, indicating the preferred replacement methodcreateFork
. This helps users understand the deprecation and transition smoothly.
1494-1497
: Add@deprecated
Javadoc tag to deprecated methodThe deprecated method
forkTo(GHOrganization org)
should include an@deprecated
tag in its Javadoc comment, specifying thatcreateFork
is the recommended replacement. This provides clearer guidance to users.src/test/java/org/kohsuke/github/GHRepositoryTest.java (1)
847-851
: Consider verifying the exception message in the testIn
testCreateForkWithInvalidParameters()
, consider asserting the exception message to ensure theIllegalArgumentException
is thrown for the correct reason.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (2)
src/main/java/org/kohsuke/github/GHRepository.java
(3 hunks)src/test/java/org/kohsuke/github/GHRepositoryTest.java
(1 hunks)
🔇 Additional comments (2)
src/main/java/org/kohsuke/github/GHRepository.java (1)
Line range hint 1512-1547
: LGTM: New createFork
method implementation
The createFork
method is well-implemented with appropriate parameter validation and error handling. It correctly handles optional parameters and waits for the asynchronous fork operation to complete.
src/test/java/org/kohsuke/github/GHRepositoryTest.java (1)
833-839
: LGTM: Test for createFork
with valid parameters
The test method testCreateForkWithValidParameters()
effectively verifies the new createFork
method with valid inputs.
@coderabbitai: before testing the exception message, I have to fix the tests that don't work:
|
Tip For best results, initiate chat on the files or code changes. It seems the tests are failing due to missing WireMock stubs for the endpoints your new test methods are hitting. For // In your test setup method
githubApi.stubFor(post(urlEqualTo("/repos/gounthar/github-api/forks"))
.willReturn(aResponse()
.withStatus(202)
.withHeader("Content-Type", "application/json")
.withBodyFile("fork.json")));
githubApi.stubFor(get(urlEqualTo("/repos/hub4j-test-org/github-api"))
.willReturn(aResponse()
.withStatus(200)
.withHeader("Content-Type", "application/json")
.withBodyFile("repository.json"))); For Let me know if you need further assistance! |
Still fails.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Outside diff range and nitpick comments (3)
src/main/java/org/kohsuke/github/GHRepository.java (3)
1501-1534
: Consider enhancing input validationWhile the current validation catches empty strings, consider adding these improvements:
- Validate that name follows GitHub repository naming conventions
- Add @nullable annotations for optional parameters
- Add @nonnull for required parameters
- public GHRepository createFork(String organization, String name, boolean defaultBranchOnly) throws IOException { + public GHRepository createFork(@Nullable String organization, @Nullable String name, boolean defaultBranchOnly) throws IOException { if (organization != null && organization.isEmpty()) { throw new IllegalArgumentException("Organization cannot be empty"); } if (name != null && name.isEmpty()) { throw new IllegalArgumentException("Name cannot be empty"); } + if (name != null && !name.matches("^[a-zA-Z0-9._-]+$")) { + throw new IllegalArgumentException("Repository name contains invalid characters"); + }
Line range hint
1538-1550
: Consider enhancing the polling mechanismThe current implementation uses fixed values for polling. Consider these improvements:
- Make retry count and delay configurable
- Implement exponential backoff
- Add more detailed error messages
- Consider adding a timeout parameter
+ private static final int DEFAULT_FORK_TIMEOUT_SECONDS = 30; + private static final int DEFAULT_FORK_INITIAL_DELAY_MS = 1000; + public GHRepository createFork(String organization, String name, boolean defaultBranchOnly) throws IOException { // ... existing code ... + return createFork(organization, name, defaultBranchOnly, DEFAULT_FORK_TIMEOUT_SECONDS); + } + + public GHRepository createFork(String organization, String name, boolean defaultBranchOnly, int timeoutSeconds) throws IOException { // ... existing request code ... // this API is asynchronous. we need to wait for a bit - for (int i = 0; i < 10; i++) { + long startTime = System.currentTimeMillis(); + long timeoutMs = timeoutSeconds * 1000L; + int delayMs = DEFAULT_FORK_INITIAL_DELAY_MS; + + while (System.currentTimeMillis() - startTime < timeoutMs) { GHRepository r = organization != null ? root().getOrganization(organization).getRepository(name != null ? name : this.name) : root().getMyself().getRepository(name != null ? name : this.name); if (r != null) { return r; } try { - Thread.sleep(3000); + Thread.sleep(delayMs); + delayMs = Math.min(delayMs * 2, 10000); // Exponential backoff capped at 10 seconds } catch (InterruptedException e) { throw (IOException) new InterruptedIOException().initCause(e); } } - throw new IOException(this + " was forked but can't find the new repository"); + throw new IOException(String.format("Repository %s was forked but the new repository was not available after %d seconds", this, timeoutSeconds));
Line range hint
1501-1550
: Implementation aligns well with GitHub's asynchronous patternsThe implementation correctly handles GitHub's asynchronous fork creation process and provides good extensibility for future enhancements. The separation of concerns between the synchronous API call and asynchronous polling is clean.
Consider these architectural improvements for future iterations:
- Add events/callbacks for fork progress monitoring
- Consider implementing a more generic async operation handler that could be reused for other async operations
- Add metrics collection for fork operation timing
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (2)
src/main/java/org/kohsuke/github/GHRepository.java
(3 hunks)src/test/java/org/kohsuke/github/GitHubWireMockRule.java
(1 hunks)
🔇 Additional comments (3)
src/test/java/org/kohsuke/github/GitHubWireMockRule.java (1)
244-253
: LGTM: Test stubs properly configured for fork operations
The added WireMock stubs correctly simulate the GitHub API endpoints for:
- Creating repository forks (POST)
- Retrieving repository information (GET)
The stubs are properly configured with correct HTTP methods, status codes, and content types.
src/main/java/org/kohsuke/github/GHRepository.java (2)
1461-1465
: LGTM: Appropriate deprecation with backward compatibility
The old fork() method is properly deprecated and delegates to the new createFork() method while maintaining backward compatibility.
1494-1499
: LGTM: Appropriate deprecation with backward compatibility
The old forkTo() method is properly deprecated and delegates to the new createFork() method while maintaining backward compatibility.
…tions and add @nullable annotations for optional parameters
Thanks a lot, but I'm afraid this is out of the scope of this PR. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Outside diff range and nitpick comments (2)
src/main/java/org/kohsuke/github/GHRepository.java (2)
1501-1533
: LGTM with a suggestion for null safetyThe implementation looks good and aligns with the PR objectives. The input validation is thorough for empty strings and invalid characters.
Consider adding
@Nullable
annotations to the parameters and usingObjects.requireNonNull
for required parameters:public GHRepository createFork(@Nullable String organization, @Nullable String name, boolean defaultBranchOnly) throws IOException { + // organization and name are already marked as @Nullable in the method signature + if (organization != null && organization.isEmpty()) { throw new IllegalArgumentException("Organization cannot be empty"); } if (name != null && name.isEmpty()) { throw new IllegalArgumentException("Name cannot be empty"); }
Line range hint
1537-1549
: Consider making retry parameters configurableThe polling mechanism uses hard-coded values for retry count (10) and delay (3000ms). Consider making these configurable to handle different network conditions and server response times.
+ private static final int DEFAULT_FORK_RETRY_COUNT = 10; + private static final long DEFAULT_FORK_RETRY_DELAY_MS = 3000; + public GHRepository createFork(@Nullable String organization, @Nullable String name, boolean defaultBranchOnly) throws IOException { // ... existing code ... - for (int i = 0; i < 10; i++) { + for (int i = 0; i < DEFAULT_FORK_RETRY_COUNT; i++) { GHRepository r = organization != null ? root().getOrganization(organization).getRepository(name != null ? name : this.name) : root().getMyself().getRepository(name != null ? name : this.name); if (r != null) { return r; } try { - Thread.sleep(3000); + Thread.sleep(DEFAULT_FORK_RETRY_DELAY_MS); } catch (InterruptedException e) { throw (IOException) new InterruptedIOException().initCause(e); } } - throw new IOException(this + " was forked but can't find the new repository"); + throw new IOException(String.format("Repository %s was forked but couldn't be found after %d attempts", + this, DEFAULT_FORK_RETRY_COUNT));
* @deprecated | ||
*/ | ||
@Deprecated | ||
public GHRepository fork() throws IOException { | ||
root().createRequest().method("POST").withUrlPath(getApiTailUrl("forks")).send(); | ||
|
||
// this API is asynchronous. we need to wait for a bit | ||
for (int i = 0; i < 10; i++) { | ||
GHRepository r = root().getMyself().getRepository(name); | ||
if (r != null) { | ||
return r; | ||
} | ||
try { | ||
Thread.sleep(3000); | ||
} catch (InterruptedException e) { | ||
throw (IOException) new InterruptedIOException().initCause(e); | ||
} | ||
} | ||
throw new IOException(this + " was forked but can't find the new repository"); | ||
return createFork(null, null, false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add @deprecated reason and replacement information
The deprecation notice should include information about the recommended replacement method to help users migrate their code.
- @Deprecated
+ @Deprecated(since = "3.0.0", forRemoval = true)
+ /** @deprecated Use {@link #createFork(String, String, boolean)} instead */
public GHRepository fork() throws IOException {
return createFork(null, null, false);
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
* @deprecated | |
*/ | |
@Deprecated | |
public GHRepository fork() throws IOException { | |
root().createRequest().method("POST").withUrlPath(getApiTailUrl("forks")).send(); | |
// this API is asynchronous. we need to wait for a bit | |
for (int i = 0; i < 10; i++) { | |
GHRepository r = root().getMyself().getRepository(name); | |
if (r != null) { | |
return r; | |
} | |
try { | |
Thread.sleep(3000); | |
} catch (InterruptedException e) { | |
throw (IOException) new InterruptedIOException().initCause(e); | |
} | |
} | |
throw new IOException(this + " was forked but can't find the new repository"); | |
return createFork(null, null, false); | |
* @deprecated Use {@link #createFork(String, String, boolean)} instead | |
*/ | |
@Deprecated(since = "3.0.0", forRemoval = true) | |
public GHRepository fork() throws IOException { | |
return createFork(null, null, false); |
* @deprecated | ||
*/ | ||
@Deprecated | ||
public GHRepository forkTo(GHOrganization org) throws IOException { | ||
root().createRequest() | ||
.method("POST") | ||
.with("organization", org.getLogin()) | ||
.withUrlPath(getApiTailUrl("forks")) | ||
.send(); | ||
return createFork(org.getLogin(), null, false); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add @deprecated reason and replacement information
The deprecation notice should include information about the recommended replacement method to help users migrate their code.
- @Deprecated
+ @Deprecated(since = "3.0.0", forRemoval = true)
+ /** @deprecated Use {@link #createFork(String, String, boolean)} instead */
public GHRepository forkTo(GHOrganization org) throws IOException {
return createFork(org.getLogin(), null, false);
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
* @deprecated | |
*/ | |
@Deprecated | |
public GHRepository forkTo(GHOrganization org) throws IOException { | |
root().createRequest() | |
.method("POST") | |
.with("organization", org.getLogin()) | |
.withUrlPath(getApiTailUrl("forks")) | |
.send(); | |
return createFork(org.getLogin(), null, false); | |
} | |
/** | |
* @deprecated Use {@link #createFork(String, String, boolean)} instead | |
*/ | |
@Deprecated(since = "3.0.0", forRemoval = true) | |
public GHRepository forkTo(GHOrganization org) throws IOException { | |
return createFork(org.getLogin(), null, false); | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Outside diff range and nitpick comments (3)
src/main/java/org/kohsuke/github/GHRepository.java (3)
1501-1513
: Enhance method documentationThe Javadoc should explain the asynchronous nature of the API and the polling behavior.
/** * Creates a fork of this repository with optional parameters. * * @param organization * the organization to fork to, or null to fork to the authenticated user's account * @param name * the name of the new repository, or null to use the same name as the original repository * @param defaultBranchOnly * whether to fork only the default branch * @return the newly forked repository * @throws IOException * if an I/O error occurs + * @throws InterruptedIOException + * if the polling is interrupted + * @implNote This method uses GitHub's asynchronous fork API. After initiating the fork, + * it polls for up to 30 seconds (10 attempts, 3 seconds apart) for the fork to complete. */
Line range hint
1538-1554
: Consider making polling parameters configurableThe polling interval (3 seconds) and maximum attempts (10) are hardcoded. Consider making these configurable or at least constants.
+ private static final int FORK_POLL_INTERVAL_MS = 3000; + private static final int FORK_POLL_MAX_ATTEMPTS = 10; + requester.send(); // this API is asynchronous. we need to wait for a bit - for (int i = 0; i < 10; i++) { + for (int i = 0; i < FORK_POLL_MAX_ATTEMPTS; i++) { GHRepository r = organization != null ? root().getOrganization(organization).getRepository(name != null ? name : this.name) : root().getMyself().getRepository(name != null ? name : this.name); if (r != null) { return r; } try { - Thread.sleep(3000); + Thread.sleep(FORK_POLL_INTERVAL_MS); } catch (InterruptedException e) { throw (IOException) new InterruptedIOException().initCause(e); } } - throw new IOException(this + " was forked but can't find the new repository"); + throw new IOException(String.format("Repository %s was forked but the new repository was not found after %d seconds", + this, (FORK_POLL_INTERVAL_MS * FORK_POLL_MAX_ATTEMPTS) / 1000));
1517-1525
: Improve input validationThe repository name validation regex could be a constant and error messages could be more descriptive.
+ private static final String VALID_REPOSITORY_NAME_PATTERN = "^[a-zA-Z0-9._-]+$"; + if (organization != null && organization.isEmpty()) { - throw new IllegalArgumentException("Organization cannot be empty"); + throw new IllegalArgumentException("Organization name cannot be empty when specified"); } if (name != null && name.isEmpty()) { - throw new IllegalArgumentException("Name cannot be empty"); + throw new IllegalArgumentException("Repository name cannot be empty when specified"); } - if (name != null && !name.matches("^[a-zA-Z0-9._-]+$")) { - throw new IllegalArgumentException("Repository name contains invalid characters"); + if (name != null && !name.matches(VALID_REPOSITORY_NAME_PATTERN)) { + throw new IllegalArgumentException( + "Repository name can only contain letters, numbers, dots, hyphens and underscores"); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (1)
src/main/java/org/kohsuke/github/GHRepository.java
(3 hunks)
🔇 Additional comments (2)
src/main/java/org/kohsuke/github/GHRepository.java (2)
1491-1499
: 🛠️ Refactor suggestion
Add @deprecated reason and replacement information
The deprecation notice should include information about the recommended replacement method to help users migrate their code.
- @Deprecated
+ @Deprecated(since = "3.0.0", forRemoval = true)
+ /** @deprecated Use {@link #createFork(String, String, boolean)} instead */
public GHRepository forkTo(GHOrganization org) throws IOException {
return createFork(org.getLogin(), null, false);
}
Likely invalid or redundant comment.
1458-1465
: 🛠️ Refactor suggestion
Add @deprecated reason and replacement information
The deprecation notice should include information about the recommended replacement method to help users migrate their code.
- @Deprecated
+ @Deprecated(since = "3.0.0", forRemoval = true)
+ /** @deprecated Use {@link #createFork(String, String, boolean)} instead */
public GHRepository fork() throws IOException {
return createFork(null, null, false);
}
Likely invalid or redundant comment.
Co-authored-by: Liam Newman <bitwiseman@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Outside diff range and nitpick comments (2)
src/main/java/org/kohsuke/github/GHRepository.java (2)
Line range hint
1534-1555
: Consider making the retry mechanism configurableThe current implementation uses hardcoded values for retry attempts (10) and delay (3000ms). Consider making these configurable to allow users to adjust based on their needs.
Consider adding configuration parameters:
public GHRepository createFork(@Nullable String organization, @Nullable String name, - boolean defaultBranchOnly) + boolean defaultBranchOnly, + @Nullable Integer maxRetries, + @Nullable Integer retryDelayMillis) throws IOException { // ... existing validation and request code ... + final int actualMaxRetries = maxRetries != null ? maxRetries : 10; + final int actualRetryDelay = retryDelayMillis != null ? retryDelayMillis : 3000; - for (int i = 0; i < 10; i++) { + for (int i = 0; i < actualMaxRetries; i++) { // ... existing retry code ... try { - Thread.sleep(3000); + Thread.sleep(actualRetryDelay); } catch (InterruptedException e) { throw (IOException) new InterruptedIOException().initCause(e); } } - throw new IOException(this + " was forked but can't find the new repository"); + throw new IOException(String.format( + "Repository %s was forked to %s/%s but the fork could not be found after %d attempts", + this, organization, name, actualMaxRetries)); } + public GHRepository createFork(@Nullable String organization, + @Nullable String name, + boolean defaultBranchOnly) throws IOException { + return createFork(organization, name, defaultBranchOnly, null, null); + }
1517-1525
: Enhance input validationThe current validation could be improved with:
- A more comprehensive repository name validation regex
- More descriptive error messages including validation rules
if (organization != null && organization.isEmpty()) { - throw new IllegalArgumentException("Organization cannot be empty. Pass null for default value."); + throw new IllegalArgumentException("Organization name cannot be empty. Pass null to fork to the authenticated user's account."); } if (name != null && name.isEmpty()) { - throw new IllegalArgumentException("Name cannot be empty. Pass null for default value."); + throw new IllegalArgumentException("Repository name cannot be empty. Pass null to use the same name as the original repository."); } - if (name != null && !name.matches("^[a-zA-Z0-9._-]+$")) { - throw new IllegalArgumentException("Repository name contains invalid characters"); + if (name != null && !name.matches("^[a-zA-Z0-9][a-zA-Z0-9._-]*$")) { + throw new IllegalArgumentException( + "Repository name must start with a letter or number and can only contain letters, numbers, periods, underscores, and dashes"); }
@coderabbitai review, please. |
@gounthar: I will review the changes. ✅ Actions performedReview triggered.
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
Fixes #1
Add functionality to fork only the default branch of a repository.
Summary by CodeRabbit