Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

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

Open
wants to merge 13 commits into
base: main
Choose a base branch
Loading
from

Conversation

gounthar
Copy link
Owner

@gounthar gounthar commented Dec 10, 2024

Fixes #1

Add functionality to fork only the default branch of a repository.

Summary by CodeRabbit

  • New Features
    • Introduced a new method for creating forks with enhanced flexibility, allowing specification of organization, repository name, and default branch options.
    • Added access to SSH key information in the metadata retrieval.
  • Bug Fixes
    • Improved error handling for invalid parameters in the fork creation process.
  • Tests
    • Added test cases to validate the new fork creation functionality, including checks for valid and invalid parameters.
    • Enhanced assertions in existing tests to verify SSH key metadata.

It takes the default_branch_only parameter into account.
Tests still fail.
Copy link

coderabbitai bot commented Dec 10, 2024

Walkthrough

The changes in this pull request involve the GHRepository class in the GitHub API client, where the existing fork and forkTo methods are deprecated and replaced with a new method, createFork. This method offers enhanced flexibility by accepting parameters for organization, repository name, and an option to fork only the default branch. Additionally, two new test methods are added to the GHRepositoryTest class to validate the functionality of createFork, ensuring proper handling of both valid and invalid parameters. The GHMeta class is also updated to include new properties related to SSH keys.

Changes

File Change Summary
src/main/java/org/kohsuke/github/GHRepository.java Deprecated fork and forkTo methods; added createFork method with parameter validation and error handling.
src/main/java/org/kohsuke/github/GHMeta.java Added new fields for SSH key fingerprints and SSH keys; included corresponding getter methods.
src/test/java/org/kohsuke/github/GHRepositoryTest.java Added two test methods for createFork: one for valid parameters and another for invalid parameters.
src/test/java/org/kohsuke/github/GitHubTest.java Added assertions to check sizes of SSH key fingerprints and SSH keys in the metadata.
src/test/resources/org/kohsuke/github/GitHubTest/wiremock/getMeta/__files/1-meta.json Updated JSON structure to include new fields for SSH key fingerprints and SSH keys.

Assessment against linked issues

Objective Addressed Explanation
Fork Only Default Branch (1)

🐰 In the meadow where the code does flow,
A new fork method is ready to show.
With options to choose, and branches to save,
Efficiency blooms, our workflows behave.
So hop along, let the changes take flight,
In the world of GitHub, everything feels right! 🌼


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 86ca3f0 and 3eecab5.

📒 Files selected for processing (3)
  • src/main/java/org/kohsuke/github/GHMeta.java (3 hunks)
  • src/test/java/org/kohsuke/github/GitHubTest.java (1 hunks)
  • src/test/resources/org/kohsuke/github/GitHubTest/wiremock/getMeta/__files/1-meta.json (1 hunks)
🔇 Additional comments (6)
src/main/java/org/kohsuke/github/GHMeta.java (4)

8-8: LGTM!

The Map import is correctly added and properly ordered.


16-17: LGTM!

The API documentation link is updated to reference the correct endpoint with proper versioning.


29-32: LGTM!

The new fields are properly declared with appropriate types and JsonProperty annotations, following the existing pattern in the class.


52-68: LGTM!

The getter methods are well-implemented with:

  • Proper Javadoc documentation
  • Unmodifiable collection views for immutability
  • Consistent style with other getters in the class
src/test/java/org/kohsuke/github/GitHubTest.java (1)

286-287: LGTM!

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)

3-13: LGTM!

The test data is well-structured with:

  • Properly formatted SSH key fingerprints for multiple algorithms (RSA, DSA, ECDSA, ED25519)
  • Realistic SSH key examples in the expected format
  • Consistent JSON structure

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?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a 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 method

The deprecated method fork() should include an @deprecated tag in its Javadoc comment, indicating the preferred replacement method createFork. This helps users understand the deprecation and transition smoothly.


1494-1497: Add @deprecated Javadoc tag to deprecated method

The deprecated method forkTo(GHOrganization org) should include an @deprecated tag in its Javadoc comment, specifying that createFork 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 test

In testCreateForkWithInvalidParameters(), consider asserting the exception message to ensure the IllegalArgumentException is thrown for the correct reason.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 58dcca1 and f750d99.

📒 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.

@gounthar
Copy link
Owner Author

@coderabbitai: before testing the exception message, I have to fix the tests that don't work:

