Skip to content

天楚锐齿

人工智能 云计算 大数据 物联网 IT 通信 嵌入式

天楚锐齿

  • 下载
  • 物联网
  • 云计算
  • 大数据
  • 人工智能
  • Linux&Android
  • 网络
  • 通信
  • 嵌入式
  • 杂七杂八

Git的安装和简单使用

2018-03-14
安装:
# yum install curl-devel expat-devel gettext-devel openssl-devel zlib-devel perl-devel
# tar -zxvf git-2.2.0.tar.gz
# cd git-2.2.0/
# ./configure
# make all
# make install
# git help
配置:
# git config -l –system
对应的配置文件:
# vi /usr/local/etc/gitconfig
# git config -l –global
对应的配置文件,只修改该用户的配置:
# vi ~/.gitconfig
配置用户信息:
# git config –global user.name “Max Shu”
# git config –global user.email maxshu_cn@163.com
配置默认编辑器:
# git config –global core.editor vi
配置差异分析工具:
# git config –global merge.tool vimdiff
查看配置(当然可以查看那两个配置文件):
# git config -l
user.name=Max Shu
user.email=maxshu_cn@163.com
core.editor=vi
merge.tool=vimdiff
查看具体的某个值:
# git config user.name
Max Shu
在当前目录下建立新的本地仓库,会生成 .git 目录,里面含有该仓库所有内容:
# mkdir TEST
# cd TEST
# touch test.c
# touch bbb.c
# touch aaa.h
# git init
把当前目录下的某些文件加入仓库:
# git add test.c
# git add bbb.c
# git add *.h
如果增加时需要忽略某些文件,则在该目录下建立.gitignore文件,里面放置需忽略的文件名:
# vi .gitignore
# 此为注释 – 将被 Git 忽略
# 忽略所有 .a 结尾的文件
*.a
# 但 lib.a 除外
!lib.a
# 仅仅忽略项目根目录下的 TODO 文件,不包括 subdir/TODO
/TODO
# 忽略 build/ 目录下的所有文件
build/
# 会忽略 doc/notes.txt 但不包括 doc/server/arch.txt
doc/*.txt
# ignore all .txt files in the doc/ directory
doc/**/*.txt
提交到本地仓库:
# git commit -m ‘initial version for TEST’
克隆一个仓库:
把刚才的TEST克隆成TEST-clone,如果不带TEST-clone参数,则使用相同名字,会覆盖:
# cd ..
# git clone ./TEST ./TEST-clone
远程克隆:
# git clone git://github.com/schacon/grit.git mygrit
# git clone http(s)://github.com/schacon/grit.git mygrit
# git clone user@server:/path.git mygrit
     工作目录下面的所有文件都不外乎这两种状态:已跟踪或未跟踪。已跟踪的文件是指本来就被纳入版本控制管理的文件,在上次快照中有它们的记录,工作一段时间后,它们的状态可能是未更新,已修改或者已放入暂存区。而所有其他文件都属于未跟踪文件。它们既没有上次更新时的快照,也不在当前的暂存区域。初次克隆某个仓库时,工作目录中的所有文件都属于已跟踪文件,且状态为未修改。
     在编辑过某些文件之后,Git 将这些文件标为已修改。我们逐步把这些修改过的文件放到暂存区域,直到最后一次性提交所有这些暂存起来的文件,如此重复:
