Skip to content

Unexpected failure with version command #1046

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

Closed
clarkecheckr opened this issue Oct 1, 2024 · 32 comments
Closed

Unexpected failure with version command #1046

clarkecheckr opened this issue Oct 1, 2024 · 32 comments

Comments

@clarkecheckr
Copy link

Question

I need some help troubleshooting an issue I'm having. Versioning is working great up until the point where psr tries to create the version tag.

  • The merge request runs the pipeline successfully but does not create a version - good
  • The merge to main runs the pipeline where the only changes are the commits - good
  • The pipeline is run for the version creation and pyproject.toml/GHANGELOG.md are modified - good
  • Version tag is created and the pipeline run and fails. - bad

image

The failure just says ERROR: Job failed: exit code 1 with no other clue as to what's wrong.

image

How do I further troubleshoot this?

Configuration

Semantic Release Configuration
[tool.semantic_release]
version_variables = ["pyproject.toml:version"]

[tool.semantic_release.branches.main]
match = "(main|master)"
prerelease = false

[tool.semantic_release.commit_author]
env = "GIT_COMMIT_AUTHOR"
default = "semantic-release <semantic-release>"

[tool.semantic_release.remote]
type = "gitlab"
@clarkecheckr clarkecheckr added question triage waiting for initial maintainer review labels Oct 1, 2024
@codejedi365
Copy link
Contributor

codejedi365 commented Oct 1, 2024

Hi @clarkecheckr, the primary issue is actually right there on line 142. You are in a detached head state. Because of this, we cannot determine which release branch configuration to use and therefore we give up (not ideal, I know but the best I have at the moment).

GitLab uses a detached head state in the way they checkout a small git tree to make it faster to execute the pipelines as most do not need the entire git tree. PSR does need the full git tree as well as being checked out to a specific branch.

My recommendation is you should run the following:

# make sure you have the full accurate history
git fetch --unshallow --prune-tags --prune --all --force

# make sure the branch Head is at the commit that was tested/started the workflow
git checkout -B $CI_COMMIT_BRANCH HEAD

semantic-release -vv version

@clarkecheckr
Copy link
Author

Thanks for the prompt response. I didn't think the detached head state was the issue as I get a similar message in my branch yet there's no error.

image

I'll try these changes and report back.

@clarkecheckr
Copy link
Author

I'm working on a repo that is just for getting this working in Gitlab and have an absolute minimal configuration for psr. I'd like to contribute my working .gitlab-ci.yml and pyproject.toml configuration to be used as an example in the docs as they would have helped me out immensely. How would I go about that?

@codejedi365
Copy link
Contributor

I'm not sure how it isn't a problem in your branch environment as a detached head state is an immediate abort for semantic release

@clarkecheckr
Copy link
Author

It's not a detached head, it's just the same message that the branch isn't in any release groups.

@codejedi365
Copy link
Contributor

codejedi365 commented Oct 1, 2024

Well that message is at the exact same part of the code, it is also an immediate abort. It succeeds though because it is not in --strict mode.

@codejedi365
Copy link
Contributor

codejedi365 commented Oct 1, 2024

I'm working on a repo that is just for getting this working in Gitlab and have an absolute minimal configuration for psr. I'd like to contribute my working .gitlab-ci.yml and pyproject.toml configuration to be used as an example in the docs as they would have helped me out immensely. How would I go about that?

Generally for a GitHub contribution, you would fork the repository, create a branch on your fork, make your changes and then open a PR from your fork's branch to this repo's master branch. The docs are in the docs folder. Review the Contributors.rst as well for info about how to build the docs and general info.

However, I have a GitLab instruction guide in the works (70% complete) but I haven't published it yet. Ultimately I will have an examples section in that area like I do for github-actions (docs/automatic-releases/github-actions.rst).

If you want to just put your examples in a gitlab.rst in that folder and explain the rationale, then I will fold yours into my format.

@clarkecheckr
Copy link
Author

BTW, git checkout -B $CI_COMMIT_BRANCH fails with the error fatal: 'HEAD' is not a valid branch name. I get the idea and I'm looking up a way to do it.

@clarkecheckr
Copy link
Author

Also, it only fails for the tag build.

image

image

@clarkecheckr
Copy link
Author

The reason this is happening on the tag creation is that the value of $CI_COMMIT_BRANCH is "", so the command reads git checkout -B HEAD and that's not valid. I tried to add a rule to the .gitlab-ci.yml, but then my pipeline didn't run at all, so I have a little work to do to figure this out.

@clarkecheckr
Copy link
Author

clarkecheckr commented Oct 1, 2024

Adding a rule to skip the pipeline if $CI_COMMIT_BRANCH is null kinda worked, but now I'm not creating a tag. I thought maybe it would create the tag, just not run the pipeline but that's not the case.

