• Icon: Bug Bug
    • Resolution: Unresolved
    • Icon: Minor Minor
    • Jenkins 2.319.3
      Git Plugin 4.10.3
      Git Client Plugin 3.11.0
      GitHub 1.34.2

      We are trying to avoid build steps as much as possible, for example skipping some of then when a build was triggered by a upstream project but did not have any changes on its own or a commit message contained a specified string (aka [skip ci]).

      Therefor we heavily rely on the currentBuild.changeSets.

      Unfortunatly we recently discovered that builds are triggered when a force push is used (which is good) to a repository, but the changes are empty (thats not expected). This can also be seen in the corresponding UI.

      There might be other constellations where it does not work but this is one we could reproduce.

          [JENKINS-68010] Changes are empty after a force push

          Mark Waite added a comment -

          This is a known limitation of the changeset computation. It assumes that the base version of the changeset exists.

          A force push frequently deletes the base version of the changeset. Selecting a new base version of the changeset is a non-trivial exercise that would need some form of heuristic to guide it. For example, after a force push, should the changeset use the version from a build that is older than the previous build? After a force push, should the changeset show only a single commit? If so, then what about users that force push a set of changes that contain multiple commits? After a force push, should the changeset show the changes since the most recent commit that has more than one successors? Each of the rules that I can envision for selecting the comparison base for a force push will have users that see the choice as flawed.

          Mark Waite added a comment - This is a known limitation of the changeset computation. It assumes that the base version of the changeset exists. A force push frequently deletes the base version of the changeset. Selecting a new base version of the changeset is a non-trivial exercise that would need some form of heuristic to guide it. For example, after a force push, should the changeset use the version from a build that is older than the previous build? After a force push, should the changeset show only a single commit? If so, then what about users that force push a set of changes that contain multiple commits? After a force push, should the changeset show the changes since the most recent commit that has more than one successors? Each of the rules that I can envision for selecting the comparison base for a force push will have users that see the choice as flawed.

          I'd like the "Changes" UI to display the equivalent of git log X..Y (or perhaps git log --no-merges X..Y), where

          • X is the commit that was used in the previous build. If the previous build did not record a commit ID, then get the commit ID from an earlier build and add a note saying which build was used. Do not look at earlier builds in any other case.
          • Y is the commit that is being used in the current build.

          If Y does not contain all the commits of X (i.e. there was a force push, or an older build was replayed), then add a note saying so, but don't list the commits that were removed.

          Maybe someone else would see this as flawed, but this seems it would be easy to implement (although I have not tried) and would be good enough for me. However, I have only been using the "Changes" UI and not currentBuild.changeSets.

          Is the current changesets algorithm documented somewhere?

          Kalle Niemitalo added a comment - I'd like the "Changes" UI to display the equivalent of git log X..Y (or perhaps git log --no-merges X..Y ), where X is the commit that was used in the previous build. If the previous build did not record a commit ID, then get the commit ID from an earlier build and add a note saying which build was used. Do not look at earlier builds in any other case. Y is the commit that is being used in the current build. If Y does not contain all the commits of X (i.e. there was a force push, or an older build was replayed), then add a note saying so, but don't list the commits that were removed. Maybe someone else would see this as flawed, but this seems it would be easy to implement (although I have not tried) and would be good enough for me. However, I have only been using the "Changes" UI and not currentBuild.changeSets . Is the current changesets algorithm documented somewhere?

          Mark Waite added a comment -

          I'd like the "Changes" UI to display the equivalent of git log X..Y (or perhaps git log --no-merges X..Y),

          As far as I understand it, that's what it does. In the case of the force push, it is quite common that X no longer exists in the repository. When X no longer exists in the repository, git log X..Y fails

          Mark Waite added a comment - I'd like the "Changes" UI to display the equivalent of git log X..Y (or perhaps git log --no-merges X..Y), As far as I understand it, that's what it does. In the case of the force push, it is quite common that X no longer exists in the repository. When X no longer exists in the repository, git log X..Y fails

          Daniel Geißler added a comment - - edited

          Because we are using GitHub Enterprise I could take a quick look at the WebHook Event that has been send and interestingly I found this information as part of the push event:

            "created": false,
            "deleted": false,
            "forced": true,
            "base_ref": null,
            "compare": "https://<url>/<org>/<repo>/compare/a5a09eb5a893...1c7eb843a13a",
          

          not sure how this can be incorporated into the ChangeLog, but wouldn't that solve the issue having to investigate the diff on the Jenkins side?
          When I open the URL in the Browser it shows me exactly the commit that has been forced.

          Actually being told that there are no changes at all (same goes for newly created branches that already have commits related to their base) is definitely more confusing than being told that Jenkins could not calculate them.

          Even without the Web event I can see these use cases where force pushes are used:

          • rebase the whole branch onto a completely different commit/branch
            • either one historic build and the rebased commit have a common ancestor then the whole history in between can be shown (actually it is the diff of the current build regarding the last one anyways)
            • or all the previous builds and their baselines are not part of the history of the current commit (e.g. first build had already changes to the base branch or rebase has been done to a older branch/release) then Jenkins has no way to find the diff and at least should point out that it's currently impossible to compute the Change Log.
          • amending the latest commit (it s actually a special case of the first one)
            • one build in the history (usually the 2nd to last build) should have the same base (although many of the changes are the same I think it's still valid to show them)

          so I think going back more than one build would at least improve the ChangeLog even though it can not work for every situation.

          Daniel Geißler added a comment - - edited Because we are using GitHub Enterprise I could take a quick look at the WebHook Event that has been send and interestingly I found this information as part of the push event: "created" : false , "deleted" : false , "forced" : true , "base_ref" : null , "compare" : "https: //<url>/<org>/<repo>/compare/a5a09eb5a893...1c7eb843a13a" , not sure how this can be incorporated into the ChangeLog, but wouldn't that solve the issue having to investigate the diff on the Jenkins side? When I open the URL in the Browser it shows me exactly the commit that has been forced. Actually being told that there are no changes at all (same goes for newly created branches that already have commits related to their base) is definitely more confusing than being told that Jenkins could not calculate them. Even without the Web event I can see these use cases where force pushes are used: rebase the whole branch onto a completely different commit/branch either one historic build and the rebased commit have a common ancestor then the whole history in between can be shown (actually it is the diff of the current build regarding the last one anyways) or all the previous builds and their baselines are not part of the history of the current commit (e.g. first build had already changes to the base branch or rebase has been done to a older branch/release) then Jenkins has no way to find the diff and at least should point out that it's currently impossible to compute the Change Log. amending the latest commit (it s actually a special case of the first one) one build in the history (usually the 2nd to last build) should have the same base (although many of the changes are the same I think it's still valid to show them) so I think going back more than one build would at least improve the ChangeLog even though it can not work for every situation.

          After a force push, if you know the previous commit ID, is it possible to git fetch the commit object from GitHub? Git generally does not allow that; there is the uploadpack.allowAnySHA1InWant configuration variable but I don't know how GitHub configures that, or whether GitHub uses an entirely different implementation.

          Bitbucket Server 5.5.0 sets uploadpack.allowReachableSHA1InWant (BSERV-8268) and it helps Jenkins after a regular push but not after a force push.

          I suppose not using lightweight checkout on the Jenkins controller could let it preserve the previous commit locally (pinned by a reflog) and then compare to that. But it would need more disk space.

          Perhaps the best that can be done is display a message that includes the commit IDs and says why Jenkins was unable to compare the commits, and lets a new method of RepositoryBrowser (javadoc) link to a comparison that might still work. But that would still not help currentBuild.changeSets at all.

          Kalle Niemitalo added a comment - After a force push, if you know the previous commit ID, is it possible to git fetch the commit object from GitHub? Git generally does not allow that; there is the uploadpack.allowAnySHA1InWant configuration variable but I don't know how GitHub configures that, or whether GitHub uses an entirely different implementation. Bitbucket Server 5.5.0 sets uploadpack.allowReachableSHA1InWant ( BSERV-8268 ) and it helps Jenkins after a regular push but not after a force push. I suppose not using lightweight checkout on the Jenkins controller could let it preserve the previous commit locally (pinned by a reflog) and then compare to that. But it would need more disk space. Perhaps the best that can be done is display a message that includes the commit IDs and says why Jenkins was unable to compare the commits, and lets a new method of RepositoryBrowser ( javadoc ) link to a comparison that might still work. But that would still not help currentBuild.changeSets at all.

          I was not aware of the uploadpack.allowAnySHA1InWant feature and dont know yet how GitHub reacts with that. Actually the push notification simply provides a link to the UI that will display smth useful. I dont think it resembles the forced changes but the changes that are in the forced commit (I did a amend in this case so it showed the whole amended commit and not only the last forced changes).

          I also think that relying on the local history is rather useless. Nowadays most people will use dynamic agents that will lose all history with each build (at least we do with k8s hosted agents).

          For the currentBuild.changeSets - would some kind of dummy ChangeSet representing only the message be possible or does that break any expectations?

          And what's your opinion about going back for more than one build in the current jobs history?

          Daniel Geißler added a comment - I was not aware of the uploadpack.allowAnySHA1InWant  feature and dont know yet how GitHub reacts with that. Actually the push notification simply provides a link to the UI that will display smth useful. I dont think it resembles the forced changes but the changes that are in the forced commit (I did a amend in this case so it showed the whole amended commit and not only the last forced changes). I also think that relying on the local history is rather useless. Nowadays most people will use dynamic agents that will lose all history with each build (at least we do with k8s hosted agents). For the currentBuild.changeSets - would some kind of dummy ChangeSet representing only the message be possible or does that break any expectations? And what's your opinion about going back for more than one build in the current jobs history?

          Kalle Niemitalo added a comment - - edited

          Bitbucket Server 6.7.0 and later versions set uploadpack.allowAnySHA1InWant = true, which allows any commit to be fetched whether or not it is reachable. Atlassian told me this is for consistency with Git protocol v2, which always allows access to unreachable commits.

          If the Git plugin no longer has the commit of the previous build, it could try to git fetch that from the server in order to compute the change log. That would succeed most of the time with Bitbucket Server, and perhaps also with GitHub.

          I suppose the git fetch might still fail if the server has already garbage-collected the unreachable commit. It might also need a lot of disk space, if someone accidentally checked in a large file and that change was erased from history. On the other hand, if the previous build in Jenkins already succeeded in fetching and checking out the same commit, then the disk cost shouldn't be too horrible.

          Kalle Niemitalo added a comment - - edited Bitbucket Server 6.7.0 and later versions set uploadpack.allowAnySHA1InWant = true, which allows any commit to be fetched whether or not it is reachable. Atlassian told me this is for consistency with Git protocol v2, which always allows access to unreachable commits. If the Git plugin no longer has the commit of the previous build, it could try to git fetch that from the server in order to compute the change log. That would succeed most of the time with Bitbucket Server, and perhaps also with GitHub. I suppose the git fetch might still fail if the server has already garbage-collected the unreachable commit. It might also need a lot of disk space, if someone accidentally checked in a large file and that change was erased from history. On the other hand, if the previous build in Jenkins already succeeded in fetching and checking out the same commit, then the disk cost shouldn't be too horrible.

            Unassigned Unassigned
            dageissl Daniel Geißler
            Votes:
            1 Vote for this issue
            Watchers:
            4 Start watching this issue

              Created:
              Updated: