semantic-release / semantic-release

:package::rocket: Fully automated version management and package publishing
https://semantic-release.gitbook.io
MIT License
20.98k stars 1.69k forks source link

Incorrect (duplicate) prerelease version determined even when git notes exist #1708

Open Xunnamius opened 4 years ago

Xunnamius commented 4 years ago

I'm using GitHub Flow/Trunk Based Development thanks to semantic-release, and it's great! I'm going to move all my packages over to a semantic-release CI/CD trunk-based flow, but most of them already have manually published releases. So, to create a uniform repo configuration for my packages, I've been playing with a dummy GitHub Actions/semantic-release repo. I tagged and manually released some initial commits to main to simulate switching a project with preexisting tags and releases over to semantic-release. I made several more commits to main and several successful semantic releases. Next, I configured a prerelease branch canary (channel is also canary) off main and pushed a breaking change. semantic-release published the prerelease no problem: v3.0.0-canary.1.

Current behavior

However, I made some more changes, I pushed them, I expected release v3.0.0-canary.2, but instead I got an error:

Screenshot_20201204_222635

So... it's trying to re-publish the same prerelease version, v3.0.0-canary.1? A quick scan of the issues shows problems similar to mine, perhaps related. The troubleshooting docs also talk about force pushing and git notes.

Some facts about my issue:

  1. v3.0.0-canary.1 is in the git history of canary:

Screenshot_20201204_220619

  1. The proper git notes exist for all releases made by semantic release; there were no force pushes or anything like that. 3d91d82 is the note with {"channels":[null]} while 6cae4d5 is the note with {"channels":['canary']}:

Screenshot_20201204_220517

Screenshot_20201204_220802

  1. It's as if semantic-release doesn't "see" the v3.0.0-canary.1 release it just made, instead finding the old v2.3.4 release:

Screenshot_20201204_222613

And the 2d branch graph:

Screenshot_20201204_220824

This same problem happens with GitHub Actions runners as well as my local machine when running npx semantic-release -d. I ended up spelunking down into the code similar to #1685 to see what was going on. It turns out that the problem may be git itself, or (perhaps more likely) the assumptions about how git notes show handles references. Specifically this line. It seems commands like git notes --ref semantic-release show v2.1.0 do not work (or at least don't seem to). However, when using the commit hash instead of the tag name, i.e. git notes --ref semantic-release show 1eeba8e, everything works:

Screenshot_20201204_220502

After forking semantic-release, adding a line that replaces the tag ref with the hash of the commit its pointing to, and replacing semantic-release with Xunnamius/semantic-release in my package.json, pushes to my prerelease branch are now released properly.

Expected behavior

v3.0.0-canary.1 is found as the previous release tag with branch canary and channel canary. The next release should then be v3.0.0-canary.2.

Environment

travi commented 4 years ago

Is it possible that some of the changes you made between versions rewrote history. It is important for the commit of the previous release to be found in the history of the branch.

I haven't dug into the details of your proposed change, but I use pre-releases regularly without seeing the issues you are describing. I've also manually added notes following the documented steps in a few cases where semantic-release was added to an existing project and had success in those cases as well.

Is it possible that something is simply not in the state that is expected in your scenario?

Xunnamius commented 4 years ago

@travi Thanks for the response!

In the case of rewriting history, all permutations of git notes --ref semantic-release show commit-hash-tag-is-pointing-at should fail too if the commit was rewritten (should say something like "no notes object found"), no? I'm no git expert yet :) But git does find the notes... if you don't use the tag name as the ref but the commit hash the tag is pointing to. Why are just tag refs causing a problem here? I even tried switching shells (and switching machines, and switching git versions) and it still happens.

Moreover, git tag --merged canary shows all tags visible in the history of canary, and that includes v3.0.0-canary.1:

Screenshot_20201204_220619

And looking at the git log and git notes --ref semantic-release list outputs above and comparing the hashes, all of the notes are added to all the correct commit hashes.

Also, running git log --notes='*' shows commits and their notes side-by-side, and all release commits in repository history are printing their proper notes as expected, e.g.:

Screenshot_20201205_132627

So all the notes exist, they have the proper contents, they point to commits that exist, those commits are properly tagged, those tags are all visible from the tip of canary...

My proposed change is the addition of line 309 in lib/git.js which runs git log -1 --pretty=format:%H ref (ref is a tag like v2.1.0) and passes that result to git notes instead of passing ref directly. Strange that replacing a tag ref like v3.0.0-canary.1 or v2.1.0 with the commits they point to seems to fix it.

Anything's possible, so I'll whip up a MRE and see if I can replicate the behavior :)

Xunnamius commented 3 years ago

Update:

So I attempted to create a MRE here on my local machine, but I couldn't initially reproduce the issue. Even when deploying directly from my development environment, it still worked. Issuing the raw command that failed in the other repo succeeded in the MRE repo:

$ git notes --ref semantic-release show v2.0.0-canary.1 | cat
{"channels":["canary"]}

Huh. In the old repo, I kept getting errors like:

git notes --ref=semantic-release show v3.0.0-canary.1
error: no note found for object 4dbe4cb6c81c408cf34869aa295c52d91eb37940

Which was very strange to me because git log show 4dbe4cb6c81c408cf34869aa295c52d91eb37940 just output commit d13f38f6aedd05840e438f62ff2092218a806984 (tag: v3.0.0-canary.1). Turns out that 4dbe4 object is the actual v3.0.0-canary.1 annotated tag object (and I learned something new about git ๐Ÿ˜). semantic-release is using annotated tags in the first repo and lightweight tags in the new MRE repo. So a command like git notes --ref=semantic-release show v3.0.0-canary.1, where v3.0.0-canary.1 is an annotated tag, tries to get notes added to the tag object itself (4dbe4) and not the release commit that semantic-release actually added the notes to (and that tag 4dbe4 is pointing to), which is why using the actual commit hash instead of the tag name fixed it in #1709 ๐Ÿฅณ

Unlike my local environment where I set up the MRE, I have my GitHub Actions pipeline setup so that all tags must be signed via tag.gpgsign=true, which apparently forces tags to be annotated rather than lightweight. Early on, when I was first playing with semantic-release, I was getting strange behavior where semantic-release would fail after trying to open the default editor in the middle of the publication pipeline. I "fixed" it by adding EDITOR='echo semantic-release-bot > $1' to the CI environment variables and thought nothing more of it, but now it makes sense why that was happening.

To evidence the two repos using different tags types, I ran git for-each-ref refs/tags on both:

Original repo (workflow-playground):

Screenshot_20201205_184410

Tag v1.0.15 was created on my local machine (to simulate there already being releases in the repo), which is why it's lightweight. Tag v2.0.0 was the first tag created by semantic-release and before I configured default tag signing, which is why it's also lightweight. After v2.0.0, all tags are managed by semantic-release and signed (and annotated).

Attempted MRE:

Screenshot_20201205_184633

All tags in this repo are lightweight (they point directly to commits).

And for the cherry on top: forcing semantic-release to sign release v2.0.0-canary.2 (complete with EDITOR patch) and then pushing another release-triggering commit makes semantic-release attempt to re-release v2.0.0-canary.2 instead of v2.0.0-canary.3, which is the buggy behavior described in the OP:

Screenshot_20201205_191142

Screenshot_20201205_193605

If I'm understanding this right, as a consequence of this bug, it appears all notes are functionally ignored by semantic-release for repositories using annotated tags. The only reason semantic-release appears to work at all with annotated tags is because of this line where note-related errors cause {"channels":[null]} (default release branch) to be returned by default.

I also saw #1192 which makes me think semantic-release should already be creating annotated tags by default (but I could easily be wrong, as I've stumbled upon #1266). Could this be a regression? Or is the existence of tag signing/annotated tags not supported by semantic-release? If they are supported, a more holistic fix might be to move the notes off the commits and onto annotated tags, which semantic-release would create instead by default (making my EDITOR hack unnecessary), but that sounds like a massively breaking change. Instead, #1709, plus providing a message when execa-ing the git tag command if semantic-release detects its about to create an annotated tag (or just populate the EDITOR environment variable in via execa options) would be a similarly holistic fix but without it being a breaking change. If annotated tags aren't supported, #1709 + the above would allow them to be supported out of the box, I think.

Arguments for supporting annotated tags too, from git-scm and stackoverflow:

Annotated tags are meant for release while lightweight tags are meant for private or temporary object labels. For this reason, some git commands for naming objects (like git describe) will ignore lightweight tags by default.

travi commented 3 years ago

i'll look deeper into your details later, but would like to at least point out that annotated tag support was reverted, as mentioned in https://github.com/semantic-release/semantic-release/issues/1262.

thanks a lot for investigating so thoroughly.

Xunnamius commented 3 years ago

As a final addition to the above, I implemented the EDITOR behavior described here:

1709, plus providing a message when execa-ing the git tag command if semantic-release detects its about to create an annotated tag (or just populate the EDITOR environment variable in via execa options) would be a similarly holistic fix but without it being a breaking change.

So now my semantic-release fork supports my repos of signed tags no hassles. Proof of concept is #1710.

Thanks for this awesome package!

gr2m commented 3 years ago

Just want to second the appreciation for your thorough investigation here Bernard ๐Ÿ‘๐Ÿผ

KunalBurangi commented 2 years ago

same is happening with me too any solutions on this ?

Xunnamius commented 2 years ago