查看当前工作目录状态:
# git status
On branch master
Your branch is up-to-date with ‘origin/master’.
nothing to commit, working directory clean
表示分支为master,工作目录很干净,所有文件都已提交。
增加一文件,看状态,这时文件为untracked(未跟踪状态):
# touch readme
# git status
On branch master
Your branch is up-to-date with ‘origin/master’.
Untracked files:
(use “git add <file>…” to include in what will be committed)
readme
nothing added to commit but untracked files present (use “git add” to track)
这里有一个untracked的文件,就是刚刚建立的这个。
把新建的文件加入跟踪,进入stage(暂存)状态:
# git add readme
# git status
On branch master
Your branch is up-to-date with ‘origin/master’.
Changes to be committed:
(use “git reset HEAD <file>…” to unstage)
new file:   readme
修改文件进入modified(已修改)状态:
# vi test.c
# git status
On branch master
Your branch is up-to-date with ‘origin/master’.
Changes to be committed:
(use “git reset HEAD <file>…” to unstage)
new file:   readme
Changes not staged for commit:
(use “git add <file>…” to update what will be committed)
(use “git checkout — <file>…” to discard changes in working directory)
modified:   test.c
已修改过的文件需要再次add,进入stage(已暂存)状态:
# git add test.c
# git status
On branch master
Your branch is up-to-date with ‘origin/master’.
Changes to be committed:
(use “git reset HEAD <file>…” to unstage)
new file:   readme
modified:   test.c
查看已暂存(staged)和已修改(modified)文件的具体不同之处:
# vi test.c
# git status
On branch master
Your branch is up-to-date with ‘origin/master’.
Changes to be committed:
(use “git reset HEAD <file>…” to unstage)
new file:   readme
modified:   test.c
Changes not staged for commit:
(use “git add <file>…” to update what will be committed)
(use “git checkout — <file>…” to discard changes in working directory)
modified:   test.c
# git diff
diff –git a/test.c b/test.c
index be532d9..da2faa0 100644
— a/test.c
+++ b/test.c
@@ -2,3 +2,4 @@ aaa
bbb
ccc
eee
+fff
查看已暂存(staged)文件和已提交(commited)文件之间的具体差别:
# git diff –staged
diff –git a/readme b/readme
new file mode 100644
index 0000000..e69de29
diff –git a/test.c b/test.c
index 1802a74..be532d9 100644
— a/test.c
+++ b/test.c
@@ -1,3 +1,4 @@
aaa
bbb
ccc
+eee
把已暂存的文件提交到仓库:
# git commit -m “ddd”
[master ebae8cf] ddd
2 files changed, 1 insertion(+)
create mode 100644 readme
把已修改、已暂存的文件一起提交:
# git commit -a -m “ddd”
删除文件:
# git rm readme
# git status
On branch master
Your branch is ahead of ‘origin/master’ by 2 commits.
(use “git push” to publish your local commits)
Changes to be committed:
(use “git reset HEAD <file>…” to unstage)
deleted:    readme
再提交,就从仓库删除了:
# git commit -m “a”
删除通配符(git使用自己的规则,不能用shell规则):
# git rm log/\*.log
移动文件:
# git mv bbb.c ccc.c
# git status
On branch master
Your branch is ahead of ‘origin/master’ by 3 commits.
(use “git push” to publish your local commits)
Changes to be committed:
(use “git reset HEAD <file>…” to unstage)
renamed:    bbb.c -> ccc.c
再提交,就从仓库移动了:
# git commit -m “a”
查看提交历史:
# git log
# git log -p -2
其中-p显示每次提交的内容差异,-2表示最近的2次更新。
# git log –stat
这里–stat显示每次提交的统计结果。
复杂例子:
git log –pretty=”%h – %s” –author=gitster –since=”2008-10-01″ –before=”2008-11-01″ –no-merges — t/
重新提交(如果之前提交的有问题,则重新提交一次):
# git commit –amend
如果到了暂存区的文件,怎么取消:
# git reset HEAD bbb.c
取消修改,重新从仓库取下来之前的文件:
# git checkout — ccc.c
建立远程仓库:
从远程仓库克隆到本地的四种方式:
# git clone file:///opt/git/project.git
# git clone git://server/project.git
# git clone ssh://user@server/project.git
# git clone http://example.com/gitproject.git
把本地目录放到远程作为远程初始仓库:
# git remote add local_proj /opt/git/project.git
部署远程仓库:
1. 把本地git仓库克隆成裸仓库:
# git clone –bare TEST test.git
2. 把裸仓库复制到服务器上:
# scp -r test.git root@192.168.56.101:/opt/git/
3. 修改权限,让他可以被其他用户推送:
# ssh root@192.168.56.101
# cd /opt/git/test.git/
# git init –bare –shared
# exit
4. 测试一下:
# git clone root@192.168.56.101:/opt/git/test.git aaa
# cd aaa
# git remote -v
origin  root@192.168.56.101:/opt/git/test.git (fetch)
origin  root@192.168.56.101:/opt/git/test.git (push)
查看远程仓库:
# git remote -v
增加一个远程仓库到本地目录:
# git remote add pb git://github.com/paulboone/ticgit.git
其中pb为别名,后面为远程地址,如果不加pb,则为默认的origin名字。
把远程仓库的内容取到本地目录:
# git fetch pb
这里的fetch只能抓远程的包到本地,不能自动合并到当前工作分支,如果要自动合并,可以使用git pull命令。
把本地仓库的更新推送到远程仓库:
#git push origin master
这里的origin和master为默认的远程名字和分支名字。
可能错误:
To git@192.168.1.X:/var/git.server/…/web
! [remote rejected] master -> master (branch is currently checked out)
error: failed to push some refs to ‘git@192.168.1.X:/var/git.server/…/web’
这是由于git默认拒绝了push操作,需要进行设置,修改.git/config添加如下代码:
[receive] denyCurrentBranch = ignore
在初始化远程仓库时最好使用
git –bare init
而不要使用:
git init
如果使用了git init初始化,则远程仓库的目录下,也包含work tree,当本地仓库向远程仓库push时, 如果远程仓库正在push的分支上(如果当时不在push的分支,就没有问题), 那么push后的结果不会反应在work tree上, 也即在远程仓库的目录下对应的文件还是之前的内容,必须得使用git reset –hard才能看到push后的内容.
查看远程仓库状态:
# git remote show origin
修改远程仓库在本地的昵称:
# git remote rename pb paul
远程仓库已经迁移或克隆镜像不再使用,则删除远程仓库在本地的信息:
# git remote rm paul
查看现有标签信息:
# git tag
打新的标签:
# git tag -a v1.4 -m ‘my version 1.4’
查看具体标签信息:
# git show v1.4
产生带有签署信息的标签:
# gpg –gen-key
这里需要等好久去完成。
# gpg –list-key
/root/.gnupg/pubring.gpg
————————
pub   2048R/44E52FC9 2014-12-06
uid                  Max Shu (No.1 key) <maxshu_cn@163.com>
sub   2048R/BD2825FC 2014-12-06

# git config –global user.name “Max Shu (No.1 key)”

注意这里的名字和上面的uid名字的一致,不带email地址,但是含有PGP的comment。
# git tag -s v1.5 -m ‘my signed 1.5 tag’
校验标签:
# git tag -v v1.5
把标签推送到远程服务器:
某个标签:
# git push origin v1.4
所有标签:
# git push orgin –tags
代码发布:
# git archive master –prefix=’project/’ | gzip > `git describe master`.tar.gz
# git archive master –prefix=’project/’ –format=zip > `git describe master`.zip
储藏工作:
如果正在工作,又要到新的分支做某个操作,则可以临时储藏没有提交的文件:
# git stash
然后切换到别的分支,处理完后再切换回来:
# git stash list
stash@{0}: WIP on master: 049d078 added the index file
stash@{1}: WIP on master: c264051… Revert “added file_size”
stash@{2}: WIP on master: 21d80a5… added number to log
# git stash apply –index
# git stash apply stash@{1} –index
切换回来后删除储藏:
# git stash drop stash@{1}

