Skip to content

Commit

Permalink
Merge pull request #110 from Cray-HPE/CASMCMS-8885
Browse files Browse the repository at this point in the history
CASMCMS-8885: Fixed bug in semver version sorting algorithm
  • Loading branch information
mharding-hpe committed Jan 5, 2024
2 parents 8928a2f + 80f008e commit f8bc203
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 51 deletions.
11 changes: 9 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.9.8] - 2024-01-04

### Fixed
- CASMCMS-8885: Fixed bug in semver version sorting

## [1.9.7] - 2023-11-07

### Changed
- copy files to target only if files exist in /shared directory.
- CASMTRIAGE-6152: Copy files to target only if files exist in /shared directory.

## [1.9.6] - 2023-07-18

Expand Down Expand Up @@ -247,7 +252,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Initial implementation @rkleinman-hpe

[Unreleased]: https://github.com/Cray-HPE/cf-gitea-import/compare/v1.9.7...HEAD
[Unreleased]: https://github.com/Cray-HPE/cf-gitea-import/compare/v1.9.8...HEAD

[1.9.8]: https://github.com/Cray-HPE/cf-gitea-import/releases/tag/v1.9.8

[1.9.7]: https://github.com/Cray-HPE/cf-gitea-import/releases/tag/v1.9.7

Expand Down
142 changes: 93 additions & 49 deletions import.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#
# MIT License
#
# (C) Copyright 2020-2023 Hewlett Packard Enterprise Development LP
# (C) Copyright 2020-2024 Hewlett Packard Enterprise Development LP
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
Expand Down Expand Up @@ -128,66 +128,110 @@ def get_gitea_repository(repo_name, org, gitea_url, session):
return resp.json()


def find_base_branch(base_branch, git_repo, gitea_repo, product_version, branch_prefix): # noqa: E501
def highest_previous_branch(git_repo, branch_prefix, product_semver):
"""
Find a base branch based on the `product_version` assuming it is of the
supported format. This function is used when the calling script chooses
'semver_previous_if_exists' for the base branch.
Lists all remote branches in the repository.
Filters out any with names that do not match the specified prefix.
Filters out any whose version strings are not valid semver.
Filter out any whose semantic version is >= the product semver.
Sort the remaining list by semver, high to low, and returns the name of the
first branch (i.e. the one with the highest version < product version).
Returns None if none are found.
"""
if base_branch == '': # no branch specified, use gitea default branch
LOGGER.info("No base branch specified, using Gitea default branch")
return gitea_repo['default_branch']
elif base_branch != "semver_previous_if_exists":
return base_branch
else:
LOGGER.debug("Searching for a previous branch by semver version")
base_branch = None # zeroing out, find a base branch based on semver

# Strip out branches that do not contain a valid semver somewhere in them
semver_branch_matches = []
remote_branch_prefix = 'origin/' + branch_prefix
for ref in git_repo.git.branch('-r').split():
if ref.startswith(remote_branch_prefix):
semver_branch_matches.append((
ref, ref.lstrip(remote_branch_prefix)
))
LOGGER.debug("Branch %r matches target branch pattern", ref)

# Sort the branches by semver and find the one that is just before the
# current version
semver_branch_matches.sort(key=itemgetter(1))
for index, (branch_name, branch_semver) in enumerate(semver_branch_matches): # noqa: E501
# Skip branches that do not match expected prefix
if not ref.startswith(remote_branch_prefix):
LOGGER.debug(
"Branch %r does not match target branch pattern; skipping it",
ref
)
continue

# Strip the branch prefix to get the version string
branch_version_string = ref.lstrip(remote_branch_prefix)

# Parse it as semver
try:
compare = semver.compare(branch_semver, product_version)
branch_semver = semver.Version.parse(branch_version_string)
except ValueError:
LOGGER.warning(f"branch {branch_semver} is not a valid semver string", exc_info=True)
LOGGER.warning(
"Branch version %s is not a valid semver string; skipping it",
branch_version_string, exc_info=True
)
continue

# other version higher than product version (edge case)
if compare >= 0:
name, semver_match = semver_branch_matches[index-1]
LOGGER.debug("Found branch by semantic versioning: %s", name)
base_branch = branch_prefix + semver_match
break
# product version higher than all others
elif compare < 0 and index + 1 == len(semver_branch_matches):
name, semver_match = semver_branch_matches[-1]
LOGGER.debug("Found branch by semantic versioning: %s", name)
base_branch = branch_prefix + semver_match
break
elif compare < 0: # this branch is lower than the current version
continue
elif compare == 0: # this branch already exists!
raise ValueError("Branch with the product version already exists")
else:
if base_branch is None:
LOGGER.info(
"No base branch found with a previous semantic version with "
"the branch format specified. Using the repository's default "
"branch"
# Skip it if its version is >= the product version
if branch_semver >= product_semver:
LOGGER.debug("Branch version %s is >= product version; skipping it",
branch_version_string
)
return gitea_repo['default_branch']
continue

# Append to our list
LOGGER.debug(
"Branch %r matches target branch prefix and has valid semantic "
"version < product version", ref
)
semver_branch_matches.append((
branch_semver, branch_version_string
))

# If the list is empty, return None
if not semver_branch_matches:
return None

# Sort the branches by semver, high to low, and return the name of the first
semver_branch_matches.sort(reverse=True)
_, branch_version_string = semver_branch_matches[0]
return branch_prefix + branch_version_string


def find_base_branch(base_branch, git_repo, gitea_repo, product_version, branch_prefix): # noqa: E501
"""
Find a base branch based on the `product_version` assuming it is of the
supported format. This function is used when the calling script chooses
'semver_previous_if_exists' for the base branch.
"""
if base_branch == '': # no branch specified, use gitea default branch
LOGGER.info("No base branch specified, using Gitea default branch: %s", gitea_repo['default_branch'])
return gitea_repo['default_branch']
if base_branch != "semver_previous_if_exists":
return base_branch

LOGGER.debug("Searching for a previous branch by semver version")

try:
product_semver = semver.Version.parse(product_version)
except ValueError:
LOGGER.warning("Product version %s is not a valid semver string; "
"Using the repository's default branch: %s",
product_version, gitea_repo['default_branch'],
exc_info=True
)
return gitea_repo['default_branch']

# First the name of the branch which matches our expected naming format and
# has the highest valid semver version that is less than the product
# version. Will be None if none are found.
base_branch = highest_previous_branch(git_repo, branch_prefix,
product_semver)

# If this is None, it means that no previous version was found
if base_branch is None:
LOGGER.info(
"No base branch found with a previous semantic version with "
"the branch format specified. Using the repository's default "
"branch: %s", gitea_repo['default_branch']
)
return gitea_repo['default_branch']

LOGGER.info(
"Using base branch with highest previous semantic version: %s",
base_branch
)
return base_branch


Expand Down

0 comments on commit f8bc203

Please sign in to comment.