以此记录、总结自己学习git的点滴
博文并非自己编写,而是根据其他人教程学习,此处仅用于记录学习过程,整理总结。原教程过于繁琐,而且分散成很多个章节,不方便查阅,现整理方便后期查阅资料用。长期更新。
原文详见:http://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000
1.安装及配置git仓库
Git 分布式版本管理系统,很常用。本文主要是在终端中进行操作。
特点:不需要中央服务器,具有强大的分支开发功能(相比于SVN等)。
安装:mac系统中只要安装了Xcode,就默认安装了git,但是版本不一定是最新的,可以用gem或brewHome安装或者更新,不再陈述。
安装完成之后需要初始化git的基本配置,也就是用户名和密码
$ git config --global user.name "Your Name"
$ git config --global user.email "email@example.com"
git中没有密码
配置完成之后,本机的所有的git仓库都会按照这个用户名和email进行push/pull,也可以单独配置
2.初始化git仓库
首先,cd到相应的文件夹
$ mkdir learngit
$ cd learngit
$ pwd
/Users/michael/learngit
PS:pwd用于查看当前路径
然后,通过git init命令把当前目录初始化成git可以管理的git仓库
$ git init
Initialized empty Git repository in /Users/michael/learngit/.git/
然后就建好了
PS:当前目录下多了一个.git
的目录,这个目录是Git来跟踪管理版本库的,没事千万不要手动修改这个目录里面的文件,不然改乱了,就把Git仓库给破坏了。
如果你没有看到.git
目录,那是因为这个目录默认是隐藏的,用ls -ah
命令就可以看见。
2.git的使用
git初始化完成,当前文件夹已经变成了git仓库,可以任意增删改查文件/文件夹,
PS:git只能对git仓库中文件、文件夹进行版本管理,放在其他地方git无能为力
1.增加文件
当前文件夹中增加了一个readme.txt文件,当前只是文件放入了仓库中,但git并不知道需要对其进行版本管理,想告知其需要进行更新管理则需要一下操作
第一步:用git add fileName 命令告诉git将fileName文件添加到仓库
$ git add readme.txt
PS:git add . 可以将当前文件夹下的所有未提交的更改进行add操作
第二步:用git commit命令让git将fileName文件提交到仓库
$ git commit -m "wrote a readme file"
[master (root-commit) cb926e7] wrote a readme file
1 file changed, 2 insertions(+)
create mode 100644 readme.txt
PS:1.-m后面的信息是上传日志,方便后期查看
2.可以add很多次文件,然后一次全部commit
3.关于以上两步的具体内容后续会详细介绍
2.常看当前git状态
继续修改文件,然后输入命令git status
$ git status
# On branch master
# 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: readme.txt
#
no changes added to commit (use "git add" and/or "git commit -a")
git status
命令可以让我们时刻掌握仓库当前的状态,上面的命令告诉我们,readme.txt被修改过了,但还没有准备提交的修改。
git status可以告诉我们哪个文件被修改了,但并不能告诉我们更改了啥,就需要用git diff命令
$ git diff readme.txt
diff --git a/readme.txt b/readme.txt
index 46d49bf..9247db6 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,2 +1,2 @@
-Git is a version control system.
+Git is a distributed version control system.
Git is free software.
顾名思义,就是看区别的嘛
3.版本回退
进行了N次修改后,可以通过git log查看历次提交记录,这时候,提交日志的作用就体现出来了
$ git log
commit 3628164fb26d48395383f8f31179f24e0882e1e0
Author: Michael Liao <askxuefeng@gmail.com>
Date: Tue Aug 20 15:11:49 2013 +0800
append GPL
commit ea34578d5496d7dd233c827ed32a8cd576c5ee85
Author: Michael Liao <askxuefeng@gmail.com>
Date: Tue Aug 20 14:53:12 2013 +0800
add distributed
commit cb926e7ea50ad11b8f9e909c05226233bf755030
Author: Michael Liao <askxuefeng@gmail.com>
Date: Mon Aug 19 17:51:55 2013 +0800
wrote a readme file
为了简化输出结果,可以添加
--pretty=oneline
参数,结果如下
$ git log --pretty=oneline
3628164fb26d48395383f8f31179f24e0882e1e0 append GPL
ea34578d5496d7dd233c827ed32a8cd576c5ee85 add distributed
cb926e7ea50ad11b8f9e909c05226233bf755030 wrote a readme file
commit后面的那一串就是commit ID,可以通过ID进行回退。之所以不用123是因为多人在同一个git仓库中进行工作,那样肯定冲突
首先,Git必须知道当前版本是哪个版本,在Git中,用HEAD
表示当前版本,也就是最新的提交3628164...882e1e0
(注意我的提交ID和你的肯定不一样),上一个版本就是HEAD^
,上上一个版本就是HEAD^^
,当然往上100个版本写100个^
比较容易数不过来,所以写成HEAD~100
。
然后,知道版本号了,可以用git reset命令进行版本回退
$ git reset --hard HEAD^
HEAD is now at ea34578 add distributed
除了上述方式,还可以用commit版本号进行回退
$ git reset --hard HEAD^
HEAD is now at ea34578 add distributed
只要知道commit版本号(通过日志等得到的版本号),就可以任意的回退/前进
Git的版本回退速度非常快,因为Git仓库中保存有所有的版本的文件。在内部有个指向当前版本的HEAD
指针,当你回退版本的时候,Git仅仅是把HEAD从指向之前版本改到
指向你希望的版本。
git会记录你每一次的回退,只需要使用git reflog命令即可
$ git reflog
ea34578 HEAD@{0}: reset: moving to HEAD^
3628164 HEAD@{1}: commit: append GPL
ea34578 HEAD@{2}: commit: add distributed
cb926e7 HEAD@{3}: commit (initial): wrote a readme file
前面的数字是commit版本号,只要用那个版本号即可,再输入时不用全部输入,输入几位git即可自动搜索适应
4.工作区和暂存区(add和commit命令区别及联系)
前面讲了我们把文件往Git版本库里添加的时候,是分两步执行的:
第一步是用git add
把文件添加进去,实际上就是把文件修改添加到暂存区;
第二步是用git commit
提交更改,实际上就是把暂存区的所有内容提交到当前分支。
你可以简单理解为,需要提交的文件修改通通放到暂存区,然后,一次性提交暂存区的所有修改。
如果不add,也没法commit,就是这个意思
5.撤销修改
如果你对工程文件进行了一定的修改,但后来想撤销这部分修改(比如尝试实现某个功能,后来失败了,想把修改的部分回退回去)
可以使用git checkout -- file
命令实现
$ git checkout -- readme.txt
简单的说,就是把file回退到上一次add的状态,已经add的不能通过这个命令修改,需要用到命令git reset HEAD file
,可以把暂存区的修改撤销掉(unstage),重新放回工作区
$ git reset HEAD readme.txt
Unstaged changes after reset:
M readme.txt
6.删除文件
git中,无论是通过finder删除还是通过终端rm删除,git并不知道文件是否需要从仓库删除。查看git状态的时候会提示某个文件丢失了。想删除git仓库中的文件需要用到命令git rm
,然后立即git commit就可以了
$ git rm test.txt
rm 'test.txt'
$ git commit -m "remove test.txt"
[master d17efd8] remove test.txt
1 file changed, 1 deletion(-)
delete mode 100644 test.txt
如果误删,可以通过git checkout file或者git reset --hard HEAD^ 进行版本回退。
7.建立远程仓库
介绍到这里,我们已经能够利用git仓库进行版本控制等操作。你也会发现git相比SVN并无明显优势,版本回退,备份等svn都有相应的功能。这显然是和git的名声不符。现在来介绍体现git强大的功能之一(后面会有234),远程仓库。
实际情况往往是这样,找一台电脑充当服务器的角色,每天24小时开机,其他每个人都从这个“服务器”仓库克隆一份到自己的电脑上,并且各自把各自的提交推送到服务器仓库里,也从服务器仓库中拉取别人的提交。也可以自己搭建一台git服务器,但实在无必要,直接用gitHub就可以了。免费的需要公开,想私密托管就需要交保护费,7$/m.
首先,注册github账号。
然后,提交ssh公钥。
github在进行数据传输时使用ssh加密,所以要把本地生成的ssh公钥提交到github服务器上。github的版本库在进行修改时会比较密钥,只有在密钥列表中的电脑提交的修改才被服务器接受
第1步:创建SSH Key。在用户主目录下,看看有没有.ssh目录,如果有,再看看这个目录下有没有id_rsa
和id_rsa.pub
这两个文件,如果已经有了,可直接跳到下一步。如果没有,打开Shell(Windows下打开Git Bash),创建SSH Key:
$ ssh-keygen -t rsa -C "youremail@example.com"
然后打开User主目录下的 .ssh/id_rsa.pub 文件,将密钥复制出来即可。我是通过终端nano命令打开的。注意复制的时候将前后的头尾都要复制上。
第二步,github中提交当前电脑密钥
登陆GitHub,打开“Account settings”,“SSH Keys”页面,点“Add SSH Key”,填上任意Title,在Key文本框里粘贴id_rsa.pub
文件的内容即可
OK,至此,就可以和github通过ssh加密传输数据了,可以通过以下命令测试是否连接正常:
Ahri:git3 pyc$ ssh -T git@github.com
Hi Flanker711! You've successfully authenticated, but GitHub does not provide shell access.
Ahri:git3 pyc$
再然后,在github上简历远程库
很简单,公开的免费(别人可以随意下载查看,但是不能修改,没有密钥)。
在2处填上仓库名,
至此,本地和网络都有相应的仓库了,可以将本地的添加上去啦
$ git remote add origin git@github.com:Flanker711/learngit.git
该命令就是讲当前的git仓库提交到Flanker711用户下的learngit仓库下,
下一步,即可把实际的文件修改提交上去,用的是push
$ git push -u origin master
Counting objects: 19, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (19/19), done.
Writing objects: 100% (19/19), 13.73 KiB, done.
Total 23 (delta 6), reused 0 (delta 0)
To git@github.com:michaelliao/learngit.git
* [new branch] master -> master
Branch master set up to track remote branch master from origin.
把本地库的内容推送到远程,用git push
命令,实际上是把当前分支master
推送到远程。
由于远程库是空的,我们第一次推送master
分支时,加上了-u
参数,Git不但会把本地的master
分支内容推送的远程新的master
分支,还会把本地的master
分支和远程的master
分支关联起来,在以后的推送或者拉取时就可以简化命令。
推送成功后,可以立刻在GitHub页面中看到远程库的内容已经和本地一模一样:
至此,远程仓库就算是完成了,以后本地有修改,也可以直接push上去。如果是多人协作,在push之前记得pull一下,原理和svn提交前更新一下相同。以后再push直接用以下命令即可,表示将当前git修改的内容提交到当前git关联的远程仓库的master分支
$ git push origin master
PS:
要关联一个远程库,使用命令git remote add origin git@server-name:path/repo-name.git
;
关联后,使用命令git push -u origin master
第一次推送master分支的所有内容;
此后,每次本地提交后,只要有必要,就可以使用命令git push origin master
推送最新修改;
分布式版本系统的最大好处之一是在本地工作完全不需要考虑远程库的存在,也就是有没有联网都可以正常工作,而SVN在没有联网的时候是拒绝干活的!当有网络的时候,再把本地提交推送一下就完成了同步,真是太方便了!
8.获取最新代码PULL
pull可以以获取最新的修改,但一般不这么用,主要是从安全角度考虑
1. git fetch :相当于是从远程获取最新版本到本地,不会自动merge
<p style="line-height:25px; margin-top:0px; margin-bottom:10px; padding-top:0px; padding-bottom:0px"><span style="font-family:'Trebuchet MS',Tahoma,Arial;color:#333333;line-height:19px; font-size:13px"><span class="pln" style="color:#000000;line-height:23px;"> </span></span><br style="line-height:23px" /><span class="pln" style="color:#000000;line-height:23px;">git fetch origin master</span><br style="line-height:23px" /><span class="pln" style="color:#000000;line-height:23px;">git log </span><span class="pun" style="color:#66660;line-height:23px;">-</span><span class="pln" style="color:#000000;line-height:23px;">p master</span><span class="pun" style="color:#66660;line-height:23px;">..</span><span class="pln" style="color:#000000;line-height:23px;">origin</span><span class="pun" style="color:#66660;line-height:23px;">/</span><span class="pln" style="color:#000000;line-height:23px;">master</span><br style="line-height:23px" /><span class="pln" style="color:#000000;line-height:23px;">git merge origin</span><span class="pun" style="color:#66660;line-height:23px;">/</span><span class="pln" style="color:#000000;line-height:23px;">master</span></p>以上命令的含义:
首先从远程的origin的master主分支下载最新的版本到origin/master分支上
然后比较本地的master分支和origin/master分支的差别
最后进行合并
上述过程其实可以用以下更清晰的方式来进行:
<p style="line-height:25px; margin-top:0px; margin-bottom:10px; padding-top:0px; padding-bottom:0px"><span style="font-family:'Trebuchet MS',Tahoma,Arial;font-size:12px;color:#333333;line-height:23px"><span style="line-height:19px"><span class="pln" style="color:#000000;line-height:23px;">git fetch origin master</span><span class="pun" style="color:#66660;line-height:23px;">:</span><span class="pln" style="color:#000000;line-height:23px;">tmp</span></span></span><br style="line-height:25px" /><span style="font-family:'Trebuchet MS',Tahoma,Arial;font-size:12px;color:#333333;line-height:23px"><span style="line-height:19px"><span class="pln" style="color:#000000;line-height:23px;">git diff tmp </span></span></span><br style="line-height:25px" /><span style="font-family:'Trebuchet MS',Tahoma,Arial;font-size:12px;color:#333333;line-height:23px"><span style="line-height:19px"><span class="pln" style="color:#000000;line-height:23px;">git merge tmp</span></span></span></p>从远程获取最新的版本到本地的test分支上
之后再进行比较合并
2. git pull :相当于是从远程获取最新版本并merge到本地
<p style="line-height:23px; margin-top:0px; margin-bottom:10px; padding-top:0px; padding-bottom:0px"><span class="pln" style="color:#000000;line-height:23px;">git pull origin master</span></p>上述命令其实相当于git fetch 和 git merge
在实际使用中,git fetch更安全一些
因为在merge前,我们可以查看更新情况,然后再决定是否合并
9.从远程仓库克隆
配置好了远程库,得到url,本地git初始化之后,从远程仓库克隆就很简单了,一行命令搞定,git clone
$ git clone git@github.com:michaelliao/gitskills.git
Cloning into 'gitskills'...
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (3/3), done.
$ cd gitskills
$ ls
README.md
PS:Git支持多种协议,包括
https
,但通过
ssh
支持的原生
git
协议速度最快。
10.分支管理
(1)分支的概念
关于分支管理,原博客的比方说的很透彻,借用如下:
分支就是科幻电影里面的平行宇宙,当你正在电脑前努力学习Git的时候,另一个你正在另一个平行宇宙里努力学习SVN。
如果两个平行宇宙互不干扰,那对现在的你也没啥影响。不过,在某个时间点,两个平行宇宙合并了,结果,你既学会了Git又学会了SVN!
分支在实际中有什么用呢?假设你准备开发一个新功能,但是需要两周才能完成,第一周你写了50%的代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能干活了。如果等代码全部写完再一次提交,又存在丢失每天进度的巨大风险。
现在有了分支,就不用怕了。你创建了一个属于你自己的分支,别人看不到,还继续在原来的分支上正常工作,而你在自己的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,这样,既安全,又不影响别人工作。
其他版本控制系统如SVN等都有分支管理,但是用过之后你会发现,这些版本控制系统创建和切换分支比蜗牛还慢,简直让人无法忍受,结果分支功能成了摆设,大家都不去用。
但Git的分支是与众不同的,无论创建、切换和删除分支,Git在1秒钟之内就能完成!无论你的版本库是1个文件还是1万个文件。
(2)分支的实现
在版本回退里,你已经知道,每次提交,Git都把它们串成一条时间线,这条时间线就是一个分支。截止到目前,只有一条时间线,在Git里,这个分支叫主分支,即master
分支。HEAD
严格来说不是指向提交,而是指向master
,master
才是指向提交的,所以,HEAD
指向的就是当前分支。
一开始的时候,master
分支是一条线,Git用master
指向最新的提交,再用HEAD
指向master
,就能确定当前分支,以及当前分支的提交点:
每次提交,master
分支都会向前移动一步,这样,随着你不断提交,master
分支的线也越来越长:
dev
时,Git新建了一个指针叫
dev
,指向
master
相同的提交,再把
HEAD
指向
dev
,就表示当前分支在
dev
上:
你看,Git创建一个分支很快,因为除了增加一个dev
指针,改改HEAD
的指向,工作区的文件都没有任何变化!
不过,从现在开始,对工作区的修改和提交就是针对dev
分支了,比如新提交一次后,dev
指针往前移动一步,而master
指针不变:
假如我们在dev
上的工作完成了,就可以把dev
合并到master
上。Git怎么合并呢?最简单的方法,就是直接把master
指向dev
的当前提交,就完成了合并:
所以Git合并分支也很快!就改改指针,工作区内容也不变!
合并完分支后,甚至可以删除dev
分支。删除dev
分支就是把dev
指针给删掉,删掉后,我们就剩下了一条master
分支。
以上是原博客原文,现总结如下:
第一,HEAD只是指向当前git工作的分支,可能是主分支,也可能是任何一个分支,HEAD的每次提交,是让他当前所指的分支向前增加一个小版本。在强调一遍,可能是主分支,也可能是任何一个分分支向前增加一个小版本。
第二,关于合并。主分支可能到了某一个版本,是master分支。a,b两个小组各自进行各自分支的开发,分别是dev_a,dev_b,两个分支。若此时,a组完成开发,则合并,仅仅是将HEAD指向dev_a的最新版本而已。简单说,哪个分支合并,master分支指向那个分支。
结合上述结论,画了下面的大作,红线是主分支,绿线是分支a,黄线分支b
①在1处,迁出分支a,a各自为战,在分支a工作的git的Head指针分别指向a上的各个节点,依次前进
②b分支同a
③部分接着在主分支工作的git,则继续前进
④首先是分支b完成了全部的工作,这时需要合并处理,实质是master指针指向了b分支(b在提交前会进行pull,已经获取了3的更新
⑤a更新同b相同
(3)分支的代码操作范例
创建dev分支:用checkout命令创建分支
$ git checkout -b dev
Switched to a new branch 'dev'
git checkout
命令加上-b
参数表示创建并切换,相当于以下两条命令:
$ git branch dev
$ git checkout dev
Switched to branch 'dev'
查看分支:用git branch
命令查看当前分支:
$ git branch
* dev
master
git branch
命令会列出所有分支,当前分支前面会标一个*
号。
然后修改,并提交:
$ git add readme.txt
$ git commit -m "branch test"
[dev fec145a] branch test
1 file changed, 1 insertion(+)
分支切换:现在,dev
分支的工作完成,我们就可以切换回master
分支:
$ git checkout master
Switched to branch 'master'
切换回master
分支后,再查看一个readme.txt文件,刚才添加的内容不见了!因为那个提交是在dev
分支上,而master
分支此刻的提交点并没有变:
合并分支:现在,我们把dev
分支的工作成果合并到master
分支上:
$ git merge dev
Updating d17efd8..fec145a
Fast-forward
readme.txt | 1 +
1 file changed, 1 insertion(+)
git merge
命令用于合并指定分支到当前分支。合并后,再查看readme.txt的内容,就可以看到,和dev
分支的最新提交是完全一样的。
注意到上面的Fast-forward
信息,Git告诉我们,这次合并是“快进模式”,也就是直接把master
指向dev
的当前提交,所以合并速度非常快。
当然,也不是每次合并都能Fast-forward
,我们后面会讲其他方式的合并。
查看分支日志:可以用之前打印日志的方法打印日志,加一个参数即可
删除分支:可以用branch -d branchName删除分支
$ git branch -d dev
Deleted branch dev (was fec145a).
删除后,查看branch
,就只剩下master
分支了:
$ git branch
* master
上面的删除是删除本地git仓库中的分支,并不能删除github上面的分支,想删除github上面的分支,使用以下命令:
$ git push origin :dev3
To git@github.com:Flanker711/GitTest.git - [deleted] dev3
PS:即使分支已经被删除,如果继续提交该分支,则gtihub会自动回复该分支,很奇怪。
因为创建、合并和删除分支非常快,所以Git鼓励你使用分支完成某个任务,合并后再删掉分支,这和直接在master
分支上工作效果是一样的,但过程更安全。
小结
Git鼓励大量使用分支:
查看分支:git branch
创建分支:git branch <name>
切换分支:git checkout <name>
创建+切换分支:git checkout -b <name>
合并某分支到当前分支:git merge <name>
查看git图形化日志:git log --oneline --graph
删除分支:git branch -d <name>
删除github端分支:git push origin :<name>
一次完成的创建分支,分支工作,合并分支过程应该是:创建分支,分支工作,提交工作,完成工作,切回主分支,获取主分支最新代码,合并分支,冲突解决,提交合并,删除分支,删除github分支
11.解决冲突
关于冲突解决,git相比svn并没有特别之处,主要是手动处理。建议是经常从主分支获取代码,防止产生严重的冲突