分支的新建与合并

现在让我们来看一个简单的分支与合并的例子,实际工作中大体也会用到这样的工作流程:

  1. 开发某个网站。
  2. 为实现某个新的需求,创建一个分支。
  3. 在这个分支上开展工作。

假设此时,你突然接到一个电话说有个很严重的问题需要紧急修补,那么可以按照下面的方式处理:

  1. 返回到原先已经发布到生产服务器上的分支。
  2. 为这次紧急修补建立一个新分支,并在其中修复问题。
  3. 通过测试后,回到生产服务器所在的分支,将修补分支合并进来,然后再推送到生产服务器上。
  4. 切换到之前实现新需求的分支,继续工作。

分支的新建与切换

首先,我们假设你正在项目中愉快地工作,并且已经提交了几次更新(见图 3-10)。

图 3-10. 一个简短的提交历史

现在,你决定要修补问题追踪系统上的 #53 问题。顺带说明下,Git 并不同任何特定的问题追踪系统打交道。这里为了说明要解决的问题,才把新建的分支取名为 iss53。要新建并切换到该分支,运行参数:

$ git checkout -b iss53
Switched to a new branch 'iss53'
这相当于执行下面这两条命令:
$ git branch iss53
$ git checkout iss53
图 3-11 示意该命令的执行结果。

图 3-11. 创建了一个新分支的指针

接着你开始尝试修复问题,在提交了若干次更新后,分支的指针也会随着向前推进,因为它就是当前分支(换句话说,当前的HEAD 指针正指向iss53,见图 3-12):

$ vim index.html
$ git commit -a -m 'added a new footer [issue 53]'
图 3-12. iss53 分支随工作进展向前推进

现在你就接到了那个网站问题的紧急电话,需要马上修补。有了 Git ,我们就不需要同时发布这个补丁和iss53 里作出的修改,也不需要在创建和发布该补丁到服务器之前花费大力气来复原这些修改。唯一需要的仅仅是切换回master分支。

不过在此之前,留心你的暂存区或者工作目录里,那些还没有提交的修改,它会和你即将检出的分支产生冲突从而阻止 Git 为你切换分支。切换分支的时候最好保持一个清洁的工作区域。稍后会介绍几个绕过这种问题的办法(分别叫做 stashing 和 commit amending)。目前已经提交了所有的修改,所以接下来可以正常转换到master分支:

$ git checkout master
Switched to branch 'master'
此时工作目录中的内容和你在解决问题 #53 之前一模一样,你可以集中精力进行紧急修补。这一点值得牢记:Git 会把工作目录的内容恢复为检出某分支时它所指向的那个提交对象的快照。它会自动添加、删除和修改文件以确保目录的内容和你当时提交时完全一样。

接下来,你得进行紧急修补。我们创建一个紧急修补分支hotfix 来开展工作,直到搞定(见图 3-13):

$ git checkout -b hotfix
Switched to a new branch 'hotfix'
$ vim index.html
$ git commit -a -m 'fixed the broken email address'
[hotfix 3a0874c] fixed the broken email address
 1 files changed, 1 deletion(-)
图 3-13. hotfix 分支是从 master 分支所在点分化出来的

有必要作些测试,确保修补是成功的,然后回到master分支并把它合并进来,然后发布到生产服务器。用git merge命令来进行合并:

$ git checkout master
$ git merge hotfix
Updating f42c576..3a0874c
Fast-forward
 README | 1 -
 1 file changed, 1 deletion(-)
请注意,合并时出现了“Fast forward”的提示。由于当前master 分支指针直接右移。换句话说,如果顺着一个分支走下去可以到达另一个分支的话,那么 Git 在合并两者时,只会简单地把指针右移,因为这种单线的历史分支不存在任何需要解决的分歧,所以这种合并过程可以称为快进(Fast forward)。

现在最新的修改已经在当前master分支所指向的提交对象中了,可以部署到生产服务器上去了(见图 3-14)。

图 3-14. 合并之后,master 分支和 hotfix 分支指向同一位置。

在那个超级重要的修补发布以后,你想要回到被打扰之前的工作。由于当前hotfix分支和master 都指向相同的提交对象,所以hotfix 已经完成了历史使命,可以删掉了。使用git branch的-d 选项执行删除操作:

$ git branch -d hotfix
Deleted branch hotfix (was 3a0874c).
现在回到之前未完成的 #53 问题修复分支上继续工作(图 3-15):
$ git checkout iss53
Switched to branch 'iss53'
$ vim index.html
$ git commit -a -m 'finished the new footer [issue 53]'
[iss53 ad82d7a] finished the new footer [issue 53]
 1 file changed, 1 insertion(+)
图 3-15. iss53 分支可以不受影响继续推进。

值得注意的是之前 hotfix 分支的修改内容尚未包含到 iss53 中来。如果需要纳入此次修补,可以用 git merge master 把 master 分支合并到iss53;或者等iss53 完成之后,再将 iss53 分支中的更新并入master。

分支的合并

在问题 #53 相关的工作完成之后,可以合并回 master 分支。实际操作同前面合并 hotfix 分支差不多,只需回到 master分支,运行 git merge 命令指定要合并进来的分支:

$ git checkout master
$ git merge iss53
Auto-merging README
Merge made by the 'recursive' strategy.
 README | 1 +
 1 file changed, 1 insertion(+)
