更多详细使用,参考Nie Yong Note's Git

远程版本和分支

从远程clone了一个项目到本地,默认只clone了master分支。使用git br查看分支,如下所示:

nieyong@mac:~/github/reveal.js > git br
* master

但是该项目在远程(github上)还有两个分支,dev和gh-pages,我在本地怎么看到呢?使用-r选项,表示remote的含义。-r选项表示查看在clone的时候已经保存在本地的远程分支信息。

nieyong@mac:~/github/reveal.js > git br -r
  origin/HEAD -> origin/master
  origin/dev
  origin/gh-pages
  origin/master

clone本地之后,别人在远程版本库github中新创建了一个分支,那么在本地就没有这个远程分支信息,使用上面的git br -r命令没用了。那么使用下面的命令:

nieyong@mac:~/github/reveal.js > git ls-remote --heads
From https://github.com/nieyong/reveal.js.git
4ff462078b9ce4acd27210764bac3950db046173	refs/heads/dev
b12a93f79cbddb1f72d5bf5a8d71fb0e0eb8a248	refs/heads/gh-pages
09507bb4eee7c6cc48f5f3c02eb70679e18cd7da	refs/heads/master

为什么本地没有分支dev和gp-pages呢?看两个地方可以看出一些端倪:

nieyong@mac:~/github/reveal.js > git show-ref
09507bb4eee7c6cc48f5f3c02eb70679e18cd7da refs/heads/master
09507bb4eee7c6cc48f5f3c02eb70679e18cd7da refs/remotes/origin/HEAD
4ff462078b9ce4acd27210764bac3950db046173 refs/remotes/origin/dev
b12a93f79cbddb1f72d5bf5a8d71fb0e0eb8a248 refs/remotes/origin/gh-pages
09507bb4eee7c6cc48f5f3c02eb70679e18cd7da refs/remotes/origin/master
c96826a3f2b2572c9cfc7ffbc24615d8e2834f8f refs/tags/v0.3
60bf19789090d1c9da1d52a2da364205ed5ae7d7 refs/tags/v1.0
2313dfef6de10128feccb28d067a2be818e690ff refs/tags/v1.1
f59e64a5711fcc90afe6c396f3ccd4e7a59afa9f refs/tags/v1.2
43da46f06b602b179df18e86f442eb8dde9e0590 refs/tags/v1.3
c513500269c770c0a18fd944c17e6941d9c936d0 refs/tags/v1.4
310ba170c1db7576fb4f77288aa1003b7d72d1bb refs/tags/v2.0
f51067b00e8099bb576858e70db6e2fc44ce3d61 refs/tags/v2.1
784fa9d2e3570054728d21f8098199dc9d4164b9 refs/tags/v2.2
d2cf21028eac378bdefe145fefa6d49841ecea1c refs/tags/v2.3
//注意,上面的refs/remotes/origin/*并不是真正意义的分支,而是类似于tag的一个引用,该引用在clone的时候从远程版本库复制到本地的.git/refs/remotes/origin/目录下。

