git fetch 和 pull

来源:互联网 发布:nginx 浏览器缓存设置 编辑:程序博客网 时间:2024/04/28 04:01

原文地址:http://www.oschina.net/translate/git-fetch-and-merge?cmp

Git 少用 Pull 多用 Fetch 和 Merge

返回原文英文原文:git: fetch and merge, don’t pull

This is too long and rambling, but to steal a joke from Mark Twain Blaise Pascal I haven’t had time to make it shorter yet.  There is some discussion of this post on the git mailing list, but much of it is tangential to the points I’m trying to make here.

One of the git tips that I find myself frequently passing on to people is:

Don’t use git pull, use git fetch and then git merge.

The problem with git pull is that it has all kinds of helpful magic that means you don’t really have to learn about the different types of branch in git. Mostly things Just Work, but when they don’t it’s often difficult to work out why. What seem like obvious bits of syntax for git pull may have rather surprising results, as even a cursory look through the manual page should convince you.

The other problem is that by both fetching and merging in one command, your working directory is updated without giving you a chance to examine the changes you’ve just brought into your repository. Of course, unless you turn off all the safety checks, the effects of a git pull on your working directory are never going to be catastrophic, but you might prefer to do things more slowly so you don’t have to backtrack.

译者信息

本文有点长而且有点乱,但就像Mark Twain Blaise Pascal的笑话里说的那样:我没有时间让它更短些。在Git的邮件列表里有很多关于本文的讨论,我会尽量把其中相关的观点列在下面。

我最常说的关于git使用的一个经验就是:

不要用git pull,用git fetch和git merge代替它。

git pull的问题是它把过程的细节都隐藏了起来,以至于你不用去了解git中各种类型分支的区别和使用方法。当然,多数时候这是没问题的,但一旦代码有问题,你很难找到出错的地方。看起来git pull的用法会使你吃惊,简单看一下git的使用文档应该就能说服你。

将下载(fetch)和合并(merge)放到一个命令里的另外一个弊端是,你的本地工作目录在未经确认的情况下就会被远程分支更新。当然,除非你关闭所有的安全选项,否则git pull在你本地工作目录还不至于造成不可挽回的损失,但很多时候我们宁愿做的慢一些,也不愿意返工重来。

Branches

Before I explain the advice about git pull any further it’s worth clarifying what a branch is. Branches are often described as being a “line of development”, but I think that’s an unfortunate expression since:

  • If anything, a branch is a “directed acyclic graph of development” rather than a line.
  • It suggests that branches are quite heavyweight objects.

I would suggest that you think of branches in terms of what defines them: they’re a name for a particular commit and all the commits that are ancestors of it, so each branch is completely defined by the SHA1sum of the commit at the tip. This means that manipulating them is a very lightweight operation – you just change that value.

译者信息

分支(Branches)

在说git pull之前,我们需要先澄清分支的概念(branches)。很多人像写代码似的用一行话来描述分支是什么,例如:

  • 准确而言,分支的概念不是一条线,而类似于开发中的有向无环图
  • 分支类似于一个重量级的大对象集合。

我认为你应该这样来理解分支的概念:它是用来标记特定的代码提交,每一个分支通过SHA1sum值来标识,所以对分支进行的操作是轻量级的--你改变的仅仅是SHA1sum值。

This definition has some perhaps unexpected implications. For example, suppose you have two branches, “stable” and “new-idea”, whose tips are at revisions E and F:

  A-----C----E ("stable")   \    B-----D-----F ("new-idea")

So the commits A, C and E are on “stable” and A, B, D and F are on “new-idea”. If you then merge “new-idea” onto “stable” with the following commands:

    git checkout stable   # Change to work on the branch "stable"    git merge new-idea    # Merge in "new-idea"

… then you have the following:

  A-----C----E----G ("stable")   \             /    B-----D-----F ("new-idea")

If you carry on committing on “new idea” and on “stable”, you get:

  A-----C----E----G---H ("stable")   \             /    B-----D-----F----I ("new-idea")

So now A, B, C, D, E, F, G and H are on “stable”, while A, B, D, F and I are on “new-idea”.

Branches do have some special properties, of course – the most important of these is that if you’re working on a branch and create a new commit, the branch tip will be advanced to that new commit. Hopefully this is what you’d expect. When merging withgit merge, you only specify the branch you want to merge into the current one, and only your current branch advances.

译者信息