I have a fork that rebases my PR fix onto semantic-release master every now and then. Once I get around to addressing gr2m's concerns (it keeps falling off my todo list sadly), I believe the maintainers might give my PR a second look and I can do away with the fork.

jrolfs commented 2 years ago

Is rebasing prerelease branches essentially not supported? I've now dug into how the notes stuff worked to be able to salvage prerelease branches that have been rebased and end up in this broken state... (I missed the troubleshooting entry) but I'd love to be able to rebase the branches without having to worry about this.

@Xunnamius does your change address this? I guess I'm a little fuzzy on exactly what it's fixing โ€” I guess it ensures that the notes point to commits orphaned from rebasing so that they still show up when calculating the next prerelease version?

shiipou commented 2 years ago

I've got this issue on some of my repos.

How to get it not work :

Branch architecture :

Step to reproduce :


Push something from a dev branch into beta :

That will create a release v1.0.0-beta.1 so everything is Ok

~> git fetch origin +refs/notes/semantic-release:refs/notes/semantic-release
~> git notes --ref semantic-release show v1.0.0-beta.1
{"channels":["beta"]}

Merge beta into rc

That will create a release v1.0.0-rc.1 on the exact same commit of the beta release. So the notes will be shared.

~> git fetch origin +refs/notes/semantic-release:refs/notes/semantic-release
~> git notes --ref semantic-release show v1.0.0-rc.1
{"channels":["rc"]}

Push something new on beta.

That will try to make a v1.0.0-beta.1 tag instead of v1.0.0-beta.2 and CI will fail because of tag already exist.

~> git fetch origin +refs/notes/semantic-release:refs/notes/semantic-release
~> git notes --ref semantic-release show v1.0.0-beta.1
{"channels":["rc"]}

I think the tag note must be :

{"channels":["rc", "beta"]}

But even in editing the note like this didn't unlock the CI and semantic-release still fail because of tag already exist.

~> # that didn't work
~> git fetch origin +refs/notes/semantic-release:refs/notes/semantic-release
~> git notes --ref semantic-release show v1.0.0-beta.1
{"channels":["rc", "beta"]}
~> git notes --ref semantic-release add -f -m '{"channels":["rc", "beta"]}' v1.0.0-beta.1
~> git push origin refs/notes/semantic-release

If someone got a workaround to unlock CI when we got this case, it can help me a lot.

Also, I've some trouble to understand why semantic-release use notes. I think it can parse tags to get the info contained in notes. Maybe it's just to know the order of the release on the same tags ?

Can someone explain to me ?

shiipou commented 2 years ago

As a workaround, I create a script that replace Semantic-Release I use it in a GitHub action

It probably need some more work. I also add some info in the README.md

Xunnamius commented 2 years ago

@jrolfs If you're experiencing the same problem I was, then at its root is that all notes are functionally ignored by semantic-release for repositories using annotated tags as explained in my very first posts above. My first PR makes semantic-release pay attention to annotated tags by using the commit hash instead of the tag ref when semantic-release executes the git notes command:

Turns out that 4dbe4 object is the actual v3.0.0-canary.1 annotated tag object ... semantic-release is using annotated tags in the first repo and lightweight tags in the new MRE repo. So a command like git notes --ref=semantic-release show v3.0.0-canary.1, where v3.0.0-canary.1 is an annotated tag, tries to get notes added to the tag object itself (4dbe4) and not the release commit that semantic-release actually added the notes to (and that tag 4dbe4 is pointing to), which is why using the actual commit hash instead of the tag name fixed it in #1709

My second PR stops semantic-release from asking for an additional message when creating an annotated tag to mark the new release, which screwed up my CD pipelines.

I merge my pre-release branches using git merge and/or GitHub PRs rather than rebase since it's easier to keep the history straight. I'm not sure semantic-release supports rebasing since it effectively rewrites history and destroys the association between notes and commits.

@shiipou This issue will happen on repos that are using annotated tags (e.g. if you use gpg to sign your commits/tags). This is because, at least at the time I opened this issue, notes are ignored by semantic-release for repositories using annotated tags. semantic-release uses notes to enable complex configurations like when releases on multiple channels are associated with a single commit.

jrolfs commented 2 years ago

@Xunnamius ah, no this was a red herring/I was totally mistaken. My issue is with Git tags as of course rebasing clobbers the previous tags. I guess I had just gotten used to being able to rebase prerelease branches with Changesets since it uses files instead of tags.

Pls to ignore โ€” ๐Ÿ˜ฌ โ€” sorry y'all

jonas-martinez commented 1 year ago

Hello, any update ?

Xunnamius commented 3 weeks ago

Hello, any update ?

https://github.com/semantic-release/semantic-release/pull/1709#issuecomment-2461410971