Can I get the same information I do from "git show" through the Bitbucket Java or REST API?
Asked by Joseph Marcel:

What follows is three example commits. In all cases 'git show' explains correctly, yet for merge commits the two API's give inconsistent results.

*** ** * ** ***

Consider this **merge commit #1** , 'git show' ***\*correctly shows the changes to the files\**** (file A was developer resolved, file B was auto-merged, file C was included w/out file-level merge changes (no new revision)):

$ git show c47af8deac6b87a  
commit c47af8deac6b87a35c54f750166a13089c35b770 (HEAD -\> master, origin/PR2, PR2)  
Merge: 4a7b7ed 705cb5f  
Author: Joe  
Date: Fri Mar 18 14:27:03 2022 -0400

Merge branch 'master' into PR2

diff --cc A  
index bb62e45,c875e2d..695279f  
--- a/A  
+++ b/A  
@@@ -1,3 -1,7 +1,8 @@@  
adios  
more stuff  
+take that!  
+ hello  
+ what are we doing  
+ this is it  
+ more of the same  
+ gimme a break  
diff --cc B  
index fecdf68,864a49b..aa01e7f  
--- a/B  
+++ b/B  
@@@ -1,6 -1,5 +1,4 @@@  
-guten tag  
this is getting ridiculous  
- what now?  
- ho hum  
goodbye  
more changes  
uh-oh

*** ** * ** ***

However the Java API shows *three* changes (when iterating through the changesets for that commit):

2022-03-18 19:46:16,817 WARN \[c.o.s.r.ScriptBindingsManager\]: Type: MODIFY  
2022-03-18 19:46:16,817 WARN \[c.o.s.r.ScriptBindingsManager\]: Path: A  
2022-03-18 19:46:16,817 WARN \[c.o.s.r.ScriptBindingsManager\]: Type: MODIFY  
2022-03-18 19:46:16,818 WARN \[c.o.s.r.ScriptBindingsManager\]: Path: B  
2022-03-18 19:46:16,818 WARN \[c.o.s.r.ScriptBindingsManager\]: Type: MODIFY  
2022-03-18 19:46:16,818 WARN \[c.o.s.r.ScriptBindingsManager\]: Path: C

*** ** * ** ***

While the REST API shows no changes (.../commits/c47af8d/changes):

{"fromHash":null,"toHash":"c47af8d","properties":{},"values":\[\],**"size":0**,"isLastPage":true,"start":0,"limit":25,"nextPageStart":null}

*** ** * ** ***

Consider this **merge commit #2** , 'git show' ***\*correctly shows that there are no changes to any files\**** (files A and C were included, but no new file revisions via file-level merge):

$ git show 8907e98fb71ffca612  
commit 8907e98fb71ffca6120f703d0e43be4787dd173e  
Merge: edb85da 2d76267  
Author: Joe  
Date: Thu Mar 17 12:57:40 2022 -0400

Merge branch 'master' into PR2

*** ** * ** ***

However the Java API shows two changes:

2022-03-18 19:53:38,354 WARN \[c.o.s.r.ScriptBindingsManager\]: Type: MODIFY  
2022-03-18 19:53:38,373 WARN \[c.o.s.r.ScriptBindingsManager\]: Path: A  
2022-03-18 19:53:38,374 WARN \[c.o.s.r.ScriptBindingsManager\]: Type: MODIFY  
2022-03-18 19:53:38,374 WARN \[c.o.s.r.ScriptBindingsManager\]: Path: C

*** ** * ** ***

While the REST API shows no changes (.../commits/8907e98/changes):

{"fromHash":null,"toHash":"8907e98","properties":{},"values":\[\],**"size":0**,"isLastPage":true,"start":0,"limit":25,"nextPageStart":null}

*** ** * ** ***

For a **simple (non-merge) commit**, results are as expected (file A is modified):

$ git show 4a7b7ed55  
commit 4a7b7ed55512a5814292ba6b1689a781c4dc225b  
Author: Joe  
Date: Fri Mar 18 14:24:44 2022 -0400

PR2

diff --git a/A b/A  
index 76b9b5a..bb62e45 100644  
--- a/A  
+++ b/A  
@@ -1,2 +1,3 @@  
adios  
more stuff  
+take that!