请注意,这次合并操作的底层实现,并不同于之前iss53 分支的直接祖先,Git 不得不进行一些额外处理。就此例而言,Git 会用两个分支的末端(C4 和 C5)以及它们的共同祖先(C2)进行一次简单的三方合并计算。图 3-16 用红框标出了 Git 用于合并的三个提交对象:

图 3-16. Git 为分支合并自动识别出最佳的同源合并点。

这次,Git 没有简单地把分支指针右移,而是对三方合并后的结果重新做一个新的快照,并自动创建一个指向它的提交对象(C6)(见图 3-17)。这个提交对象比较特殊,它有两个祖先(C4 和 C5)。

值得一提的是 Git 可以自己裁决哪个共同祖先才是最佳合并基础;这和 CVS 或 Subversion(1.5 以后的版本)不同,它们需要开发者手工指定合并基础。所以此特性让 Git 的合并操作比其他系统都要简单不少。

图 3-17. Git 自动创建了一个包含了合并结果的提交对象。

既然之前的工作成果已经合并到master 了,那么iss53 也就没用了。你可以就此删除它,并在问题追踪系统里关闭该问题。

$ git branch -d iss53

遇到冲突时的分支合并

有时候合并操作并不会如此顺利。如果在不同的分支中都修改了同一个文件的同一部分,Git 就无法干净地把两者合到一起(译注:逻辑上说,这种问题只能由人来裁决。)。如果你在解决问题 #53 的过程中修改了hotfix 中修改的部分,将得到类似下面的结果:

$ git merge iss53
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.
Git 作了合并,但没有提交,它会停下来等你解决冲突。要看看哪些文件在合并时发生冲突,可以用git status查阅:
$ git status
On branch master
You have unmerged paths.
  (fix conflicts and run "git commit")

Unmerged paths:
  (use "git add <file>..." to mark resolution)

        both modified:      index.html

no changes added to commit (use "git add" and/or "git commit -a")
任何包含未解决冲突的文件都会以未合并(unmerged)的状态列出。Git 会在有冲突的文件里加入标准的冲突解决标记,可以通过它们来手工定位并解决这些冲突。可以看到此文件包含类似下面这样的部分:
<<<<<<< HEAD
<div id="footer">contact : email.support@github.com</div>
=======
<div id="footer">
  please contact us at support@github.com
</div>
>>>>>>> iss53
可以看到 iss53 分支中的内容。解决冲突的办法无非是二者选其一或者由你亲自整合到一起。比如你可以通过把这段内容替换为下面这样来解决:
<div id="footer">
please contact us at email.support@github.com
</div>
这个解决方案各采纳了两个分支中的一部分内容,而且我还删除了 git mergetool,它会调用一个可视化的合并工具并引导你解决所有冲突:
$ git mergetool

This message is displayed because 'merge.tool' is not configured.
See 'git mergetool --tool-help' or 'git help config' for more details.
'git mergetool' will now attempt to use one of the following tools:
opendiff kdiff3 tkdiff xxdiff meld tortoisemerge gvimdiff diffuse diffmerge ecmerge p4merge araxis bc3 codecompare vimdiff emerge
Merging:
index.html

Normal merge conflict for 'index.html':
  {local}: modified file
  {remote}: modified file
Hit return to start merge resolution tool (opendiff):

如果不想用默认的合并工具(Git 为我默认选择了opendiff,因为我在 Mac 上运行了该命令),你可以在上方”merge tool candidates”里找到可用的合并工具列表,输入你想用的工具名。我们将在第七章讨论怎样改变环境中的默认值。

退出合并工具以后,Git 会询问你合并是否成功。如果回答是,它会为你把相关文件暂存起来,以表明状态为已解决。

再运行一次 git status 来确认所有冲突都已解决:

$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   index.html

如果觉得满意了,并且确认所有冲突都已解决,也就是进入了暂存区,就可以用git commit 来完成这次合并提交。提交的记录差不多是这样:

Merge branch 'iss53'

Conflicts:
  index.html
#
# It looks like you may be committing a merge.
# If this is not correct, please remove the file
#       .git/MERGE_HEAD
# and try again.
#

如果想给将来看这次合并的人一些方便,可以修改该信息,提供更多合并细节。比如你都作了哪些改动,以及这么做的原因。有时候裁决冲突的理由并不直接或明显,有必要略加注解。

显示分支:
# git branch -v
* master b14b067 a
# git branch –merged
# git branch –no-merged
删除分支:
# git branch -d br1
以上所说的分支都是在本地的分支,下面才是远程分支的处理。

远程分支

远程分支(remote branch)是对远程仓库中的分支的索引。它们是一些无法移动的本地分支;只有在 Git 进行网络交互时才会更新。远程分支就像是书签,提醒着你上次连接远程仓库时上面各分支的位置。

我们用 (远程仓库名)/(分支名) 这样的形式表示远程分支。比如我们想看看上次同 origin 仓库通讯时 master分支的样子,就应该查看 origin/master 分支。如果你和同伴一起修复某个问题,但他们先推送了一个iss53 分支到远程仓库,虽然你可能也有一个本地的iss53 分支,但指向服务器上最新更新的却应该是origin/iss53 分支。

可能有点乱,我们不妨举例说明。假设你们团队有个地址为 git.ourcompany.com 的 Git 服务器。如果你从这里克隆,Git 会自动为你将此远程仓库命名为 origin,并下载其中所有的数据,建立一个指向它的 master分支的指针,在本地命名为 origin/master,但你无法在本地更改其数据。接着,Git 建立一个属于你自己的本地 master 分支,始于 origin 上master 分支相同的位置,你可以就此开始工作(见图 3-22):

