新手问题 git 里 push request 注意事项

i5ting · 2014年11月18日 · 最后由 luikore 回复于 2014年11月21日 · 10065 次阅读

忽略本地本地

忽略本地本地,而不想给 push request 主项目改变的做法

git update-index --assume-unchanged Gemfile
git update-index --assume-unchanged Gemfile.lock 

fork 出来的项目和原有项目保持同步

当我们 在 github 上 fork 出一个项目后,如果原有的项目更新了,怎样保持我们 fork 出来的项目和原有项目保持同步呢并提交我们的代码更新呢?即怎样保持 fork 出的项目和上游项目保持更新,怎样创建 pull request?关键步骤是使用 git 的 rebase 命令。

步骤:

1、 在 Fork 的代码库中添加上游代码库的 remote 源,该操作只需操作一次即可。

如: 其中 # upstream 表示上游代码库名, 可以任意。

git remote add upstream https://github.scm.corp.ebay.com/montage/frontend-ui-workspace

2、将本地的修改提交 commit

3、 在每次 Pull Request 前做如下操作,即可实现和上游版本库的同步。

3.1 : git remote update upstream

3.2 : git rebase upstream/{branch name}

需要注意的是在操作 3.2 之前,一定要将 checkout 到{branch name}所指定的 branch,

如:

git checkout develop

4、 Push 代码到 Github

git push
  1. 要忽略本地,直接改 .gitignore 就好了。如有些文件确实别人不会有,比如 ctags/GNU Global 生成的 tag 文件。那就修改 $HOME/.gitignore_global 文件。
  2. 同步上游版本应该用 merge 而不是 rebase。

#1 楼 @hbin 首先谢谢回复

我这里的需求是 Gemfile,我用的是淘宝的源,而上游版本的是官方源,这有可能是部署在 linode 上的原因,所以我不能提交 Gemfile 的

用 .gitignore 是做不到这点的

你回复的第二个,我说不清楚,还是请高手们来吧

#1 楼 @hbin 事实上 rebase 比 merge 更好(同步上游的例子)。

当然可以直接使用 rebase 策略来 pull:

$ git pull --rebase upstream

这样可以省去 update 或 fetch 的步骤,不过若你要先预览对比再考虑 rebase(或 merge),那就还是分两步来做。

一般来说,一个人干活的项目无所谓,merge 也无妨,历史记录会难看点,但是只有你一个人总还是看得明白。而多人项目大家都用 merge 的时候,你看历史记录的时间线只会觉得头疼,rebase 会达到 merge 一样的目的,但是历史记录就好看多了。

当然,merge 的好处(without fast forward)是可以把一串历史记录合并成一个,这样你可以选择单分支浏览历史记录倒也不会嫌乱。不过 rebase 的意义在于如果一开始就坚持使用,那就根本不存在 “视觉筛选” 的难题,而且对于琐碎的提交记录,rebase 也可以做 squash 等等更细粒度的操作(前提是大家都有这个意识和习惯),怎么也比 merge 优雅的多。

不过 rebase 是有门槛的,相对 merge 而言,理解 rebase 的工作方式会更复杂,需要的操作也会比 merge 多一些。rebase 有搞乱历史记录的风险。rebase 不适用于 pull request(但是很适合同步上游库)。等等。

选择 merge 或 rebase 取决于团队的版本管理策略,以 pull request + review 为主的模式,merge 是合适的,特别是从 feature 合并到主干的过程,(不过单个 feature 自己的控制以 rebase 为主会很好)。

以中心式仓库为主的模式,rebase 显然是更好的选择——只要掌握它不会是障碍。

两者都是工具,根据情况选择合适的就是了。

#3 楼 @nightire 亲,我太喜欢你这种回答方式了,赞一个

@nightire 解释得很清楚。我个人也是非常喜欢 rebase,尤其 interactive 模式非常实用。

但是,如果你 fork 出的项目是以下情况,你应该用 merge 而不是 rebase。

  • 多人一起开发一个分支。因为用 rebase 去更新 upstream 之后,再 push 到 origin 就必须用 force update。这样其它的协作者再使用会有很多麻烦。
  • fork 出的项目需要长期开发,多次 commit,尤其是同一文件,同一处地方会多次改动。因为 git-rebase 会把你的 commits 在 upstream/{branch name} 之后 reapply one by one, in order。除非你能记得住你每一次 commit 的改动,否则,每一次 rebase 都会非常多的冲突需要去解决。