image

stages:
  - release

variables:
  GITLAB_TOKEN: $gitlab_pat

release:
    stage: release
    image: python:latest
    before_script:
      - pip install python-semantic-release
    script:
      - git fetch --unshallow --prune-tags --prune --all --force
      - git checkout -B $CI_COMMIT_BRANCH HEAD
      - semantic-release -v version
    rules:
      - if: $CI_COMMIT_BRANCH != null

@clarkecheckr
Copy link
Author

I was able to get this working with the following config. The reason it was failing when using git checkout -B $CI_COMMIT_BRANCH HEAD was because when we get to the tag creation step the value of $CI_COMMIT_BRANCH is null. The following config checks to see if it's null, if it is then we set the variable to $CI_COMMIT_REF_NAME otherwise we use $CI_COMMIT_BRANCH. This seems to work but I will be testing it more to confirm. Can you see anything wrong with this method?

stages:
  - release

variables:
  GITLAB_TOKEN: $gitlab_pat

release:
    stage: release
    image: python:latest
    before_script:
      - pip install python-semantic-release
    script:
      - git fetch --unshallow --prune-tags --prune --all --force
      - echo "CI_COMMIT_REF_NAME" $CI_COMMIT_REF_NAME
      - git checkout -B $SOURCE_BRANCH HEAD
      - semantic-release -v version
    rules:
      # if the commit branch is not null, use the commit branch name
      - if: $CI_COMMIT_BRANCH != null
        variables:
          SOURCE_BRANCH: $CI_COMMIT_BRANCH
      # if the commit branch is null, use the commit ref name
      # this is to account for the fact that the version tag has a null value for $CI_COMMIT_BRANCH
      - if: $CI_COMMIT_BRANCH == null
        variables:
          SOURCE_BRANCH: $CI_COMMIT_REF_NAME

@codejedi365
Copy link
Contributor

codejedi365 commented Oct 2, 2024

@clarkecheckr, sorry I was unavailable while you worked the issue.

There isn't anything particularly wrong with your approach but I can't say it would be my way I would solve the problem. I will try to provide some conceptual rationale behind my approach first. In my solution, I utilize workflow:rules to restrict the types of pipelines that occur. I generally recommend to never run semantic-release on a tag triggered pipeline. This defeats the purpose of using semantic-release. The idea behind PSR and conventional commit strategies is to eliminate the emotional attachment to release versions, which crudely means no human makes a tag anymore--PSR handles that based on what you documented that changed. Secondly, I think its a bit of a waste of resources to spin up another pipeline which has no comprehension of what was accomplished prior to the tagged release and rather just set a deploy stage (set of deploy jobs, many times there is more than one deployment target) that is dependent on release job outcomes.

Also note, that I generally use GitHub Flow branching strategy coupled with a Squash commit merge strategy and in my projects, I rarely create pre-releases (more of a result of my environment, but small amount of preference). Lastly, I haven't done too much of multi-version release support (ie v2 in maintenance, v3 is stable, v4 is unstable) so my example below doesn't handle this.

So in my workflows, I run when there is a (1) push to the default branch, (2) a merge request event, (3) a pipeline source that is manual or deliberately triggered. Regardless of the workflow trigger, I run a check stage that simultaneously runs a build, code linting, commit-linting, unit testing. Then I move into a test stage which focuses on runtime (system, integration, etc) testing. If this is a MR/PR, then the pipeline ends. If it is the push to the default branch (ie my release branch), then release job & deploy jobs are enabled depending on if I either set it to manually be triggered or allow auto-delivery/deployment. The release job is using semantic-release to determine if a release is required and then the deployment jobs depend on the result of that job.

Python Semantic Release Configuration
[tool.semantic_release.branches.release]
match = "^(main|master)$"
prerelease = false

[tool.semantic_release.branches.other]
# Used for enabling build jobs during PRs where a pre-release version is created on any
# branch name but not published (ie. internal use from workflow artifacts)
match = ".*"
prerelease_token = "dev"
prerelease = true

[tool.semantic_release.remote]
type = "gitlab"
ignore_token_for_push = true
token = { 'env': "GITLAB_TOKEN" }
GitLab Workflow
workflow:
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
    - if: '$CI_PIPELINE_SOURCE =~ "/api|web|schedule|web|trigger|pipeline/"'
    - when: never


stages:
  - .pre
  - check
  - test
  - release
  - deploy


commit-lint:
  stage: check


build:
  stage: check
  needs: 
    - job: commit-lint
  script: 
    - if git rev-parse --is-shallow-repository | grep -q "true"; then
         git fetch --unshallow --prune-tags --prune --all --force;
      fi
    - git checkout -B "$CI_COMMIT_BRANCH" HEAD
    - semantic-release version --no-commit --no-tag 
  artifacts:
    paths:
      - dist/*.whl
      - dist/*.tar.gz
    expire_in: 1 week


test:
  stage: test


release:
  stage: release
  rules:
    - if: '$CI_DEFAULT_BRANCH == $CI_COMMIT_BRANCH'
      when: manual       # <--- leave this out if you want auto-deployment
  allow_failure: false      # must have when it is set to manual otherwise GitLab will move on to deploy
  environment:
    name: repository       # custom environment that holds the PAT (and only available to this job)
  script:
    - if git rev-parse --is-shallow-repository | grep -q "true"; then
         git fetch --unshallow --prune-tags --prune --all --force;
      fi
    - git checkout -B "$CI_COMMIT_BRANCH" HEAD
    
    # set the remote url to the PAT (write repository access)
    - git remote set-url origin "https://__token__:$GITLAB_TOKEN@${CI_REPOSITORY_URL#*@}"
    
    # This provides an exit code of whether or not to release, on exitcode 0 it runs the release,
    # on non-zero it writes a status to a file that is passed on to the next job as an environment variable
    - if semantic-release -vv --strict version --print; then
         semantic-release version --skip-build;
      else
         printf '%s\n' "PSR_RELEASED=false" >> deploy.env;
      fi
  artifacts:
    reports:
      dotenv: deploy.env


deploy-gl-pypi:
  stage: deploy
  needs:
    - job: release
      artifacts: true
    - job: build
      artifacts: true
  rules:
    - if: '$CI_DEFAULT_BRANCH == $CI_COMMIT_BRANCH'
  variables:
    TWINE_USERNAME: gitlab-ci-token
    TWINE_PASSWORD: $CI_JOB_TOKEN
  script:
    - if [ "PSR_RELEASED" = "true" ]; then
        twine upload --repository-url "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/pypi" dist/*;
      fi

This pipeline handles the different version situations without the pipeline jobs failing (on a no release) because technically they did work as intended and there is no need for human intervention or review. OR you can just have the deploy fail when the version has not been bumped. It's a matter of preference.

One additional nuance that I do is actually run semantic-release in the build job but in a non-persistent way because I can gain the nicety to only have to have 1 place where the build command is defined. Secondly when building the artifact (versions stamped and everything) at the beginning of the pipeline I can now actually test the exact (hashed) item that is going to be released. Generally, using semantic-release to build at the end actually builds a new artifact (hashes are different) that wasn't tested--many people wave this risk. Since I have previously built and tested the built distribution, I just import the artifact from the build job during deployment while the release job skips the build_command with --skip-build

@codejedi365 codejedi365 added awaiting-reply Waiting for response and removed triage waiting for initial maintainer review labels Oct 2, 2024
@clarkecheckr
Copy link
Author

No worries about the delay, and thank you for the very detailed answer. This gives me a lot to work with and I'll be digesting it and rolling some of these concepts into my project as I implement this. Initially I was just trying to get things to work without errors, but now that that's happening I will be working to make it what I need it to be given my environment.

@codejedi365
Copy link
Contributor

Good to hear, and I'm glad it provides some value to you. Let me know if you have further questions as I could have made a typo (and I left out some job implementations for simplicity) as I was transplanting my answer.

@clarkecheckr
Copy link
Author

I thought I saw this in the documentation, but can't find it now. Is it possible to force semver comment syntax? Maybe fail the pipeline if the syntax is wrong or comment is missing? As I understand it if it's not the right syntax, there just isn't a version created.

@codejedi365
Copy link
Contributor

codejedi365 commented Oct 2, 2024

I thought I saw this in the documentation, but can't find it now. Is it possible to force semver comment syntax? Maybe fail the pipeline if the syntax is wrong or comment is missing? As I understand it if it's not the right syntax, there just isn't a version created.

At this time there is nothing built into PSR to validate commit syntax. For those using the angular parser, my recommendation is to use already published commit linting tools that adhere to the same syntax standards. One such tool is commitizen and the use of the check subcommand. There is also a few GitHub actions that do it as well but may use the JS version of commitizen. Luckily the standard is set by conventional-commits.org which all the tools implement.

Just for clarification of definitions there isn't a semver syntax but it's a commit message convention. And currently PSR will throw a ParseError when a commit does not match the regex and that commit will not be considered in version determination. The commit will be passed to the changelog generation though with the type of unknown.

@codejedi365 codejedi365 changed the title Pipeline fails on version tag creation Unexpected failure with version command Oct 3, 2024
@daniel-albuschat
Copy link

Hi @codejedi365,

from what I understand, I think every GitLab CI is cloning repose with a detached head, so every user of python-semantic-release in GitLab CI would need to apply this workaround:

# make sure you have the full accurate history
git fetch --unshallow --prune-tags --prune --all --force

# make sure the branch Head is at the commit that was tested/started the workflow
git checkout -B $CI_COMMIT_BRANCH HEAD

semantic-release -vv version

If my understanding is correct, wouldn't it be great if python-semantic-release would automatically do this?

(Additionally: git fetch --unshallow bails out in my case with the error message fatal: --unshallow on a complete repository does not make sense. Maybe because the repo has a very small commit history.)

Greetings,

Daniel Albuschat

@daniel-albuschat
Copy link

(Additionally: git fetch --unshallow bails out in my case with the error message fatal: --unshallow on a complete repository does not make sense. Maybe because the repo has a very small commit history.)

I've replaced --unshallow with --depth=2147483647, and this seems to help.

@codejedi365
Copy link
Contributor

@daniel-albuschat, I haven't gotten around to that but I did have it as an action item one of these days.

There is a way to check if the repo is already unshallowed and should be inluded as a bash conditional before the execution but I can't remember what it is at the moment. Will check later.

@codejedi365
Copy link
Contributor

codejedi365 commented Oct 8, 2024

I use a bash conditional around the --unshallow command because of the failure that @daniel-albuschat mentioned above. Happens with small repositories under 50 commits (50 is the default fetch depth for GitLab) or if someone has already increased the fetch depth elsewhere.

if git rev-parse --is-shallow-repository | grep -q "true"; then
    git fetch --unshallow --prune-tags --prune --all --force
fi

@clarkecheckr, I recommend updating your workflow to handle this situation that I didn't include in my above example. (I did update the example above)

Copy link

This issue has not received a response in 14 days. If no response is received in 7 days, it will be closed. We look forward to hearing from you.

Copy link

This issue was closed because no response was received.

@github-actions github-actions bot closed this as not planned Won't fix, can't repro, duplicate, stale Oct 31, 2024
@cfxegbert
Copy link
Contributor

Sorry about commenting on a closed issue. There are mechanisms in GitLab to clone the whole repository instead of doing a shallow clone. You can do

release:
  stage: release
  variables:
    GIT_STRATEGY: clone
    GIT_DEPTH: "0"
...

instead of

release:
  stage: release
  script:
    - if git rev-parse --is-shallow-repository | grep -q "true"; then
         git fetch --unshallow --prune-tags --prune --all --force;
      fi
...

See:

@codejedi365
Copy link
Contributor

@cfxegbert, it's a good thing to note for others. Do you know which version of GitLab that was introduced? I feel like it was pretty recent.

Also can you validate that it actually prunes or clones all the tags if the build directory is cached? That is a problem I had with GitLab was that deleted tags do not get cleaned up from a repo without --prune-tags.

@cfxegbert
Copy link
Contributor

cfxegbert commented Jan 3, 2025

The GIT_ variables have been available for several years. I've been using it for over 3 years. The --prune-tags may be able to be set with GIT_FETCH_EXTRA_FLAGS but I'm not sure if the flags are applied to clones or only fetch. Would you need --prune-tag on a clone?

From the docs:
clone is the slowest option. It clones the repository from scratch for every job, ensuring that the local working copy is always pristine. If an existing worktree is found, it is removed before cloning.

So it will not use the cached worktree like fetch does

@cfxegbert
Copy link
Contributor

Looking at the source
https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/shells/abstract.go#L535-540
https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/shells/abstract.go#L629-639

to replicate your setup would be:

variables:
  GIT_STRATEGY: fetch
  GIT_DEPTH: "0"
  GIT_FETCH_EXTRA_FLAGS: --prune-tags --prune --all --force

With the clone strategy since it removes the directory I don't think you need to prune.

@codejedi365
Copy link
Contributor

The GIT_ variables have been available for several years. I've been using it for over 3 years.

I haven't tried to solve this problem in over 3 years so that's probably why it seems recent.

Would you need --prune-tag on a clone?

I doubt it because clone would fail if the directory exists.

And looks like you found another solution for my env, why thanks!

@cfxegbert
Copy link
Contributor

cfxegbert commented Jan 3, 2025

I haven't tried to solve this problem in over 3 years so that's probably why it seems recent.

FYI GIT_STRATEGY and GIT_DEPTH were added in v1.3.0 (2016). GIT_FETCH_EXTRA_FLAGS was added in v13.1 (2021).

@codejedi365
Copy link
Contributor

Well there's a million ways to solve everything and I chose git rather than finding it in GitLab Docs.

@hhaidrr
Copy link

hhaidrr commented Mar 6, 2025

Thank you all for your insights.

This issue thread along with the comments in #666 allowed me to setup my Gitlab pipeline in a short-ish amount of time.

The perfect addition for others now would be to add all this very helpful information as an official page in the docs automatic-releases.

@codejedi365
Copy link
Contributor

That is the premise of #666.

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

No branches or pull requests

5 participants