图 3-22. 一次 Git 克隆会建立你自己的本地分支 master 和远程分支 origin/master,并且将它们都指向origin 上的 master 分支。

如果你在本地 master 分支做了些改动,与此同时,其他人向 git.ourcompany.com 推送了他们的更新,那么服务器上的 master 分支就会向前推进,而于此同时,你在本地的提交历史正朝向不同方向发展。不过只要你不和服务器通讯,你的 origin/master 指针仍然保持原位不会移动(见图 3-23)。

图 3-23. 在本地工作的同时有人向远程仓库推送内容会让提交历史开始分流。

可以运行 git fetch origin 来同步远程服务器上的数据到本地。该命令首先找到 origin 是哪个服务器(本例为 git.ourcompany.com),从上面获取你尚未拥有的数据,更新你本地的数据库,然后把 origin/master的指针移到它最新的位置上(见图 3-24)。

图 3-24. git fetch 命令会更新 remote 索引。

为了演示拥有多个远程分支(在不同的远程服务器上)的项目是如何工作的,我们假设你还有另一个仅供你的敏捷开发小组使用的内部服务器 git.team1.ourcompany.com。可以用第二章中提到的 git remote add 命令把它加为当前项目的远程分支之一。我们把它命名为teamone,以便代替完整的 Git URL 以方便使用(见图 3-25)。

图 3-25. 把另一个服务器加为远程仓库

现在你可以用 git fetch teamone 来获取小组服务器上你还没有的数据了。由于当前该服务器上的内容是你origin 服务器上的子集,Git 不会下载任何数据,而只是简单地创建一个名为 teamone/master 的远程分支,指向 teamone 服务器上 master 分支所在的提交对象 31b8e(见图 3-26)。

图 3-26. 你在本地有了一个指向 teamone 服务器上 master 分支的索引。

推送本地分支

要想和其他人分享某个本地分支,你需要把它推送到一个你拥有写权限的远程仓库。你创建的本地分支不会因为你的写入操作而被自动同步到你引入的远程服务器上,你需要明确地执行推送分支的操作。换句话说,对于无意分享的分支,你尽管保留为私人分支好了,而只推送那些协同工作要用到的特性分支。

如果你有个叫 serverfix 的分支需要和他人一起开发,可以运行 git push (远程仓库名) (分支名):

$ git push origin serverfix
Counting objects: 20, done.
Compressing objects: 100% (14/14), done.
Writing objects: 100% (15/15), 1.74 KiB, done.
Total 15 (delta 5), reused 0 (delta 0)
To git@github.com:schacon/simplegit.git
 * [new branch]      serverfix -> serverfix

这里其实走了一点捷径。Git 自动把 serverfix 分支名扩展为 refs/heads/serverfix:refs/heads/serverfix,意为“取出我在本地的 serverfix 分支,推送到远程仓库的 serverfix 分支中去”。我们将在第九章进一步介绍refs/heads/ 部分的细节,不过一般使用的时候都可以省略它。也可以运行 git push origin serverfix:serverfix 来实现相同的效果,它的意思是“上传我本地的 serverfix 分支到远程仓库中去,仍旧称它为 serverfix 分支”。通过此语法,你可以把本地分支推送到某个命名不同的远程分支:若想把远程分支叫作awesomebranch,可以用 git push origin serverfix:awesomebranch 来推送数据。

接下来,当你的协作者再次从服务器上获取数据时,他们将得到一个新的远程分支 origin/serverfix,并指向服务器上 serverfix 所指向的版本:

$ git fetch origin
remote: Counting objects: 20, done.
remote: Compressing objects: 100% (14/14), done.
remote: Total 15 (delta 5), reused 0 (delta 0)
Unpacking objects: 100% (15/15), done.
From git@github.com:schacon/simplegit
 * [new branch]      serverfix    -> origin/serverfix

值得注意的是,在 fetch 操作下载好新的远程分支之后,你仍然无法在本地编辑该远程仓库中的分支。换句话说,在本例中,你不会有一个新的 serverfix 分支,有的只是一个你无法移动的 origin/serverfix 指针。

如果要把该远程分支的内容合并到当前分支,可以运行 git merge origin/serverfix。如果想要一份自己的serverfix 来开发,可以在远程分支的基础上分化出一个新的分支来:

$ git checkout -b serverfix origin/serverfix
Branch serverfix set up to track remote branch serverfix from origin.
Switched to a new branch 'serverfix'

这会切换到新建的 serverfix 本地分支,其内容同远程分支 origin/serverfix 一致,这样你就可以在里面继续开发了。

跟踪远程分支

从远程分支 checkout 出来的本地分支,称为 跟踪分支 (tracking branch)。跟踪分支是一种和某个远程分支有直接联系的本地分支。在跟踪分支里输入 git push,Git 会自行推断应该向哪个服务器的哪个分支推送数据。同样,在这些分支里运行 git pull 会获取所有远程索引,并把它们的数据都合并到本地分支中来。

在克隆仓库时,Git 通常会自动创建一个名为 master 的分支来跟踪 origin/master。这正是 git push 和git pull 一开始就能正常工作的原因。当然,你可以随心所欲地设定为其它跟踪分支,比如 origin 上除了master 之外的其它分支。刚才我们已经看到了这样的一个例子:git checkout -b [分支名] [远程名]/[分支名]。如果你有 1.6.2 以上版本的 Git,还可以用 –track 选项简化:

$ git checkout --track origin/serverfix
Branch serverfix set up to track remote branch serverfix from origin.
Switched to a new branch 'serverfix'