这个定义或许会有意想不到的影响。比如,假设你有两个分支,“stable” 和 “new-idea”, 它们的顶端在版本 E 和 F:

  A-----C----E ("stable")   \    B-----D-----F ("new-idea")

所以提交(commits) A, C和 E 属于“stable”,而 A, B, D 和 F 属于 “new-idea”。如果之后你用下面的命令 将“new-idea” merge 到 “stable” :

    git checkout stable   # Change to work on the branch "stable"    git merge new-idea    # Merge in "new-idea"

…那么你会得到这个:

  A-----C----E----G ("stable")   \             /    B-----D-----F ("new-idea")

要是你继续在“new idea” 和“stable”分支提交, 会得到:

  A-----C----E----G---H ("stable")   \             /    B-----D-----F----I ("new-idea")

因此现在A, B, C, D, E, F, G 和 H 属于 “stable”,而A, B, D, F 和 I 属于 “new-idea”。

当然了,分支确实有些特殊的属性——其中最重要的是,如果你在一个分支进行作业并创建了一个新的提交(commits),该分支的顶端将前进到那个提交(commits)。这正是你所希望的。当用git merge进行合并(merge)的时候,你只是指定了要合并到当前分支的那个并入分支,以及当前分支的当前进展。

Another common situation where this view of branches helps a lot is the following: suppose you’re working on the main branch of a project (called “master”, say) and realise later that what you’ve been doing might have been a bad idea, and you would rather it were on a topic branch. If the commit graph looks like this:

   last version from another repository      |      v  M---N-----O----P---Q ("master")

Then you separate out your work with the following set of commands (where the diagrams show how the state has changed after them):

  git branch dubious-experiment  M---N-----O----P---Q ("master" and "dubious-experiment")  git checkout master  # Be careful with this next command: make sure "git status" is  # clean, you're definitely on "master" and the  # "dubious-experiment" branch has the commits you were working  # on first...  git reset --hard <SHA1sum of commit N>       ("master")  M---N-------------O----P---Q ("dubious-experiment")  git pull # Or something that updates "master" from           # somewhere else...  M--N----R---S ("master")      \       O---P---Q ("dubious-experiment")

This is something I seem to end up doing a lot… :)

译者信息

另一个表明使用分支会有很大帮助的观点的常见情形是:假设你直接工作在一个项目的主要分支(称为“主版本”),当你意识到你所做的可能是一个坏主意时已经晚了,这时你肯定宁愿自己是工作在一个主题分支上。如果提交图看起来像这样:

   last version from another repository      |      v  M---N-----O----P---Q ("master")

那么你把你的工作用下面的一组命令分开做(如图显示的是执行它们之后所更改的状态):

  git branch dubious-experiment  M---N-----O----P---Q ("master" and "dubious-experiment")  git checkout master  # Be careful with this next command: make sure "git status" is  # clean, you're definitely on "master" and the  # "dubious-experiment" branch has the commits you were working  # on first...  git reset --hard <SHA1sum of commit N>       ("master")  M---N-------------O----P---Q ("dubious-experiment")  git pull # Or something that updates "master" from           # somewhere else...  M--N----R---S ("master")      \       O---P---Q ("dubious-experiment")

这是个看起来我最终做了很多的事情。

Types of Branches

The terminology for branches gets pretty confusing, unfortunately, since it has changed over the course of git’s development. I’m going to try to convince you that there are really only two types of branches. These are:

(a) “Local branches”: what you see when you type git branch, e.g. to use an abbreviated example I have here:

       $ git branch         debian         server       * master

(b) “Remote-tracking branches”: what you see when you type git branch -r, e.g.:

       $ git branch -r       cognac/master       fruitfly/server       origin/albert       origin/ant       origin/contrib       origin/cross-compile

The names of tracking branches are made up of the name of a “remote” (e.g. origin, cognac, fruitfly) followed by “/” and then the name of a branch in that remote respository. (“remotes” are just nicknames for other repositories, synonymous with a URL or the path of a local directory – you can set up extra remotes yourself with “git remote”, but “git clone” by default sets up “origin” for you.)

译者信息

分支类型

分支这个术语不太容易理解,而且在git的开发过程中发生了很多变化。但简单来说git的分支只有两种:

a)“本地分支(local branches)” ,当你输入“git branch”时显示的。例如下面这个小例子:

       $ git branch         debian         server       * master
b)“远程跟踪分支(Remote-tracking branches)” ,当你输入“git branch -r”是显示的,如:


       $ git branch -r       cognac/master       fruitfly/server       origin/albert       origin/ant       origin/contrib       origin/cross-compile
