转载自http://blog.csdn.net/sxh951026/article/details/77200003
这里先推荐几个学习Git的网址,希望能帮到大家!
- Git官网,在Documentation栏目里有非常系统的Git教程
- 廖雪峰的Git教程,简单易懂,不过没有官网里的系统,有些东西没有
- Git工作流教程
- 理解Git工作流
目录
- 目录
- 一 Git简介
- 版本控制系统的作用
- Git的诞生
- 二 Git配置和仓库初始化
- Git配置
- 创建Git仓库
- 三 用Git备份代码
- 在本地备份代码
- 改动类型一添加文件
- 改动类型二删除文件
- 改动类型三修改文件
- 更新所有改动到暂存区并提交
- git status原理
- 撤销修改
- 版本回退
- gitignore文件的使用
- 备份代码到远程仓库
- 使用SSH协议的方式
- 配置SSH Key
- 创建远程仓库
- 在本地添加远程仓库
- 把本地仓库的内容push到远程仓库
- 使用HTTPS协议的方式
- 使用SSH协议的方式
- 克隆
- 在本地备份代码
- 四 Git中的分支
- 分支的实现原理
- 分支的作用
- 分支操作
- 分支操作一创建分支
- 分支操作二分支间切换
- 分支操作三分支的合并
- 分支操作四变基
- 五 用Git进行协同开发
- 一个干净的push
- push失败
一 Git简介
01 版本控制系统的作用
版本控制系统的功能主要有三个。
- 备份代码。很常见的,今天的工作做完了,提交一下,明天改了改,发现还没昨天的好,使用Git的话,直接回退到上一个版本即可,再也不需要手动分日期保存代码了。爱玩游戏的同学,可以发现这和游戏里的存档功能很像。
- 在多人合作开发时,同步并合并对代码的修改。多个人开发的时候,小明开发了一个功能,小红开发了一个功能,他们需要把各自的功能合并到一起。于是小明先要把自己的代码发给小红,小红把自己要的代码挑出来,合并到自己的代码里(合并的过程真的很麻烦),然后把整个代码再发回给小明,然后继续开发。上述操作通过Git的指令可以自动完成,十分简便。
- 方便项目开发的管理。比如说,在开发下一个版本的同时,可以在前一个版本的基础上修复bug,使用Git可以使这两个工作互不干扰。再比如说,软件出bug了,通过Git可以诊断出是谁在哪次提交上引入了这个bug,方便分锅哇。
注意一下,并不是只能管理代码,word文档,图片,设计稿之类的东西都是可以管理的。
02 Git的诞生
Git是在Linux内核开源项目的维护工作中诞生的,于2005年。父亲是Linux之父Linus Torvalds,从亲缘关系上讲,Git是Linux的弟弟。
那个时候的情况是这个样子的!因为Linux是一款自由软件(具体什么是自由软件,请出门百度),开源,免费,而且谁都可以参与项目开发,所以吸引了一大批牛掰的程序员,他们修改Linux源码,将自己的代码发给Linus,再由Linus将代码合并到Linux内核中去。由于参与者非常多,代码的维护非常繁琐。所以在2002年的时候,启用了一个叫做BitKeeper的分布式版本控制系统。
BitKeeper是一个商业软件,是付费的,不过出于人道主义精神,BitKeeper所属的公司授予了Linux社区免费使用BitKeeper的权限。但是在2005年的时候,由于Andrew Tridgell(Samba的开发者)违反BitKeeper的使用原则,对BitKeeper开始进行逆向工程,使Linux社区失去了免费使用BitKeeper的权限。
然后重点来了,Linus在失去BitKeeper的支持后,根据他对BitKeeper的理解,并综合了当时版本控制系统的一些缺陷,花了一两个星期,用C语言写出了Git!
二 Git配置和仓库初始化
下面会介绍Git的使用,每个小节里会讲解各个功能在命令行中的实现方式,并在每小节的最后介绍在Android Studio中的图形界面里怎么使用对应的功能。
01 Git配置
在安装完Git后,会有一个叫做Git Bash
的程序,打开之后会出现一个跟Linux命令行操作界面神似的命令行窗口,其中可以输入各种Git指令和一些Linux指令。
在使用Git之前,需要进行一步设置,在命令行中输入:
$ git config --global user.name "Your Name"
$ git config --global user.email "[email protected]"
- 1
- 2
- 1
- 2
这两条指令就相当于给自己的电脑取了个名字。在之后的每次提交(备份)中,这两个属性都会被添加到那一次提交生成的提交对象中。在协同开发时,大家可以通过这个名字来区分是谁完成了这个功能,是谁写出了那个Bug。
当然Git中可以进行的设置还有很多,有兴趣的可以查阅git config
这个命令。另外,--global
这个参数表示电脑上的所有仓库都会使用这个名字,当然你也可以给每个仓库指定不同的用户名和Email地址。
在Android Studio中使用Git还需进行如下配置。
在Android Studio中使用Git需要将安装完成的Git.exe文件配置到Android Stdudio中。具体步骤如下,首先点击File->Settings
打开Settings对话框,接着点击Version Control->Git
进入相应目录,在Path to Git executable
中填入Git.exe
所在位置。之后点击Test进行测试,如果弹出成功信息,则配置完成。
02 创建Git仓库
在Git中,创建Git仓库对应的指令为git init
,它会把当前目录初始化为一个Git仓库。使用命令行的话,你可以先把Android工程建好,再是一顿cd
和mkdir
和ls
来到相应的目录,执行git init
来初始化仓库,也可以先建立好仓库,再把工程建在里面,没毛病!
如果用Android Studio初始化仓库。
首先新建一个工程。再点击VCS->Create Git Repository
,会弹出一个对话框,让你选择Git仓库的初始化目录,一般都会选择工程的根目录。注意: 其实在Android Studio中也可以打命令行,在底部的Terminal栏目里输入命令即可。但是因为各种别扭,打命令行还是喜欢用Git bash。
点击OK即可完成仓库的创建。 在新建Git仓库之后,会发现Android Studio底下多出了一个Version
Control栏目。在其中可以进行许多方便的Git操作,包括分支的图形化显示,文件状态的浏览等。细心的同学应该会发现其中的文件是红色的,这里的颜色表示了文件的状态——文件还没有被加入到暂存区中(后面会具体讲暂存区)。
三 用Git备份代码
我们通常是这么备份代码的。第一步,在上一个备份点继续开发。第二步,在想要备份的时候,打个包,存到电脑上,如果怕电脑中病毒瘫痪之类的,可能还会发一份到云盘上,第三步,继续在这一个备份点上开发,……。在开发中,考虑到包的大小和一些自动生成的配置文件和编译过程中产生的文件等,会在上述步骤中再添加一步。即在备份之前,把需要的改动挑选出来,再进行备份。
Git备份代码,其实也就是这三步。
- 在上一个版本的基础上继续开发,修改代码。
- 用
git add
或者git rm
指令,将需要的改动添加到暂存区(顾名思义,一个暂存文件的区域),对应上面的挑选改动操作。 - 用
git commit
指令,将暂存区中的改动与上一个提交(备份)结合,形成一个新的提交(备份),对应上述操作中的备份操作。
注意:这里的改动并不单单指对原文件的修改,也包括创建文件,删除文件。
01 在本地备份代码
在Git中用git add
和git rm
操作来添加改动到暂存区,并用git commit
来生成一个提交(备份)。接下来分别介绍添加文件,修改,删除这三种改动添加到到暂存区的方法和生成一次提交的方法。
关于暂存区到底是个什么东西。
我们把平时看到的电脑上的目录叫做工作区,里面是我们实际操作的文件。而暂存里面一开始会包含上个提交时的所有文件,然后我们会通过git add
和git rm
操作来把工作区里的改动添加到暂存区,比如添加新文件到暂存区,把修改后的文件添加到暂存区以及删除暂存区中的文件。当最后提交时,Git把暂存区中的所有文件组织成一棵树,再加入时间,作者等信息形成一个commit对象,再把commit对象与上一次提交生成的commit对象连接起来,就完成了一次新的提交。(上述表述不是非常严谨额,大家别太较真,意思懂了就行。Git管理文件的方式与Unix的文件系统类似,想具体了解可以去看Git官网的教程)
在新创建Git仓库后,所有文件都还没有加入到版本控制系统中,当前仓库是空的。这里有的同学就奇怪了???你不是用git init
在这个目录下初始化了Git仓库吗,我这个目录下好多文件呢,你怎么说他是空的呢。!!!注意,当前目录下的文件并不属于Git仓库!!!在git init
之后,会在当前目录下生成一个.git
目录(在windows里这个目录隐藏的,设置了显示隐藏文件之后可以看到),里面有一个objects
目录,这个目录下的文件才是属于Git仓库的。刚初始化的仓库的objects
目录有两个目录,一个info
,一个pack
,不过里面啥也没有,都是空的。
这时,对于Git来说,存在了许多新建文件。在命令行中输入git status
可以查看仓库的状态,其中的Untracked files
中的内容,就是我们还未添加的改动——添加文件。
改动类型一:添加文件
在上面的git status
中显示的untracked files
均是在当前版本中还未添加到暂存区里的文件。可以用git add
来将他们加入暂存区。在加入之后用git status
查看仓库的状态。
$ git add app/src/main/java/com/bigboss/blelock/myapplication/MainActivity.java
$ git status
- 1
- 2
- 1
- 2
会发现,多了一段提示,Changes to be committed
(等待提交的改动),其中显示的就是new file
(新文件)。这说明,我们已经将需要的改动添加到暂存区了。这个时候再去查看.git\objects
目录,会发现多了一个文件。
添加完成后用git commit -m "提交信息"
将暂存区中的内容生成一个提交(备份)。
$ git commit -m "添加了MainActivity"
- 1
- 1
用git log
指令可以查看当前的提交历史。可以看到多了一个提交,提交信息就是我们刚才输入的添加了MainActivity
。从这里也可以看到,在第一步Git配置时,设置的用户名和邮箱都自动添加到了提交信息中。
改动类型二:删除文件
在我们删除文件后,可以通过git rm
来把暂存区中的文件也给删掉,从而将改动更新到暂存区。在我们的第一个版本中,我们添加了一个新的文件MainActivity.java
,下面我们把它删除,并添加另一个activity_main.xml
文件(为了方便修改操作的演示)。
在把工作区中的MainActivity.java
删除后,运行git status
,Git会很机智的告诉我们MainActivity.java
已经被删除了,但还没有更新到暂存区。
我们用git rm
将对MainActivity.java
的改动更新到暂存区,并添加activity_main.xml
到暂存区里。
$ git rm app/src/main/java/com/bigboss/blelock/myapplication/MainActivity.java
$ git add app/src/main/res/layout/activity_main.xml
- 1
- 2
- 1
- 2
执行完成后,用git status
查看仓库状态。可以看到Changes to be commited
里有了deleted和new file两条改动信息。
最后提交一下修改。
$ git commit -m "删除了MainActivity.java,添加了activity_main.xml"
$ git log
- 1
- 2
- 1
- 2
改动类型三:修改文件
在我们修改了文件之后,将修改更新到暂存区的操作也是git add
,这里可能有同学会觉得奇怪,为什么修改文件也用git add
,add
不是添加文件的意思吗,其实从本质上讲Git对已修改文件的处理方式就是将修改后的文件整个加入到暂存区替换掉原文件,那用add更新修改也就顺理成章了。
现在我在activity_main.xml
的最后加一行modifyTest
,运行git status
会有下面的modified
提示。
我们用git add
来把修改添加到暂存区,然后查看仓库状态。
$ git add app/src/main/res/layout/activity_main.xml
$ git status
- 1
- 2
- 1
- 2
最后提交一下修改。再用git log
查看一下提交历史。
$ git commit -m "在activity_main.xml最后加了一行,modifyTest"
$ git log
- 1
- 2
- 1
- 2
提交历史如下,又生成了一个提交了哈。
更新所有改动到暂存区并提交
大部分的时候,我们会把所有的改动都更新到缓存区并提交,可以使用git commit -am "提交信息"
,它会自动把所有已经跟踪过的文件暂存起来一并提交,包括修改和删除,他是git add -u
和git commit -m "提交信息"
的结合。如果要把全部文件的添加,修改,删除三种改动全部添加到暂存区去,可以用git add -A
。
通常我们还需要忽略一些指定的文件,比如在java编译后生成的.class文件等,这可以通过配置.gitignore文件来完成。具体见.gitignore的使用这一节。
至此,添加三种改动到暂存区的方式就讲完了,大家可以组合这三种操作完成各种各样的任务!
git status原理
在用git status
查看仓库状态时,经常会看到Untracked files
,Changes not staged for commit
和Changes to be committed
三个标题。大家大概能知道里面的内容是什么意思,但有时还是会感到迷惑。接下来我们会介绍这三个标题下内容的由来。
这三个标题中的内容是根据三个文件树产生的。这三个文件树分别为工作区,暂存区和上一个提交中的文件区(仓库刚初始化的时候,没有提交,相当于文件树为空)。Untracked files
显示的是工作区中存在而暂存区中没有的文件,通常为新建的文件,而Changes not staged for commit
里是工作区中和暂存区中内容不同的文件,通常是因为修改了文件,显示为modified
,以及暂存区里存在但工作区里没有的文件,通常是因为删除了文件,显示为deleted
。而Changes to be committed
里显示的是暂存区里有,但是上一次提交中没有的内容。
Android Studio中的相应操作
先切换到Version Control栏目的子栏目local Changes中,在这里可以看到修改的文件。在里面会看到两个目录,一个是Defult,一个是Unversioned Files。前者中显示的是Git命令行中Changes not staged for commit
栏目的对应文件,后者中显示的是未添加到暂存区的文件。相比命令行的方式,图形界面有更方便的方法来生成一个提交。用户可以直接勾选所需添加的改动,然后直接提交。(Git会有一个暂存区的存在,其中一个原因就是为了模拟挑选改动的行为)。在Android Studio中,点击图中框出的按钮会弹出Commit Changes对话框,来进行提交操作。
之后可以在弹出的窗口中,选择要更新到暂存区的文件,填写提交信息,完成后就按下提交即可完成。
在Version Control的Log子栏目下可以已图形化的方式查看提交历史,其中HEAD标志了当前所处的版本,master标志了master分支的位置。
02 撤销修改
撤销修改有关的命令主要就是两个,git checkout --
和git reset HEAD
,关于这两个命令,主要记住两点。
git checkout --
是把工作目录下的文件用暂存区里的文件代替git reset HEAD
是把暂存区里的文件用上一次提交时的文件代替。
先看一个例子来了解他们的区别。在上一次修改activity_main.xml
后,这个文件内容如下:
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.bigboss.blelock.myapplication.MainActivity">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
android.support.constraint.ConstraintLayout>
modifyTest
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
我们把最后一行modifyTest
删掉,先不更新修改到暂存区,然后执行以下指令
$ git checkout -- app/src/main/res/layout/activity_main.xml
- 1
- 1
再回到activity_main.xml
中,会发现删除的内容又回来了。
这次我们再把最后一行modifyTest
删掉,执行以下指令,将修改更新到暂存区再执行git checkout
。
$ git add app/src/main/res/layout/activity_main.xml
$ git checkout -- app/src/main/res/layout/activity_main.xml
- 1
- 2
- 1
- 2
再回到activity_main.xml
中,会发现删除的内容并没有回来。
为什么呢?因为git checkout --
撤销修改是把暂存区对应的文件替换工作目录下的文件,所以没有更新到暂存区的修改可以撤销,而更新到暂存区的修改不能撤销。
如果要取消添加到暂存区的修改可以采用git reset HEAD
,这个命令可以撤销暂存区里对应文件的修改,但工作目录中的文件不会改变,因为这个命令是用上一次提交中的文件替换暂存区的文件。如果想把工作目录和暂存区中的文件都恢复到上次提交时的样子,可以用git reset HEAD
再git checkout --
。
在工作中,如果你的工作还没有添加到暂存区,那么用git checkout --
即可,如果添加到了暂存区,想要恢复,可以先git reset HEAD
再git checkout --
。
Android Studio中的相应操作
在Android Studio中撤销修改非常简单,在Version Control中选中要撤销修改的文件,鼠标右键,在弹出的菜单中选择Revert
即可撤销修改,注意这边的撤销修改是恢复到上次提交时的样子,相当于使用git reset
和git checkout
。
03 版本回退
在Git中你可以使用git reset commitID
把当前版本回退到之前提交的任意一个版本中。是不是觉得很熟悉!在上一节中使用过这样一个指令,git reset HEAD
,这其实是同一个指令!在Git中,HEAD
表示最近一次提交,而最近一次提交的上一次提交可以用HEAD^
,那上上一次呢?HEAD^^
,上上上一一次?HEAD^^^
,那……你懂得。还记得git reset HEAD
的作用吗?用最近一次提交中的文件替代暂存区中的文件。仔细想一想,其实版本回退就把所有文件都替换掉就好了!在git reset
指令中,在不加
这个参数后,会把整个暂存区用对应提交中的文件替换掉。
特别注意一下,git reset commitID
只是回退了暂存区中的文件,工作区中的文件是不会改变的。举个栗子。目前我们的activity_main.xml
如下:
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.bigboss.blelock.myapplication.MainActivity">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
android.support.constraint.ConstraintLayout>
modifyTest
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
我们回退到上一个版本,在上一个版本中我们的activity_main.xml
中还没有添加最后一行。
$ git reset HEAD^
- 1
- 2
- 1
- 2
执行后,查看activity_main.xml
,发现并没有改变!因为git reset commitID
回退的是暂存区的内容,工作区的内容是不变的。如果你不相信,可以用git status
查看一下仓库状态,里面应该会有一条Changes not staged for commit
,里面的内容是modified: activity_main.xml
。如果想把工作区也回退到上一个版本中的样子,可以在reset之后,执行git checkout -- .
将当前目录用暂存区的内容替代。或者可以直接使用git reset --hard commitID
,其中的--hard
参数会使工作区和暂存区都被恢复成上一次提交时的样子。
要回退的版本可以直接用提交的SHA1值指定。提交的SHA1值可以用git log
中看到。
用SHA1值来回退到上一个版本就像这样。
git reset 952979b2057f6324f204130c88ab8d7452f33489
- 1
- 1
git reset
命令可以将当前版本设置为任意一个提交,只要知道那个提交的SHA1值,就能用git reset
命令将它设置为当前版本。现在我们要把当前版本跳转回去,回到那个添加了modifyTest的版本去,我们用git log
命令来查看这个版本的SHA1值,发现……并没有!
git log
只会显示当前版本之前的提交历史。不过还是有办法的,我们用git reflog
指令,他记录了HEAD
这个头指针变动的历史。
用查找到的版本号进行git reset
操作就可以回去了。
Android Studio中的相应操作
在Android Studio中进行版本地方法如图所示,在Version Control的Log下,选中要跳转的版本,右键右键在弹出的菜单中,选择Reset Current Branch to Here
即可。
04 .gitignore文件的使用
一般在使用Git时,会使用git add -A
将工作区里的所有文件都更新到暂存区,但是总有些文件是我们不想添加进版本控制系统的(通常是一些自动生成的文件,想编译后产生的文件),这时要么一个一个添加文件,要么先全部添加,再把不要的删掉,很不方便。而且在每次用git status
总会有大量的红字,提示我们有文件没添加进版本管理。使用.gitignore文件可以告诉Git哪些文件是不想纳入版本管理的,让Git把这些文件忽略掉。
在Android Studio中,有两个.gitignore文件,一个是Project根目录下的,一个是module下的。对于Android项目,根目录下的.gitignore文件可以如下配置。
# Built application files
*.apk
*.ap_
# Files for the ART/Dalvik VM
*.dex
# Java class files
*.class
# Generated files
bin/
gen/
out/
# Gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
# Log Files
*.log
# Android Studio Navigation editor temp files
.navigation/
# Android Studio captures folder
captures/
# Intellij
*.iml
.idea/workspace.xml
.idea/tasks.xml
.idea/gradle.xml
.idea/dictionaries
.idea/libraries
# Keystore files
*.jks
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
# Google Services (e.g. APIs or Firebase)
google-services.json
# Freeline
freeline.py
freeline/
freeline_project_description.json
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
module下的.gitignore
文件可以如下配置:
/build
*.iml
- 1
- 2
- 1
- 2
如果还有什么需要忽略的文件,可以自行添加,添加的语法如上,其中#
开头的是注释,*
表所有,像*.iml
表示所有后缀为.iml
的文件。
配置前后用git status
查看文件状态,可以发现我们指定的忽略文件已经被忽略了。
如果一个文件已经被检入到了暂存区里面,那么即使在.gitignore文件里添加这个文件名,Git也不会忽略他。所以最好在一开始的时候就把.gitignore给配置好。如果很不幸地出现了这种情况,用下面的指令把相应的文件从暂存区里面删除即可。
# --cached参数表明只删除暂存区里的文件,不加时工作区和暂存区里的文件都会被删除
$ git rm --cached FILENAME
- 1
- 2
- 1
- 2
既然已经把不需要的文件都忽略了,以后添加改动和提交只需要把他们全加上了!执行以下指令。
$ git add .
$ git commit -m "添加了忽略规则"
- 1
- 2
- 1
- 2
现在再用git status
查看仓库状态,显示当前没有任何改动,工作树(工作区)是干净的。其实早就想这么干的,红字看的真心难受。。。
帮助: GitHub 有一个十分详细的针对数十种项目及语言的 .gitignore 文件列表,你可以在
https://github.com/github/gitignore 找到它.
05 备份代码到远程仓库
由于电脑可能会中途报废,我们可能需要个像云盘一样的东西用来备份。又或者在好多个地方有好多台电脑,今天在那里工作明天在这里工作,我们就需要一个地方来中转代码。又或者需要进行协同开发……
这里我们使用Github来创建远程仓库,当然也可以采用别的Git仓库托管服务,像码云,Bitbucket等,当然自己搭也是可以的,在Git官网上有详细的教程。Github账号是必须的,请自行注册。既然要在网络中传输数据,那必然要涉及到协议。目前Git传输数据最常用的两种协议是SSH和HTTPS。
使用SSH协议的方式
1. 配置SSH Key
在命令行中执行以下命令,生成SSH Key。
$ ssh-keygen -t rsa -C "[email protected]" // 把邮箱替换成自己的
- 1
- 1
输入后,他会让我们输入Key存放的位置,如果直接回车,那么默认就是冒号前面那段路径了,之后要根据这个路径找到Key。
之后他还会叫你两次输入密码,如果没什么重要的东西,直接回车就好了。成功后应该是下面这个样子的。
然后来到生成Key的目录下面,有下图所示的两个文件,打开后缀为.pub
的文件(这个是公钥,另一个是私钥),过会儿我们会要把它放到github的账户里。
按下图的步骤把SSH添加到账户里,按下Add SSH Key,就完成了SSH Key的配置。
2. 创建远程仓库
按下图进入创建远程仓库页面。
按下图创建一个远程仓库。Github上的私人仓库是要钱的,7美元一个月哈。虽然是仓库公开的,但是因为有ssh key,别人是不能直接修改你的代码的,不过他们可以看到你的代码。
点击创建后,就拥有自己的远程仓库啦!
3. 在本地添加远程仓库
现在我们已经创建好远程仓库了,我们在本地仓库把它添加上去,就可以把代码备份到远程库了。添加远程库的命令如下。
git remote add <shortname> <url>
- 1
- 1
其中shortname
相当于是url
的简称,以后用shortname
就可以代表url
。url
是远程仓库的地址,可以在如下页面中得到。
一般用origin
表示远程库。在我们的仓库里,执行以下命令。
$ git remote add origin git@github.com:WoHohohoho/MyApplication.git
- 1
- 1
可以用git remote
来查看现有的远程库,上一个命令成功后再执行可以看到多了一个origin
。
4. 把本地仓库的内容push到远程仓库
在添加完远程库后,可以用git push [remote-name] [branch-name]
命令来将本地仓库中的内容推送到远程仓库上。其中remote-name
是远程仓库名,就是上一步中我们设置的shortname
,branch-name
是分支名,分支的具体内容下面再介绍。git push
可以把本地的提交推送到远程仓库中,在执行时,本地仓库与远成仓库会比较提交历史,把远程仓库没有的提交挑选出来,然后发上去,完成更新。
与git push
对应的还有git pull [remote-name] [branch-name]
,push是推,而pull是拉,当我们在另一台电脑上完成工作并推送提交到远程仓库后,为了在原来的电脑上继续工作,我们需要把远程仓库中的更新拉下来,更新本地仓库。
注意: 在使用
git pull
时,要保证本地仓库是最新的,即远程仓库中所有的提交本地都需要有,不然Git会提示pull操作失败。在一个人开发时,因为自己知道自己的代码是否是最新的,所以简单地使用push
和pull
就可以了。在多人开发中,由于别人也会向远程仓库提交代码,在每次push前需要先试探性的获取远程的更新,没有的话就直接push,有的话需要把更新合并到本地的代码中,由于这涉及到分支和合并,之后再详细讲。
仓库初始化时,会默认创建一个master
分支,我们的版本现在都在这个分支上,现在我们把这个分支推送上去就好了。
在我们的仓库里输入下列指令。
$ git push origin master
- 1
- 1
执行完成后,远程仓库里就存在了一份一模一样的工程。
以后每有更新,用git push
将改动更新到远程仓库即可。
使用HTTPS协议的方式
使用HTTPS协议操作起来比较简单。只要从使用SSH协议的第三步开始,将远程仓库的url
换成https
形式即可。
之后操作和SSH方式相同,不过在push
和之后会提到的fetch
(拉取)时,会要求验证账号和密码,不过可以通过设置,来保存账户名和密码,而且一些客户端可以帮助存取账户名和密码,十分方便。HTTPS虽然使用起来很方便,但服务器搭建时配置HTTPS比配置SSH麻烦许多。
Android Studio中的相应操作
可以直接在Android Studio连接Github,建立远程仓库,完成推送。首先需要在Android Studio中设置对应Github的账号和密码。操作路径为File->Settings->Version Control->Github
。其中
Clone git repositories using ssh
这个选项勾选了,就会使用ssh
协议,不勾选就会使用https
协议。用Test
可以测试是否能够连接上Github。使用ssh
协议的话,在创建之前需要配置SSH Key,方式和前面相同。点击之后,可能会要求你输入密码等,最后会弹出一个如下对话框,填写信息,按下Share按钮,就完成了创建远程仓库,在本地添加远程仓库,推送代码到远程仓库的一系列操作,十分方便。
Android Studio中的push操作和pull操作分别是
VCS->Git->Push
和VCS->Git->Pull
。
06 克隆
上一小节我们讲了怎么创建一个远程仓库并把本地仓库同步到上面去,这节会讲如何将远程仓库拷贝到本地来。以后,只要有一台能联网的电脑,我们就能继续我们的工作。
在Git中,可以用git clone
来克隆远程仓库到本地。接下来,我们试着把上一节中创建的远程仓库clone到本地来。
我们执行以下命令,其中remote-url
就是我们在创建远程仓库时得到的远程仓库的地址,local-directory
指明了我们要把仓库放在这个目录(文件夹)里,如果这个目录不存在,Git会自动帮我们新建。
$ git clone git@github.com:WoHohohoho/MyApplication.git ~/Desktop/MyApplicationCloneVersion
- 1
- 1
执行完成后,打开对应目录,会发现东西都已经拷下来了,比U盘什么的方便多了。
Android Studio中的相应操作
Android Studio中的Clone操作沿着VCS->Git->Clone
路径可以找到。之后填写相关参数即可,点击Clone即可。
四 Git中的分支
01 分支的实现原理
Git的分支特性常常被称为“必杀技特性”,因为分支给团队开发提供了很大的便利,而且在Git中的分支实现非常轻量,创建分支,切换分支等复杂操作在Git中只是改变指针指向而已。
在学习分支之前,我们先来回顾一下一次提交的形成过程。Git中有三个存放文件的区域,工作区,暂存区,上一次的提交对象,使用Git的过程其实是操作这三个区域的过程。工作区就是我们在电脑上看到的目录(不包含.git目录,.git目录默认是隐藏的),我们的新建,删除,修改操作都在这里进行。
我们现在的工作流程是这样的。在对工作区改动过后,Git发现工作区里的内容和暂存区里的内容不一样了,就会发出提示说有未暂存的改动(即git status
命令显示出的Changes unstaged for commit
和 Untracked files
),然后我们把需要的改动更新到暂存区里,Git又发现暂存区里的内容和上一次提交的内容不一样了,就发出提示说有待提交的改动(即git status
命令显示出的Changes to be commited
中的内容),最后我们用git commit
进行提交,Git会把暂存区保存成一个文件,然后把指向这个文件的指针与提交者的用户名和邮箱,提交时间等内容,放到一个新建的commit对象里,并让这个commit对象链接到上一个对象,当然commit对象也会被保存起来,而且会计算出一个SHA1校验和让我们来找到这个commit对象,最后把上一个提交对象替换为刚生成的commit对象(其实就是把HEAD指针的指向从上一个commit对象改为了新建的commit对象),一次提交就这么结束了。按上述步骤一直工作下去,我们的commit对象可能会链接成下面这样子。
然后我们发现现在这个功能的实现方式并不好,我们要回退到这个功能开发前的那一个版本重新开发,于是我们用git reset --hard 提交2
指令跳回去了,随着这个指令的执行,Git把HEAD指针的指向改成了提交2,并且把暂存区内容更新为了提交2中的内容,因为加了--hard
参数,Git会接着将工作区的内容更新为暂存区中的内容。 注意Git并不会把提交删掉,有同学会想,既然没删掉,那为什么我用git log
看不到他们了,因为git log
是从HEAD指向的那个提交开始,依次打log的。
然后我们接着开发,中途提交了两次,commit对象就变成了这样。
之后,你可能又觉得不好,又从头来一遍,或者你觉得还是第一次那种好,就用git reflog
查出了那次提交4的commitID,用git reset --hard commitID
跳回去了。其实这样我们已经创建了两个分支了,在这个例子中他们分别代表了我们功能的两种实现方法。之后你可能又会开发很多功能,每个功能里,可能还会有很多小功能,……最后你的commit对象就连接成了这个样子。
额。总之就变成了一棵树的形状,有很多的分支。Commit对象的树形结构为分支特性提供了天然的支持,剩下要做的就只是把分支标志出来而已,于是Git给了每个分支一个指向提交的指针,从这个指针一直遍历到结尾就是这个分支的提交历史。而且对分支的操作,都只是改变指针指向而已。
以前我们说过,HEAD表示了当前版本,其实HEAD是指向分支的,它标志了当前分支,而当前分支的分支指针确定了当前的版本。在每次提交的时候,Git只是在新建commit对象的基础上,把当前分支的分支指针指向新的提交而已。而以前我们所说的git reset
操作只是把当前分支的分支指针指向对应提交,然后替换暂存区而已,如果有--hard
参数那么再替换工作区。分支间的切换也非常简单,如果要从分支1切换到分支2上去开发,只要把HEAD指针指向分支2,然后把暂存区和工作区的内容用分支2的分支指针对应的Commit对象中的内容替换即可。
像我们之前看到过的master
,是Git仓库初始化时默认生成的分支,我们之前的操作都是在master
分支上进行的。
上面的例子用Git中的分支模型表示的话就像下面这样。
在实际开发中,通常会在主分支外,给各个功能的开发新建一个分支,并在功能开发完了之后用分支的合并操作,将分支合并到主分支上去。就像是一个汽车工厂,分开建造各种零件,在零件造好了之后合并到汽车架子上去。
02 分支的作用
分支间互不影响的特点可以在团队开发中发挥很大的作用。两个人可以各建一条分支来开发新功能,只要在开发完后将代码合并到主分支即可,而不必频繁地合并代码,提高了效率,同时也保持了主分支的整洁。
在开发中,经常会遇到线上版本出现Bug,不得不放下手头新功能的开发去修复bug的情况。在Git中,我们通常会建两条分支,一条用来发布新版本,一条用来开发新版本,在新版本开发完后再把这个版本合并回主分支。如果出现了bug,只要在主分支上修改即可,不会和新功能的开发混合起来。有的同学可能会说,那我一条分支时,出现了bug,也只要修复代码就好了呀!注意,向外发布的是稳定的版本,肯定不能把还没开发好的新功能混在一起发布出去啊,那你还得把新功能的代码删掉,在发布后又要把新功能的代码恢复回来继续开发。
另外,用好分支可以保持主分支的整洁,也能让整个项目的开发历史清晰明了。为了更好的利用分支,还演化出了各种各样的工作流,这里不多介绍,大家可以通过文章顶部的链接自行了解。
03 分支操作
常用的分支操作有创建分支,分支间切换,分支的合并(merge)和变基(rebase)。接下来会依次介绍。
分支操作一:创建分支
分支的创建可以用git checkout -b
来完成,branch_name
为分支名。如果该分支不存在,就会创建这个分支并切换到该分支上。其实,Git只是创建了一个指针,并且把HEAD指向了这个指针而已。
当前我们的提交历史如下,只有一个master
分支,origin/master
是远程分支,对应远程库中的master
分支。
用以下指令在当前位置创建两个新的分支,名为produceWheel
和produceEngine
。
$ git checkout -b produceWheel
$ git checkout -b produceEngine
- 1
- 2
- 1
- 2
可以看到多了两个标签,它代表了我们刚创建的两个分支。
现在我们在当前分支上新建一个提交试试,我们修改activity_main.xml文件,在最后添加一行produceEngine,然后提交。
$ git commit -am "EngineCompleted"
- 1
- 1
可以看到,HEAD标签和produceEngine标签前进了一个提交。Git在新建提交对象之后,把produceEngine分支的分支指针指向了新的提交对象,而HEAD标签随着当前分支的移动而移动。
可以用git branch -D
来删除对应分支。
Android Studio中的相应操作
分支的创建操作如下,选中对应的提交,鼠标右键,在弹出的菜单中按下New Branch
,在弹出的对话框中填写分支名即可。如果要删除分支,也是选中分支所在的提交,然后鼠标右键,在弹出的菜单中按如下路径找到删除操作,删除即可(注意不能删除当前分支,删除当前分支前,先切换到别的分支)。
分支操作二:分支间切换
分支间的切换可以用git checkout
来完成。要注意它和git reset [--hard]
的区别。他们的本质区别是git checkout
切换的是HEAD指针的指向来改变当前版本的,准确的说是切换当前分支,而git reset [--hard]
是改变当前分支的分支指针的只想来改变当前版本的。
这里演示一下他们的区别。当前提交历史如下,当前分支为produceEngine。
我们切换分支到produceWheel上。
$ git checkout produceWheel
- 1
- 1
再查看提交历史,只是HEAD标签换了个指向,指向了produceWheel,从而改变了当前版本。
将分支切换回去,我们用git reset [--hard]
回退到同一个版本看一下效果。
$ git reset --hard HEAD^
- 1
- 1
再查看提交历史,发现除了HEAD以外,produceEngine也一起回到了上一个版本。其实HEAD的内容没变,变的是produceEngine,只是因为HEAD是指向produceEngine的,所以连带的回到了上一个版本。
当然,除了指针指向的切换以外,git checkout
还会把暂存区和工作区的内容替换为切换后的提交中的内容。这一点和git reset --hard
操作一样,不过git checkout
在发现有还未提交的改动时,它会报错并提醒用户将这些改动贮藏起来(用git stash
操作可以保存当前所有改动,到需要的时候再恢复进工作区),或者删掉(git reset --hard HEAD
)他们,等到清理完了这些改动后才会允许用户进行git checkout
操作,但其实Git是很机智的,他会先尝试着将改动合并到目标分支的工作区里,如果有冲突才会报错。
Android Studio中的相应操作
先选中分支所在提交,鼠标右键,按如下路径进行操作即可。
分支操作三:分支的合并
可以用git merge
来合并分支,它能将branch_name
所指的分支和并到当前分支上来,合并的时候注意一下是谁合并到谁上。合并其实可以看成是一种特殊的提交,因为它把两条分支上的改动合并到一起,在当前分支上生成一个新的提交,与普通提交不同的是,合并的改动来自两个提交,所有它会连接到两个父提交对象上。
Git中的合并通常被叫做三方合并。合并时会确定三个提交,当前分支对应的提交,被合并分支对应的提交,和两条分支的共同祖先提交。Git会把其他两个提交相对于共同祖先提交的改动提取出来,如果这两份改动里对同一个文件进行了改动,那么Git就会提示自动合并失败,让我们手动修改冲突的文件,在修改完后,使用git commit
把所有改动提交,如果没有对同一个文件进行改动,Git就会自动帮我们添加所有改动到新提交中。
下面合并操作的演示。我们当前的提交历史如下。
我们试着将分支produceEngine
合并到主分支上。
$ git checkout master // 先切换到master分支上
$ git merge produceEngine -m "引擎生产完成" // 把produceEngine分支合并到master分支上
- 1
- 2
- 1
- 2
结果如图所示。当前分支是master
(因为这个从图里看不出来,再截一张图又太累赘,我就直接跟你们讲了),合并之后,master
从添加了忽略规则
前进到了EngineCompleted
,这时同学们又要问了,前面不是说master
分支会新建一个提交,然后这个提交会同时连接两个分支的吗!这是个特殊情况,从两个分支的共同祖先出发,master
分支压根就没有改动,只有produceEngine
分支上有改动,那还合并什么,produceEngine
分支的当前提交就是我master
分支要的结果呀!于是Git就偷了个懒,直接把master
分支的分支指针指向produceEngine
分支的当前提交了。这种简化的合并模式叫做——快进(fast-forward)。
有的时候我们不想要用快进模式来合并,我们想看保留我们的工作轨迹——在哪开始开发这个功能,在哪这个工作完成,中间有哪几步。我们可以用--no-ff
来关闭快进模式。让我们用--no-ff
重新来一遍。
$ git reset --hard HEAD^ // 撤销上次合并
$ git merge --no-ff produceEngine -m "引擎生产完成"
- 1
- 2
- 1
- 2
不用快进模式时的提交历史如下。跟我们之前设想的一样。
现在我们转去生产轮胎。先切换到produceWheel
分支上。
$ git checkout produceWheel
- 1
- 1
再在produceWheel
分支上进行开发,这边我们在activity_main.xml
的最后加上一行ProduceWheel
,然后提交。
$ git commit -am "WheelCompleted"
- 1
- 1
开发完后,我们切换回master
分支把改动合并回来。
$ git merge produceWheel -m "轮子生产完成"
- 1
- 1
结果,额。报错了。
Git提示说它尝试了自动合并,但是失败了。因为在master
分支上我们改了activity_main.xml
文件,而在produceWheel
分支上我们也改了activity_main.xml
文件,Git不知道要怎么处理这些两个改动。于是Git提示我们,让我们解决冲突后,提交结果。
我们打开冲突文件,Git已经帮我们标记出了各分支的修改。
我们手动修改这个文件,修改结果如下。
然后提交。
$ git commit -m "轮胎生产完成"
- 1
- 1
提交之后,合并就完成了,现在我们的提交历史是下面这个样子的。
Android Studio中的相应操作
选择要合并进当前分支的分支,鼠标右键,按如图所示路径操作。如果有冲突存在,Android Studio会弹出
Files Merged with Confilcts
对话框,右边有三个选项,分别是,采用自己的改动,采用其他分支的改动,合并改动,我们选择合并改动。然后会弹出合并改动的窗口,左边是当前分支的改动,右边是其他分支的改动(要合并进来的那个分支) ,中间是冲突合并的结果。选择箭头可以采用改动到结果中,而选择叉号会忽略这个修改
这里我们两边的改动都采用了。
冲突解决完成后,点击
Apply
按钮即可完成合并。上述方式虽然方便,但是没有办法自己写提交信息。如果要写提交信息,可以用
VCS->Git->Merge Changes
下的合并操作,窗口如下,在Commit Message栏里可以填写提交信息。用这种方法合并,在解决冲突后需要自己提交一下。
分支操作四:变基
除了合并之外,变基也可以将两条分支的内容整合到一起。使用变基操作时,Git会先确定两条分支的共同祖先,然后会依次当前分支中各个提交的修改提取出来,并且结合另一个分支上的修改,形成新的提交,一个一个拼接到指定分支之后。
变基可以使提交历史整洁,但是它会修改已有的提交历史。
对已经推送到中央仓库的分支,要慎重考虑能不能用变基。变基会修改已有的提交历史,而且会删去原提交历史上的提交(内容没删,只是不再连接到原有的树形结构上了), 但是对中央仓库的fetch操作只能拿取本地分支没有的提交,而不会删除中央仓库没有但本地存在的提交。如果其他人拉取过变基前的分支,那他可能要手动把它删除,如果他不仅拉取过,而且在之上进行了一些开发,进行过提交,合并等等操作,那他可能会打你一顿。
变基可以用命令git rebase
完成,它会将当前分支整合到指定分支上。举个栗子,我们当前的提交历史如下。
我们将produceWheel分支rebase
到master
上。
$ git rebase master
- 1
- 1
运行后,Git提示我们它正在把WheelCompleted
这个提交应用到master分支上,但是在合并activity_main.xml
文件时,发生了冲突,让我们解决冲突后用git rebase --continue
继续rebase
。
用冲突解决工具,显示如下,左边是提交WheelCompleted
的内容,右边是master
分支上提交引擎生产完成
的内容,它们均在祖先提交添加了忽略规则上
,添加了一行。
我们把改动都添加上去。冲突解决后输入命令git rebase --continue
继续变基。在这之后master
分支上就生成了一个新的WheelCompleted
提交。
接着,Git又告诉我们它正在把BetterWheelCompleted
这个提交应用到master
分支上,但是在合并activity_main.xml
文件时,发生了冲突,让我们解决冲突后用git rebase --continue
继续rebase
。
再次打开冲突解决工具,显示如下,左边是提交BetterWheelCompleted
的内容,右边是master
分支上新WheelCompleted
提交的内容,他们均在老WheelCompleted
提交的基础上添加了一行。
老样子,我们把改动都添加上,然后输入命令git rebase --continue
继续变基。然后,master分支上就又生成了新的BetterWheelCompleted
提交,至此提交已经全部转移完成了,Git将produceWheel
分支的分支指针指向新的BetterWheelCompleted
提交,变基至此就结束了。完成后的提交历史如下,这时只要再进行一次简单地快进合并就把produceWheel
分支的内容整合到master
分支上了。
大家可能会对rebase
过程中的各个新节点的产生过程感到迷惑。其实新节点可以看做是由旧节点和上一个提交的新节点以上一个提交的旧节点为祖先节点合并产生,举个栗子,新BetterWheelCompleted
节点可以看做是由旧BetterWheelCompleted
节点和新WheelCompleted
节点以旧WheelCompleted
节点为祖先合并产生的。
Android Studio中的相应操作
首先,切换到要变基的分支,然后在如下路径中进行操作,因为解决冲突等操作与合并中相同,就不再多说了。也可以使用
VCS->Git->Rebase
路径中的操作。
五 用Git进行协同开发
在协同开发中,为了项目管理的方便,会采用各种各样的工作流,有关工作流方面的内容,可以到开头提供的第三个网址里学习。这里只简单介绍两个实用的技巧。
01 一个干净的push!
在Git中,我们很少会改变中央仓库的已有的提交历史。因为那样会使所有的开发者的本地仓库和中央仓库处在不一致的状态,所有开发者都需要因为这个改动调整自己的本地仓库来与中央仓库保持一致。如果改动很大,调整将会很复杂。只要不是及其严重的事情,我们都不会去调整中央仓库的提交历史。所以,推送上去是什么样的,以后就会是什么样的,对于中央仓库来说,第一印象会伴随一生。
所以,就像女生出门前要化妆一样,在推送本地分支到中央仓库前,要把本地分支整理干净。一般,我们会给每个功能新建一个分支,在功能开发完成后,可以用git rebase -i
命令删去那些无关紧要的提交,并将提交历史书写成我们逻辑中的样子。
比如我们接到了生产车门的任务,现在我们把它完成了,开发历史如下。
在我们把它推送到中央仓库前,我们用git rebase -i
整理一下它的提交历史,commitID
参数用来指定我们要从哪个提交开始修改。由于我们是从BetterWheelCompleted
这个提交开始开发车门的,所以commitID
参数应该是这个提交的版本号,为99c7ed1578c0a16a0f1de58548883a8d6e8eeb8a
。执行以下指令。
$ git rebase -i 99c7ed1578c0a16a0f1de58548883a8d6e8eeb8a
- 1
- 1
之后,在命令行中会显示vi
文本编辑窗口。
在最前面的是各个提交,每个提交的前面是要对该提交执行的命令,默认是pick
采用提交,我们主要会用到squash
命令,squash
命令可以让我们把对应提交合并到上一次提交中,举个栗子,给69218a4
提交加上squash
命令就可以把它合并到f7a6770
提交上。我们开发车门主要有三个阶段,设计车架,设计车窗,加入手势开门功能,所以我们要分别合并第二、三、四次提交。我们把它编辑如下,然后保存并退出编辑窗口。
之后在合并第二、三、四次提交时,Git又会弹出一个文本编辑窗口,让我们合并三次提交的提交信息。
因为这一次合并提交的信息应该为“设计了车窗”,刚好是第二个提交的提交信息,所以我们在不要的信息前加#
把他们忽略即可。合并完成后,我们的提交历史就变成了如下模样。
然后就可以把他们推送到远端,由其他人检查代码,然后合并或者变基到主分支上了。如果在开发完后就已经决定要这条分支变基到主分支上,那也可以直接用git rebase -i
来修改提交历史并变基。
很多时候,我们会把一个功能的实现分为好几个功能点,然后给功能点新建一条分支,然后在这个功能点开发完后把修改整合回功能分支。这样做的好处是可以保持功能分支的整洁,不会在功能分支上生成很多备份代码的提交,但是如果用普通的merge
操作的话,合并生成的提交除了指向主功能分支外,也会指向功能点分支,那样还是会把功能点分支上的提交历史混合进来。
这时我们可以使用git merge --squash
,在merge
指令中加入--squash
后会把branch_name
分支上的所有提交压缩成一个加入到当前分支上来,并且这个提交不会指向被合并的分支。举个栗子,在上次我们把车门的分支推送上去给老大检查后,老大说要再加一个手势关门的功能!然后我们为了主分支的整洁,给这个小功能新建了一个分支进行开发。接着,经过了不懈努力,我们终于开发完了,现在提交历史如下。
我们用以下指令把feature-CloaseDoorwithGesture
分支的上的提交压缩成一个一条放到produceDoor
分支上。
$ git checkout produceDoor
$ git merge --squash feature-CloseDoorwithGesture
$ git commit -m "给车门加上了手势关门的功能"
- 1
- 2
- 3
- 1
- 2
- 3
执行完成后我们的提交历史就变成了下图所示模样。
可以看到新建的提交并没有指向feature-CloaseDoorwithGesture
分支。在合并完后,我们可以删除feature-CloaseDoorwithGesture
分支。
$ git branch -D feature-CloseDoorwithGesture
- 1
- 1
OK。老大说干的不错,让我们把分支合并到主分支上,然后推送到中央仓库来结束这个功能的开发。
$ git merge --no-ff produceDoor -m "车门生产完成"
$ git push origin master
- 1
- 2
- 1
- 2
执行完成后,提交历史如下。
Android Studio中的相应操作
用VCS->Git->Merge Changes
下的合并操作,并选中squash commit
选项,即可完成git merge --squash
操作。
git rebase -i
可以用VCS->Git->Rebase
完成。点击后会弹出如下对话框,选中Interactive
选项,在Branch
选项中选中要变基的分支,在Onto
选项中,填写新基点(会从这个提交开始修改历史)。点击Rebase之后,会弹出如下对话框, 在这里你可以选择要对每个提交执行的命令。选完命令之后,按下Start Rebasing就会开始变基。
02 push失败?
·我们把前面开发车门那个同学叫做小明,在小明开发车门,引擎和轮子的同时,还有一个叫小刚的同学在开发制动系统。小刚现在刚把制动系统开发完毕,他现在的提交历史如下。
现在他要把master
分支推送到中央仓库上。小刚运行了如下指令。
$ git push origin master
- 1
- 1
但是Git告诉他,push
被拒绝了,因为中央仓库的master
分支里有本地还没有的改动,并让我们把改动集成到本地仓库中再进行push
。
所以在每次push
前,应该先抓取中央仓库上的更新,然后集成到本地,再提交。抓取更新的指令是git fetch
。现在小明用以下指令拉取了中央仓库上的更新。
$ git fetch origin master
- 1
- 1
拉取后,本地的提交历史变成了这样。其中,origin/master
表示的就是远程仓库上的master
分支,远程分支是只读的,不能修改。
现在小明要把远程仓库的master
分支上的改动集成到本地的master
上。根据之前学的知识,有两种方法,rebase
和merge
。
如果使用merge
,把本地origin/master
分支的内容合并到master
上,合并后提交历史如下。很乱,而且因为合并而新生成了一个节点。
如果用rebase
,把本地master
分支的内容变基到origin/master
上,合并后提交历史如下。清楚了很多,而且看起来像是串行开发的一样。一般把远端更新整合到本地会用rebase
指令。
注意:
rebase
操作会把可以快进的合并记录删掉,最后rebase
结果就像是合并时使用了快进一样。如果要保留合并记录,可以加入--preserve-merges
参数。
上面提到过git pull
操作,它其实是git fetch
和git merge
的结合。当然也可以转化为rebase
版本,在中间加个参数,git pull --rebase
。
其实上面的操作有更好的处理方法。在功能开发完成后,可以先把远端的更新用git pull --rebase
拉取下来,并把本地分支的更新变基到上面去。之后,把功能分支rebase
到主分支上,这个时候远端的更新就全部整合进功能分支了,这个时候我们可以测试一下合并之后代码还正不正确。最后再把功能分支合并到主分支并推送上去。
Android Studio中的相应操作
在Android Studio中,可以选择用merge
版本还是rebase
版本的pull
操作。按下下图中的按钮。
在弹出的对话框中,选择拉取方式。右边的选项是存储当前工作区修改的方式。选择完成后,点击ok即可开始拉取。