要为本地分支设定不同于远程分支的名字,只需在第一个版本的命令里换个名字:

$ git checkout -b sf origin/serverfix
Branch sf set up to track remote branch serverfix from origin.
Switched to a new branch 'sf'

现在你的本地分支 sf 会自动将推送和抓取数据的位置定位到 origin/serverfix 了。

删除远程分支

如果不再需要某个远程分支了,比如搞定了某个特性并把它合并进了远程的 master 分支(或任何其他存放稳定代码的分支),可以用这个非常无厘头的语法来删除它:git push [远程名] :[分支名]。如果想在服务器上删除 serverfix 分支,运行下面的命令:

$ git push origin :serverfix
To git@github.com:schacon/simplegit.git
 - [deleted]         serverfix

咚!服务器上的分支没了。你最好特别留心这一页,因为你一定会用到那个命令,而且你很可能会忘掉它的语法。有种方便记忆这条命令的方法:记住我们不久前见过的 git push [远程名] [本地分支]:[远程分支] 语法,如果省略 [本地分支],那就等于是在说“在这里提取空白然后把它变成[远程分支]”。

分支的衍合

把一个分支中的修改整合到另一个分支的办法有两种:merge 和 rebase(译注:rebase 的翻译暂定为“衍合”,大家知道就可以了。)。在本章我们会学习什么是衍合,如何使用衍合,为什么衍合操作如此富有魅力,以及我们应该在什么情况下使用衍合。

基本的衍合操作

请回顾之前有关合并的一节(见图 3-27),你会看到开发进程分叉到两个不同分支,又各自提交了更新。

图 3-27. 最初分叉的提交历史。

之前介绍过,最容易的整合分支的方法是 merge 命令,它会把两个分支最新的快照(C3 和 C4)以及二者最新的共同祖先(C2)进行三方合并,合并的结果是产生一个新的提交对象(C5)。如图 3-28 所示:

图 3-28. 通过合并一个分支来整合分叉了的历史。

其实,还有另外一个选择:你可以把在 C3 里产生的变化补丁在 C4 的基础上重新打一遍。在 Git 里,这种操作叫做衍合(rebase)。有了 rebase 命令,就可以把在一个分支里提交的改变移到另一个分支里重放一遍。

在上面这个例子中,运行:

$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command

它的原理是回到两个分支最近的共同祖先,根据当前分支(也就是要进行衍合的分支 experiment)后续的历次提交对象(这里只有一个 C3),生成一系列文件补丁,然后以基底分支(也就是主干分支 master)最后一个提交对象(C4)为新的出发点,逐个应用之前准备好的补丁文件,最后会生成一个新的合并提交对象(C3’),从而改写 experiment 的提交历史,使它成为 master 分支的直接下游,如图 3-29 所示:

图 3-29. 把 C3 里产生的改变到 C4 上重演一遍。

现在回到 master 分支,进行一次快进合并(见图 3-30):

图 3-30. master 分支的快进。

现在的 C3′ 对应的快照,其实和普通的三方合并,即上个例子中的 C5 对应的快照内容一模一样了。虽然最后整合得到的结果没有任何区别,但衍合能产生一个更为整洁的提交历史。如果视察一个衍合过的分支的历史记录,看起来会更清楚:仿佛所有修改都是在一根线上先后进行的,尽管实际上它们原本是同时并行发生的。

一般我们使用衍合的目的,是想要得到一个能在远程分支上干净应用的补丁 — 比如某些项目你不是维护者,但想帮点忙的话,最好用衍合:先在自己的一个分支里进行开发,当准备向主项目提交补丁的时候,根据最新的 origin/master 进行一次衍合操作然后再提交,这样维护者就不需要做任何整合工作(译注:实际上是把解决分支补丁同最新主干代码之间冲突的责任,化转为由提交补丁的人来解决。),只需根据你提供的仓库地址作一次快进合并,或者直接采纳你提交的补丁。

请注意,合并结果中最后一次提交所指向的快照,无论是通过衍合,还是三方合并,都会得到相同的快照内容,只不过提交历史不同罢了。衍合是按照每行的修改次序重演一遍修改,而合并是把最终结果合在一起。

有趣的衍合

衍合也可以放到其他分支进行,并不一定非得根据分化之前的分支。以图 3-31 的历史为例,我们为了给服务器端代码添加一些功能而创建了特性分支 server,然后提交 C3 和 C4。然后又从 C3 的地方再增加一个client 分支来对客户端代码进行一些相应修改,所以提交了 C8 和 C9。最后,又回到 server 分支提交了 C10。

图 3-31. 从一个特性分支里再分出一个特性分支的历史。

假设在接下来的一次软件发布中,我们决定先把客户端的修改并到主线中,而暂缓并入服务端软件的修改(因为还需要进一步测试)。这个时候,我们就可以把基于 server 分支而非 master 分支的改变(即 C8 和 C9),跳过 server 直接放到 master 分支中重演一遍,但这需要用 git rebase 的 –onto 选项指定新的基底分支 master:

$ git rebase --onto master server client

这好比在说:“取出 client 分支,找出 client 分支和 erver 分支的共同祖先之后的变化,然后把它们在master 上重演一遍”。是不是有点复杂?不过它的结果如图 3-32 所示,非常酷(译注:虽然 client 里的 C8, C9 在 C3 之后,但这仅表明时间上的先后,而非在 C3 修改的基础上进一步改动,因为 server 和client 这两个分支对应的代码应该是两套文件,虽然这么说不是很严格,但应理解为在 C3 时间点之后,对另外的文件所做的 C8,C9 修改,放到主干重演。):

