diff --git a/gitlab4j-api/src/main/java/org/gitlab4j/api/RepositoryFileApi.java b/gitlab4j-api/src/main/java/org/gitlab4j/api/RepositoryFileApi.java index 39be583fa..a5a05112d 100644 --- a/gitlab4j-api/src/main/java/org/gitlab4j/api/RepositoryFileApi.java +++ b/gitlab4j-api/src/main/java/org/gitlab4j/api/RepositoryFileApi.java @@ -15,7 +15,9 @@ import org.gitlab4j.api.models.Blame; import org.gitlab4j.api.models.RepositoryFile; +import org.gitlab4j.api.models.RepositoryFileCreate; import org.gitlab4j.api.models.RepositoryFileResponse; +import org.gitlab4j.api.models.RepositoryFileUpdate; import org.gitlab4j.models.Constants; /** @@ -183,7 +185,7 @@ public RepositoryFile getFile(Object projectIdOrPath, String filePath, String re } /** - * Create new file in repository + * Create new file in repository. * *
GitLab Endpoint: POST /projects/:id/repository/files
* @@ -194,15 +196,44 @@ public RepositoryFile getFile(Object projectIdOrPath, String filePath, String re * commit_message (required) - Commit message * * @param projectIdOrPath the id, path of the project, or a Project instance holding the project ID or path - * @param file a ReposityoryFile instance with info for the file to create + * @param file a RepositoryFile instance with info for the file to create * @param branchName the name of branch * @param commitMessage the commit message - * @return a RepositoryFile instance with the created file info + * @return a RepositoryFileResponse instance with the created file info * @throws GitLabApiException if any exception occurs + * @deprecated Use {@link #createFile(Object, RepositoryFileCreate, String, String)} instead. + * {@link RepositoryFile} mixes read-only response fields with write input fields; + * {@link RepositoryFileCreate} exposes only the parameters accepted by the endpoint and adds support + * for {@code start_branch}, {@code execute_filemode}, {@code author_email} and {@code author_name}. */ + @Deprecated public RepositoryFileResponse createFile( Object projectIdOrPath, RepositoryFile file, String branchName, String commitMessage) throws GitLabApiException { + return createFile(projectIdOrPath, toCreate(file), branchName, commitMessage); + } + + /** + * Create new file in repository. + * + *
GitLab Endpoint: POST /projects/:id/repository/files/:file_path
+ * + * file_path (required) - Full path to new file. Ex. lib/class.rb + * branch_name (required) - The name of branch + * encoding (optional) - 'text' or 'base64'. Text is default. + * content (required) - File content + * commit_message (required) - Commit message + * + * @param projectIdOrPath the id, path of the project, or a Project instance holding the project ID or path + * @param file a RepositoryFileCreate instance with the input parameters for the file to create + * @param branchName the name of branch + * @param commitMessage the commit message + * @return a RepositoryFileResponse instance with the created file info (path and branch) + * @throws GitLabApiException if any exception occurs + */ + public RepositoryFileResponse createFile( + Object projectIdOrPath, RepositoryFileCreate file, String branchName, String commitMessage) + throws GitLabApiException { Form formData = createForm(file, branchName, commitMessage); Response response = post( @@ -217,7 +248,7 @@ public RepositoryFileResponse createFile( } /** - * Create new file in repository + * Create new file in repository. * *
GitLab Endpoint: POST /projects/:id/repository/files
* @@ -227,13 +258,13 @@ public RepositoryFileResponse createFile( * content (required) - File content * commit_message (required) - Commit message * - * @param file a ReposityoryFile instance with info for the file to create + * @param file a RepositoryFile instance with info for the file to create * @param projectId the project ID * @param branchName the name of branch * @param commitMessage the commit message - * @return a RepositoryFile instance with the created file info + * @return a RepositoryFileResponse instance with the created file info * @throws GitLabApiException if any exception occurs - * @deprecated Will be removed in version 6.0, replaced by {@link #createFile(Object, RepositoryFile, String, String)} + * @deprecated Use {@link #createFile(Object, RepositoryFileCreate, String, String)} instead. */ @Deprecated public RepositoryFileResponse createFile( @@ -242,7 +273,7 @@ public RepositoryFileResponse createFile( } /** - * Update existing file in repository + * Update existing file in repository. * *
GitLab Endpoint: PUT /projects/:id/repository/files
* @@ -253,15 +284,45 @@ public RepositoryFileResponse createFile( * commit_message (required) - Commit message * * @param projectIdOrPath the id, path of the project, or a Project instance holding the project ID or path - * @param file a ReposityoryFile instance with info for the file to update + * @param file a RepositoryFile instance with info for the file to update * @param branchName the name of branch * @param commitMessage the commit message - * @return a RepositoryFile instance with the updated file info + * @return a RepositoryFileResponse instance with the updated file info * @throws GitLabApiException if any exception occurs + * @deprecated Use {@link #updateFile(Object, RepositoryFileUpdate, String, String)} instead. + * {@link RepositoryFile} mixes read-only response fields with write input fields; + * {@link RepositoryFileUpdate} exposes only the parameters accepted by the endpoint and adds support + * for {@code start_branch}, {@code execute_filemode}, {@code last_commit_id}, {@code author_email} + * and {@code author_name}. */ + @Deprecated public RepositoryFileResponse updateFile( Object projectIdOrPath, RepositoryFile file, String branchName, String commitMessage) throws GitLabApiException { + return updateFile(projectIdOrPath, toUpdate(file), branchName, commitMessage); + } + + /** + * Update existing file in repository. + * + *
GitLab Endpoint: PUT /projects/:id/repository/files/:file_path
+ * + * file_path (required) - Full path to new file. Ex. lib/class.rb + * branch_name (required) - The name of branch + * encoding (optional) - 'text' or 'base64'. Text is default. + * content (required) - File content + * commit_message (required) - Commit message + * + * @param projectIdOrPath the id, path of the project, or a Project instance holding the project ID or path + * @param file a RepositoryFileUpdate instance with the input parameters for the file to update + * @param branchName the name of branch + * @param commitMessage the commit message + * @return a RepositoryFileResponse instance with the updated file info (path and branch) + * @throws GitLabApiException if any exception occurs + */ + public RepositoryFileResponse updateFile( + Object projectIdOrPath, RepositoryFileUpdate file, String branchName, String commitMessage) + throws GitLabApiException { Form formData = createForm(file, branchName, commitMessage); Response response = put( @@ -276,23 +337,17 @@ public RepositoryFileResponse updateFile( } /** - * Update existing file in repository + * Update existing file in repository. * *
GitLab Endpoint: PUT /projects/:id/repository/files
* - * file_path (required) - Full path to new file. Ex. lib/class.rb - * branch_name (required) - The name of branch - * encoding (optional) - 'text' or 'base64'. Text is default. - * content (required) - File content - * commit_message (required) - Commit message - * - * @param file a ReposityoryFile instance with info for the file to update + * @param file a RepositoryFile instance with info for the file to update * @param projectId the project ID * @param branchName the name of branch * @param commitMessage the commit message - * @return a RepositoryFile instance with the updated file info + * @return a RepositoryFileResponse instance with the updated file info * @throws GitLabApiException if any exception occurs - * @deprecated Will be removed in version 6.0, replaced by {@link #updateFile(Object, RepositoryFile, String, String)} + * @deprecated Use {@link #updateFile(Object, RepositoryFileUpdate, String, String)} instead. */ @Deprecated public RepositoryFileResponse updateFile( @@ -433,18 +488,21 @@ public InputStream getRawFile(Object projectIdOrPath, String ref, String filepat } /** - * Gets the query params based on the API version. + * Builds the form payload sent on create/update requests. * - * @param file the RepositoryFile instance with the info for the query params + * @param file the RepositoryFileCreate (or RepositoryFileUpdate) instance with the input parameters * @param branchName the branch name * @param commitMessage the commit message * @return a Form instance with the correct query params. */ - protected Form createForm(RepositoryFile file, String branchName, String commitMessage) { + private Form createForm(RepositoryFileCreate file, String branchName, String commitMessage) { Form form = new Form(); addFormParam(form, "branch", branchName, true); + addFormParam(form, "start_branch", file.getStartBranch(), false); addFormParam(form, "encoding", file.getEncoding(), false); + addFormParam(form, "author_email", file.getAuthorEmail(), false); + addFormParam(form, "author_name", file.getAuthorName(), false); // Cannot use addFormParam() as it does not accept an empty or whitespace only string String content = file.getContent(); @@ -454,9 +512,41 @@ protected Form createForm(RepositoryFile file, String branchName, String commitM form.param("content", content); addFormParam(form, "commit_message", commitMessage, true); + addFormParam(form, "execute_filemode", file.getExecuteFilemode(), false); + + if (file instanceof RepositoryFileUpdate) { + addFormParam(form, "last_commit_id", ((RepositoryFileUpdate) file).getLastCommitId(), false); + } + return (form); } + /** + * Copies the input fields of a {@link RepositoryFile} into a new {@link RepositoryFileCreate}. + * Used by the deprecated {@code createFile(Object, RepositoryFile, ...)} overload. + */ + private RepositoryFileCreate toCreate(RepositoryFile file) { + RepositoryFileCreate create = new RepositoryFileCreate(); + create.setFilePath(file.getFilePath()); + create.setContent(file.getContent()); + create.setEncoding(file.getEncoding()); + return create; + } + + /** + * Copies the input fields of a {@link RepositoryFile} into a new {@link RepositoryFileUpdate}, + * preserving {@code lastCommitId} as the optimistic-locking hint. + * Used by the deprecated {@code updateFile(Object, RepositoryFile, ...)} overload. + */ + private RepositoryFileUpdate toUpdate(RepositoryFile file) { + RepositoryFileUpdate update = new RepositoryFileUpdate(); + update.setFilePath(file.getFilePath()); + update.setContent(file.getContent()); + update.setEncoding(file.getEncoding()); + update.setLastCommitId(file.getLastCommitId()); + return update; + } + /** * Get a List of file blame from repository. Allows you to receive blame information. * Each blame range contains lines and corresponding commit information. diff --git a/gitlab4j-models/src/main/java/org/gitlab4j/api/models/RepositoryFileCreate.java b/gitlab4j-models/src/main/java/org/gitlab4j/api/models/RepositoryFileCreate.java new file mode 100644 index 000000000..d993856c8 --- /dev/null +++ b/gitlab4j-models/src/main/java/org/gitlab4j/api/models/RepositoryFileCreate.java @@ -0,0 +1,149 @@ +package org.gitlab4j.api.models; + +import java.io.Serializable; +import java.util.Base64; + +import org.gitlab4j.models.Constants.Encoding; +import org.gitlab4j.models.utils.JacksonJson; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +/** + * This class is used as input when creating a file in a repository. + * + * @see Create a file in a repository + */ +public class RepositoryFileCreate implements Serializable { + private static final long serialVersionUID = 1L; + + private String filePath; + private String startBranch; + private Encoding encoding; + private String authorEmail; + private String authorName; + private String content; + private Boolean executeFilemode; + + public String getFilePath() { + return filePath; + } + + public void setFilePath(String filePath) { + this.filePath = filePath; + } + + public RepositoryFileCreate withFilePath(String filePath) { + this.filePath = filePath; + return this; + } + + public String getStartBranch() { + return startBranch; + } + + public void setStartBranch(String startBranch) { + this.startBranch = startBranch; + } + + public RepositoryFileCreate withStartBranch(String startBranch) { + this.startBranch = startBranch; + return this; + } + + public Encoding getEncoding() { + return encoding; + } + + public void setEncoding(Encoding encoding) { + this.encoding = encoding; + } + + public RepositoryFileCreate withEncoding(Encoding encoding) { + this.encoding = encoding; + return this; + } + + public String getAuthorEmail() { + return authorEmail; + } + + public void setAuthorEmail(String authorEmail) { + this.authorEmail = authorEmail; + } + + public RepositoryFileCreate withAuthorEmail(String authorEmail) { + this.authorEmail = authorEmail; + return this; + } + + public String getAuthorName() { + return authorName; + } + + public void setAuthorName(String authorName) { + this.authorName = authorName; + } + + public RepositoryFileCreate withAuthorName(String authorName) { + this.authorName = authorName; + return this; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public RepositoryFileCreate withContent(String content) { + this.content = content; + return this; + } + + public Boolean getExecuteFilemode() { + return executeFilemode; + } + + public void setExecuteFilemode(Boolean executeFilemode) { + this.executeFilemode = executeFilemode; + } + + public RepositoryFileCreate withExecuteFilemode(Boolean executeFilemode) { + this.executeFilemode = executeFilemode; + return this; + } + + /** + * Encodes the provided String using Base64 and sets it as the content. + * The encoding property of this instance will be set to base64. + * + * @param content the String content to encode and set as the base64 encoded String content + */ + @JsonIgnore + public void encodeAndSetContent(String content) { + encodeAndSetContent(content != null ? content.getBytes() : null); + } + + /** + * Encodes the provided byte array using Base64 and sets it as the content. + * The encoding property of this instance will be set to base64. + * + * @param byteContent the byte[] content to encode and set as the base64 encoded String content + */ + @JsonIgnore + public void encodeAndSetContent(byte[] byteContent) { + if (byteContent == null) { + this.content = null; + return; + } + this.content = Base64.getEncoder().encodeToString(byteContent); + this.encoding = Encoding.BASE64; + } + + @Override + public String toString() { + return (JacksonJson.toJsonString(this)); + } +} diff --git a/gitlab4j-models/src/main/java/org/gitlab4j/api/models/RepositoryFileUpdate.java b/gitlab4j-models/src/main/java/org/gitlab4j/api/models/RepositoryFileUpdate.java new file mode 100644 index 000000000..77c1da55d --- /dev/null +++ b/gitlab4j-models/src/main/java/org/gitlab4j/api/models/RepositoryFileUpdate.java @@ -0,0 +1,25 @@ +package org.gitlab4j.api.models; + +/** + * This class is used as input when updating a file in a repository. + * + * @see Update a file in a repository + */ +public class RepositoryFileUpdate extends RepositoryFileCreate { + private static final long serialVersionUID = 1L; + + private String lastCommitId; + + public String getLastCommitId() { + return lastCommitId; + } + + public void setLastCommitId(String lastCommitId) { + this.lastCommitId = lastCommitId; + } + + public RepositoryFileUpdate withLastCommitId(String lastCommitId) { + this.lastCommitId = lastCommitId; + return this; + } +} diff --git a/gitlab4j-models/src/test/java/org/gitlab4j/models/TestGitLabApiBeans.java b/gitlab4j-models/src/test/java/org/gitlab4j/models/TestGitLabApiBeans.java index f54ca6cb4..c74e39a56 100644 --- a/gitlab4j-models/src/test/java/org/gitlab4j/models/TestGitLabApiBeans.java +++ b/gitlab4j-models/src/test/java/org/gitlab4j/models/TestGitLabApiBeans.java @@ -568,6 +568,18 @@ public void testRepositoryFile() throws Exception { assertTrue(compareJson(file, "repository-file.json")); } + @Test + public void testRepositoryFileCreate() throws Exception { + RepositoryFileCreate file = unmarshalResource(RepositoryFileCreate.class, "repository-file-create.json"); + assertTrue(compareJson(file, "repository-file-create.json")); + } + + @Test + public void testRepositoryFileUpdate() throws Exception { + RepositoryFileUpdate file = unmarshalResource(RepositoryFileUpdate.class, "repository-file-update.json"); + assertTrue(compareJson(file, "repository-file-update.json")); + } + @Test public void testRepositoryFileResponse() throws Exception { RepositoryFileResponse file = unmarshalResource(RepositoryFileResponse.class, "repository-file-response.json"); diff --git a/gitlab4j-models/src/test/resources/org/gitlab4j/models/repository-file-create.json b/gitlab4j-models/src/test/resources/org/gitlab4j/models/repository-file-create.json new file mode 100644 index 000000000..749da9abd --- /dev/null +++ b/gitlab4j-models/src/test/resources/org/gitlab4j/models/repository-file-create.json @@ -0,0 +1,9 @@ +{ + "file_path": "app/models/key.rb", + "start_branch": "main", + "encoding": "base64", + "author_email": "author@example.com", + "author_name": "Author Name", + "content": "IyA9PSBTY2hlbWEgSW5mb3...", + "execute_filemode": true +} diff --git a/gitlab4j-models/src/test/resources/org/gitlab4j/models/repository-file-update.json b/gitlab4j-models/src/test/resources/org/gitlab4j/models/repository-file-update.json new file mode 100644 index 000000000..9b64eca22 --- /dev/null +++ b/gitlab4j-models/src/test/resources/org/gitlab4j/models/repository-file-update.json @@ -0,0 +1,10 @@ +{ + "file_path": "app/models/key.rb", + "start_branch": "main", + "encoding": "base64", + "author_email": "author@example.com", + "author_name": "Author Name", + "content": "IyA9PSBTY2hlbWEgSW5mb3...", + "execute_filemode": true, + "last_commit_id": "570e7b2abdd848b95f2f578043fc23bd6f6fd24d" +}