This is an automated email from the ASF dual-hosted git repository.

exceptionfactory pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/main by this push:
     new b684cbf4946 NIFI-15673 Added support for Author and Committer with 
git-based Flow Registry Clients (#10972)
b684cbf4946 is described below

commit b684cbf4946f3345aad13f2e8eda50f338932608
Author: Pierre Villard <[email protected]>
AuthorDate: Mon Jun 1 06:57:31 2026 -0700

    NIFI-15673 Added support for Author and Committer with git-based Flow 
Registry Clients (#10972)
    
    Signed-off-by: David Handermann <[email protected]>
---
 .../bitbucket/BitbucketRepositoryClient.java       |  7 +++
 .../azure/devops/AzureDevOpsRepositoryClient.java  | 18 ++++---
 .../flow/git/AbstractGitFlowRegistryClient.java    | 56 +++++++++++++++++++++-
 .../flow/git/client/GitCreateContentRequest.java   | 24 ++++++++++
 .../flow/git/client/GitRepositoryClient.java       | 19 ++++++++
 .../nifi-github-extensions/pom.xml                 |  6 +++
 .../nifi/github/GitHubFlowRegistryClient.java      |  2 +-
 .../apache/nifi/github/GitHubRepositoryClient.java | 30 +++++++-----
 .../nifi/github/GitHubFlowRegistryClientTest.java  | 28 +++++++++++
 nifi-extension-bundles/nifi-github-bundle/pom.xml  |  2 +-
 .../apache/nifi/gitlab/GitLabRepositoryClient.java | 19 ++++++--
 11 files changed, 186 insertions(+), 25 deletions(-)

diff --git 
a/nifi-extension-bundles/nifi-atlassian-bundle/nifi-atlassian-extensions/src/main/java/org/apache/nifi/atlassian/bitbucket/BitbucketRepositoryClient.java
 
b/nifi-extension-bundles/nifi-atlassian-bundle/nifi-atlassian-extensions/src/main/java/org/apache/nifi/atlassian/bitbucket/BitbucketRepositoryClient.java
index a1dc2adbf92..ca5c0066ab9 100644
--- 
a/nifi-extension-bundles/nifi-atlassian-bundle/nifi-atlassian-extensions/src/main/java/org/apache/nifi/atlassian/bitbucket/BitbucketRepositoryClient.java
+++ 
b/nifi-extension-bundles/nifi-atlassian-bundle/nifi-atlassian-extensions/src/main/java/org/apache/nifi/atlassian/bitbucket/BitbucketRepositoryClient.java
@@ -448,6 +448,13 @@ public class BitbucketRepositoryClient implements 
GitRepositoryClient {
             multipartBuilder.addPart(FIELD_BRANCH, 
StandardHttpContentType.TEXT_PLAIN, branch.getBytes(StandardCharsets.UTF_8));
             multipartBuilder.addPart(FIELD_PARENTS, 
StandardHttpContentType.TEXT_PLAIN, 
branchHead.getBytes(StandardCharsets.UTF_8));
 
+            final String authorName = request.getAuthorName();
+            final String authorEmail = request.getAuthorEmail();
+            if (authorName != null && authorEmail != null) {
+                final String authorValue = "%s <%s>".formatted(authorName, 
authorEmail);
+                multipartBuilder.addPart(FIELD_AUTHOR, 
StandardHttpContentType.TEXT_PLAIN, 
authorValue.getBytes(StandardCharsets.UTF_8));
+            }
+
             final HttpResponseEntity response = 
this.webClient.getWebClientService()
                     .post()
                     .uri(uri)
diff --git 
a/nifi-extension-bundles/nifi-azure-bundle/nifi-azure-registry-clients/src/main/java/org/apache/nifi/azure/devops/AzureDevOpsRepositoryClient.java
 
b/nifi-extension-bundles/nifi-azure-bundle/nifi-azure-registry-clients/src/main/java/org/apache/nifi/azure/devops/AzureDevOpsRepositoryClient.java
index dc6859317c0..cf4d2b2ea69 100644
--- 
a/nifi-extension-bundles/nifi-azure-bundle/nifi-azure-registry-clients/src/main/java/org/apache/nifi/azure/devops/AzureDevOpsRepositoryClient.java
+++ 
b/nifi-extension-bundles/nifi-azure-bundle/nifi-azure-registry-clients/src/main/java/org/apache/nifi/azure/devops/AzureDevOpsRepositoryClient.java
@@ -376,6 +376,10 @@ public class AzureDevOpsRepositoryClient implements 
GitRepositoryClient {
                 .addQueryParameter(API, API_VERSION)
                 .build();
 
+        final String authorName = request.getAuthorName();
+        final String authorEmail = request.getAuthorEmail();
+        final Author author = (authorName != null && authorEmail != null) ? 
new Author(authorName, authorEmail) : null;
+
         for (int attempt = 1; attempt <= MAX_PUSH_ATTEMPTS; attempt++) {
             if (expectedFileCommit != null) {
                 final Optional<String> currentFileCommit = 
getContentSha(request.getPath(), branch);
@@ -385,7 +389,7 @@ public class AzureDevOpsRepositoryClient implements 
GitRepositoryClient {
             }
 
             final String branchHead = fetchBranchHead(branch);
-            final HttpResponseEntity response = executePush(pushUri, branch, 
branchHead, encoded, message, changeType, path);
+            final HttpResponseEntity response = executePush(pushUri, branch, 
branchHead, encoded, message, changeType, path, author);
 
             if (response.statusCode() == HttpURLConnection.HTTP_CREATED) {
                 try {
@@ -421,10 +425,10 @@ public class AzureDevOpsRepositoryClient implements 
GitRepositoryClient {
 
     private HttpResponseEntity executePush(final URI pushUri, final String 
branch, final String oldObjectId,
                                            final String encodedContent, final 
String message,
-                                           final String changeType, final 
String path) throws FlowRegistryException {
+                                           final String changeType, final 
String path, final Author author) throws FlowRegistryException {
         final PushRequest pushRequest = new PushRequest(
                 List.of(new RefUpdate(REFS_HEADS_PREFIX + branch, 
oldObjectId)),
-                List.of(new Commit(message, List.of(new Change(changeType, new 
Item(path), new NewContent(encodedContent, CONTENT_TYPE_BASE64)))))
+                List.of(new Commit(message, List.of(new Change(changeType, new 
Item(path), new NewContent(encodedContent, CONTENT_TYPE_BASE64))), author))
         );
 
         final String json;
@@ -455,8 +459,7 @@ public class AzureDevOpsRepositoryClient implements 
GitRepositoryClient {
 
         final PushRequest pushRequest = new PushRequest(
                 List.of(new RefUpdate(REFS_HEADS_PREFIX + branch, 
oldObjectId)),
-                List.of(new Commit(commitMessage,
-                        List.of(new Change(CHANGE_TYPE_DELETE, new Item(path), 
null))))
+                List.of(new Commit(commitMessage, List.of(new 
Change(CHANGE_TYPE_DELETE, new Item(path), null)), null))
         );
 
         final String json = MAPPER.writeValueAsString(pushRequest);
@@ -506,7 +509,10 @@ public class AzureDevOpsRepositoryClient implements 
GitRepositoryClient {
 
     private record RefUpdate(String name, String oldObjectId) { }
 
-    private record Commit(String comment, List<Change> changes) { }
+    @JsonInclude(JsonInclude.Include.NON_NULL)
+    private record Commit(String comment, List<Change> changes, Author author) 
{ }
+
+    private record Author(String name, String email) { }
 
     private record Item(String path) { }
 
diff --git 
a/nifi-extension-bundles/nifi-extension-utils/nifi-git-flow-registry/src/main/java/org/apache/nifi/registry/flow/git/AbstractGitFlowRegistryClient.java
 
b/nifi-extension-bundles/nifi-extension-utils/nifi-git-flow-registry/src/main/java/org/apache/nifi/registry/flow/git/AbstractGitFlowRegistryClient.java
index b866f9d604c..740763b7296 100644
--- 
a/nifi-extension-bundles/nifi-extension-utils/nifi-git-flow-registry/src/main/java/org/apache/nifi/registry/flow/git/AbstractGitFlowRegistryClient.java
+++ 
b/nifi-extension-bundles/nifi-extension-utils/nifi-git-flow-registry/src/main/java/org/apache/nifi/registry/flow/git/AbstractGitFlowRegistryClient.java
@@ -119,6 +119,18 @@ public abstract class AbstractGitFlowRegistryClient 
extends AbstractFlowRegistry
             .identifiesControllerService(SSLContextProvider.class)
             .build();
 
+    public static final PropertyDescriptor COMMIT_AUTHOR_SOURCE = new 
PropertyDescriptor.Builder()
+            .name("Commit Author Source")
+            .description("""
+                    Specifies how the commit author is determined for Git 
commits. \
+                    When set to Service User, the authenticated service 
account is used as the commit author. \
+                    When set to Application User, the identity of the NiFi 
user performing the action is used as \
+                    both the author name and author email, while the service 
account remains the committer.""")
+            .allowableValues(CommitAuthorSource.class)
+            .defaultValue(CommitAuthorSource.SERVICE_USER)
+            .required(true)
+            .build();
+
     static final String DEFAULT_BUCKET_NAME = "default";
     static final String DEFAULT_BUCKET_KEEP_FILE_PATH = DEFAULT_BUCKET_NAME + 
"/.keep";
     static final String DEFAULT_BUCKET_KEEP_FILE_CONTENT = "Do Not Delete";
@@ -148,6 +160,7 @@ public abstract class AbstractGitFlowRegistryClient extends 
AbstractFlowRegistry
         combinedPropertyDescriptors.add(REPOSITORY_PATH);
         combinedPropertyDescriptors.add(DIRECTORY_FILTER_EXCLUDE);
         combinedPropertyDescriptors.add(PARAMETER_CONTEXT_VALUES);
+        combinedPropertyDescriptors.add(COMMIT_AUTHOR_SOURCE);
         combinedPropertyDescriptors.add(SSL_CONTEXT_SERVICE);
         propertyDescriptors = 
Collections.unmodifiableList(combinedPropertyDescriptors);
 
@@ -238,6 +251,7 @@ public abstract class AbstractGitFlowRegistryClient extends 
AbstractFlowRegistry
         final FlowLocation flowLocation = new FlowLocation(branch, 
flow.getBucketIdentifier(), flow.getIdentifier());
         final String filePath = getSnapshotFilePath(flowLocation);
         final String commitMessage = 
REGISTER_FLOW_MESSAGE_FORMAT.formatted(flow.getIdentifier());
+        final String userIdentity = resolveAuthorIdentity(context);
 
         final Optional<String> existingFileSha = 
repositoryClient.getContentSha(filePath, branch);
         if (existingFileSha.isPresent()) {
@@ -258,6 +272,8 @@ public abstract class AbstractGitFlowRegistryClient extends 
AbstractFlowRegistry
                 .path(filePath)
                 .content(flowSnapshotSerializer.serialize(flowSnapshot))
                 .message(commitMessage)
+                .authorName(userIdentity)
+                .authorEmail(userIdentity)
                 .build();
 
         repositoryClient.createContent(request);
@@ -278,7 +294,8 @@ public abstract class AbstractGitFlowRegistryClient extends 
AbstractFlowRegistry
         final String branch = flowLocation.getBranch();
         final String filePath = getSnapshotFilePath(flowLocation);
         final String commitMessage = 
DEREGISTER_FLOW_MESSAGE_FORMAT.formatted(flowLocation.getFlowId());
-        try (final InputStream deletedSnapshotContent = 
repositoryClient.deleteContent(filePath, commitMessage, branch)) {
+        final String userIdentity = resolveAuthorIdentity(context);
+        try (final InputStream deletedSnapshotContent = 
repositoryClient.deleteContent(filePath, commitMessage, branch, userIdentity, 
userIdentity)) {
             final RegisteredFlowSnapshot deletedSnapshot = 
getSnapshot(deletedSnapshotContent);
             populateFlowAndSnapshotMetadata(deletedSnapshot, flowLocation);
             updateBucketReferences(repositoryClient, deletedSnapshot, 
flowLocation.getBucketId());
@@ -437,6 +454,8 @@ public abstract class AbstractGitFlowRegistryClient extends 
AbstractFlowRegistry
         final String originalFlowContentsGroupId = 
replaceGroupId(flowSnapshot.getFlowContents(), FLOW_CONTENTS_GROUP_ID);
         final Position originalFlowContentsPosition = 
replacePosition(flowSnapshot.getFlowContents(), new Position(0, 0));
 
+        final String userIdentity = resolveAuthorIdentity(context);
+
         final GitCreateContentRequest createContentRequest = 
GitCreateContentRequest.builder()
                 .branch(branch)
                 .path(filePath)
@@ -444,6 +463,8 @@ public abstract class AbstractGitFlowRegistryClient extends 
AbstractFlowRegistry
                 .message(commitMessage)
                 .existingContentSha(existingBlobSha)
                 .expectedCommitSha(expectedVersion)
+                .authorName(userIdentity)
+                .authorEmail(userIdentity)
                 .build();
 
         final String createContentCommitSha = 
repositoryClient.createContent(createContentRequest);
@@ -656,6 +677,11 @@ public abstract class AbstractGitFlowRegistryClient 
extends AbstractFlowRegistry
         }
     }
 
+    private String resolveAuthorIdentity(final 
FlowRegistryClientConfigurationContext context) {
+        final CommitAuthorSource source = 
context.getProperty(COMMIT_AUTHOR_SOURCE).asAllowableValue(CommitAuthorSource.class);
+        return CommitAuthorSource.APPLICATION_USER.equals(source) ? 
context.getNiFiUserIdentity().orElse(null) : null;
+    }
+
     private void verifyReadPermissions(final GitRepositoryClient 
repositoryClient) throws AuthorizationException {
         if (!repositoryClient.hasReadPermission()) {
             throw new AuthorizationException("Client does not have read access 
to the repository");
@@ -839,6 +865,34 @@ public abstract class AbstractGitFlowRegistryClient 
extends AbstractFlowRegistry
         return new JacksonFlowSnapshotSerializer();
     }
 
+    enum CommitAuthorSource implements DescribedValue {
+        SERVICE_USER("Service User", "The commit author is the authenticated 
service account configured on this registry client"),
+        APPLICATION_USER("Application User", "The commit author is the NiFi 
user performing the action, using the identity as both author name and author 
email");
+
+        private final String displayName;
+        private final String description;
+
+        CommitAuthorSource(final String displayName, final String description) 
{
+            this.displayName = displayName;
+            this.description = description;
+        }
+
+        @Override
+        public String getValue() {
+            return name();
+        }
+
+        @Override
+        public String getDisplayName() {
+            return displayName;
+        }
+
+        @Override
+        public String getDescription() {
+            return description;
+        }
+    }
+
     enum ParameterContextValuesStrategy implements DescribedValue {
         RETAIN("Retain", "Retain Values in Parameter Contexts without 
modifications"),
         REMOVE("Remove", "Remove Values from Parameter Context"),
diff --git 
a/nifi-extension-bundles/nifi-extension-utils/nifi-git-flow-registry/src/main/java/org/apache/nifi/registry/flow/git/client/GitCreateContentRequest.java
 
b/nifi-extension-bundles/nifi-extension-utils/nifi-git-flow-registry/src/main/java/org/apache/nifi/registry/flow/git/client/GitCreateContentRequest.java
index 3c8729bd25a..dd8d4986058 100644
--- 
a/nifi-extension-bundles/nifi-extension-utils/nifi-git-flow-registry/src/main/java/org/apache/nifi/registry/flow/git/client/GitCreateContentRequest.java
+++ 
b/nifi-extension-bundles/nifi-extension-utils/nifi-git-flow-registry/src/main/java/org/apache/nifi/registry/flow/git/client/GitCreateContentRequest.java
@@ -27,6 +27,8 @@ public class GitCreateContentRequest {
     private final String message;
     private final String existingContentSha;
     private final String expectedCommitSha;
+    private final String authorName;
+    private final String authorEmail;
 
     private GitCreateContentRequest(final Builder builder) {
         this.branch = Objects.requireNonNull(builder.branch);
@@ -37,6 +39,8 @@ public class GitCreateContentRequest {
         this.existingContentSha = builder.existingContentSha;
         // Commit SHA for providers that support atomic commits via commit SHA
         this.expectedCommitSha = builder.expectedCommitSha;
+        this.authorName = builder.authorName;
+        this.authorEmail = builder.authorEmail;
     }
 
     public String getBranch() {
@@ -63,6 +67,14 @@ public class GitCreateContentRequest {
         return expectedCommitSha;
     }
 
+    public String getAuthorName() {
+        return authorName;
+    }
+
+    public String getAuthorEmail() {
+        return authorEmail;
+    }
+
     public static Builder builder() {
         return new Builder();
     }
@@ -74,6 +86,8 @@ public class GitCreateContentRequest {
         private String message;
         private String existingContentSha;
         private String expectedCommitSha;
+        private String authorName;
+        private String authorEmail;
 
         public Builder branch(final String branch) {
             this.branch = branch;
@@ -105,6 +119,16 @@ public class GitCreateContentRequest {
             return this;
         }
 
+        public Builder authorName(final String authorName) {
+            this.authorName = authorName;
+            return this;
+        }
+
+        public Builder authorEmail(final String authorEmail) {
+            this.authorEmail = authorEmail;
+            return this;
+        }
+
         public GitCreateContentRequest build() {
             return new GitCreateContentRequest(this);
         }
diff --git 
a/nifi-extension-bundles/nifi-extension-utils/nifi-git-flow-registry/src/main/java/org/apache/nifi/registry/flow/git/client/GitRepositoryClient.java
 
b/nifi-extension-bundles/nifi-extension-utils/nifi-git-flow-registry/src/main/java/org/apache/nifi/registry/flow/git/client/GitRepositoryClient.java
index 3d02cdbeaba..6c2853c71b5 100644
--- 
a/nifi-extension-bundles/nifi-extension-utils/nifi-git-flow-registry/src/main/java/org/apache/nifi/registry/flow/git/client/GitRepositoryClient.java
+++ 
b/nifi-extension-bundles/nifi-extension-utils/nifi-git-flow-registry/src/main/java/org/apache/nifi/registry/flow/git/client/GitRepositoryClient.java
@@ -155,6 +155,25 @@ public interface GitRepositoryClient {
      */
     InputStream deleteContent(String filePath, String commitMessage, String 
branch) throws FlowRegistryException, IOException;
 
+    /**
+     * Deletes the file at the given path on the given branch, attributing the 
commit to the specified author.
+     *
+     * The caller of this method is responsible for closing the returned 
InputStream.
+     *
+     * @param filePath the path of the file
+     * @param commitMessage the commit message
+     * @param branch the branch
+     * @param authorName the name of the commit author, or null to use the 
authenticated user
+     * @param authorEmail the email of the commit author, or null to use the 
authenticated user
+     * @return the input stream to the deleted content
+     * @throws IOException if an I/O error occurs
+     * @throws FlowRegistryException if a non-I/O error occurs
+     */
+    default InputStream deleteContent(final String filePath, final String 
commitMessage, final String branch,
+                                      final String authorName, final String 
authorEmail) throws FlowRegistryException, IOException {
+        return deleteContent(filePath, commitMessage, branch);
+    }
+
     /**
      * Closes any resources held by the client.
      *
diff --git 
a/nifi-extension-bundles/nifi-github-bundle/nifi-github-extensions/pom.xml 
b/nifi-extension-bundles/nifi-github-bundle/nifi-github-extensions/pom.xml
index 1eb89489df7..477a25227bf 100644
--- a/nifi-extension-bundles/nifi-github-bundle/nifi-github-extensions/pom.xml
+++ b/nifi-extension-bundles/nifi-github-bundle/nifi-github-extensions/pom.xml
@@ -38,6 +38,12 @@
             <groupId>org.kohsuke</groupId>
             <artifactId>github-api</artifactId>
             <version>${github-api.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>com.infradna.tool</groupId>
+                    <artifactId>bridge-method-annotation</artifactId>
+                </exclusion>
+            </exclusions>
         </dependency>
         <dependency>
             <groupId>io.jsonwebtoken</groupId>
diff --git 
a/nifi-extension-bundles/nifi-github-bundle/nifi-github-extensions/src/main/java/org/apache/nifi/github/GitHubFlowRegistryClient.java
 
b/nifi-extension-bundles/nifi-github-bundle/nifi-github-extensions/src/main/java/org/apache/nifi/github/GitHubFlowRegistryClient.java
index da085231eb5..668c2cfc574 100644
--- 
a/nifi-extension-bundles/nifi-github-bundle/nifi-github-extensions/src/main/java/org/apache/nifi/github/GitHubFlowRegistryClient.java
+++ 
b/nifi-extension-bundles/nifi-github-bundle/nifi-github-extensions/src/main/java/org/apache/nifi/github/GitHubFlowRegistryClient.java
@@ -115,7 +115,7 @@ public class GitHubFlowRegistryClient extends 
AbstractGitFlowRegistryClient {
     }
 
     @Override
-    protected GitHubRepositoryClient createRepositoryClient(final 
FlowRegistryClientConfigurationContext context) throws IOException, 
FlowRegistryException {
+    protected GitRepositoryClient createRepositoryClient(final 
FlowRegistryClientConfigurationContext context) throws IOException, 
FlowRegistryException {
         return GitHubRepositoryClient.builder()
                 .logger(getLogger())
                 .apiUrl(context.getProperty(GITHUB_API_URL).getValue())
diff --git 
a/nifi-extension-bundles/nifi-github-bundle/nifi-github-extensions/src/main/java/org/apache/nifi/github/GitHubRepositoryClient.java
 
b/nifi-extension-bundles/nifi-github-bundle/nifi-github-extensions/src/main/java/org/apache/nifi/github/GitHubRepositoryClient.java
index ca20991d8c1..0a023b8cda6 100644
--- 
a/nifi-extension-bundles/nifi-github-bundle/nifi-github-extensions/src/main/java/org/apache/nifi/github/GitHubRepositoryClient.java
+++ 
b/nifi-extension-bundles/nifi-github-bundle/nifi-github-extensions/src/main/java/org/apache/nifi/github/GitHubRepositoryClient.java
@@ -29,11 +29,13 @@ import 
org.apache.nifi.registry.flow.git.client.GitRepositoryClient;
 import org.apache.nifi.ssl.SSLContextProvider;
 import org.kohsuke.github.GHCommit;
 import org.kohsuke.github.GHContent;
+import org.kohsuke.github.GHContentBuilder;
 import org.kohsuke.github.GHContentUpdateResponse;
 import org.kohsuke.github.GHMyself;
 import org.kohsuke.github.GHPermissionType;
 import org.kohsuke.github.GHRef;
 import org.kohsuke.github.GHRepository;
+import org.kohsuke.github.GHUser;
 import org.kohsuke.github.GitHub;
 import org.kohsuke.github.GitHubAbuseLimitHandler;
 import org.kohsuke.github.GitHubBuilder;
@@ -50,7 +52,6 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.net.http.HttpClient;
 import java.security.PrivateKey;
-import java.time.Instant;
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -201,13 +202,20 @@ public class GitHubRepositoryClient implements 
GitRepositoryClient {
         logger.debug("Creating content at path [{}] on branch [{}] in repo 
[{}] ", resolvedPath, branch, repository.getName());
         return execute(() -> {
             try {
-                final GHContentUpdateResponse response = 
repository.createContent()
+                final GHContentBuilder contentBuilder = 
repository.createContent()
                         .branch(branch)
                         .path(resolvedPath)
                         .content(request.getContent())
                         .message(request.getMessage())
-                        .sha(request.getExistingContentSha())
-                        .commit();
+                        .sha(request.getExistingContentSha());
+
+                final String authorName = request.getAuthorName();
+                final String authorEmail = request.getAuthorEmail();
+                if (authorName != null && authorEmail != null) {
+                    contentBuilder.author(authorName, authorEmail);
+                }
+
+                final GHContentUpdateResponse response = 
contentBuilder.commit();
                 return response.getCommit().getSha();
             } catch (final FileNotFoundException fnf) {
                 throwPathOrBranchNotFound(fnf, resolvedPath, branch);
@@ -477,15 +485,13 @@ public class GitHubRepositoryClient implements 
GitRepositoryClient {
 
         if (commit == null) {
             final GHCommit.ShortInfo shortInfo = ghCommit.getCommitShortInfo();
-            final String author = ghCommit.getAuthor() != null
-                ? ghCommit.getAuthor().getLogin()
-                : shortInfo.getAuthor().getName();
-
+            final GHUser ghUser = ghCommit.getAuthor();
+            final String author = ghUser != null ? ghUser.getLogin() : 
shortInfo.getAuthor().getName();
             commit = new GitCommit(
-                ghCommit.getSHA1(),
-                author,
-                shortInfo.getMessage(),
-                Instant.ofEpochMilli(shortInfo.getCommitDate().getTime()));
+                    ghCommit.getSHA1(),
+                    author,
+                    shortInfo.getMessage(),
+                    shortInfo.getCommitDate());
             commitCache.put(ghCommit.getSHA1(), commit);
         }
 
diff --git 
a/nifi-extension-bundles/nifi-github-bundle/nifi-github-extensions/src/test/java/org/apache/nifi/github/GitHubFlowRegistryClientTest.java
 
b/nifi-extension-bundles/nifi-github-bundle/nifi-github-extensions/src/test/java/org/apache/nifi/github/GitHubFlowRegistryClientTest.java
index e7a0a49e27a..6683dfbe0f5 100644
--- 
a/nifi-extension-bundles/nifi-github-bundle/nifi-github-extensions/src/test/java/org/apache/nifi/github/GitHubFlowRegistryClientTest.java
+++ 
b/nifi-extension-bundles/nifi-github-bundle/nifi-github-extensions/src/test/java/org/apache/nifi/github/GitHubFlowRegistryClientTest.java
@@ -130,6 +130,29 @@ public class GitHubFlowRegistryClientTest {
         
assertEquals("%s/%s.json".formatted(incomingFlow.getBucketIdentifier(), 
incomingFlow.getIdentifier()), capturedArgument.getPath());
         assertEquals(serializedSnapshotContent, capturedArgument.getContent());
         assertNull(capturedArgument.getExistingContentSha());
+        assertEquals("[email protected]", capturedArgument.getAuthorName());
+        assertEquals("[email protected]", 
capturedArgument.getAuthorEmail());
+    }
+
+    @Test
+    public void testRegisterFlowWithServiceUserAuthor() throws IOException, 
FlowRegistryException {
+        setupClientConfigurationContextWithDefaults();
+
+        final PropertyValue commitAuthorPropertyValue = 
createMockPropertyValueWithEnum("SERVICE_USER");
+        
when(clientConfigurationContext.getProperty(GitHubFlowRegistryClient.COMMIT_AUTHOR_SOURCE)).thenReturn(commitAuthorPropertyValue);
+
+        final String serializedSnapshotContent = "placeholder";
+        
when(flowSnapshotSerializer.serialize(any(RegisteredFlowSnapshot.class))).thenReturn(serializedSnapshotContent);
+
+        final RegisteredFlow incomingFlow = createIncomingRegisteredFlow();
+        flowRegistryClient.registerFlow(clientConfigurationContext, 
incomingFlow);
+
+        final ArgumentCaptor<GitCreateContentRequest> argumentCaptor = 
ArgumentCaptor.forClass(GitCreateContentRequest.class);
+        verify(repositoryClient).createContent(argumentCaptor.capture());
+
+        final GitCreateContentRequest capturedArgument = 
argumentCaptor.getValue();
+        assertNull(capturedArgument.getAuthorName());
+        assertNull(capturedArgument.getAuthorEmail());
     }
 
     @Test
@@ -433,6 +456,11 @@ public class GitHubFlowRegistryClientTest {
 
         final PropertyValue parametersPropertyValue = 
createMockPropertyValue("Retain");
         
when(clientConfigurationContext.getProperty(GitHubFlowRegistryClient.PARAMETER_CONTEXT_VALUES)).thenReturn(parametersPropertyValue);
+
+        final PropertyValue commitAuthorPropertyValue = 
createMockPropertyValueWithEnum("APPLICATION_USER");
+        
when(clientConfigurationContext.getProperty(GitHubFlowRegistryClient.COMMIT_AUTHOR_SOURCE)).thenReturn(commitAuthorPropertyValue);
+
+        
when(clientConfigurationContext.getNiFiUserIdentity()).thenReturn(Optional.of("[email protected]"));
     }
 
     private void setupClientConfigurationContextWithDefaults() {
diff --git a/nifi-extension-bundles/nifi-github-bundle/pom.xml 
b/nifi-extension-bundles/nifi-github-bundle/pom.xml
index dc4a68230b6..68f4c246698 100644
--- a/nifi-extension-bundles/nifi-github-bundle/pom.xml
+++ b/nifi-extension-bundles/nifi-github-bundle/pom.xml
@@ -26,7 +26,7 @@
     <packaging>pom</packaging>
 
     <properties>
-        <github-api.version>1.330</github-api.version>
+        <github-api.version>2.0-rc.6</github-api.version>
     </properties>
 
     <modules>
diff --git 
a/nifi-extension-bundles/nifi-gitlab-bundle/nifi-gitlab-extensions/src/main/java/org/apache/nifi/gitlab/GitLabRepositoryClient.java
 
b/nifi-extension-bundles/nifi-gitlab-bundle/nifi-gitlab-extensions/src/main/java/org/apache/nifi/gitlab/GitLabRepositoryClient.java
index a6292e4ab28..14eeb0ede65 100644
--- 
a/nifi-extension-bundles/nifi-gitlab-bundle/nifi-gitlab-extensions/src/main/java/org/apache/nifi/gitlab/GitLabRepositoryClient.java
+++ 
b/nifi-extension-bundles/nifi-gitlab-bundle/nifi-gitlab-extensions/src/main/java/org/apache/nifi/gitlab/GitLabRepositoryClient.java
@@ -270,9 +270,9 @@ public class GitLabRepositoryClient implements 
GitRepositoryClient {
                             projectPath,
                             branch,
                             request.getMessage(),
-                            null, // start_branch - null means use the branch 
parameter
-                            null, // author_email - null means use the 
authenticated user
-                            null, // author_name - null means use the 
authenticated user
+                            null,
+                            request.getAuthorEmail(),
+                            request.getAuthorName(),
                             List.of(commitAction));
 
             final String commitId = commit.getId();
@@ -284,11 +284,22 @@ public class GitLabRepositoryClient implements 
GitRepositoryClient {
 
     @Override
     public InputStream deleteContent(final String filePath, final String 
commitMessage, final String branch) throws FlowRegistryException {
+        return deleteContent(filePath, commitMessage, branch, null, null);
+    }
+
+    @Override
+    public InputStream deleteContent(final String filePath, final String 
commitMessage, final String branch,
+                                     final String authorName, final String 
authorEmail) throws FlowRegistryException {
         final String resolvedPath = getResolvedPath(filePath);
         logger.debug("Deleting content at path [{}] on branch [{}] in 
repository [{}] ", resolvedPath, branch, projectPath);
         return execute(() -> {
             final InputStream content = 
gitLab.getRepositoryFileApi().getRawFile(projectPath, branch, resolvedPath);
-            gitLab.getRepositoryFileApi().deleteFile(projectPath, 
resolvedPath, branch, commitMessage);
+
+            final CommitAction commitAction = new CommitAction();
+            commitAction.setAction(CommitAction.Action.DELETE);
+            commitAction.setFilePath(resolvedPath);
+
+            gitLab.getCommitsApi().createCommit(projectPath, branch, 
commitMessage, null, authorEmail, authorName, List.of(commitAction));
             return content;
         });
     }

Reply via email to