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 1fa4b35b4aa NIFI-16087 Fixed GitHub Flow Registry commit retrieval on 
author resolution failures (#11406)
1fa4b35b4aa is described below

commit 1fa4b35b4aa50507434425fc8349c66682122263
Author: Pierre Villard <[email protected]>
AuthorDate: Wed Jul 8 23:02:14 2026 +0200

    NIFI-16087 Fixed GitHub Flow Registry commit retrieval on author resolution 
failures (#11406)
    
    Signed-off-by: David Handermann <[email protected]>
---
 .../apache/nifi/github/GitHubRepositoryClient.java | 60 ++++++++++++---
 .../nifi/github/GitHubRepositoryClientTest.java    | 87 ++++++++++++++++++++++
 2 files changed, 138 insertions(+), 9 deletions(-)

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 14c9616c35d..b6d8093a7ac 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
@@ -40,6 +40,7 @@ import org.kohsuke.github.GHUser;
 import org.kohsuke.github.GitHub;
 import org.kohsuke.github.GitHubAbuseLimitHandler;
 import org.kohsuke.github.GitHubBuilder;
+import org.kohsuke.github.GitUser;
 import org.kohsuke.github.PagedIterable;
 import org.kohsuke.github.PagedIterator;
 import org.kohsuke.github.authorization.AppInstallationAuthorizationProvider;
@@ -76,6 +77,7 @@ public class GitHubRepositoryClient implements 
GitRepositoryClient {
 
     private static final String BRANCH_REF_PATTERN = "refs/heads/%s";
     private static final int COMMIT_PAGE_SIZE = 10;
+    private static final String UNKNOWN_AUTHOR = "unknown";
 
     private final String repoOwner;
     private final String repoName;
@@ -310,6 +312,7 @@ public class GitHubRepositoryClient implements 
GitRepositoryClient {
         logger.debug("Getting commits for [{}] from branch [{}] in repo [{}]", 
resolvedPath, branch, repository.getName());
 
         return execute(() -> {
+            final List<GHCommit> ghCommits;
             try {
                 final GHRef branchGhRef = repository.getRef(branchRef);
                 final PagedIterable<GHCommit> ghCommitsIterable = 
repository.queryCommits()
@@ -320,17 +323,17 @@ public class GitHubRepositoryClient implements 
GitRepositoryClient {
 
                 // Get the first page of commits
                 final PagedIterator<GHCommit> ghCommitsIterator = 
ghCommitsIterable.iterator();
-                final List<GHCommit> ghCommits = ghCommitsIterator.nextPage();
-
-                final List<GitCommit> commits = new ArrayList<>();
-                for (final GHCommit ghCommit : ghCommits) {
-                    commits.add(toGitCommit(ghCommit));
-                }
-                return commits;
+                ghCommits = ghCommitsIterator.nextPage();
             } catch (final FileNotFoundException fnf) {
                 throwPathOrBranchNotFound(fnf, resolvedPath, branchRef);
                 return null;
             }
+
+            final List<GitCommit> commits = new ArrayList<>();
+            for (final GHCommit ghCommit : ghCommits) {
+                commits.add(toGitCommit(ghCommit));
+            }
+            return commits;
         });
     }
 
@@ -538,8 +541,7 @@ public class GitHubRepositoryClient implements 
GitRepositoryClient {
 
         if (commit == null) {
             final GHCommit.ShortInfo shortInfo = ghCommit.getCommitShortInfo();
-            final GHUser ghUser = ghCommit.getAuthor();
-            final String author = ghUser != null ? ghUser.getLogin() : 
shortInfo.getAuthor().getName();
+            final String author = resolveCommitAuthor(ghCommit, shortInfo);
             commit = new GitCommit(
                     ghCommit.getSHA1(),
                     author,
@@ -551,6 +553,46 @@ public class GitHubRepositoryClient implements 
GitRepositoryClient {
         return commit;
     }
 
+    /**
+     * Resolves the author of the given commit. The GitHub account login is 
preferred, but resolving it
+     * requires an additional lookup that fails when the account cannot be 
found, for example bot accounts
+     * or deleted users whose user lookup returns Not Found. In that case, and 
when the commit has no
+     * associated GitHub account, the raw Git author name from the commit is 
used, falling back to a
+     * placeholder when the commit does not provide an author name.
+     *
+     * @param ghCommit the commit whose author is resolved
+     * @param shortInfo the commit short info providing the raw Git author
+     * @return the resolved author, never null
+     * @throws IOException if a non-recoverable error happens calling GitHub
+     */
+    static String resolveCommitAuthor(final GHCommit ghCommit, final 
GHCommit.ShortInfo shortInfo) throws IOException {
+        final GHUser ghUser = getCommitAuthor(ghCommit);
+        if (ghUser != null) {
+            return ghUser.getLogin();
+        }
+
+        final GitUser gitAuthor = shortInfo.getAuthor();
+        final String authorName = gitAuthor == null ? null : 
gitAuthor.getName();
+        return authorName == null ? UNKNOWN_AUTHOR : authorName;
+    }
+
+    /**
+     * Returns the GitHub account associated with the commit author, or null 
when the account cannot be
+     * resolved. Resolving the account requires an additional lookup that 
returns Not Found for accounts
+     * that do not exist, such as bot accounts or deleted users, which are 
treated as an unresolved author.
+     *
+     * @param ghCommit the commit whose author account is resolved
+     * @return the GitHub account for the commit author, or null when it 
cannot be resolved
+     * @throws IOException if a non-recoverable error happens calling GitHub
+     */
+    private static GHUser getCommitAuthor(final GHCommit ghCommit) throws 
IOException {
+        try {
+            return ghCommit.getAuthor();
+        } catch (final FileNotFoundException e) {
+            return null;
+        }
+    }
+
     private <T> T execute(final GHRequest<T> action) throws 
FlowRegistryException, IOException {
         try {
             return action.execute();
diff --git 
a/nifi-extension-bundles/nifi-github-bundle/nifi-github-extensions/src/test/java/org/apache/nifi/github/GitHubRepositoryClientTest.java
 
b/nifi-extension-bundles/nifi-github-bundle/nifi-github-extensions/src/test/java/org/apache/nifi/github/GitHubRepositoryClientTest.java
new file mode 100644
index 00000000000..e603df2e063
--- /dev/null
+++ 
b/nifi-extension-bundles/nifi-github-bundle/nifi-github-extensions/src/test/java/org/apache/nifi/github/GitHubRepositoryClientTest.java
@@ -0,0 +1,87 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.nifi.github;
+
+import org.junit.jupiter.api.Test;
+import org.kohsuke.github.GHCommit;
+import org.kohsuke.github.GHFileNotFoundException;
+import org.kohsuke.github.GHUser;
+import org.kohsuke.github.GitUser;
+
+import java.io.IOException;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class GitHubRepositoryClientTest {
+
+    @Test
+    void testResolveCommitAuthorReturnsLoginWhenAccountResolvable() throws 
IOException {
+        final GHUser ghUser = mock(GHUser.class);
+        when(ghUser.getLogin()).thenReturn("octocat");
+
+        final GHCommit ghCommit = mock(GHCommit.class);
+        when(ghCommit.getAuthor()).thenReturn(ghUser);
+
+        final GHCommit.ShortInfo shortInfo = mock(GHCommit.ShortInfo.class);
+
+        final String author = 
GitHubRepositoryClient.resolveCommitAuthor(ghCommit, shortInfo);
+        assertEquals("octocat", author);
+    }
+
+    @Test
+    void testResolveCommitAuthorFallsBackToGitAuthorWhenAccountNotFound() 
throws IOException {
+        final GHCommit ghCommit = mock(GHCommit.class);
+        when(ghCommit.getAuthor()).thenThrow(new 
GHFileNotFoundException("/users/Copilot 404 Not Found"));
+
+        final GitUser gitAuthor = mock(GitUser.class);
+        when(gitAuthor.getName()).thenReturn("Copilot");
+        final GHCommit.ShortInfo shortInfo = mock(GHCommit.ShortInfo.class);
+        when(shortInfo.getAuthor()).thenReturn(gitAuthor);
+
+        final String author = 
GitHubRepositoryClient.resolveCommitAuthor(ghCommit, shortInfo);
+        assertEquals("Copilot", author);
+    }
+
+    @Test
+    void testResolveCommitAuthorFallsBackToGitAuthorWhenNoAssociatedAccount() 
throws IOException {
+        final GHCommit ghCommit = mock(GHCommit.class);
+        when(ghCommit.getAuthor()).thenReturn(null);
+
+        final GitUser gitAuthor = mock(GitUser.class);
+        when(gitAuthor.getName()).thenReturn("Jane Developer");
+        final GHCommit.ShortInfo shortInfo = mock(GHCommit.ShortInfo.class);
+        when(shortInfo.getAuthor()).thenReturn(gitAuthor);
+
+        final String author = 
GitHubRepositoryClient.resolveCommitAuthor(ghCommit, shortInfo);
+        assertEquals("Jane Developer", author);
+    }
+
+    @Test
+    void testResolveCommitAuthorReturnsPlaceholderWhenNoAuthorAvailable() 
throws IOException {
+        final GHCommit ghCommit = mock(GHCommit.class);
+        when(ghCommit.getAuthor()).thenThrow(new 
GHFileNotFoundException("/users/Copilot 404 Not Found"));
+
+        final GHCommit.ShortInfo shortInfo = mock(GHCommit.ShortInfo.class);
+        when(shortInfo.getAuthor()).thenReturn(null);
+
+        final String author = 
GitHubRepositoryClient.resolveCommitAuthor(ghCommit, shortInfo);
+        assertEquals("unknown", author);
+    }
+}

Reply via email to