What should remotes/origin/HEAD set to?

柔情痞子 提交于 2019-12-13 10:16:13

问题


If the developers are working on develop branch, for a new project

1) Clone

git clone <git_url> should be able to automatically clone develop branch locally without using -b option, so that

$ git branch -a # after clone should give
* develop
  remotes/origin/HEAD -> origin/develop
  remotes/origin/develop

2) Push

When developer pushes local branch(develop) changes to remote repository(origin/develop) using command git push origin develop, my understanding is, changes are pushed to origin/master, if remotes/origin/HEAD points to origin/master, unlike mentioned in this comment


Question:

1) Is it recommended to run git remote set-head origin develop and set HEAD pointer in remote repository, before performing above two tasks? by any developer

2) Does git push origin develop pushes changes origin/develop irrespective of remotes/origin/HEAD value in remote repository?

3) We use webhook between GitLab & Jenkins. Does env.gitlabSourceBranch provided by GitLab plugin, gives branch name that remotes/origin/HEAD point to? If yes, how to get the branch name on which, push event happen? through webhook.

Below are the settings done in GitLab, to retrieve branch name on which new commit occured:

Below is the code in Jenkins:

node('worker_node'){

    stage('stage1'){

        def repoName = env.gitlabSourceRepoName
        println "Repository Name: " + repoName // gives correct repo name


        def branchName = env.gitlabSourceBranch
        println "Branch name: " + branchName // gives always 'master' as value
   }
}

回答1:


Is it recommended to run git remote set-head origin develop and set HEAD pointer in remote repository ...

This might be a language issue, but it's worth pointing out here that this doesn't set HEAD in the remote repository. It has no effect on anyone else's git clone command.

Git's terminology here is very confusing. Let's break it down a bit:

  • A remote is a short name you use in your Git repository to refer to some other Git repository. Hence origin is a remote: it stands in for some URL. If your Git dials that URL, some other Git answers this "phone call". Note that the word remote here is a noun: it is a thing on its own.

  • A remote repository is a repository other than your own. When you have your Git dial some URL and call some other Git, that other Git is a remote repository. The word remote here is an adjective.

  • The word branch is rather ambiguous in Git. (See What exactly do we mean by "branch"?) I prefer to use the phrase branch name, where branch is an adjective modifying name, and remote-tracking name (Git calls this a remote-tracking branch name), which has an entire adjective phrase modifying the word name, to refer to things like master and origin/master respectively.

    The word branch can also mean a vaguely-defined series of commits. In this particular set of circumstances—where we use git clone to copy a Git repository from some URL to our own machine, or git push to send commits from our Git repository on our machine to some other Git repository elsewhere (probably on another machine)—we won't need this other sense of the word. But be aware that it exists!

We also need one more or two more pieces of Git terminology:

  • A reference (or ref) is a branch name, tag name, or other similar name. In fact, reference is just a generalization of these: branch names, tag names, and remote-tracking names are all just specific kinds of references. A reference has a full spelling that starts with refs/.

    This is usually immediately followed by its classification: for instance, all branch names start with refs/heads/. That, in fact, is how Git knows that refs/heads/master is a branch. All tag names start with refs/tags/, which is how Git knows that refs/tags/v1.2 is a tag, and all remote-tracking names start with refs/remotes/.

    In most cases you can drop the refs/heads/ or refs/tags/ or refs/remotes/ part of a reference. If you just say master, for instance, Git will search through all of your references. If you have a refs/heads/master and no refs/tags/master, the name master must be that branch name, so Git will treat it that way—as a branch. Likewise, if you just say v2.1, and Git searches and finds a tag named refs/tags/v2.1 but nothing else, then v2.1 must be that tag name, so Git will treat it that way—as a tag.

    All of the remote-tracking names that your Git uses to remember names on the Git your Git calls origin start with refs/remotes/origin/. That is, your Git remembers their master as your origin/master. This is so that if you have another remote, it's not ambiguous which other master you mean. Suppose, for instance, that you add a second remote—a third Git repository—that you call upstream for short. If the Git at the URL you call upstream has a master branch, your Git will call that upstream/master, and it's easy to tell this apart from the thing your Git calls origin/master.

  • A refspec is, in its second-simplest form, a pair of references with a colon : stuck between them. Hence master:master is a refspec, for instance. So is refs/heads/master:refs/remotes/origin/master. The thing on the left of the colon is the source, and the thing on the right of the colon is the destination.

The git fetch and git push commands use refspecs, as we will see in a moment. While you're asking about git clone rather than git fetch, a significant part of git clone is that it runs git fetch.

With all of this in mind, let's look next at how Git resolves a symbolic name, and when Git resolves a symbolic name. This part is a bit tricky because git fetch and git push use positional arguments.

Removing a lot of detail (such as flag arguments), the arguments to git push and git fetch are spelled out this way in the documentation:

        git fetch [repository] [refspec [refspec ...]]
        git push [repository] [refspec [refspec ...]]

That is, after the push or fetch sub-command, the next item, if there is a next item, is, by definition, a repository. Any items after the repository arguments are, by definition, refspec arguments. You can put a raw URL in for the repository part, but in general, you should use a remote name, for two reasons:

  • it's easier to type and get right; and
  • it enables useful special features.

To put in any refspec arguments, you have to insert a repository argument, because without one, Git will just think that whatever you typed is a repository argument. That is, if you accidentally run git push master, Git won't figure out that you meant to type git push origin master. It will just try to treat master as a remote, or failing that, as a URL. Probably it won't work either way and you'll get this puzzling output:

$ git push master
fatal: 'master' does not appear to be a git repository
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

So with git push, you have options like this:

git push
git push origin
git push origin somebranch
git push origin branch1:branch1 branch2:branch2 branch3 tag1:tag1

When you leave stuff out, as in the first two examples, Git picks the remote and refspec for you (first example), or the refspec for you (second example). When you include a refspec (third example) or multiple refspecs (fourth example), you control all the parts.

Let's go on to your second question now, before we come back to the first:

Does git push origin develop pushes changes origin/develop irrespective of remotes/origin/HEAD value in remote repository?

This contains multiple terminology errors (again they might just be language issues). The overall idea is, I think, clear enough, though, and the answer is yes: this totally disregards remotes/origin/HEAD.

To be explicit, this:

  • specifies origin as the remote, and
  • specifies develop as the refspec.

Remember that we said that the second simplest form of refspec is a pair of names with a colon between them. This uses the most simple form, which omits the second name. The behavior of omitting the second name is different in push and fetch. Let's only worry about push here.

For git push, omitting the second part of a refspec means first part and second part are the same. That is, develop means develop:develop. The name develop itself is short—it doesn't start with refs/—but both your Git and their Git probably have a branch named develop and no tag named develop.1 So develop is actually short for refs/heads/develop:refs/heads/develop here.

Remember that their Git repository is a Git repository. It therefore has its own refs/heads/develop—its own branch name spelled develop. Your refs/heads/develop identifies some commit in your repository. Their refs/heads/develop identifies some commit in their repository.

When you run git push, you tell your Git: Connect to some other Git. Then make sure they have enough commits and other internal Git objects to achieve the last part, by giving them any commits I have that they don't that they will need. Last, you've finished that, ask them—or command them—to set some of their branch and/or tag names to point to some particular commits or other appropriate Git objects.

The branch and/or tag names that you will ask them to set come from the destination part of the refspec. So if you said git push ... develop:develop, the branch or tag name you'll have your Git ask their Git to change is develop. The commit you'll ask them to set as their develop is the one identified by the source name—the one on the right. So you're going to ask them to set their develop to identify the same commit that your develop identifies.


1If you do have both a branch refs/heads/develop and a tag refs/tags/develop, some not-very-good things happen. Git has rules for this, but Git's rules are a bit weird and unsettling. Your best bet is to avoid this situation entirely: never use the same short name for both a tag and a branch.


They can say either yes or no

If they say yes to this request, your Git now knows that their develop represents that commit. Your Git remembers their develop by updating your own origin/develop. So your Git updates your origin/develop—your refs/remotes/origin/develop, to use its full name—to save that commit hash ID.

Note that nowhere in this process did your Git look at your own refs/remotes/origin/HEAD. You didn't put refs/remotes/origin/HEAD on the left side of your refspec. You could do that if you wanted. For instance, you could write:

git push origin refs/remotes/origin/HEAD:refs/heads/develop

This will have your Git resolve your refs/remotes/origin/HEAD to a commit hash ID, call up the Git over at origin, and ask that Git to set their refs/heads/develop to that commit hash ID. It probably already is set to that ID so this is probably just a waste of time, but it is something you could run, if you wanted.

You can also run:

git push origin HEAD:refs/heads/develop

which resolves your HEAD to a commit hash ID, calls up the Git over at origin, and asks them to set their branch develop based on that; and you can run:

git push origin a123456:refs/heads/develop

if a123456 is some valid commit in your repository. Most of these forms require the two-part refspec, with the colon in it, because the thing on the left of the colon here is not necessarily a branch name at all, and you're going to want to ask their Git to set one of their branch names.

In general, though, when using git push, you will start with your own branch name or names (like develop and/or master) and want to ask them to set their branch names, of the same name, to the same commit hash ID. Occasionally—such as after you've just made a new tag name—you will want to have your Git call up their Git and ask them to set a new tag of the same name, to the same hash ID. So git push's default of mirroring the one name into two serves you well, because you can just write:

git push origin master develop v1.3

and thereby ask them to set both of their branches, and create a new tag, all in one go.

If they say no, they can do it piecemeal. Suppose you ask them to set all three of these names, and they accept your request to update their develop and create a new v1.3, but reject your request to update their master. You'll get two successes one one failure, and your Git will update your refs/remotes/origin/develop to remember the branch success, but not update your refs/remotes/origin/master because the branch failure means you don't know what their master is after all. (There is no such thing as a remote tag so success or failure at asking them to create v1.3 has no effect on any names in your repository.)

Back to origin/HEAD: what is it and what good is it?

To put it briefly (and maybe slightly too aggressively), refs/remotes/origin/HEAD is useless.

What it is is a symbolic reference that your Git has.2 You can set it, any time you like, using git remote set-head origin. It's initially created during git clone. But it has, as far as I can tell, no practical purpose whatsoever.

I mentioned above that you can shorten references: you can say master to mean refs/heads/master and v2.1 to mean refs/tags/v2.1. Try this sort of thing in your own Git repository: run git rev-parse and give it short and long names for branches and tags. (Use git branch to list your branches, and git tag to list your tags.) You will see things like this:

$ git rev-parse master
b5101f929789889c2e536d915698f58d5c5c6b7a
$ git rev-parse v2.1.0
7452b4b5786778d5d87f5c90a94fab8936502e20

The rev-parse command translates from a symbolic name, like master, to a hash ID, like b5101f929789889c2e536d915698f58d5c5c6b7a. (It can also translate from a short hash ID to a full one, or do many other tricks, but that's one of its main jobs: to turn a name into a hash ID.)

In general, when you give Git a short name, Git searches through all your references to figure out what the long one is. The git rev-parse command does this and then spits out the hash ID. Most other Git commands also do this, but then go on to use the hash ID in some way. In almost all cases, though,3 this searching process is outlined in the gitrevisions documentation, which is worthy of close and careful study over time. But take a quick look at it now and scroll down just a little bit to find a six-step list.

The six-step list here is how Git turns a short name into a long one. Note that it's step 3 that tries v2.1 as a tag name: if refs/tags/v2.1 exists, then v2.1 is a tag name, and that's how git rev-parse knows and finds its hash ID. It's step 4 that tries master as a branch name: if refs/heads/master exists, then master is a branch name and that's how git rev-parse knows.

Down at the bottom of the six-step list, there is a step 6. This last step takes whatever string you typed in—which could be origin, for instance—and tries it as refs/remotes/string/HEAD. If that exists, that must be what you meant. So while origin is typically a remote—it's what you type in as the repository argument for git fetch and git push—it's also a valid short name for a commit, if you put it in a place where some Git command, like git rev-parse, will use it as a commit.

It's possible to remove refs/remotes/origin/HEAD and git remote origin set-head -d does exactly that. If you have removed it, origin won't match step 6 and git rev-parse origin will just fail. It's possible to use git remote origin set-head to change the name stored in your refs/remotes/origin/HEAD to any other origin/* branch name, so that step 6 succeeds but uses that other name. None of this ever goes back to the other Git over at origin!


2I'm glossing over symbolic reference here. A symbolic reference occurs when a name like HEAD contains, instead of the hash ID of some Git object, another name. The mechanism that Git uses is intended to be general, but is full of weird quirks and flaws, and actually really only works right with HEAD, and—to a lesser extent—with these remote-tracking origin/HEAD style names.

You can, for instance, create your own symbolic references in the branch name space. Using git symbolic-ref refs/heads/INDIR refs/heads/master creates the name INDIR as a symbolic reference branch. Unfortunately, if you do this and then attempt to delete the name INDIR, Git deletes the name master instead! The whole thing is not really ready for this sort of use (abuse?). Don't do it!

3I say almost all cases because certain Git commands know that their name argument is a branch or tag name, and others suspect it. For instance, git branch knows that you are going to give it a branch name, not a tag name, as the next argument. So it doesn't use the six-step resolution process, and in fact, it demands that you not spell a branch name with the full refs/heads/ form when you create a new branch.

The git checkout command is the weirdest, because the positional argument after checkout (and any flags) is allowed to be a branch name, a tag name, or anything that resolves to a valid commit. So it seems like it should use the six-step process. If you create a branch and tag both named X but pointing to two different commits, you might expect git checkout X to check out the the tagged X. In fact, though, it checks out the branch. So while git checkout will try all six steps, it tries step 4 earlier than step 3.

(Technically, it isn't going through the six-step process when it tries the name as a branch name. Instead, it just tries it as a branch name first. If that works, git checkout puts you on the branch and is done. If it fails, then the code calls the six-step name-to-ID-resolver, and as long as a commit hash comes back, git checkout puts you on a detached HEAD on that commit. So it actually tries step 4 twice, as it were—but if it failed the first time, it's extremely likely to fail the second time too.)


How git clone works

When you run git clone url, you have Git:

  1. Create a new, empty directory (or take over some existing empty directory). The remaining steps all happen in that directory.
  2. Run git init to create an empty Git repository.
  3. Run git remote add to create a remote. The name of this remote is whatever you chose with your -o flag, but if you didn't choose one, it's just origin. The URL for this new remote is the URL you gave to git clone.
  4. Set some default refspecs for git fetch. The actual refspecs depend on command-line flags, but a typical standard one is +refs/heads/*:refs/remotes/origin/*. Note how closely this resembles the refspecs we were using for git push. Configure the symbolic HEAD for this remote as if by git remote set-head. Run any additional git config commands specified by additional command-line flags.
  5. Run git fetch. This uses the remote created in step 3 and the refspecs set in step 4.
  6. Run git checkout name. The name argument to this step depends on command line flags and on information obtained from the other Git. See below for details.

It's step 6 that puts your new clone on master or, perhaps, on develop, or even on no branch at all if you chose something like v2.1 here. If step 6 puts you on your master branch, that's what creates your master branch. If step 6 puts you own your develop branch, that's what creates your develop branch. If step 6 leaves you with a detached HEAD, your Git doesn't create any branches!

You can, if you like, specify which name this last clone step should use, using git clone -b name. If you do that, nothing the other Git says matters, except of course that name has to match one of its names. But if you don't use a -b argument, then—and only then—what the other Git says matters a lot.

The other Git repository is a Git repository. This means the other Git repository—let's call it the server repository for simplicity—has a HEAD. The HEAD in the server repository tells which branch is checked-out in the server repository. If the server repository is a --bare repository, it has no work-tree, so its HEAD is somewhat irrelevant. But it still has one, and that means there's still one that the server is "on", unless the server's Git is in detached HEAD mode (which is possible).

When your Git calls up the server Git, one of the things your Git can ask the server is: Which branch are you on? That is, your Git can ask the server about the server's HEAD. If you fail to specify a chosen branch for -b during git clone, that's the name that your Git will use for your git checkout.

That's also the name that your Git will use for your git remote set-head origin --auto, and the name that your git clone will set up automatically as your origin/HEAD. So the server's HEAD setting is the default for your origin/HEAD copy at git clone time, and the default for the last-step-of-clone git checkout command.

That's all it really is good for. If you override it with -b at git clone time, that meaning doesn't matter, and since origin/HEAD is quite useless anyway, that meaning doesn't matter either.

Conclusion

Don't worry about origin/HEAD. It's useless. It doesn't do you any good.

Don't worry much about HEAD in the server repository. It does affect new clones, but only if the person doing the clone doesn't pick a branch. If you do want to set it, you can do that however your server allows. (Different web services have different ways to set or change this.)

Last, regarding this question:

We use webhook between GitLab & Jenkins

I know nothing about the particular plugin you are using. Jenkins documentation varies: some of it is slightly useful, most of it seems to be missing, and some of it seems to be just plain wrong. In general Jenkins gets notifications from whatever hosting server you use, that something has happened, and then you write code that decodes the event(s) that occurred. But this part:

how to get the branch name on which push event happen(s)

is a fundamentally bad question, because there isn't necessarily any branch name—we might git push to a tag only, for instance—and if there is a branch name, there might be many branch names, and they might have changed hash IDs, or not. The right question to ask is whether it's appropriate to have Jenkins start some pipeline(s). I can't really help answer that, but this is your guide to asking the right questions.



来源:https://stackoverflow.com/questions/55083441/what-should-remotes-origin-head-set-to

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!