'Detached head in gitlab ci pipeline - how to push correctly

I got some problems with a detached head for my repository in a CI pipeline. In the build stage of the pipeline I am running a script, which changes a specific file. After changing this file get pushed to the repository.

before_script:
  - git config --global user.name "Bot"
  - git config --global user.email "[email protected]"
  - git status
script:
  - npx ts-node ./generate.ts
  - git push "https://git:${GIT_PUSH_TOKEN}@${CI_REPOSITORY_URL#*@}" "HEAD:main"
  - git status

Running the script give me the output

Fetching changes with git depth set to 50...
Initialized empty Git repository in /builds/fKSu5-y_/0/project/.git/
Created fresh repository.
Checking out 9b4a88be as main...

$ git status
HEAD detached at 9b4a88be

$ git push "https://git:${GIT_PUSH_TOKEN}@${CI_REPOSITORY_URL#*@}" "HEAD:main"
To https://gitlab.domain.com/project.git
9b4a88b..98be83e  HEAD -> main

$ git status
HEAD detached from 9b4a88b

I do not understand why the first git status in the before_script already gives me a detached head.

I think the pipeline creates a detached repository by initial fetching. So the question is how to handle my push in a correct way. In my release stage I'm running semantic-release which fails because of detached head if I do the previous push. If I disable the push, semantic release is working as expected. But of course I need the push. I do not see, what I am doing wrong.


Update

Adding git checkout main in the before_script give me for the first git status the expected result. But after the push command I still have the detached head - which I do not understand.

$ git checkout main
Branch 'main' set up to track remote branch 'main' from 'origin'.
Switched to a new branch 'main'

$ git status
On branch main
Your branch is up to date with 'origin/main'.

$ git push "https://git:${GIT_PUSH_TOKEN}@${CI_REPOSITORY_URL#*@}" "HEAD:main"
To https://gitlab.domain.com/project.git
  336b065..8299e43  HEAD -> main

$ git status
On branch main
Your branch is ahead of 'origin/main' by 1 commit.
  (use "git push" to publish your local commits)

Although everything the push is working on the release state semantic-release is still not working. I do get: The local branch main is behind the remote one, therefore a new version won't be published.



Solution 1:[1]

I do not understand why the first git status in the before_script already gives me a detached head.

I think the pipeline creates a detached repository by initial fetching.

You are right, GitLab CI does checkout a specific commit as opposed to a certain branch. You could however add git checkout "$CI_COMMIT_REF_NAME" to your before_script:

before_script:
  - git config --global user.name "Bot"
  - git config --global user.email "[email protected]"
  - git checkout "$CI_COMMIT_REF_NAME"
  - git status

Solution 2:[2]

I spend a lot of time to try to resolve the same problem and finally I found that there are git strategy: https://docs.gitlab.com/ee/ci/runners/configure_runners.html#git-strategy

When there is set fetch then it re-uses the local working copy (and falls back to clone if it doesn’t exist). So this is the reason why it worked me for the first time and then it stopped working and my checkouted branch was behind and number of commits ahead increased with another commit.

Then the solution is pretty easy. You just need to define the GIT_STRATEGY variable to clone.

variables:
  GIT_STRATEGY: clone

And my working publish job example:

publish:
  stage: publish
  rules:
    - if: $CI_COMMIT_BRANCH == "master"
      when: manual
  variables:
    GIT_STRATEGY: clone
  script:
    - git checkout -b $CI_COMMIT_BRANCH
    - echo "define what you want to change"
    - 'git commit -am "your commit message" || echo "No changes to commit"'
    - git push --set-upstream origin $CI_COMMIT_BRANCH

You can also define clone strategy in global variables or pipeline configuration https://docs.gitlab.com/ee/ci/pipelines/settings.html#choose-the-default-git-strategy.

Solution 3:[3]

This might help someone. Based on gitlab.

The detached state is actually intended, as the runner is specifically designed to checkout the repository using a specific commit (the one that triggered the pipeline). When a specific commit is checked out, the repository is then considered to be in a "detached HEAD" state.

Here is an example from my pipeline that after changing some files, it pushes them to master (change master to main in new repos).

git remote set-url origin http://$user:[email protected]/<group>/<group>/$service.git
git add <file that changed>
git config --global user.email "<user email>"
git config --global user.name "<user name>"
git commit -m "Updated service $service"
git push -o ci.skip origin HEAD:master

-o ci.skip - is to stop running another pipeline after push to repo.

In gitlab you can do below to select the default branch from gitlab variables:

git push -o ci.skip origin HEAD:$CI_DEFAULT_BRANCH

To show all variables add CI_DEBUG_TRACE: "true" to variables section.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 slauth
Solution 2 Saljack
Solution 3