如果使用完全限定的名称(以refs/开头),则可以确定要获得哪个名称:分支,标记或其他名称.如果您使用部分合格或不合格的名称,Git通常会弄清楚您的意思.您有时会遇到Git 无法弄清楚您的意思的情况;在这种情况下,请使用完全限定的名称.您可以通过省略两个名称之一来进一步简化refspec. Git知道您在冒号的哪一侧省略了哪个名称::dst没有源名称,而src:没有目标名称.如果您写name,则Git会将其视为name::无目的地的源.这些平均值的含义有所不同. git push的空来源表示删除: git push origin :branch让您的Git要求其Git完全删除该名称. git push的空目的地表示使用默认的,该名称通常与分支名称相同:git push origin branch通过要求其Git设置其名为branch的分支来推送branch. 6 请注意,直接git push直接将其分支 是正常的:您将提交内容发送给他们,然后要求他们设置其refs/heads/branch.这与普通的fetch完全不同!对于git fetch,目标地址为空表示不更新我的任何引用.非空目的地意味着更新我提供的参考.但是,与git push不同,您可能在此处使用的通常目的地是远程跟踪名称:将它们的refs/heads/master转换为您自己的refs/remotes/origin/master.这样,您的分支机构名称master(您的refs/heads/master)将保持不变.但是,由于历史原因,git fetch的通常形式只是写为git fetch remote branch,而省略了目的地.在这种情况下,Git做一些看似矛盾的事情:它写分支名称更新 nowhere .缺少目的地意味着没有(本地)分支得到更新.它将哈希ID写入.git/FETCH_HEAD. git fetch提取的所有内容始终都在此处.这是git pull在哪里以及如何找出git fetch所做的事情.它更新了远程跟踪名称,例如refs/remotes/origin/master,甚至以为没有告知这样做. Git将此称为机会更新".(其中许多实际上是由默认refspec 控制的,您可以在.git/config文件中找到它.)您还可以通过添加前导加号+使​​refspec复杂化.这将设置力"标志,该标志将覆盖分支名称运动的默认快进"检查.这是您的远程跟踪名称的正常情况:您希望您的Git更新您的refs/remotes/origin/master以匹配其Git的refs/heads/master ,即使这是非快进的更改,因此您的Git始终记得他们的 master的位置,这是您的Git上一次与他们的Git交谈的时候.请注意,前导加号仅在有要更新的目的地时才有意义.这里有三种可能性:您正在创建一个新名称.通常没关系. 您没有更改名称:它曾经用于映射以提交哈希 H ,而请求则说将其设置为映射以提交哈希 H .显然没关系.您正在更改名称.这分为三个子可能性:这根本不是一个类似于分支的名称,例如,它是一个标记,不应移动.您将需要一个强制标志来覆盖默认拒绝. 这是一个类似分支的名称,分支运动是一个快进的动作.您不需要强制标志.这是一个类似分支的名称,但是动作不是 快进.您将需要强制标志. 这涵盖了更新引用的所有规则,除了最后一条规则的 ,我们还需要更多的背景知识. 您可以通过将push.default设置为upstream来使 this 复杂化.在这种情况下,如果分支fred的上游设置为origin/barney,则git push origin fred要求其Git设置其名为barney的分支. 对于各种更新情况,您可以编写钩子来执行您想验证名称和/或更新的任何操作. 在1.8.3之前的Git版本中,Git意外地使用了分支规则来更新标签.因此,这仅适用于1.8.3和更高版本. HEAD很特别请记住,像master这样的分支名称仅标识某些特定的提交哈希:$ git rev-parse master468165c1d8a442994a825f3684528361727cd8c0您还已经看到,git checkout branchname表现为一种方式,而git checkout --detach branchname或git checkout hash表现为另一种方式,给出了有关分离的HEAD"的可怕警告.虽然HEAD在大多数情况下都像参考,但在某些方面却非常特殊.特别是HEAD通常是符号引用,其中包含分支名称的全名.那就是:$ git checkout masterSwitched to branch 'master'$ cat .git/HEADref: refs/heads/master告诉我们当前分支名称是master:HEAD附加到master.但是:$ git checkout --detach masterHEAD is now at 468165c1d... Git 2.17$ cat .git/HEAD468165c1d8a442994a825f3684528361727cd8c0之后,git checkout master像往常一样使我们回到master.这意味着当我们有一个分离的HEAD 时,Git知道我们已经签出了哪个提交,因为正确的哈希ID就在其中,名称为HEAD.如果我们要对存储在refs/heads/master中的值进行任意的更改,Git仍然会知道我们已经签出了哪个提交.但是,如果HEAD仅包含名称 master,则Git知道 current 提交的唯一方法是,例如468165c1d8a442994a825f3684528361727cd8c0 refs/heads/master映射到468165c1d8a442994a825f3684528361727cd8c0.如果我们做了将更改 refs/heads/master更改为其他哈希ID的操作,Git会认为我们已经签出了其他提交.这有关系吗?是的,它确实!让我们看看原因:$ git status... nothing to commit, working tree clean$ git rev-parse master^1614dd0fbc8a14f488016b7855de9f0566706244$ echo 1614dd0fbc8a14f488016b7855de9f0566706244 > .git/refs/heads/master$ git status...Changes to be committed:... modified: GIT-VERSION-GEN$ echo 468165c1d8a442994a825f3684528361727cd8c0 > .git/refs/heads/master$ git status...nothing to commit, working tree clean更改存储在master中的哈希ID改变了Git的状态观念!状态包括HEAD vs索引加上index vs工作树 git status命令运行两个git diff(内部是git diff --name-status es):比较HEAD与索引比较索引与工作树请记住,索引(又名暂存区或缓存)保存了当前提交的内容. >直到我们开始对其进行修改以保存我们将进行的下次提交的内容.工作树只是整个更新索引,然后提交过程的次要助手.我们只需要它,因为索引中的文件采用特殊的仅Git格式,因此我们系统上的大多数程序都无法使用.如果HEAD保留了当前提交的原始哈希ID,则无论我们对分支名称进行什么操作,比较HEAD vs索引均保持不变.但是,如果HEAD拥有一个特定的分支名称,并且我们更改一个特定的分支名称的值,然后进行比较,我们将比较一个不同的提交到我们的索引. 索引和工作树将保持不变,但是Git关于(不同的)当前提交和索引之间的相对差异的想法将会改变. 这是为什么 git fetch默认情况下拒绝更新当前分支名称的原因.这也是为什么您不能推送到非裸存储库的当前分支的原因:该非裸存储库具有索引和工作树,其内容可能旨在与当前提交匹配.如果您通过更改存储在分支名称中的哈希来更改Git关于当前提交的概念,则索引和工作树可能会停止与提交匹配.这不是致命的-实际上根本没有.这正是git reset --soft所做的:在不触及索引和工作树中内容的情况下,它更改了附加了HEAD的分支名称.同时,git reset --mixed更改分支名称​​和,但保持工作树不变,而git reset --hard一次性更改分支名称,索引和工作树. /p>快进合并"基本上是git reset --hard 使用git pull依次运行git fetch和git merge时,git merge步骤通常能够执行Git所谓的快进合并.不过,这根本不是合并:对当前分支名称进行快速操作,然后立即将索引和工作树内容更新为新提交,就像会.关键区别在于git pull检查-应当应该检查-此git reset --hard不会破坏正在进行的工作,而特意不检查,以使您放弃不再需要的正在进行的工作. 从历史上看,git pull一直会出错,并且在有人丢掉一堆工作后得到修复.避免git pull!将所有这些放在一起运行git pull upstream master:master时,Git首先运行:git fetch --update-head-ok upstream master:master让您的Git在upstream列出的URL上调用另一个Git,并从它们中收集提交,如通过它们的名称master(master:master refspec的左侧)所找到的.然后,您的Git使用refspec的右侧更新您自己的master(可能是refs/heads/master).如果master是当前分支(如果.git/HEAD包含ref: refs/heads/master),则fetch步骤通常会失败,但是-u或--update-head-ok标志可以防止失败.(如果一切顺利,您的git pull将运行第二个步骤git merge:git merge -m <message> <hash ID extracted from .git/FETCH_HEAD>但是让我们先完成第一步.快进规则确保您的master更新是快进操作.如果不是,则提取失败并且您的master保持不变,并且pull在此处停止.因此,到目前为止,我们还可以:您的master只要且仅在从upstream获得新提交(如果有)的情况下,才可以快速转发.这时,如果您的master已更改 ,并且它是您当前的分支,则您的存储库现在不同步:您的索引和工作树不再与您的master匹配.但是,git fetch也将正确的哈希ID保留在.git/FETCH_HEAD中,并且您的git pull现在继续进行类似重置的更新.这实际上使用的是git read-tree而不是git reset的等效项,但是只要它成功-在pull之前的检查中,它应该成功-最终效果是相同的:您的索引和工作树将与新提交匹配.或者,也许master不是您当前的分支.也许您的.git/HEAD包含了ref: refs/heads/branch.在这种情况下,即使没有--update-head-ok,您的refs/heads/master也可以安全地快速前进git fetch.您的.git/FETCH_HEAD包含与更新后的master相同的哈希ID,并且您的git pull运行git merge尝试合并-这可能是快进操作,也可能不是快进操作,具体取决于分支的提交名称branch指向现在.如果合并成功,则Git会进行提交(实际合并),或者像以前一样调整索引和工作树(快进合并"),并将适当的哈希ID写入.git/refs/heads/branch中.如果合并失败,则Git会因合并冲突而停止,并让您像往常一样清理混乱.最后一种可能的情况是您的HEAD是分离的,但这的工作方式与ref: refs/heads/branch情况相同.唯一的区别是说完了所有新的哈希ID后,它们直接进入.git/HEAD而不是.git/refs/heads/branch.I know the difference between git fetch and git pull. git pull is basically a git fetch + git merge in one command.However, I was researching on how to update my fork (master branch) with the upstream without checking out the master branch. I came across this SO answer - Merge, update and pull Git branches without checkoutsBut when I used git fetch upstream master:master after I was already checked out on master, I ran into this error -fatal: Refusing to fetch into current branch refs/heads/master of non-bare repositorySo, I tried git pull upstream master:master and it worked. What is interesting is that doing git pull upstream master:master updates my fork with upstream regardless of whether I am on master or not. Whereas git fetch upstream master:master only works when I am NOT on master branch.It will be very interesting to read explanation on this, from the knowledgeable folks out here. 解决方案 Yes—but, as you suspected, there is more to it than that.Bennett McElwee's comment, in the answer you linked-to, actually has one of the key items. He mentions that you can:Another is not very well documented: it's the -u aka --update-head-ok option in git fetch, which git pull sets. The documentation does define what it does, but is a bit mysterious and scary:This gets us to your observation:This is due to that -u flag. If you ran git fetch upstream master:master, that would work, for some sense of the meaning work, but leave you with a different problem. The warning is there for a reason. Let's look at what that reason is, and see whether the warning is overly harsh. Warning: there is a lot here! Much of the complication below is to make up for historical mistakes, while retaining backwards compatibility.Branch names, references, and fast-forwardingFirst, let's talk about references and fast-forward operations.In Git, a reference is just a fancy way of talking about a branch name like master, or a tag name like v1.2, or a remote-tracking name like origin/master, or, well, any number of other names, all in one common and sensible fashion: we group each specific kind of name into a name space, or as a single word, namespace. Branch names live under refs/heads/, tag names live under refs/tags/, and so on, so that master is really just refs/heads/master.Every one of these names, all of which start with refs/, is a reference. There are a few extra references that don't start with refs as well, although Git is a little bit erratic internally in deciding whether names like HEAD and ORIG_HEAD and MERGE_HEAD are actually references. In the end, though, a reference mainly serves as a way to have a useful name for a Git object hash ID. Branch names in particular have a funny property: they move from one commit to another, typically in a way that Git refers to as a fast forward.That is, given a branch with some commits, represented by uppercase letters here, and a second branch with more commits that include all the commits on the first branch:...--E--F--G <-- branch1 \ H--I <-- branch2Git is allowed to slide the name branch1 forward so that it points to either of the commits that were, before, reachable only through the name branch2. Compare this, to, say:...--E--F--G------J <-- branch1 \ H--I <-- branch2If we were to move the name branch1 to point to commit I instead of commit J, what would happen to commit J itself? This kind of motion, which leaves a commit behind, is a non-fast-forward operation on the branch name.These names can be shortened by leaving off the refs/ part, or often, even the refs/heads/ part or the refs/tags/ part or whatever. Git will look in its reference-name database for the first one that matches, using the six-step rules described in the gitrevisions documentation. If you have a refs/tags/master and a refs/heads/master, for instance, and say master, Git will match refs/tags/master first and use the tag.If a reference is a name that has, or can have, a reflog, then HEAD is a reference while ORIG_HEAD and the other *_HEAD names are not. It's all a little fuzzy at the edges here, though.These commits might be reachable through more names. The important thing is that they weren't reachable through branch1 before the fast-forward, and are afterward.The immediate answer is actually that nothing happens, but eventually, if commit I is not reachable through some name, Git will garbage collect the commit.This "database" is really just the combination of the directory .git/refs plus the file .git/packed-refs, at least for the moment. If Git finds both a file entry and a pathname, the pathname's hash overrides the one in the packed-refs file.Exception: git checkout tries the argument as a branch name first, and if that works, treats master as a branch name. Everything else in Git treats it as a tag name, since prefixing with refs/tags is step 3, vs step 4 for a branch name.RefspecsNow that we know that a reference is just a name pointing to a commit, and a branch name is a specific kind of reference for which fast forwards are normal everyday things, let's look at the refspec. Let's start with the most common and explainable form, which is just two reference names separated by a colon, such as master:master or HEAD:branch.Git uses refspecs whenever you connect two Gits to each other, such as during git fetch and during git push. The name on the left is the source and the name on the right is the destination. If you are doing git fetch, the source is the other Git repository, and the destination is your own. If you are doing git push, the source is your repository, and the destination is theirs. (In the special case of using ., which means this repository, both source and destination are yourself, but everything still works just as if your Git is talking to another, separate Git.) If you use fully-qualified names (starting with refs/), you know for sure which one you will get: branch, tag, or whatever. If you use partially-qualified or unqualified names, Git will usually figure out what you mean anyway. You will occasionally run into a case where Git can't figure out what you mean; in that case, use a fully qualified name.You can simplify a refspec even further by omitting one of the two names. Git knows which name you omit by which side of the colon is gone: :dst has no source name, while src: has no destination name. If you write name, Git treats that as name:: a source with no destination.What these mean varies. An empty source for git push means delete: git push origin :branch has your Git ask their Git to delete the name entirely. An empty destination for git push means use the default which is normally the same branch name: git push origin branch pushes your branch by asking their Git to set their branch named branch. Note that it's normal to git push to their branch directly: you send them your commits, then ask them to set their refs/heads/branch. This is quite different from the normal fetch!For git fetch, an empty destination means don't update any of my references. A non-empty destination means update the reference I supply. Unlike git push, though, the usual destination you might use here is a remote-tracking name: you would fetch their refs/heads/master into your own refs/remotes/origin/master. That way, your branch name master—your refs/heads/master—is left untouched.For historical reasons, though, the usual form of git fetch is just written as git fetch remote branch, omitting the destination. In this case, Git does something seemingly self-contradictory:It writes the branch name update nowhere. The lack of a destination means that no (local) branch gets updated.It writes the hash ID into .git/FETCH_HEAD. Everything git fetch fetches always goes here. This is where and how git pull finds out what git fetch did.It updates the remote-tracking name, such as refs/remotes/origin/master, even thought it was not told to do so. Git calls this an opportunistic update.(Much of this is actually controlled through a default refspec that you will find in your .git/config file.)You can also complicate a refspec by adding a leading plus sign +. This sets the "force" flag, which overrides the default "fast forward" check for branch name motion. This is the normal case for your remote-tracking names: you want your Git to update your refs/remotes/origin/master to match their Git's refs/heads/master even if that's a non-fast-forward change, so that your Git always remembers where their master was, the last time your Git talked with their Git.Note that the leading plus only makes sense if there is a destination to update. There are three possibilities here:You're creating a new name. This is generally OK.You're making no change to the name: it used to map to commit hash H and the request says to set it to map to commit hash H. This is obviously OK.You are changing the name. This one breaks down into three more sub-possibilities:It's not a branch-like name at all, e.g., it's a tag and should not move. You will need a force flag to override the default rejection.It's a branch-like name, and the branch motion is a fast-forward. You won't need the force flag.It's a branch-like name, but the motion is not a fast-forward. You will need the force flag.This covers all the rules for updating references, except for one last rule, for which we need yet more background.You can complicate this by setting push.default to upstream. In this case, if your branch fred has its upstream set to origin/barney, git push origin fred asks their Git to set their branch named barney.For various cases of updates, you can write hooks that do whatever you like to verify names and/or updates.In Git versions before 1.8.3, Git accidentally used branch rules for tag updates. So this only applies to 1.8.3 and later.HEAD is very specialRemember that a branch name like master just identifies some particular commit hash:$ git rev-parse master468165c1d8a442994a825f3684528361727cd8c0You have also seen that git checkout branchname behaves one way, and git checkout --detach branchname or git checkout hash behaves another way, giving a scary warning about a "detached HEAD". While HEAD acts like a reference in most ways, in a few, it's very special. In particular, HEAD is normally a symbolic reference, in which it contains the full name of a branch name. That is:$ git checkout masterSwitched to branch 'master'$ cat .git/HEADref: refs/heads/mastertells us that the current branch name is master: that HEAD is attached to master. But:$ git checkout --detach masterHEAD is now at 468165c1d... Git 2.17$ cat .git/HEAD468165c1d8a442994a825f3684528361727cd8c0after which git checkout master puts us back on master as usual.What this means is that when we have a detached HEAD, Git knows which commit we have checked out, because the correct hash ID is right there, in the name HEAD. If we were to make some arbitrary change to the value stored in refs/heads/master, Git would still know which commit we have checked out.But if HEAD just contains the name master, the only way that Git knows that the current commit is, say, 468165c1d8a442994a825f3684528361727cd8c0, is that refs/heads/master maps to 468165c1d8a442994a825f3684528361727cd8c0. If we did something that changed refs/heads/master to some other hash ID, Git would think that we have that other commit checked out.Does this matter? Yes, it does! Let's see why:$ git status... nothing to commit, working tree clean$ git rev-parse master^1614dd0fbc8a14f488016b7855de9f0566706244$ echo 1614dd0fbc8a14f488016b7855de9f0566706244 > .git/refs/heads/master$ git status...Changes to be committed:... modified: GIT-VERSION-GEN$ echo 468165c1d8a442994a825f3684528361727cd8c0 > .git/refs/heads/master$ git status...nothing to commit, working tree cleanChanging the hash ID stored in master changed Git's idea of the status!The status involves HEAD vs index plus index vs work-treeThe git status command runs two git diffs (well, git diff --name-statuses, internally):compare HEAD vs indexcompare index vs work-treeRemember, the index, aka the staging area or the cache, holds the contents of the current commit until we start modifying it to hold the contents of the next commit we will make. The work-tree is merely a minor helper for this whole update the index, then commit process. We only need it because the files in the index are in the special Git-only format, that most of the programs on our systems cannot use.If HEAD holds the raw hash ID for the current commit, then comparing HEAD vs index stays the same regardless of what we do with our branch names. But if HEAD holds one specific branch name, and we change that one specific branch name's value, and then do the comparison, we'll compare a different commit to our index. The index and work-tree will be unchanged, but Git's idea of the relative difference between the (different) current commit and the index will change.This is why git fetch refuses to update the current branch name by default. It's also why you cannot push to the current branch of a non-bare repository: that non-bare repository has an index and work-tree whose contents are probably intended to match the current commit. If you change that Git's idea of what the current commit is, by changing the hash stored in the branch name, the index and work-tree are likely to stop matching the commit.That's not fatal—not at all, in fact. That's precisely what git reset --soft does: it changes the branch name to which HEAD is attached, without touching the contents in the index and the work-tree. Meanwhile git reset --mixed changes the branch name and the index, but leaves the work-tree untouched, and git reset --hard changes the branch name, the index, and the work-tree all in one go.A fast-forward "merge" is basically a git reset --hardWhen you use git pull to run git fetch and then git merge, the git merge step is very often able to do what Git calls a fast-forward merge. This is not a merge at all, though: it's a fast-forward operation on the current branch name, followed immediately by updating the index and work-tree contents to the new commit, the same way git reset --hard would. The key difference is that git pull checks—well, is supposed to check—that no in-progress work will be destroyed by this git reset --hard, while git reset --hard itself deliberately does not check, to let you throw away in-progress work that you no longer want.Historically, git pull keeps getting this wrong, and it gets fixed after someone loses a bunch of work. Avoid git pull!Putting all this togetherWhen you run git pull upstream master:master, Git first runs:git fetch --update-head-ok upstream master:masterwhich has your Git call up another Git at the URL listed for upstream and collect commits from them, as found via their name master—the left side of the master:master refspec. Your Git then updates your own master, presumably refs/heads/master, using the right side of the refspec. The fetch step would normally fail if master is your current branch—if your .git/HEAD contains ref: refs/heads/master—but the -u or --update-head-ok flag prevents the failure.(If all goes well, your git pull will run its second, git merge, step:git merge -m <message> <hash ID extracted from .git/FETCH_HEAD>but let's finish with the first step first.)The fast-forward rules make sure that your master update is a fast-forward operation. If not, the fetch fails and your master is unchanged, and the pull stops here. So we're OK so far: your master is fast-forwarded if and only if that's possible given the new commit(s), if any, obtained from upstream.At this point, if your master has been changed and it's your current branch, your repository is now out of sync: your index and work-tree no longer match your master. However, git fetch has left the correct hash ID in .git/FETCH_HEAD as well, and your git pull now goes on to the reset-like update. This actually uses the equivalent of git read-tree rather than git reset, but as long as it succeeds—given the pre-pull checks, it should succeed—the end effect is the same: your index and work-tree will match the new commit.Alternatively, perhaps master is not your current branch. Perhaps your .git/HEAD contains instead ref: refs/heads/branch. In this case, your refs/heads/master is safely fast-forwarded the way git fetch would have done even without --update-head-ok. Your .git/FETCH_HEAD contains the same hash ID as your updated master, and your git pull runs git merge to attempt a merge—which may or may not be a fast-forward operation, depending on the commit to which your branch name branch points right now. If the merge succeeds, Git either makes a commit (real merge) or adjusts index and work-tree as before (fast-forward "merge") and writes the appropriate hash ID into .git/refs/heads/branch. If the merge fails, Git stops with a merge conflict and makes you clean up the mess as usual.The last possible case is that your HEAD is detached, but this works in the same way as for the ref: refs/heads/branch case. The only difference is that the new hash ID, when all is said and done, goes straight into .git/HEAD rather than into .git/refs/heads/branch. 这篇关于当您执行`git fetch上游master:master`和`git pull上游master:master`时,有什么区别?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!
10-11 05:25