从上面的输出可以看到,跟踪分支的名称前有一个“远程的”标记名称(如 :origin, cognac, fruitfly)后面跟一个“/”,然后远程仓库里分支的真正名称。(“远程名称”是一个代码仓库别名,和本地目录或URL是一个含义,你可以通过"git remote"命令自由定义额外的“远程名称”。但“git clone”命令默认使用的是“origin”这个名称。)

If you’re interested in how these branches are stored locally, look at the files in:

  • .git/refs/heads/[for local branches]
  • .git/refs/remotes/[for tracking branches]

Both types of branches are very similar in some respects – they’re all just storedlocally as single SHA1 sums representing a commit. (I emphasize “locally” since some people see “origin/master” and assume that in some sense this branch is incomplete without access to the remote server – that isn’t the case.)

Despite this similarity there is one particularly important difference:

  • The safe ways to change remote-tracking branches are with git fetch or as a side-effect ofgit-push; you can’t work on remote-tracking branches directly. In contrast, you can always switch to local branches and create new commits to move the tip of the branch forward.

So what you mostly do with remote-tracking branches is one of the following:

  • Update them with git fetch
  • Merge from them into your current branch
  • Create new local branches based on them

译者信息 如果你对分支在本地是如何存储感兴趣的话,看看下面文件:
  •   .git/refs/head/[本地分支]
  •   .git/refs/remotes/[正在跟踪的分支]
两种类型的分支在某些方面十分相似-它们都只是在本地存储一个表示提交的SHA1校验和。(我强调“本地”,因为许多人看到"origin/master" 就认为这个分支在某种意义上说是不完整的,没有访问远端服务器的权限- 其实不是这种情况。)
不管如何相似,它们还是有一个特别重大的区别:
  •   更改远端跟踪分支的安全方法是使用git fetch或者是作为git-push副产品,你不能直接对远端跟踪分支这么操作。相反,你总得切换到本地分支,然后创建可移动到分支顶端的新提交 。
因此,你对远端跟踪分支最多能做的是下面事情中的一件:
  •  使用git fetch 更新远端跟踪分支
  •  合并远端跟踪分支到当前分支
  •  根据远端跟踪分支创建本地分支

Creating local branches based on remote-tracking branches

If you want to create a local branch based on a remote-tracking branch (i.e. in order to actually work on it) you can do that withgit branch –track or git checkout –track -b, which is similar but it also switches your working tree to the newly created local branch. For example, if you see ingit branch -r that there’s a remote-tracking branch called origin/refactored that you want, you would use the command:

    git checkout --track -b refactored origin/refactored

In this example “refactored” is the name of the new branch and “origin/refactored” is the name of existing remote-tracking branch to base it on. (In recent versions of git the “–track” option is actually unnecessary since it’s implied when the final parameter is a remote-tracking branch, as in this example.)

译者信息

基于远程跟踪分支创建本地分支

如果你想基于远程跟踪分支创建本地分支(在本地分支上工作),你可以使用如下命令:git branch –trackgit checkout –track -b,两个命令都可以让你切换到新创建的本地分支。例如你用git branch -r命令看到一个远程跟踪分支的名称为“origin/refactored”是你所需要的,你可以使用下面的命令:

    git checkout --track -b refactored origin/refactored
在上面的命令里,“refactored”是这个新分支的名称,“origin/refactored”则是现存远程跟踪分支的名称。(在git最新的版本里,例子中‘-track’选项已经不需要了,如果最后一个参数是远程跟踪分支,这个参数会被默认加上。)

The “–track” option sets up some configuration variables that associate the local branch with the remote-tracking branch. These are useful chiefly for two things:

  • They allow git pull to know what to merge after fetching new remote-tracking branches.
  • If you do git checkout to a local branch which has been set up in this way, it will give you a helpful message such as:
    Your branch and the tracked remote branch 'origin/master'    have diverged, and respectively have 3 and 384 different    commit(s) each.

… or:

    Your branch is behind the tracked remote branch    'origin/master' by 3 commits, and can be fast-forwarded.

The configuration variables that allow this are called “branch.<local-branch-name>.merge” and “branch.<local-branch-name>.remote”, but you probably don’t need to worry about them.

译者信息

