Uploaded image for project: 'Jenkins'
  1. Jenkins
  2. JENKINS-73240

git-plugin falls back to hard-coded `master` branch

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Unresolved
    • Icon: Minor Minor
    • git-plugin
    • None
    • Jenkins LTS 2.452.1
      git-plugin 5.2.2

      Originally raised in Gitter: https://matrix.to/#/!ouJVNKRtaWHFflDvBW:gitter.im/$ric5V0euwF0jSREIWwLmZVRK_-h1ZleJDy7BpPcJfwY?via=gitter.im&via=matrix.org&via=minds.com

       

      Cheers, got a bit of a problem with the git-plugin, and wondering if my expectations are against some grand design or just... coincidence/circumstance?

      Namely: when in command line I go git clone SOME_URL I get the default branch as configured in the SCM platform provider (gitlab, github, etc.) for that repository (master for some, main for others, trunk or staging on yet others).

      In Jenkins, if I use a checkout step with an empty or absent list of branches, I expect to get such a default branch whichever way that particular project defines one, however it stumbles on checkout of repos whose default is not master (noted on a project that lacks one altogether). I think I've tracked this down to https://github.com/jenkinsci/git-plugin/blob/master/src/main/java/hudson/plugins/git/GitSCM.java#L214-L220 or possibly https://github.com/jenkinsci/git-plugin/blob/master/src/main/java/jenkins/plugins/git/GitStep.java#L53

      Relevant part of the job's log looks like this: 

          Line  7:  > git --version # timeout=10
          Line 11:  > git --version # 'git version 2.39.3'
          Line 23:  > git fetch --tags --force --progress -- git@gitlab.local:tests/domain-it.git +refs/heads/*:refs/remotes/origin/* # timeout=40
          Line 27:  > git config remote.origin.url git@gitlab.local:tests/domain-it.git # timeout=10
          Line 31:  > git config --add remote.origin.fetch +refs/heads/*:refs/remotes/origin/* # timeout=10
          Line 35:  > git rev-parse refs/remotes/origin/master^{commit} # timeout=10
          Line 39:  > git rev-parse origin/master^{commit} # timeout=10
      
      Executing git in workdir /home/jenkins-worker/jenkins/workspace/release-candidate-test/domain-it
      Couldn't find any revision to build. Verify the repository and branch configuration for this job.
      

      The latter message is certainly from this plugin: https://github.com/jenkinsci/git-plugin/blob/87afc1cbf39df74c7b4378dc0320bc8f94b1465a/src/main/java/hudson/plugins/git/GitSCM.java#L1155

      The repository does appear in the filesystem, but not the workspace. It claims to be checked out into a "master" branch that has no commits yet; the remotes/origin/main is known but not used: 

      [jenkins-worker@jenkins domain-it]$ ls -la
      total 4
      drwxrwxr-x.  3 jenkins-worker jenkins-worker   18 May 29 09:49 .
      drwxrwxr-x. 17 jenkins-worker jenkins-worker 4096 May 29 09:49 ..
      drwxrwxr-x.  8 jenkins-worker jenkins-worker  149 May 29 15:16 .git
      
      [jenkins-worker@jenkins domain-it]$ cat .git/HEAD
      ref: refs/heads/master
      
      [jenkins-worker@jenkins domain-it]$ git status
      On branch master
      
      No commits yet
      
      nothing to commit (create/copy files and use "git add" to track)
      
      [jenkins-worker@jenkins domain-it]$ git log
      fatal: your current branch 'master' does not have any commits yet
      
      [jenkins-worker@jenkins domain-it]$ git log main
      fatal: ambiguous argument 'main': unknown revision or path not in the working tree.
      Use '--' to separate paths from revisions, like this:
      'git <command> [<revision>...] -- [<file>...]'
      
      [jenkins-worker@jenkins domain-it]$ git branch -a
        remotes/origin/main
      

       

      So, the question is if requiring a specific source specifier like "master" in Jenkins jobs is by grand design of some development theory, or just happens to be so and can be changed (e.g. if it is reasonable to trust the SCM server that it knows better; maybe only fall back to trying "master" and possibly other names if an anonymous clone is not successful).

      Originally in the discussion I was not sure what the git query for that would be, seeing how the plugin does first fetch the index and then locally checks out something. Later investigation was fruitful:

      While there are many queries working with a local index of already-fetched information (and the bit we need is not even fetched originally), modern git (by some accounts, like 2.8.0+ from 3 years ago) allows remote queries for this even when there is no local repository yet.

       

      As suggested in https://stackoverflow.com/questions/64904364/how-can-i-ask-git-for-the-name-of-a-repositorys-default-branch git ls-remote --symref ... can report what the remote repo thinks of its HEAD value (as an un-expanded symbolic reference if it is one); the example below uses a named origin from an initialized repo, but an URL can also be used: 

      [jenkins-worker@jenkins domain-it]$ git ls-remote --symref origin HEAD
      ref: refs/heads/main    HEAD
      b8185f414758ae5b57796b58342d2d4bd29a39c2        HEAD 

       

      For an initialized repository some more operations are feasible:

      • we can auto-set the remote HEAD tracking as a named branch (and change it later), which allows us to directly rely more on git facilities, at least since "some version", and code less of git in the plugin: 
        [jenkins-worker@jenkins domain-it]$ git branch -a
          remotes/origin/main
        
        [jenkins-worker@jenkins domain-it]$ git remote set-head origin --auto
        origin/HEAD set to main
        
        [jenkins-worker@jenkins domain-it]$ git branch -a
          remotes/origin/HEAD -> origin/main
          remotes/origin/main
        

         

      • Also there is git remote show that can be parsed (to find that main branch), at least as a possible fallback: 
        [jenkins-worker@jenkins domain-it]$ git branch -a
          remotes/origin/main
        
        [jenkins-worker@jenkins domain-it]$ git remote show origin
        * remote origin
          Fetch URL: git@gitlab.local:tests/domain-it.git
          Push  URL: git@gitlab.local:tests/domain-it.git
          HEAD branch: main
          Remote branch:
            main tracked

         

      For comparison, here's the info available (and not) in the local repository prepared by Jenkins with the current plugin: a remotename/HEAD at least specifies the commit on whatever the default branch is at; probably known branches can be interpolated so if there is one (exactly?) at that commit, that is the one to check out. Not sure what to do with such heuristic if several branches happen to be at that same commit.

      The expected tip of main branch is the same commit as resolved remotely above; there is no locally known branch (or a commit on one): 

      [jenkins-worker@jenkins domain-it]$ cat .git/refs/remotes/origin/main
      b8185f414758ae5b57796b58342d2d4bd29a39c2
      
      [jenkins-worker@jenkins domain-it]$ cat .git/refs/heads/^C
      ### Nothing here, in locally known refs/heads
      

      Trying various query syntaxes:

      [jenkins-worker@jenkins domain-it]$ git ls-remote origin HEAD
      b8185f414758ae5b57796b58342d2d4bd29a39c2        HEAD
      
      [jenkins-worker@jenkins domain-it]$ git ls-remote origin -- HEAD
      b8185f414758ae5b57796b58342d2d4bd29a39c2        HEAD
      
      [jenkins-worker@jenkins domain-it]$ git ls-remote origin
      b8185f414758ae5b57796b58342d2d4bd29a39c2        HEAD
      b8185f414758ae5b57796b58342d2d4bd29a39c2        refs/heads/main
      9a518ecf007a10893b67f5529f84c1fc4647d338        refs/tags/RELEASE-domain-it-1.0.0
      205ca7c24f42b6fceba2b7b8fa5db35f25642741        refs/tags/RELEASE-domain-it-1.0.0^{}
      ...

      Note that we can not just look in the local repo index, as it lacks the needed object after the git init + git fetch perpetrated by the Jenkins git-plugin (log in original post): 

      [jenkins-worker@jenkins domain-it]$ git symbolic-ref refs/remotes/origin/HEAD
      fatal: ref refs/remotes/origin/HEAD is not a symbolic ref

      FWIW, added such logic (to try detecting the branch name for a remote/HEAD) in a local JSL which calls the checkout step and it seems to work reasonably.

       
      So... I've surprisingly learned a lot today. There are technical ways to use the Git server's current (even updatable!) definition of a default branch, instead of hard-coding master as the first and only fall-back.

      Are there any reasons (that I'd be unaware of) to forbid such a change in the plugin?

            Unassigned Unassigned
            jimklimov Jim Klimov
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

              Created:
              Updated: