学习Git:跟踪与设置上游(-u)遥控器? [英] learning Git: tracking vs. setting upstream (-u) for remotes?

查看:103
本文介绍了学习Git:跟踪与设置上游(-u)遥控器?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在学习Git,并试图理解追踪远程和定义上游关系(使用 -u 标签)之间的区别。



对于master / origin / master,我一直使用

  git push origin master 

这似乎自动定义关系(尽管我不确定它是什么) 。



对于分支机构,我一直在使用

  git branch newbranch 
git push -u origin newbranch

我知道这设置了一个上游关系,但我再次不了解这种区别。



有人可以解释这个区别吗?

解决方案

是正确的,但我会描述底层的机制,因为直到我发现它是什么时,我发现跟踪的整个概念非常神秘。

Git分手这个跟踪信息分为两部分:远程的名称(通常是单词 origin ,就像你正在使用的那样),然后是git命令的名称 1 换句话说,如果你有远程登录访问权限,并且在那里登录并进入仓库,你可以运行 git log master 来查看已提交的内容。



如果您查看 .git / config 文件,你会看到,对于每个本地分支跟踪的东西,这两部分。例如,假设您有一个名为 experiment 的本地分支,您已设置跟踪 origin / master 。这会导致:

$ $ p $ [branchexperiment]
remote = origin
merge = master

但是这个跟踪分支还有一部分内容:当你运行 git fetch origin ,并且在 origin 的分支 master 上有新的东西, fetch 步骤更新您的本地 origin / master 。这个名称 - 首先使用远程名称 origin ,然后使用斜杠 / ,然后显示分支名称遥控器 - 就是你如何看到遥控器上发生了什么。在完成 git fetch 之后,它会将远程的分支名称(及其相应的分支提示的SHA-1)复制到本地存储库,并将其重命名为远程名称在前面。

它实际上是更新 origin / master的 git fetch 步骤等等,只有一次完成,这个跟踪的东西是否有任何有用的效果。 Git现在可以告诉你,你提前和/或落后于一些提交。而且,现在您可以运行一个像 git log origin / master 这样的命令来查看发生了什么 - 或者更有趣的是, git log --oneline master。 .origin / master 来查看你自己还没有的他们的提交:实质上是什么提取带来的 - 和 git log --oneline origin / master。 .master 看到你的提交,他们还没有。 (如果你已经完成了合并或重新绑定,那么看到你的获取带来了什么已经太迟了,因为现在你已经拥有了他们的东西,因为合并或重新绑定。)



所有这些古怪的东西都是 git pull git pull 命令实际上只是一个快捷方式,首先运行 git fetch ,然后运行 git merge (或者,如果你重定向它, git rebase )。要分别执行这些步骤,请运行 git fetch origin ,然后运行 git rebase origin / master 。由于历史原因, 2 git pull 将分支的 remote 命名为,在这种情况下<$ c

$ hr

$ master $ ,而不是将它重命名的名称更改为您的存储库。因此,以此为背景,我们来看看一些命令:


  • git fetch remote :这根本不需要任何分支名称。它调用给定的远程,询问它现在的所有分支,并更新你的存储库,在 origin / 名称下记录所有这些更新(以免影响任何你当地的分行)。换句话说,这会更新分支机构可能(或不可能)追踪的名称,但它不需要知道什么是或不追踪什么。


  • 正在跟踪 origin / X git status 也可以告诉你,如果你的 X 上的提交不在 origin / X ,反之亦然。 $ c> git rebase
    :这些需要一些方法来知道要合并的内容,或者要重新绑定到的内容。你可以明确地命名它,但是如果你告诉你的git你的分支 X 正在跟踪 origin / X ,那么每当你在分支 X git merge git rebase 将知道该怎么做。 :这是设置或更改您的当前分支正在跟踪的主要命令。换句话说,如果你现在在分支上 X ,这会更新 branch.X.remote branch.X.merge ,这样你就不必使用两个单独的 git config 命令。您还可以使用 git branch --unset-upstream 来删除
    跟踪信息。


    <如果你没有提供额外的信息,它使用当前分支的远程(它的跟踪信息的前半部分)来决定哪个远程打电话。无论您是否给 git push 一个远程名称,下一部分取决于您是否给它一个refspec。如果你不这样做, git push 使用 push.default 来决定使用哪个refspec。




等等,什么是refspec?



第二种最简单的形式refspec只是两个分支名称,它们之间有冒号,如 master:master 。对于 git push ,左边的名称是您的分支名称,右边的​​名称是他们的 - 其他git的分支名称。如果省略,您可以得到最简单的形式,其中远程端名称 - 后面的名称: - 是由一个有点复杂的过程选择的(在 git push 文档),它实际上取决于更多配置变量以及是否设置了上游。



那么 git push -u ?这只是一个方便的捷径:在许多方面, git branch --set-upstream-to 是执行两个 git config 命令, git push -u refspec 是执行推送的快捷方式,然后执行 git分支--set-upstream-to 。你必须给 push a refspec这个做任何有用的事。



如果你给一个half refspec如 master ?那么,如上所述,你的git选择给远程git的名字是由一个复杂的过程找到的,但是如果你还没有设置上游(这很可能,如果你是首先执行 git push -u )它将与您的本地名称相同。所以 git push -u origin master 可能是意味着 git push -u origin master:master 那么在最后意味着 git branch --set-upstream-to origin / master

如果您提供了更充分的refspec,例如 git push -u origin experiment:feature ,这会推动您的实验分支到 origin ,要求 origin 将其称为特征,然后执行 - set-upstream-to origin / feature 。请注意,此时,本地分支的上游名称与本地名称不同。 Git对此很好,只要确定你也是。 : - )

git有更多聪明的技巧:


  • <如果你运行 git checkout 分支 分支 还没有存在,有一个明显的远程跟踪分支,如 origin / branch ,git将创建一个新的本地 分支 ,它已经在跟踪 origin / 分支 。 (也就是说,本地分支的远程设置为 origin ,并且它的 merge 设置为 分支 。)
  • 你运行 git branch local-name remote-tracking-name ,git会自动设置本地分支来跟踪相应的远程追踪分支。 (你可以配置git是否需要这个,但这是默认设置。)



摘要: git fetch 用于更新跟踪使用的内容( origin / * 条目,用于远程 origin )。一旦完成了,包括使用 git pull ,它运行 git fetch 3 然后,你会看到更多来自命令的信息,比如 git status ;并且像 git rebase 这样的命令可以用它来知道如何做rebase,而不必再告诉它更多。



<有一个更有趣的转折:任何分支的上游可以在你自己的本地存储库中。为了达到这个目的,你将该分支的 remote 设置为(一个字面点),合并到分支名称。你不必知道如何做到这一点,因为你可以做 git branch --set-upstream-to master ,例如,让你当前的分支跟踪你的自己 master



传入和传出



Mercurial用户可能想知道如何获得 hg incoming hg outgoing 的效果。前者告诉你你的上游是什么,你不知道。后者告诉你你有什么,他们没有。事实证明,这在现代git中很容易实现,因为git有一个特殊的语法 @ {u} 来查找当前分支的上游。



换句话说,如果你在 master master 轨道上 origin / master @ {u} (您可以拼写为 @ {upstream } )是写入 origin / master 的另一种方法。所以 origin / master..master 只是写 @ {u} .. master 的更长的方法。 ,如果您位于 master HEAD 也名称 master ,并省略分支名称告诉git使用 HEAD ,所以 @ {u} .. 就足够了。



如上所述,在适当的远程运行 git fetch 你可以使用 git log 来找到他们有什么你不知道的和你有什么他们不知道的。你必须运行这个 git fetch 步骤(并且不要希望此时发生合并或重新绑定)。



因此:

  git config --global alias.incoming'!git fetch& &安培; git log --oneline .. @ {u}'
git config --global alias.outgoing'!git fetch&& git log --oneline @ {u} ..'

(在某些shell中,您可能需要一个<在$ 之前的code> \ ,或者其他引用技巧,并且可能更容易插入别名编辑器运行 git config --global --edit )。



当然,您可以更改 - oneline 部分给你喜欢的任何选项。 (而且我喜欢为我自己手动运行我自己的 git fetch 步骤,这样可以简化别名到 alias.incoming = log - oneline .. @ {u} ,例如。 4 这主要避免不断纠缠上游。)






1 如果你保持你的分支名称与他们的相同,你没有机会看到它。但是一旦你开始大量使用分支机构,你可能会得到几个分支机构,这些分支机构都跟踪相同的上游,然后它真的很重要。

2 git pull 实际上早于遥控器和远程追踪分支。它仍然有各种各样的怪异因此。

3 如果您的git版本早于1.8.4,当 git pull 运行 git fetch fetch 步骤不会更新远程跟踪分支。这是作为一个功能,但它是一个坏的功能,更新的git版本更新。然而,这确实意味着如果你有一个旧的git,你应该小心使用 pull 脚本:这是一个不方便的方便。



4 在编辑中修复:我意外地写了 alias.incoming = git log ... 。假设Git别名是其他的git命令(如 log ),所以您希望省略 git 除非整个别名以感叹号开头,在这种情况下,整个别名会传递给shell跑。我实际上已经忘记了当所有命令的拼写方式如 git-log git-branch git-fetch 等等,但它一定不那么复杂......: - )


I am learning Git and am attempting to understand the difference between "tracking" a remote and defining an "upstream" relationship with it (with the -u tag).

For master to origin/master, I have been using

git push origin master

which seems to automatically define a relationship (although I am not sure what it is).

For branches, I have been using

git branch newbranch
git push -u origin newbranch

I know this sets an upstream relationship, but again I don't understand the distinction.

Can someone explain the difference?

解决方案

Both answers here are correct, but I'll describe the underlying mechanism, because until I found out what it was, I found the whole notion of "tracking" quite mysterious.

Git breaks up this "tracking" information into two parts: the name of the remote—usually the word origin, like you're using—and then the name that git commands on that remote use to name the branch.1 In other words, if you have login access to the remote, and you log in over there and go into the repository, you might run git log master to see what has been committed.

If you peek into your .git/config file, you will see, for each local branch that's "tracking" something, these two parts. For instance, let's say you had a local branch named experiment, that you have set up to track origin/master. This would result in:

[branch "experiment"]
    remote = origin
    merge = master

But there is one more part to this tracking-branch stuff: when you run git fetch origin, and there's something new on branch master on origin, the fetch step updates your local origin/master. This name—with the remote-name origin first, then a slash /, then the branch name as it appears on the remote—is how you can see what's happened on the remote. After your git fetch is done, it copies the remote's branch-names (and their corresponding SHA-1s for their branch-tips) to your local repository, renaming them with the remote-name in front.

It's actually the git fetch step that updates origin/master and so on, and only once that's done, does this "tracking" stuff have any useful effect. Git can now tell you that you're ahead and/or behind by some number of commits. And, you can now run a command like git log origin/master to see what's happening there—or more interestingly, git log --oneline master..origin/master to see "their" commits that you don't have yet: essentially, "what fetch brought in"—and git log --oneline origin/master..master to see "your" commits that they don't have yet. (If you've already done a merge or rebase, it's too late to see what your fetch brought in, because now you have what they had then, as a result of your merge-or-rebase.)

The oddball in all of this is git pull. The git pull command is really just a short-cut that runs git fetch first, and then runs git merge (or, if you redirect it, git rebase). To do these steps separately, you run git fetch origin, then git merge origin/master or git rebase origin/master. For historical reasons,2 git pull takes the remote's name for the branch, in this case master, rather than the name it winds up being renamed-to in your repository.


So, with that as background, let's look at some commands:

  • git fetch remote: This doesn't need any branch names at all. It calls up the given remote, asks it where all its branches are now, and updates your repository, recording all these updates underneath the origin/ names (so as not to affect any of your local branches). In other words, this updates the names your branches may (or may not) be tracking, but it doesn't need to know anything about what is or isn't tracking what.

  • git status: If it says you're "on branch X", and branch X is tracking origin/X, git status can also tell you if you have, on your X, commits that are not on origin/X, and vice versa.

  • git merge and git rebase: These need some way to know what to merge, or what to rebase onto. You can name it explicitly, but if you tell your git that your branch X is tracking origin/X, then whenever you're on branch X, git merge or git rebase will know what to do.

  • git branch --set-upstream-to origin/X: This is the main command that sets or changes what your current branch is tracking. In other words, if you're on branch X now, this updates branch.X.remote and branch.X.merge for you, so that you don't have to use two separate git config commands. You can also use git branch --unset-upstream to remove the tracking information.

  • git push: if you give it no additional information, it uses the current branch's "remote"—the first half of its tracking info—to decide which remote to call up. Whether or not you give git push a remote name, the next part depends on whether you give it a "refspec". If you don't, git push uses push.default to decide what refspec to use.

Wait, what's a refspec?

The second-simplest form of a refspec is just two branch names with a colon between them, like master:master. For git push, the name on the left is your branch name, and the name on the right is their—the other git's—branch name. If you omit the : you get the simplest form, where the remote-side name—the one that would follow the :—is chosen by a somewhat complicated process (described in the git push documentation), which actually depends on more configuration variables and whether you've set an upstream.

What about git push -u? That's just a convenient shortcut: in much the way that git branch --set-upstream-to is a shortcut for doing two git config commands, git push -u refspec is a shortcut for doing a push, and then doing a git branch --set-upstream-to as well. You must give push a refspec for this to do anything useful.

What if you give a "half refspec" like master? Well, as noted above, the name your git chooses to give to the remote's git is found by a complicated process, but if you haven't set an upstream yet (which is fairly likely if you're doing git push -u in the first place) it's going to be the same as your local name. So git push -u origin master probably "means" git push -u origin master:master, and that then means git branch --set-upstream-to origin/master, in the end.

If you give a fuller refspec, like git push -u origin experiment:feature, this will push your experiment branch to origin, asking origin to call it feature, and then do a --set-upstream-to origin/feature. Note that at this point, the upstream name of your local branch differs from the local name. Git is fine with this; just be sure you are, too. :-)

There are more clever tricks that git has:

  • If you run git checkout branch and branch does not yet exist, and there's a single "obvious" remote-tracking branch such as origin/branch, git will create a new, local branch that is already tracking origin/branch. (That is, the local branch will have its remote set to origin and its merge set to branch.)

  • If you run git branch local-name remote-tracking-name, git will automatically set up the local branch to track the corresponding remote-tracking branch. (You can configure git as to whether you want this, but that's the default.)

Summary: git fetch updates the things that tracking uses (the origin/* entries, for remote origin). Once that's done—including if it's done by using git pull, which runs git fetch3then you see more information from commands like git status; and commands like git rebase use it to know how to do the rebase, without your having to tell it anything more.

There's one more interesting twist: any branch's "upstream" can be in your own local repository. To get this, you set the remote for that branch to . (a literal dot), and the merge to the name of the branch. You don't have to know how to do this, because you can do git branch --set-upstream-to master, for instance, to make your current branch track your own master.

"Incoming" and "outgoing"

Mercurial users may wonder how you can get the effect of hg incoming or hg outgoing. The former tells you what your upstream has, that you don't. The latter tells you what you have, that they don't. As it turns out, this is easy to do in modern git, because git has a special syntax, @{u}, to find the current branch's upstream.

In other words, if you're on master and master tracks origin/master, @{u} (which you can spell out as @{upstream}) is just another way to write origin/master. So origin/master..master is just a longer way to write @{u}..master. And, if you're on master, HEAD also names master, and omitting a branch name tells git to use HEAD, so @{u}.. suffices.

As noted above, after you've run git fetch on the appropriate remote, you can use git log to find "what they have that you don't" and "what you have that they don't". You do have to run this git fetch step (and do not want a merge or rebase to occur at this point).

So:

git config --global alias.incoming '!git fetch && git log --oneline ..@{u}'
git config --global alias.outgoing '!git fetch && git log --oneline @{u}..'

(in some shells you may need a \ in front of the !, or other quoting tricks, and it may be easier to just insert the aliases with your editor by running git config --global --edit).

You can, of course, change the --oneline part to whatever options you prefer. (And I like to leave the git fetch step for me to manually run myself anyway, which simplifies the alias to just alias.incoming = log --oneline ..@{u}, for instance.4 This mainly just avoids constantly pestering the upstream.)


1If you keep your branch names the same as theirs, you don't get a chance to see this. But once you start using branches heavily, you'll probably wind up with several branches that all track the same upstream, and then it really matters.

2git pull actually predates remotes and remote-tracking branches. It still has all kinds of weirdness because of this.

3If your git version is older than 1.8.4, when git pull runs git fetch, the fetch step doesn't update remote-tracking branches. This was intended as a feature, but it was a bad feature, and newer git versions update. This does mean, though, that if you have an old git, you should be wary of using the pull script: it's an inconvenient convenience.

4Fixed in an edit: I accidentally wrote alias.incoming = git log .... Git aliases are assumed to be other git commands (like log), so you want to leave out the git part, unless the whole alias starts with an exclamation point !, in which case the whole alias is passed to the shell to run. I actually forget, now, how aliases worked back when all the commands were spelled like git-log, git-branch, git-fetch, and so on, but it must have been less complicated... :-)

这篇关于学习Git:跟踪与设置上游(-u)遥控器?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