[ERROR] Errors:
[ERROR]   GHRepositoryTest.testCreateForkWithInvalidParameters »  Unexpected exception, expected<java.lang.IllegalArgumentException> but was<org.kohsuke.github.GHFileNotFoundException>
[ERROR]   GHRepositoryTest.testCreateForkWithValidParameters:834->getRepository:50->getRepository:54 » GHFileNotFound http://localhost:43947/orgs/hub4j-test-org No response could be served as there are no stub mappings in this WireMock instance.
[INFO]
[ERROR] Tests run: 518, Failures: 0, Errors: 2, Skipped: 21
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  08:36 min
[INFO] Finished at: 2024-12-10T11:21:06+01:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:3.5.2:test (default-test) on project github-api:
[ERROR]
[ERROR] See /mnt/c/support/users/gsoc/github-api/target/surefire-reports for the individual test results.
[ERROR] See dump files (if any exist) [date].dump, [date]-jvmRun[N].dump and [date].dumpstream.
[ERROR] -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException

Copy link

coderabbitai bot commented Dec 10, 2024

Tip

For best results, initiate chat on the files or code changes.

@gounthar:

It seems the tests are failing due to missing WireMock stubs for the endpoints your new test methods are hitting.

For testCreateForkWithValidParameters, the error indicates that there's no stub mapping for http://localhost:43947/orgs/hub4j-test-org. You'll need to add WireMock stubs to simulate the GitHub API responses. Here's how you can set up the stubs:

// 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 testCreateForkWithInvalidParameters, ensure that the createFork method is correctly throwing IllegalArgumentException when invalid parameters are provided. If it's currently throwing a GHFileNotFoundException, you might need to adjust the validation logic in the createFork method. Make sure to validate the parameters before making any API calls.

Let me know if you need further assistance!

@gounthar
Copy link
Owner Author

[WARNING] Tests run: 6, Failures: 0, Errors: 0, Skipped: 5, Time elapsed: 0.869 s -- in org.kohsuke.github.WireMockStatusReporterTest
[INFO]
[INFO] Results:
[INFO]
[ERROR] Errors:
[ERROR]   GHRepositoryTest.testCreateForkWithInvalidParameters »  Unexpected exception, expected<java.lang.IllegalArgumentException> but was<org.kohsuke.github.GHFileNotFoundException>
[ERROR]   GHRepositoryTest.testCreateForkWithValidParameters:834->getRepository:50->getRepository:54 » GHFileNotFound http://localhost:35449/orgs/hub4j-test-org No response could be served as there are no stub mappings in this WireMock instance.
[INFO]
[ERROR] Tests run: 518, Failures: 0, Errors: 2, Skipped: 21
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  10:11 min
[INFO] Finished at: 2024-12-10T14:39:31+01:00
[INFO] ------------------------------------------------------------------------

Copy link

@coderabbitai coderabbitai bot left a 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 validation

While the current validation catches empty strings, consider adding these improvements:

  1. Validate that name follows GitHub repository naming conventions
  2. Add @nullable annotations for optional parameters
  3. 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 mechanism

The current implementation uses fixed values for polling. Consider these improvements:

  1. Make retry count and delay configurable
  2. Implement exponential backoff
  3. Add more detailed error messages
  4. 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 patterns

The 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:

  1. Add events/callbacks for fork progress monitoring
  2. Consider implementing a more generic async operation handler that could be reused for other async operations
  3. Add metrics collection for fork operation timing
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between f750d99 and ec41545.

📒 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:

  1. Creating repository forks (POST)
  2. 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
@gounthar
Copy link
Owner Author

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (3)
📜 Review details

Thanks a lot, but I'm afraid this is out of the scope of this PR.
Nice catch, nonetheless!

Copy link

@coderabbitai coderabbitai bot left a 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 safety

The 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 using Objects.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 configurable

The 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));
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between ec41545 and 97eabee.

📒 Files selected for processing (1)
  • src/main/java/org/kohsuke/github/GHRepository.java (3 hunks)

Comment on lines 1461 to 1465
* @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);
Copy link

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.

Suggested change
* @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);

Comment on lines 1494 to 1499
* @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);
}
Copy link

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.

Suggested change
* @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);
}

Copy link

@coderabbitai coderabbitai bot left a 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 documentation

The 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 configurable

The 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 validation

The 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

📥 Commits

Reviewing files that changed from the base of the PR and between 97eabee and fcb6a72.

📒 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>
Copy link

@coderabbitai coderabbitai bot left a 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 configurable

The 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 validation

The current validation could be improved with:

  1. A more comprehensive repository name validation regex
  2. 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");
         }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fcb6a72 and 618b36f.

📒 Files selected for processing (1)
  • src/main/java/org/kohsuke/github/GHRepository.java (3 hunks)

src/main/java/org/kohsuke/github/GHRepository.java Outdated Show resolved Hide resolved
@gounthar
Copy link
Owner Author

@coderabbitai review, please.

Copy link

coderabbitai bot commented Dec 12, 2024

@gounthar: I will review the changes.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@gounthar
Copy link
Owner Author

@coderabbitai review

Copy link

coderabbitai bot commented Dec 12, 2024

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Feature Request: Fork Only Default Branch
2 participants
Morty Proxy This is a proxified and sanitized view of the page, visit original site.