nieyong@mac:~/github/reveal.js > more .git/config
//...
[remote "origin"]
        fetch = +refs/heads/*:refs/remotes/origin/*
        url = https://github.com/nieyong/reveal.js.git
[branch "master"]
        remote = origin
        merge = refs/heads/master

那么在clone之后,如何将一个远程分支真正checkout到本地呢?我们在使用上面的命令知道了远程还有dev分支之后,就可以使用下面的命令将该分支checkout到本地。

nieyong@mac:~/github/reveal.js > git co dev
Branch dev set up to track remote branch dev from origin.
Switched to a new branch 'dev'

//上面的命令相当于git co -b dev origin/dev

使用github的gui端无需手动查看远程分支。

本地创建了一个新的分支,如何上传到远程呢?

$ git push origin test

二进制文件冲突

对于二进制文件的冲突,你肯定不想通过编辑二进制文件来解决冲突,那是不可能完成的事情。你要做的就是:要么选择对方的修改,要么选择自己的修改。

你可以用git checkout的--theirs或--ours选项。

git pull
git checkout --theirs YOUR_BINARY_FILE
// git checkout --ours YOUR_BINARY_FILE
git add YOUR_BINARY_FILE
git commit -m 'merged with the remote repos.'
git push

删除某一次提交

使用git reset或者git revert命令。注意两点:

  1. git resetgit revert的区别,git reset是直接删除提交记录进行回溯,在graph上可以看到,被回溯的那几个提交记录已经不再任何分支的控制范围内。在这种情况下,默认情况下,是无法看到这几次提交的记录的。而git revert是使用一次相反的提交覆盖上次提交;
  2. git reset命令有3个重要的参数,分别是--mixed(默认)、--soft、--hard:
    • 其中--hard选项会造成修改内容不可恢复的后果,原因是该选项会将当前HEAD所指的内容覆盖掉缓存区域和工作空间中的内容;
    • --mixed选项则只会用HEAD所指内容将缓存区域的内容覆盖,这就是为什么使用命令git reset能够将已经缓存的内容清除掉,--mixed为reset的默认选项。
    • 选项--soft则只会将HEAD移动,并不会将现在HEAD所指的内容覆盖缓存空间和工作空间。如果使用reset命令之后工作空间并未用HEAD所指的内容覆盖(--soft、--mixed选项),就会存在工作空间处于修改状态,需要提交。

清除已经缓存的(staged)文件

修改了两个文件,然后使用命令git add .将这两个文件的修改都提交为缓存,也就是处于staged状态。这时,如果你想撤销掉某个文件的staged状态,使用命令git reset filename,其实这条命令的完整表达应该是git reset --mixed HEAD filename

参见《progit》的p28。

tag记录发布版本

多份代码拷贝的合并

区别于一份代码中有多个分支的合并,这里的应用场景描述如下:

有一份代码, 使用了git做版本控制 。然后这份代码被拷贝了两份,然后在每一份上分别有多次提交。最后,需要把两份代码上做的修改合并到同一份代码上。

一般用于没有server端的多人协作开发,一份代码用U盘拷贝给多人开发。

使用命令git pull可以达到目的。

git分支与合并

git分支与合并主要阅读了《pro git》一书的分支一章,强烈建议。

查看现在所有分支,分支前带有*号,表示现在所在分支

创建一个新的分支

切换到另外一个分支

创建一个新的分支并且同时切换到该分支

删除一个分支

合并某一个分支到本身所在的分支

git使用小技巧

将修改添加到版本库中,使用git add .命令,而不要使用git add *命令。因为*代表所有的文件,那么别.gitignore文件忽略的文件(或者文件夹)也会被添加。

使用git log时要实现自动换行,不要长行截断模式,可以设置LESS环境变量(分页器):

#export LESS=FRX

或者,你可以配置git的分页器(pager)配置文件为-FRX。如果你想使用原来的长行截断模式,那么就使用-FRSX选项。

#git config --global core.pager 'less -+$LESS -FRX'

中文无法显示,使用8进制字符编码来显示的问题:

#git config --global core.quotepath false

git diff的用法

查看文件的修改情况,使用命令 git diff src。

git diff命令没有选项,不会去比较已经位于暂存区(staged)的文件和上次提交的文件的区别。所以,查看使用命令git add加载到暂存区的文件(staged)所做的修改,使用命令:

git diff --cached

git checkout

代码在回溯时,到了一个未命名的分支。

使用下面的命令,会出现该情况:

ylqmatoMac-mini:CommLibrary ylq$ git checkout 563b17
Note: checking out '563b17'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b new_branch_name

HEAD is now at 563b178... *测试首页显示当前连接的mc
ylqmatoMac-mini:CommLibrary ylq$ git branch
* (no branch)
  master
  refact

git ignore

The idea is to keep locally your ".DS_Store", while removing them from the git repo.

The .gitignore will work only if those ".DS_Store" are first removed from the index, hence the "git rm --cached -f" (see git rm).

find . -name ".DS_Store" -exec git rm --cached -f {} \;.
git commit -m "delete files"
git push

下面这两种写法的区别: .xcodeproj/ .xcodeproj/

git log的用法

以适当的形式显示整个工程,或者多个文件,整个目录等的log信息。

[ny@localhost android-xbmcremote]$ git log --pretty=format:"%h - %ar : %s"
3dc5868 - 3 months ago : New release: v0.8.7 beta1
3dc5868 - 3 months ago : New release: v0.8.7 beta1
aebb506 - 4 months ago : Changed: Set network location as non-required for better tablet3dc5868 - 3 months ago : New release: v0.8.7 beta1

[ny@localhost android-xbmcremote]$ git log --date=short --pretty=format:"%h - %ad : %s"
3dc5868 - 2011-08-28 : New release: v0.8.7 beta1
aebb506 - 2011-08-14 : Changed: Set network location as non-required for better tablet support.
ced6d79 - 2011-08-14 : Changed: New icon for text entry

[ny@localhost android-xbmcremote]$ git log --date=short --pretty=format:"%ad : %s" --stat ./src/  ./res/layout/

详细的显示某次提交的log信息,修改了哪些文件的什么内容。

git show
git show 3dc5868    //显示某次提交

显示某次提交的修改了哪些文件。

github mac gui的sync操作

两人合作开发,从某个共同的节点开始,A做了几次提交,并且上传github。B也做了几次提交,在本地。然后B使用github的mac端gui做一个syn(同步到github)。如果A和B的修改没有冲突,那么B的提交就会rebase到A的提交上,并且上传github成功,这样看上去就是一个线性的提交纪录。如果B的修改和A有冲突,那么有冲突的提交版本就会rebase失败。

此时,可以查看到git的状态如下。

nieyong@mac:~/github/DJIAssistantForIphone > git st
# Not currently on any branch.
# You are currently rebasing.
#   (fix conflicts and then run "git rebase --continue")
#   (use "git rebase --skip" to skip this patch)
#   (use "git rebase --abort" to check out the original branch)
#
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#	modified:   AssistantForIphone/AssistantForIphone/ASSMore/ASSMore.m
#	modified:   AssistantForIphone/AssistantForIphone/ASSMore/subView/AccountsViewController.h
#	modified:   AssistantForIphone/AssistantForIphone/ASSMore/subView/AccountsViewController.m
#	new file:   AssistantForIphone/AssistantForIphone/ASSMore/subView/FeedbackViewController.h
#	new file:   AssistantForIphone/AssistantForIphone/ASSMore/subView/FeedbackViewController.m
#	new file:   AssistantForIphone/AssistantForIphone/ASSMore/subView/ImportExportParametersViewController.h
#	new file:   AssistantForIphone/AssistantForIphone/ASSMore/subView/ImportExportParametersViewController.m
#	modified:   AssistantForIphone/AssistantForIphone/ASSMore/subView/UserRegisterController.h
#	modified:   AssistantForIphone/AssistantForIphone/ASSMore/subView/UserRegisterController.m
#
# Unmerged paths:
#   (use "git reset HEAD <file>..." to unstage)
#   (use "git add <file>..." to mark resolution)
#
#	both modified:      AssistantForIphone/AssistantForIphone.xcodeproj/project.pbxproj

此时,我们不想rabase存在,那么就使用git rebase --abort放弃所作的其它rebase。使用该动作之后,就可以看到下面的信息,显示refact分支和origin/refact分支分叉了。

nieyong@mac:~/github/DJIAssistantForIphone > git st
# On branch refact
# Your branch and 'origin/refact' have diverged,
# and have 3 and 2 different commits each, respectively.
#
nothing to commit, working directory clean

此时,使用merge的方式合并A和B做的提交。使用命令git merge origin/refact,然后解决冲突提交就OK。

整个过程,只需要明白一点,那就是github的mac gui在sync的时候,默认是做的rebase操作。知晓了这一点,其他的就迎刃而解了。

merge 时没有合并到origin分支的警告

在开发过程中,出现下面这样的警告

nieyong@mac:~/github/DJIAssistantForIphone > git br -d I18N
warning: not deleting branch 'I18N' that is not yet merged to
         'refs/remotes/origin/I18N', even though it is merged to HEAD.
error: The branch 'I18N' is not fully merged.
If you are sure you want to delete it, run 'git branch -D I18N'.

it knows that I18N is a tracking branch for origin/I18N, so it wants to know that changes done on I18N have been pushed to origin.

git reset --hard # removes staged and working directory changes

git clean -f -d # remove untracked files git clean -f -x -d # CAUTION: as above but removes ignored files like config.

git中不可恢复的操作

删除工作空间中的某个文件的所有修改,恢复到上一次提交时的状态。

如果该文件的修改已经使用git add提交为缓存,则不会起作用,这是需要使用git reset HEAD .将文件由缓存空间unstage到工作空间,然后再使用该命令撤销所有的修改。注意,对于删除了的文件,该命令也一样起效。对于新加入的文件,还处于Untrack状态,则不会起作用。

恢复到某一次提交之后的状态,删除在这次提交之后到所有提交、修改记录。

参考资料

git pull

git的pull默认使用--merge参数。如果想获得线性的提交记录,可以使用--rebase参数。如下:

ny@ny-server2:~/workspace/4412/u-boot-4412$ git pull --rebase
Cannot pull with rebase: You have unstaged changes.
Please commit or stash them.

本地有修改没有提交,远程有新的提交,所以出现了上面的提示。使用git fetch命令之后,参看log记录如下:

ny@ny-server2:~/workspace/4412/u-boot-4412$ git ll origin/master
579f18d (origin/master, origin/HEAD) 1. utscript脚本加入对cache与userdata分区的格式化操作。解决安卓启动后不能保存用户配置的问题。
f1057f4 (HEAD, master) *添加升级时LCD提示信息,font为8*8
036fc67 *修改lcd分辨率等参数
9897bf4 *修改uboot的fb驱动,实现uboot的lcd显示

从上面的记录可以看到,本地的提交记录还是f1057f4,远程已经是579f18d,并且远程的修改已经拉到了本地。然后再使用git rebase命令进行合并(git fetch和git rebase origin/master命令就是对应git pull --rebase命令)。

ny@ny-server2:~/workspace/4412/u-boot-4412$ git rebase origin/master
Cannot rebase: You have unstaged changes.
Please commit or stash them.

那么下面如何合并origin/master分支呢?使用git stash命令进行暂时存储:

$git stash
$git stash list
$git rebase origin/master
$git stash pop