3

学习完廖雪峰老师的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。

clipboard.png

回忆一下,把文件添加到版本库分两步。

第一步: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提示我们有未提交的修改,我们图示一下暂存区状态。

clipboard.png

通过图示可清楚看到,通过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)

图示状态

clipboard.png

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:

  1. 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 status

On 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 "youremail@example.com"

然后用户主目录里可以找到.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

clipboard.png

clipboard.png

(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 git@github.com: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 git@github.com: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 git@github.com: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 git@github.com: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.
剩下的推送就简单了。


April
159 声望7 粉丝

润新苗于细雨 成万物于美田


下一篇 »
git reset 进阶