图 3-32. 将特性分支上的另一个特性分支衍合到其他分支。

现在可以快进 master 分支了(见图 3-33):

$ git checkout master
$ git merge client

图 3-33. 快进 master 分支,使之包含 client 分支的变化。

现在我们决定把 server 分支的变化也包含进来。我们可以直接把server 分支衍合到 master,而不用手工切换到 server 分支后再执行衍合操作 — git rebase [主分支] [特性分支] 命令会先取出特性分支 server,然后在主分支 master 上重演:

$ git rebase master server

于是,server 的进度应用到 master 的基础上,如图 3-34 所示:

图 3-34. 在 master 分支上衍合 server 分支。

然后就可以快进主干分支 master 了:

$ git checkout master
$ git merge server

现在 client 和 server 分支的变化都已经集成到主干分支来了,可以删掉它们了。最终我们的提交历史会变成图 3-35 的样子:

$ git branch -d client
$ git branch -d server

图 3-35. 最终的提交历史

衍合的风险

呃,奇妙的衍合也并非完美无缺,要用它得遵守一条准则:

一旦分支中的提交对象发布到公共仓库,就千万不要对该分支进行衍合操作。

如果你遵循这条金科玉律,就不会出差错。否则,人民群众会仇恨你,你的朋友和家人也会嘲笑你,唾弃你。

在进行衍合的时候,实际上抛弃了一些现存的提交对象而创造了一些类似但不同的新的提交对象。如果你把原来分支中的提交对象发布出去,并且其他人更新下载后在其基础上开展工作,而稍后你又用 git rebase 抛弃这些提交对象,把新的重演后的提交对象发布出去的话,你的合作者就不得不重新合并他们的工作,这样当你再次从他们那里获取内容时,提交历史就会变得一团糟。

下面我们用一个实际例子来说明为什么公开的衍合会带来问题。假设你从一个中央服务器克隆然后在它的基础上搞了一些开发,提交历史类似图 3-36 所示:

图 3-36. 克隆一个仓库,在其基础上工作一番。

现在,某人在 C1 的基础上做了些改变,并合并他自己的分支得到结果 C6,推送到中央服务器。当你抓取并合并这些数据到你本地的开发分支中后,会得到合并结果 C7,历史提交会变成图 3-37 这样:

图 3-37. 抓取他人提交,并入自己主干。

接下来,那个推送 C6 上来的人决定用衍合取代之前的合并操作;继而又用 git push –force 覆盖了服务器上的历史,得到 C4’。而之后当你再从服务器上下载最新提交后,会得到:

图 3-38. 有人推送了衍合后得到的 C4’,丢弃了你作为开发基础的 C4 和 C6。

下载更新后需要合并,但此时衍合产生的提交对象 C4′ 的 SHA-1 校验值和之前 C4 完全不同,所以 Git 会把它们当作新的提交对象处理,而实际上此刻你的提交历史 C7 中早已经包含了 C4 的修改内容,于是合并操作会把 C7 和 C4′ 合并为 C8(见图 3-39):

图 3-39. 你把相同的内容又合并了一遍,生成一个新的提交 C8。

C8 这一步的合并是迟早会发生的,因为只有这样你才能和其他协作者提交的内容保持同步。而在 C8 之后,你的提交历史里就会同时包含 C4 和 C4’,两者有着不同的 SHA-1 校验值,如果用 git log 查看历史,会看到两个提交拥有相同的作者日期与说明,令人费解。而更糟的是,当你把这样的历史推送到服务器后,会再次把这些衍合后的提交引入到中央服务器,进一步困扰其他人(译注:这个例子中,出问题的责任方是那个发布了 C6 后又用衍合发布 C4′ 的人,其他人会因此反馈双重历史到共享主干,从而混淆大家的视听。)。

如果把衍合当成一种在推送之前清理提交历史的手段,而且仅仅衍合那些尚未公开的提交对象,就没问题。如果衍合那些已经公开的提交对象,并且已经有人基于这些提交对象开展了后续开发工作的话,就会出现叫人沮丧的麻烦。

git协作工作方式一:

最简单的协作方式之一:先在自己的特性分支中工作一段时间,完成后合并到自己的 master 分支;然后下载合并 origin/master 上的更新(如果有的话),再推回远程服务器。

git协作工作方式二:
许多开发小组改用 Git 就是因为它允许多个小组间并行工作,而在稍后恰当时机再行合并。通过共享远程分支的方式,无需干扰整体项目代码便可以开展工作,因此使用 Git 的小型团队间协作可以变得非常灵活自由。

子树合并

子树归并的思想是你拥有两个工程,其中一个项目映射到另外一个项目的子目录中,反过来也一样。当你指定一个子树归并,Git可以聪明地探知其中一个是另外一个的子树从而实现正确的归并——这相当神奇。

首先你将 Rack 应用加入到项目中。你将 Rack 项目当作你项目中的一个远程引用,然后将它检出到它自身的分支:

$ git remote add rack_remote git@github.com:schacon/rack.git
$ git fetch rack_remote
warning: no common commits
remote: Counting objects: 3184, done.
remote: Compressing objects: 100% (1465/1465), done.
remote: Total 3184 (delta 1952), reused 2770 (delta 1675)
Receiving objects: 100% (3184/3184), 677.42 KiB | 4 KiB/s, done.
Resolving deltas: 100% (1952/1952), done.
From git@github.com:schacon/rack
 * [new branch]      build      -> rack_remote/build
 * [new branch]      master     -> rack_remote/master
 * [new branch]      rack-0.4   -> rack_remote/rack-0.4
 * [new branch]      rack-0.9   -> rack_remote/rack-0.9
