Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Last repository tag picked over first reachable when determining version #419

Open
jxlambda opened this issue Feb 1, 2024 · 3 comments
Open

Comments

@jxlambda
Copy link

jxlambda commented Feb 1, 2024

Hi,

I have the following situation:

I have a Git repository, where the master branch serves as the development head, with tags for release points, and maintenance branches for previous major releases that receive bugfixes and tags for updates.

.
.
C
| B'
| |
B / A'
|   |
A __/
|
o

Where A, B and C are releases (let's presume v1.0.0, v2.0.0 and v3.0.0 respectively), and A' and B' are maintenance commits for the respective releases (let's assume v1.0.1 and v2.0.1).

When I generate an SBOM document for the module, as it is at B', the version detection malfunctions, and determines the version of the module to be whatever the latest tag is (in the example, this is C, i.e. v3.0.0). I've verified that it is the version control system tags that affect this, as after removing all tags from the repository, the result was a pseudo-version.

Is this the Go tooling that's doing this, or the cyclonedx-gomod utility incorrectly determining the version of the module?

Apologies in advance if this isn't the cyclonedx-gomod utilities fault - my Go-fu isn't great, and I'm just managing this aspect of the subject project.

@nscuro
Copy link
Member

nscuro commented Feb 1, 2024

Hi, this is indeed custom logic on cdx-gomod's side. However, it will (should) only pick the tag when the current HEAD commit is tagged. So if the current HEAD is the same on both A and B, you would get the same version.

// GetVersionFromTag checks if the HEAD commit is annotated with a tag and if it is, returns that tag's name.
// If the HEAD commit is not tagged, a pseudo version will be generated and returned instead.
func GetVersionFromTag(logger zerolog.Logger, moduleDir string) (string, error) {
repo, err := git.PlainOpen(moduleDir)
if err != nil {
return "", err
}
headRef, err := repo.Head()
if err != nil {
return "", err
}
headCommit, err := repo.CommitObject(headRef.Hash())
if err != nil {
return "", err
}
latestTag, err := GetLatestTag(logger, repo, headCommit)
if err != nil {
if errors.Is(err, plumbing.ErrObjectNotFound) {
return module.PseudoVersion("v0", "", headCommit.Author.When, headCommit.Hash.String()[:12]), nil
}
return "", err
}
if latestTag.commit.Hash.String() == headCommit.Hash.String() {
return latestTag.name, nil
}
return module.PseudoVersion(
semver.Major(latestTag.name),
latestTag.name,
latestTag.commit.Author.When,
latestTag.commit.Hash.String()[:12],
), nil
}

Are you able to provide a bash script or GitHub repo something that resembles the situation you're describing? That would help a lot with reproducing this.

@jxlambda
Copy link
Author

jxlambda commented Feb 1, 2024

Hey, @nscuro!

Surely!

The repository in question is git.zabbix.com/scm/ap/plugin-support.

The master branch is the development head, and branches release/6.0 and release/6.4 are the maintenance branches.

There was an attempt at one point to start properly versioning these with tags, for publishing to the module index, but that got canceled, hence why the tags are in a bit of a disarray. If you check out the release/6.0 branch and make, it should generate an sbom.json file (the Makefile contains only rules for generating these).

If you look at the metadata.component and metadata.version fields

jq '.metadata.component | .["bom-ref"], .version' sbom.json

the version that is detected is v6.4.3-0.20230425081451-9e40ea6472c9 (6.4.3 which is the last tag that was issued, to commit 9c948d2bdd6, without a v prefix, and 9e40ea6472c9 is actually v6.4.2).

The version I would expect to be detected for that branch is 6.0.18-0..., as that is the last tag on that branch.

The cyclonedx-gomod utility behaves more like git log --tags, because, as man 1 git-log states:

--tags[=<pattern>]
           Pretend as if all the refs in refs/tags are listed on the command line as <commit>.
           If <pattern> is given, limit tags to ones matching given shell glob.
           If pattern lacks ?, *, or [, /* at the end is implied.

This would then make sense, as if you issue git log --tags, you get a commit log of tagged commits and commits leading up to the tags that were part of a merge.

I'm reading the version detection code in (GetLatestTag in internal/gomod/version.go mainly) to properly understood what is being derived.

@jxlambda
Copy link
Author

jxlambda commented Feb 22, 2024

Hey, @nscuro!

Sorry for the lack of activity; I was ill the last couple of weeks, so this didn't really move along anywhere during that time.

What I've found is the following:

In internal/gomod/version.go:GetVersionFromTag we start off by determining the commit the HEAD reference is currently pointing at. Using that, we then attempt to get the latest tag, relative to that commit object, via GetLatestTag.

In internal/gomod/version.go:GetLatestTag we start off by getting a list of all the tags in the repository, with the list being in order from oldest to latest tag (probably just an implementation detail).

We then iterate over the list, skipping those tags that do not conform to the SemVer specification. For the tags that do, we then determine whether it occurs before or at the commit pointed to by HEAD. This is where the issue lays.

We determine whether the the tagged commit object occurs before or at the HEAD commit by whether it is older than the commit at HEAD based on HEADs commit and author date. The issue here, as in my case, is that the commit on the maintenance branch is chronologically newer than the latest tag in the repository, making this test succeed.

The additional issue here is that we process a list of all tags, regardless of whether they are reachable from the HEAD commit, and only rely on the date.

I briefly looked at the go-git module documentation, and didn't see a way of retrieving tags only reachable from a specified commit. The LogOptions type has a From field which, when specified, does do this, but this isn't exactly what we want.

The only option I see here, with what is available to us, is requesting a full log of the repository, up to the commit object HEAD is pointing at, and then collecting references to the tagged commit objects, discarding those that can't be reached from the HEAD commit (go-git doesn't provide a method to determine this). This would be sub-optimal, especially in the case of repositories with a very large commit history.

That seems like quite a hack to me, just to get an appropriate tag list.

Maybe this should be filed as an issue/feature request with the go-git project people? This would require change in cyclonedx-gomod itself regardless of the path you take.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants