问题
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 setHEAD
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
andorigin/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, orgit 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 thatrefs/heads/master
is a branch. All tag names start withrefs/tags/
, which is how Git knows thatrefs/tags/v1.2
is a tag, and all remote-tracking names start withrefs/remotes/
.In most cases you can drop the
refs/heads/
orrefs/tags/
orrefs/remotes/
part of a reference. If you just saymaster
, for instance, Git will search through all of your references. If you have arefs/heads/master
and norefs/tags/master
, the namemaster
must be that branch name, so Git will treat it that way—as a branch. Likewise, if you just sayv2.1
, and Git searches and finds a tag namedrefs/tags/v2.1
but nothing else, thenv2.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 withrefs/remotes/origin/
. That is, your Git remembers theirmaster
as yourorigin/master
. This is so that if you have another remote, it's not ambiguous which othermaster
you mean. Suppose, for instance, that you add a second remote—a third Git repository—that you callupstream
for short. If the Git at the URL you callupstream
has amaster
branch, your Git will call thatupstream/master
, and it's easy to tell this apart from the thing your Git callsorigin/master
.A refspec is, in its second-simplest form, a pair of references with a colon
:
stuck between them. Hencemaster:master
is a refspec, for instance. So isrefs/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 changesorigin/develop
irrespective ofremotes/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:
- Create a new, empty directory (or take over some existing empty directory). The remaining steps all happen in that directory.
- Run
git init
to create an empty Git repository. - 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 justorigin
. The URL for this new remote is the URL you gave togit clone
. - 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 forgit push
. Configure the symbolic HEAD for this remote as if bygit remote set-head
. Run any additionalgit config
commands specified by additional command-line flags. - Run
git fetch
. This uses the remote created in step 3 and the refspecs set in step 4. - 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