“–track”选项会设置一些变量,来保持本地分支和远程跟踪分支的相关性。他们对下面的情况很有用:

  • git pull命令下载新的远程跟踪分支之后,可以知道合并到哪个本地分支里
  • 使用git checkout检查本地分支时,可以输出一些有用的信息:
    Your branch and the tracked remote branch 'origin/master'    have diverged, and respectively have 3 and 384 different    commit(s) each.
或者:
    Your branch is behind the tracked remote branch    'origin/master' by 3 commits, and can be fast-forwarded.
允许使用的配置变量是:“branch.<local-branch-name>.merge”和“branch.<local-branch-name>.remote”,但通常情况下你不用考虑他们的设置。

You have probably noticed that after cloning from an established remote repositorygit branch -r lists many remote-tracking branches, but you only have one local branch. In that case, a variation of the command above is what you need to set up local branches that track those remote-tracking branches.

You might care to note some confusing terminology here: the word “track” in “–track” means tracking of a remote-tracking branch by a local branch, whereas in “remote-tracking branch” it means the tracking of a branch in a remote repository by the remote-tracking branch. Somewhat confusing…

Now, let’s look at an example of how to update from a remote repository, and then how to push changes to a new repository.

译者信息

当从远程代码仓库创建一个本地分支之后,你会注意到,“git branch -r”能列出很多远程跟踪分支,但你的电脑上只有一个本地分支,你需要给上面的命令设置一个参数,来指定本地分支和远程分支的对应。

有一些术语上的说法容易混淆需要注意一下:“track”在当作参数"-track"使用时,意思指通过本地分支对应一个远程跟踪分支。在远程跟踪分支中则指远程代码仓库中的跟踪分支。有点绕口。。。
下面我们来看一个例子,如何从远程分支中更新本地代码,以及如何把本地分支推送到一个新的远程仓库中。

Updating from a Remote Repository

So, if I want get changes from the remote repository called “origin” into my local repository I’ll typegit fetch origin and you might see some output like this:

  remote: Counting objects: 382, done.  remote: Compressing objects: 100% (203/203), done.  remote: Total 278 (delta 177), reused 103 (delta 59)  Receiving objects: 100% (278/278), 4.89 MiB | 539 KiB/s, done.  Resolving deltas: 100% (177/177), completed with 40 local objects.  From ssh://longair@pacific.mpi-cbg.de/srv/git/fiji     3036acc..9eb5e40  debian-release-20081030 -> origin/debian-release-20081030   * [new branch]      debian-release-20081112 -> origin/debian-release-20081112   * [new branch]      debian-release-20081112.1 -> origin/debian-release-20081112.1     3d619e7..6260626  master     -> origin/master

The most important bits here are the lines like these:

     3036acc..9eb5e40  debian-release-20081030 -> origin/debian-release-20081030   * [new branch]      debian-release-20081112 -> origin/debian-release-20081112

The first line of these two shows that your remote-tracking branch origin/debian-release-20081030 has been advanced from the commit 3036acc to 9eb5e40. The bit before the arrow is the name of the branch in the remote repository. The second line similarly show that since we last did this, a new remote tracking branch has been created. (git fetch may also fetch new tags if they have appeared in the remote repository.)

译者信息

从远端仓库进行更新

如果我想从远端的源仓库更新到本地的代码仓库,可以输入“git fetch origin”的命令,该命令的输入类似如下格式:

  remote: Counting objects: 382, done.  remote: Compressing objects: 100% (203/203), done.  remote: Total 278 (delta 177), reused 103 (delta 59)  Receiving objects: 100% (278/278), 4.89 MiB | 539 KiB/s, done.  Resolving deltas: 100% (177/177), completed with 40 local objects.  From ssh://longair@pacific.mpi-cbg.de/srv/git/fiji     3036acc..9eb5e40  debian-release-20081030 -> origin/debian-release-20081030   * [new branch]      debian-release-20081112 -> origin/debian-release-20081112   * [new branch]      debian-release-20081112.1 -> origin/debian-release-20081112.1     3d619e7..6260626  master     -> origin/master
最重要的是这两行:
     3036acc..9eb5e40  debian-release-20081030 -> origin/debian-release-20081030   * [new branch]      debian-release-20081112 -> origin/debian-release-20081112
第一行表明远端的origin/debian-release-20081030分支的提交(commit)ID已经从3036acc更新为9eb5e40。箭头前的部分是远端分支的名称。第二行是我们采取的动作,创建远程跟踪分支(如果远程仓库有新的tags,git fetch也会一并下载到本地)。

0 0
原创粉丝点击