学习完廖雪峰老师的git课堂,结合教学内容总结一下。
Git简介
Git是分布式版本控制系统。
集中式vs分布式
1.集中式
典型:CVS,SVN
解析:版本库是集中存放在中央服务器的,中央服务器就好比是一个图书馆,你要改一本书,必须先从图书馆借出来,然后回到家自己改,改完了,再放回图书馆。
2.分布式
分布式版本控制系统根本没有“中央服务器”,每个人的电脑上都是一个完整的版本库。但其实,有一台充当“中央服务器”的电脑,但这个服务器的作用仅仅是用来方便“交换”大家的修改,没有它大家也一样干活,只是交换修改不方便而已。
Git的安装
查看git是否安装
$git
The program 'git' is currently not installed. You can install it by typing:
sudo apt-get install git
1.Debian或Ubuntu Linux像如上提示安装
2.老版本Debian或Ubuntu Linux :sudo apt-get install git-core
3.其他linux发行版,用源码安装
(1).Git官网下载源码,然后解压
(2).依次执行
./config,make,sudo make install
Git的使用
1.初始化一个Git仓库
仓库,也叫版本库,英文名repository,你可以简单理解成一个目录。
1.创建一个空目录
$mkdir learngit
$cd learngit
$pwd
/Users/michael/learngit
2.通过git init命令把这个目录变成Git可以管理的仓库
$git init
Initialized empty Gitrepositoryin /Users/michael/learngit/.git/
2.文件管理
1.将文件添加到Git仓库
以readme.txt为例。注意这个文件一定要在git仓库下,即目录learngit或其子目录下。
1.用命令git add告诉Git,把文件添加到仓库
$git add readme.txt
2.用命令git commit告诉Git,把文件添加到仓库
$ git commit -m "wrote a readme file"
[master (root-commit) cb926e7] wrote a readme file
1file changed,2insertions(+)
create mode 100644 readme.txt
2.查看仓库状态 & 提交修改
将文件添加到版本库后,用命令git status查看仓库状态。
$git status
On branch master
nothing to commit (working directory clean)
git告诉我们工作目录是干净(working directory clean)!
修改readme.txt文件后,用命令git status查看仓库状态。
$git status
On branch master
Changes not staged for commit:
(use "git add ..." to update what will be committed)
(use "git checkout -- ..." to discard changes in working directory)
modified: readme.txt
no changes added to commit (use"git add"and/or"git commit -a")
上面的命令告诉我们,readme.txt被修改过了,但还没有准备提交的修改。
查看修改:
$ git diff readme.txt
如上命令会提示修改详细。
提交修改:
第一步:git add *
$git add readme.txt
在进行第二步git commit前,查看一下仓库状态
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD..." to unstage) modified: readme.txt
git告诉我们,将要被提交的修改包括readme.txt,下一步:放心提交
第二步:git commit *
$ git commit -m "add distributed"
[master ea34578]add distributed
1 file changed,1 insertion(+),1 deletion(-)
提交后,再次查看仓库状态
$ git status
On branch master
nothing to commit (working directory clean)
Git告诉我们工作目录是干净(working directory clean)的,没有要提交的修改。
3.版本回退
Git允许我们在版本的历史之间穿梭,使用命令 git reset --hard commit_id
1.HEAD指向当前版本,上一个版本就是HEAD^,上上一个版本就是HEAD^^
2.回到上个版本:git reset --hard HEAD^ , 回到上上个版本:git reset --hard HEAD^^
3.回到历史某个版本:首先用git log 查看提交历史,然后利用commit id使用命令如:git reset --hard 3628164 (版本号没必要写全,前几位就可以了,Git会自动去找)
4.从历史某个版本回到现在:首先用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
回到append GPL版本即: git reset --hard 3628164
4.工作区,暂存区和版本库
1.工作区(Working Directory)
就是你在电脑里能看到的目录,比如我的learngit文件夹就是一个工作区。
2.版本库(Repository)
工作区有一个隐藏目录.git,这个不算工作区,而是Git的版本库。
3.暂存区(stage/index)
Git的版本库里存了很多东西,其中最重要的就是成为stage或者index的暂存区。还有Git为我们自动创建的master分支,以及指向master的指针叫HEAD。
回忆一下,把文件添加到版本库分两步。
第一步:git add,添加文件。实际就是把文件添加到暂存区。
第二步:git commit,提交修改。实际就是把暂存区的内容提交到当前分支。
可以简单理解为:第一步是将要提交的修改统统放到暂存区。第二步是一次性提交所有修改。
继续实践:
现在git库有个文件readme.txt,并且工作空间干净了。
我们修改文件readme.txt,再添加一个新文件test.txt。
然后查看工作空间状态。
$ git status
On branch master
Changes not staged for commit:
(use "git add..." to update what will be committed)
(use "git checkout --..." to discard changes in working directory)
modified: readme.txt
Untracked files:
(use "git add..." to include in what will be committed)
test.txt
no changes added to commit (use "git add" and/or "git commit -a")
Git非常清楚地告诉我们,readme.txt被修改了,而test.txt还从来没有被添加过,所以它的状态是Untracked。
把两个文件添加到暂存区
$git add readme.txt
$git add test.txt
再次查看状态
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD..." to unstage)new file: LICENSE modified: readme.txt
Git提示我们有未提交的修改,我们图示一下暂存区状态。
通过图示可清楚看到,通过git add命令将文件提交到了暂存区。
下面我们提交文件到Git版本库。
$git commit -m "understand how stage works"
[master 27c9860] understand how stage works
2 files changed, 675 insertions(+)
create mode 100644 LICENSE
查看状态
$git status
On branch master
nothing to commit (working directory clean)
图示状态
5.管理修改
GIt之所以优秀,是因为与众不同的是,它管理的是修改,并非文件。
提交修改:
①修改→git add添加修改→git commit提交修改
②第一次修改→git add添加修改→git commit提交修改→第二次修改→git add添加修改→git commit提交修改
③第一次修改→git add添加修改→第二次修改→git add添加修改→git commit一起提交修改
如果②中第二次修改没有git add添加而直接git commit提交,那么git status查看状态,就会如下提示
$git status
......
no changes added to commit (use"git add"and/or"git commit -a")
用git diff HEAD -- readme.txt命令可以查看工作区和版本库里面最新版本的区别。
6.撤销修改
1.修改文件,还未git add
使用命令git checkout -- file丢弃工作区的修改
1.如果暂存区无未提交的修改,版本库会覆盖本地
2.如果暂存区有未提交的修改,暂存区会覆盖本地
总之,就是让这个文件回到最近一次git commit或git add时的状态。
2.修改文件,已经git add
第一步:使用命令git reset HEAD file可以把暂存区的修改撤销掉(unstage),重新放回工作区。
$ git reset HEAD readme.txt
Unstaged changes after reset:
readme.txt
第二步:丢弃工作区的修改
$git checkout -- readme.txt
查看一下状态,工作空间干净了。
$ git status
On branch master
nothing to commit (working directory clean)
7.删除文件
在Git中,删除也是一个修改操作。
1.新建一个文件,并提交到版本库
$ git add test.txt
$ git commit -m "add test.txt"
2.从版本库删除它
第一步:在工作空间中删了,然后查看状态,git告诉我们工作区和版本库不一样了
$rm test.txt
$git statusOn branch master
Changes not staged for commit:
(use "git add/rm..." to update what will be committed) (use "git checkout --..." to discard changes in working directory) deleted: test.txt
no changes added to commit (use "git add" and/or "git commit -a")
第二步:从版本库删除该文件
$ 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 -- test.txt
git checkout其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。
8.远程仓库
设置
由于你的本地Git仓库和GitHub仓库之间的传输是通过SSH加密的,所以,需要一点设置:
第1步:创建SSH Key。打开Shell(Windows下打开Git Bash)
$ ssh-keygen -t rsa -C "[email protected]"
然后用户主目录里可以找到.ssh目录,里面有id_rsa和id_rsa.pub两个文件,这两个就是SSH Key的秘钥对,id_rsa是私钥,id_rsa.pub是公钥。
第2步:登陆GitHub,打开“Account settings”→“SSH Keys”页面→“New SSH key”,填上任意Title,在Key文本框里粘贴id_rsa.pub文件的内容。
总结:Git支持SSH协议,通过SSH Key,它就能识别出你推送的提交确实是你推送的,而不是别人冒充的。
8.1 添加远程库
(1) 登录Github 点击右上角+ 选择"New repository",添入Repository name,点击Create repository
(2)Git提示我们如何新建本地仓库,并与之关联,或把本地已有仓库与之关联。
1.新建本地仓库,并与之关联
$ mkdir GitTest
$ cd GitTest
$ git init
Initialized empty Git repository in /home/linn/GitTest/.git/
$ echo "# GitTest">>README.md
$ git add README.md
$ git commit -m "first commit"
$ git remote add origin https://github.com/dancenn/Gi...
$ git push -u origin master
2.与既存本地仓库关联
$ git remote add origin https://github.com/dancenn/Gi...
$ git push -u origin master
8.2 从远程库克隆
(1) 登录Github 点击右上角+ 选择"New repository",添入Repository name,点击Create repository
勾选Initialize this repository with a README,这样GitHub会自动为我们创建一个README.md文件。
(2)用命令git clone克隆一个本地库,并查看
$ git clone https://github.com/dancenn/Gi...
Cloning into 'GitTestClone'...
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), done.
$ cd GitTestClone
$ ll
total 4
-rw-rw-r--. 1 linn linn 14 Aug 23 23:38 README.md
8.3分支管理
8.3.1.创建与合并分支
一开始先有master分支的时候,分支是一条线。master指向最新的提交,HEAD指向master。以此确认当前分支以及当前分支的提交点。
每次提交,master分支都向前移动一步,所以master分支会越来越长。如下:
如果新建一个dev分支,Git新建了一个指针叫dev,指向master相同的提交,再把HEAD指向dev,就表示当前分支在dev上:
$ git checkout -b dev
Switched to a new branch'dev'
用git branch命令查看当前分支:
$ git branch
dev
master
现在,对工作区的提交就是针对dev分支的了,比如新提交一次后,dev指针往前移动一步,而master指针不变:
比如对readme.txt做个修改,然后提交:
$ 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'
然后就可以把dev合并到master上。Git直接把master指向dev当前的提交,就完成了合并:
$ git merge dev
Updating d17efd8..fec145a
Fast-forward
readme.txt | 1 +
1 file changed, 1 insertion(+)
注意到上面的Fast-forward信息,Git告诉我们,这次合并是“快进模式”,也就是直接把master指向dev的当前提交,所以合并速度非常快。
当然,也不是每次合并都能Fast-forward,我们后面会讲其他方式的合并。
合并完甚至可以删除dev,就又只剩master分支了:
$ git branch -d dev
Deleted branch dev (was fec145a).
$ git branch
master
小结
Git鼓励大量使用分支:
查看分支:git branch
创建分支:git branch
切换分支:git checkout
创建+切换分支:git checkout -b
合并某分支到当前分支:git merge
删除分支:git branch -d
2.解决冲突
准备了新的feature1分支,并提交了一些修改。切换到master分支,提交了不同的修改。
现在,master分支和feature1分支各自都分别有新的提交,变成了这样:
这种情况下,Git无法执行“快速合并”,只能试图把各自的修改合并起来,但这种合并就可能会有冲突:
$ git merge feature1
Auto-merging readme.txt
CONFLICT (content): Merge conflict in readme.txt
Automatic merge failed; fix conflicts and then commit the result.
然后,查看冲突,手动解决冲突。再次提交。然后再git merge feature1。分支变成了下图所示:
用带参数的git log也可以看到分支的合并情况:
$ git log --graph --pretty=oneline --abbrev-commit
59bc1cb conflict fixed
|\
| * 75a857c AND simple
| 400b400 & simple
|/
fec145a branch test
...
最后,删除feature1分支:
$ git branch -d feature1
Deleted branch feature1 (was 75a857c).
工作完成。
小结
当Git无法自动合并分支时,就必须首先解决冲突。解决冲突后,再提交,合并完成。
用git log --graph命令可以看到分支合并图。
3.分支管理策略
合并分支:
1.快速合并:Fast forward模式,如果可能,Git就会用这种模式,但删除分支后,会丢掉分支信息。
2.普通合并:禁用Fast forward模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。
比如我们要把dev分支合并到master分支上。
$ git checkout master
Switched to branch'master'
$ git merge --no-ff -m "merge with no-ff" dev
Merge made by the 'recursive' strategy.
readme.txt | 1 +
1 file changed, 1 insertion(+)
用git log看看分支历史:
$ git log --graph --pretty=oneline --abbrev-commit
7825a50 merge with no-ff
|\
| * 6224937 add merge
|/
59bc1cb conflict fixed
...
可以看到,不使用Fast forward模式,merge后就像这样:
小结:
1.master分支应该是稳定的,用来发布版本,不在上面开发
2.dev分支用来开发,需要发布版本时,就合并到master
3.不是直接在dev分支干活,不同开发团队在dev上创建自己的临时分支,开发完往dev上合并
4.合并方式,尽量使用普通合并,即加参数--no-ff。能看到合并历史,而fast forward看不到合并历史。
8.3.4.bug分支
如果你正在dev上开发,突然接到一个紧急bug修改的任务,这时候本地工作还没完,怎么办?幸好Git提供了git stash的功能,它能把当前工作现场“储藏”起来。
(1).“储藏”现场
$ git stash
Saved working directory and index state WIP on dev: 6224937 add merge
HEAD is now at 6224937 add merge
(2).修改bug
$ git checkout -b issue-101
$ git add readme.txt
$ git commit -m "fix bug 101"
$ git checkout master
$ git merge --no-ff -m "merged bug fix 101"issue-101
$ git branch -d issue-101
(3).恢复现场
(3.1)先查看
$ git stash list
stash@{0}: WIP on dev: 6224937 add merge
(3.2).1 方法一
$ git stash apply
$ git stash drop
(3.2).2 方法二
$ git stash pop
(3.2).3 多次stash场合
先用git stash list查看,然后恢复指定的stash,用命令:
$ git stash list
$ git stash apply stash@{0}
(3.3)再次查看,就没有储藏内容了
$ git stash list
5.Feature分支
新加一个功能,最好新建一个feature分支。
假设接到了一个新任务:开发代号为new01的新功能,于是
$ git checkout -b feature-new01
$ git add new01.py
$ git commit -m"add feature new01"
此时新功能开发完并提交,下一步合并。可是突然接到命令该功能废弃。所以需要删除分支。
$ git branch -d feature-new01
error: The branch 'feature-new01' is not fully merged.
If you are sure you want to delete it, run 'git branch -D feature-new01'.
Git友情提醒,feature-new01分支还没有被合并,如果删除,将丢失掉修改,如果要强行删除,需要使用命令git branch -D feature-new01。
$ git branch -D feature-new01
Deleted branch feature-new01 (was756d4af).
8.4.标签管理
简述:我们通常习惯在版本库打一个标签来标识某一时刻的版本。以后可以通过标签取历史版本。标签是指向某个commit的指针,很像分支,但是分支能移动,标签不能移动。
举个栗子:
“请把上周一的那个版本打包发布,commit号是6a5819e...”
“一串乱七八糟的数字不好找!”
“我要找的commit id是打了tag “v1.1”的那个版本”
“找到了:git show v1.1”
8.4.1.创建标签
(1).切换到需要打标签的分支上
$ git checkout master
Switched to branch 'master'
(2).创建标签
$ git tag v1.0
默认标签是打在最新提交的commit上的。如果想给历史提交打标签
(2.1).找到历史提交的commit id
$ git log --pretty=oneline --abbrev-commit
6a5819e merged bug fix 101
cc17032 fix bug 101
7825a50 merge with no-ff
6224937 add merge
(2.2).给历史提交创建标签
$ git tag v0.9 6224937
git tag查看标签:
$ git tag
v0.9
v1.0
8.4.2.删除标签
(1).删除标签
$ git tag -d v0.1
Deleted tag 'v0.1' (was e078af9)
创建的标签都只存储在本地,不会自动推送到远程。所以,打错的标签可以在本地安全删除。
(2).推送标签
$ git push origin v0.1
Total 0 (delta 0), reused 0 (delta 0)
To [email protected]:michaelliao/learngit.git
[new tag] v1.0 -> v1.0
(3).一次性推送全部尚未推送到远程的本地标签:
$ git push origin --tags
Counting objects: 1, done.
Writing objects: 100% (1/1), 554 bytes, done.
Total 1 (delta 0), reused 0 (delta 0)
To [email protected]:michaelliao/learngit.git
[new tag] v0.2 -> v0.2
[new tag] v0.9 -> v0.9
(4).删除远程标签
(4.1) 先从本地删除
$ git tag -d v0.9
Deleted tag 'v0.9' (was 6224937)
(4.2)从远程删除
$ git push origin :refs/tags/v0.9
To [email protected]:michaelliao/learngit.git
[deleted] v0.9
可以登陆git hub看看是否删除成功。
8.5 使用Github
如何参与开源项目?
8.5.1.Fork开源仓库
以人气比较高的非常强大的CSS框架bootstrap项目为例。
访问它的项目主页https://github.com/twbs/boots...,点“Fork”就在自己的账号下克隆了一个bootstrap仓库。
8.5.2.从自己的账号下clone
git clone [email protected]:april/bootstrap.git
一定要从自己的账号下clone仓库,这样你才有权限推送修改。
Bootstrap的官方仓库twbs/bootstrap、你在GitHub上克隆的仓库my/bootstrap,以及你自己克隆到本地电脑的仓库,他们的关系就像下图显示的那样:
如果你希望bootstrap的官方库能接受你的修改,你就可以在GitHub上发起一个pull request。当然,对方是否接受你的pull request就不一定了。
8.6自定义Git
8.6.1 忽略Git工作目录中的特殊文件
在Git工作区的根目录下创建一个特殊的.gitignore文件,然后把要忽略的文件名填进去,Git就会自动忽略这些文件。
如以下这样,想添加一个文件到Git,但发现添加不了,原因是这个文件被.gitignore忽略了:
$ git add App.class
The following paths are ignored by one of your .gitignore files:
App.class
Use -f if you really want to add them.
如果你确实想添加该文件,可以用-f强制添加到Git:
$ git add -f App.class
用git check-ignore命令检查:
$ git check-ignore -v App.class
.gitignore:3:*.class App.class
Git会告诉我们,.gitignore的第3行规则忽略了该文件,于是我们就可以知道应该修订哪个规则。
小结
忽略某些文件时,需要编写.gitignore;
.gitignore文件本身要放到版本库里,并且可以对.gitignore做版本管理!
8.6.2 配置别名
$git config --globalalias.st status
以上命令就是给status配置了别名。git status 可以写成git st了。
8.6.3 搭建Git服务器
第一步,安装git:
$ sudo apt-get install git
第二步,创建一个git用户,用来运行git服务:
$ sudo adduser git
第三步,创建证书登录:
收集所有需要登录的用户的公钥,就是他们自己的id_rsa.pub文件,把所有公钥导入到/home/git/.ssh/authorized_keys文件里,一行一个。
第四步,初始化Git仓库:
先选定一个目录作为Git仓库,假定是/srv/sample.git,在/srv目录下输入命令:
$ sudo git init --bare sample.git
把owner改为git:
$ sudo chown -R git:git sample.git
第五步,禁用shell登录:
出于安全考虑,第二步创建的git用户不允许登录shell,这可以通过编辑/etc/passwd文件完成。找到类似下面的一行:
git: x:1001:1001:,,,:/home/git:/bin/bash
改为:
git: x:1001:1001:,,,:/home/git:/usr/bin/git-shell
第六步,克隆远程仓库:
$ git clone git@server:/srv/sample.git
Cloning into 'sample'...
warning: You appear to have cloned an empty repository.
剩下的推送就简单了。