*** ** * ** ***

2022-03-18 20:11:46,977 WARN \[c.o.s.r.ScriptBindingsManager\]: Type: MODIFY  
2022-03-18 20:11:47,012 WARN \[c.o.s.r.ScriptBindingsManager\]: Path: A

*** ** * ** ***

{"fromHash":null,"toHash":"4a7b7ed55","properties":{},"values":\[{"contentId":"bb62e451e87164abcba481ef8f73d719230e0b62","fromContentId":"76b9b5ab7d2242c33ab6ee0d9ede24233add0b66","path":{"components":\["A"\],"parent":"","name":"A","toString":"A"},"executable":false,"percentUnchanged":-1,"type":"MODIFY","nodeType":"FILE","srcExecutable":false,"links":{...},"properties":{"gitChangeType":"MODIFY"}}\],**"size":1**,"isLastPage":true,"start":0,"limit":25,"nextPageStart":null}


Answers:
author: Joseph Marcel
body:     Can execute the 'git show' command from bitbucket...

    // check if the commit has new file revisions
    // use 'git show' and count the number of new file revisions
    def outputHandler = new SingleLineOutputHandler()
    gitCommandBuilderFactory.builder(commit.repository)
    .command("show")
    .argument("--oneline")
    .argument("--name-status")
    .argument(commit.id)
    .build(outputHandler)
    .call()
    def new_file_revisions = (outputHandler.getOutput().split("\t").size()-1)


author: Joseph Marcel
body: From a top-level perspective, we've implemented a pre-receive hook to prevent pushes to master if all the files don't have review credit in Crucible...

```
...

def REVIEWS_PATH = 'rest-service-fe/search-v1/queryAsRows/'

commitCallback =
new PreRepositoryHookCommitCallback() {

  @Override boolean onCommitAdded(@Nonnull CommitAddedDetails commitDetails) {
    def commit = commitDetails.commit

    // for commits on master, check to see if the commit includes file revisions
    // that are part of a CLOSED review
    if (commitDetails.ref.id.startsWith("refs/heads/master")) {

   ?  // check if the commit has new file revisions
      // use 'git show' and count the number of occurrences of diff output
      def outputHandler = new SingleLineOutputHandler()
      gitCommandBuilderFactory.builder(commit.repository)
                                .command("show")
                                .argument("--oneline")
                                .argument("--name-status")
                                .argument(commit.id)
                                .build(outputHandler)
                                .call()
      def int new_file_revisions = (outputHandler.getOutput().split("\t").size()-1)

      if (new_file_revisions > 0) {

        // check Crucible
        def REVIEWS_QUERY = URLEncoder.encode("select revisions where csid = ${commit.id} and reviewed return count(revisions)", "UTF-8")
        def getReviews = authenticatedRequestFactory
                           .createRequest(GET, "${FISHEYE_URL}${REVIEWS_PATH}${commit.repository.name}?query=${REVIEWS_QUERY}")
                             .addHeader("Accept", "application/json")
                             .addHeader("Content-Type", "application/json")

        // {headings=[countReviews], row=[{item=[0]}]}
        def result = getReviews.execute(handler)["row"]["item"][0] as ArrayList
        def reviewedRevisions = result[0] as int

        if (new_file_revisions == reviewedRevisions) {
          // allow commits w/ reviewed revisions
          return true
        } else {
          // the number or reviewed revisions does not match the number of new file revisions
         ?resultBuilder.veto("*** Unreviewed commit to master!!! ***",
                             "*** Commit ID: " + commit.id +
                             " *** New file revisions: " + new_file_revisions +
                             " *** Reviewed revisions: " + reviewedRevisions + " ***")          return false
        }

      } else {
        // allow commit w/ no new file revisions (likely a clean merge-commit)
        return true
      }

    } else {
      // allow non-master commits
      return true
    }
  }

  @Override RepositoryHookResult getResult() {
    resultBuilder.build()
  }
}

commitFilters << RepositoryHookCommitFilter.ADDED_TO_ANY_REF

return RepositoryHookResult.accepted()
```


Tags: adaptavist

bitbucket-server

fisheye-crucible

pre-receive-hook

scriptrunner