在哪用 rebase?

  • 一个 feature 开发完成,可以合到 master 之前,应该 rebase 一次。

#5 楼 @hbin 嗯,你列举的两个例子很有典型性,很认同。不过我想补充一些个人的想法:

  1. 多人一起开发一个分支,是否是 feature 分支?因为多人一起去开发一个主干分支(比如 develop)在我看来是一件不可思议的事情,而通常针对上游的更新都是由主干分支去 tracking 的,所以在我常用的工作模式下,这个问题不存在。如果是 feature 分支,由于它长期处于不稳定状态中(直到 feature stable 为止),因此它不应该直接去 tracking upstream,也就不会有从 upstream rebase 这种操作出现了。一旦 feature 完成可以合并到主干,然后主干 rebase upstream;或者主干直接 rebase upstream 之后再 cherrypick feature(feature 预先 squash 或 merge 了)。因此你描述的状况究其根源还是 Git 模型不太合理。

  2. 无论是 merge 还是 rebase 亦或是 pull 的时候采用其中一种方式,都是可以指定冲突解决策略的,诚如你所说,rebase 在 reapply one by one 的每一步中其实都在做和 merge 一样的差异对比,和 merge 的区别就在于是一次性留到最后再解决还是一步一步去解决罢了。

然而在实际中,每一步的解决策略不一样是非常罕见的(也就是某一步要 ours,某一步要 theirs,某一步要各取一点这种),出现这种情况的,即使是 merge 的最终合并,也一样需要你耗费很大精力去逐行处理。

绝大部分情况下,我们对于每一步的处理决策都是一致的,而且通常都是要 ours(因为如果全都是 theirs 那索性 rollback 得了),于是你可以在执行 rebase 的时候就指明解决冲突的策略,比如这样:

$ git rebase -s recursive -X ours

如此一来,Git 会自动递归的处理每一步的冲突,并且以 “我方” 的版本优先。于是你的问题也就不是问题了。

当然,绝大部分的情况并不能覆盖所有的可能,我在工作中也遇见过令人头疼的特例,但是 一)特例不应该影响我使用更好的选择,不能因噎废食,往往面对特例束手无策恰恰说明自己不够了解,还有成长的空间;二)就 rebase 这件事情来说,冲突解决策略非常多样化,我提到的 ours theirs 只是其中两种而已,有心人不妨通读一下 man pages 一定会收获更多。

总之我自己遵循的一个原则就是能不用 merge 就不用 merge,遇到棘手的情况就花时间找找更好的办法——不过我也觉得自己是有点偏执的,因此只作陈述,不作推荐。

无脑 pull 当前分支:

gup() {
  local br
  br=`git branch 2> /dev/null|\\grep '^*'|sed -e 's/..//;s/\\n//'`
  tainted=`git status --porcelain | \\grep -v '^\\?\\?'`
  if [[ $br == master ]]; then
    if [[ $tainted == '' ]]; then
      echo git stash
      git stash
    fi
    echo git fetch
    git fetch
    echo git rebase FETCH_HEAD $br
    git rebase FETCH_HEAD $br
  else
    if [[ -n $br ]]; then
      if [[ $tainted == '' ]]; then
        echo git stash
        git stash
      fi
      echo git pull --rebase origin $br
      git pull --rebase origin $br
    else
      echo seems not in any branch
    fi
  fi
}

还是觉得对冲突挨个看挨个删最简单... 有时没冲突也不代表 merge 成功, 测试没过还得改

#7 楼 @luikore 这个看起来好高级啊,求出处

#8 楼 @i5ting 自己写的... 就是简单看看在哪个分支就 rebase 哪个, 也没有应用什么策略

#9 楼 @luikore 感谢分享,很有用呀很有用,花了 2 分钟用你的代码 wrapper 了一下

npm install -g gup

然后执行gup命令就可以了

#10 楼 @i5ting orz 不至于吧...

需要 登录 后方可回复, 如果你还没有账号请 注册新账号