$ git checkout -b rack_branch rack_remote/master
Branch rack_branch set up to track remote branch refs/remotes/rack_remote/master.
Switched to a new branch "rack_branch"

现在在你的rack_branch分支中就有了Rack项目的根目录,而你自己的项目在master分支中。如果你先检出其中一个然后另外一个,你会看到它们有不同的项目根目录:

$ ls
AUTHORS           KNOWN-ISSUES   Rakefile      contrib           lib
COPYING           README         bin           example           test
$ git checkout master
Switched to branch "master"
$ ls
README

要将 Rack 项目当作子目录拉取到你的master项目中。你可以在 Git 中用git read-tree来实现。你会在第9章学到更多与read-tree和它的朋友相关的东西,当前你会知道它读取一个分支的根目录树到当前的暂存区和工作目录。你只要切换回你的master分支,然后拉取rack_branch到你主项目的master分支的rack子目录:

$ git read-tree --prefix=rack/ -u rack_branch

当你提交的时候,看起来就像你在那个子目录下拥有Rack的文件——就像你从一个tarball里拷贝的一样。有意思的是你可以比较容易地归并其中一个分支的变更到另外一个。因此,如果 Rack 项目更新了,你可以通过切换到那个分支并执行拉取来获得上游的变更:

$ git checkout rack_branch
$ git pull

然后,你可以将那些变更归并回你的 master 分支。你可以使用git merge -s subtree,它会工作的很好;但是 Git 同时会把历史归并到一起,这可能不是你想要的。为了拉取变更并预置提交说明,需要在-s subtree策略选项的同时使用–squash和–no-commit选项。

$ git checkout master
$ git merge --squash -s subtree --no-commit rack_branch
Squash commit -- not updating HEAD
Automatic merge went well; stopped before committing as requested

所有 Rack 项目的变更都被归并可以进行本地提交。你也可以做相反的事情——在你主分支的rack目录里进行变更然后归并回rack_branch分支,然后将它们提交给维护者或者推送到上游。

为了得到rack子目录和你rack_branch分支的区别——以决定你是否需要归并它们——你不能使用一般的diff命令。而是对你想比较的分支运行git diff-tree:

$ git diff-tree -p rack_branch

或者,为了比较你的rack子目录和服务器上你拉取时的master分支,你可以运行

$ git diff-tree -p rack_remote/masterde>

1,295次阅读

Post navigation

前一篇:

Docker —— 从入门到实践(完)

后一篇:

CentOS永久保存多个静态路由,重启network服务不丢失

发表回复 取消回复

要发表评论,您必须先登录。

个人介绍

需要么,有事情这里找联系方式:关于天楚锐齿

=== 美女同欣赏,好酒共品尝 ===

微信扫描二维码赞赏该文章:

扫描二维码分享该文章:

分类

  • Linux&Android (81)
  • Uncategorized (1)
  • 下载 (28)
  • 云计算 (38)
  • 人工智能 (9)
  • 大数据 (35)
  • 嵌入式 (34)
  • 杂七杂八 (35)
  • 物联网 (65)
  • 网络 (25)
  • 通信 (22)

归档

近期文章

  • 飞书机器人发送卡片interactive消息
  • Springboot JPA实现对数据库表统一的增删改查
  • WEB的内容安全策略CSP(Content-Security-Policy)
  • CSS利用@media和viewport实现响应式布局自动适配手机电脑等
  • VUE前端增加国际化支持

近期评论

  • linux爱好者 发表在《Linux策略路由及iptables mangle、ip rule、ip route关系及一种Network is unreachable错误》
  • maxshu 发表在《使用Android的HIDL+AIDL方式编写从HAL层到APP层的程序》
  • Ambition 发表在《使用Android的HIDL+AIDL方式编写从HAL层到APP层的程序》
  • Ambition 发表在《使用Android的HIDL+AIDL方式编写从HAL层到APP层的程序》
  • maxshu 发表在《Android9下用ethernet 的Tether模式来做路由器功能》

阅读量

  • 使用Android的HIDL+AIDL方式编写从HAL层到APP层的程序 - 23,641次阅读
  • 卸载深信服Ingress、SecurityDesktop客户端 - 18,322次阅读
  • 车机技术之车规级Linux-Automotive Grade Linux(AGL) - 10,436次阅读
  • linux下的unbound DNS服务器设置详解 - 9,143次阅读
  • 在Android9下用ndk编译vSomeIP和CommonAPI以及使用例子 - 9,018次阅读
  • linux的tee命令导致ssh客户端下的shell卡住不动 - 8,522次阅读
  • 车机技术之360°全景影像(环视)系统 - 8,027次阅读
  • Linux策略路由及iptables mangle、ip rule、ip route关系及一种Network is unreachable错误 - 8,027次阅读
  • 车机技术之Android Automotive - 7,887次阅读
  • Windows下安装QEMU并在qemu上安装ubuntu和debian - 7,761次阅读

其他操作

  • 注册
  • 登录
  • 条目 feed
  • 评论 feed
  • WordPress.org

联系方式

地址
深圳市科技园

时间
周一至周五:  9:00~12:00,14:00~18:00
周六和周日:10:00~12:00

标签

android AT命令 CAN centos docker Hadoop hdfs ip java kickstart linux mapreduce mini6410 modem nova OAuth openstack os python socket ssh uboot 内核 协议 安装 嵌入式 性能 报表 授权 操作系统 数据 数据库 月报 模型 汽车 深信服 源代码 统计 编译 脚本 虚拟机 调制解调器 车机 金融 鉴权
© 2025 天楚锐齿