git基础

Git安装

安装完成后,用以下命令将用户信息全局初始化。

1
2
$ git config --global user.name "Your Name"
$ git config --global user.email "email@example.com"

因为Git是分布式版本控制系统,所以,每个机器都必须自报家门:你的名字和Email地址。你也许会担心,如果有人故意冒充别人怎么办?这个不必担心,首先我们相信大家都是善良无知的群众,其次,真的有冒充的也是有办法可查的。

**注意: **git config命令的–global参数,用了这个参数,表示你这台机器上所有的Git仓库都会使用这个配置,当然也可以对某个仓库指定不同的用户名和Email地址。

创建版本库并初始化

1
2
3
$ mkdir learngit
$ cd learngit
$ pwd

以上三行命令分别为创建目录、进入目录以及查看当前目录位置。
注意: 路径中最好不要有中文

1
2
$ git init
Initialized empty Git repository in /Users/michael/learngit/.git/

初始化,会在此目录下建立一个隐藏的.git文件夹,用来跟踪管理版本库。
言归正传,现在我们编写一个readme.txt文件,内容如下:

1
$ git add readme.txt

添加文件,可连续多次添加。

1
$ git commit -m "wrote a readme file"

提交文件,把添加的所有文件一次提交,-m后面是提交信息,最好写有意义的描述。然后可以看到修改信息。

时光机穿梭

查看状态

先将原文件的第7行加入单词distributed ,然后在第8行后面添加一行Very Good!
显示当前库的状态,会提示变动过哪些文件。

1
$ git status

版本对比

查看和上一版本的具体变动内容

1
$ git diff test.txt

显示内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
diff --git a/test.txt b/test.txt
index 629d9c8..3d98a7f 100644
--- a/test.txt
+++ b/test.txt
@@ -4,8 +4,9 @@ test line3.
test line4.
test line5.
test line6.
-Git is a version control system.
+Git is a distributed version control system.
Git is free software.
+Very Good!
test line7.
test line8.
test line9.

详解:

  • diff –git a/test.txt b/test.txt ——对比两个文件,其中a改动前,b是改动后,以git的diff格式显示;
  • index 629d9c8..3d98a7f 100644 ——两个版本的git哈希值,index区域(add之后)的 629d9c8 对象和工作区域的 3d98a7f 对象, 100表示普通文件,644表示权限控制;
  • — a/test.txt +++ b/test.txt ——减号表示变动前,加号表示变动后;
  • @@ -4,8 +4,9 @@ test line3. ——@@表示文件变动描述合并显示的开始和结束,一般在变动前后多显示3行,其中-+表示变动前后,逗号前是起始行位置,逗号后为从起始行往后几行。合起来变动前后都是从第4行开始,变动前文件往后数8行对应变动后文件往后数9行。
  • 变动内容 ——+表示增加了这一行,-表示删除了这一行,没符号表示此行没有变动。

版本回退

1
$ git log

用来查看最近三次提交的记录

1
$ git log --pretty=oneline

合并每条记录到一行

1
$ git reset --hard HEAD^

向前回退版本,其中HEAD后面跟几个^就是往回退几个版本,如果回退100个版本,可以写成 HEAD~100 。

1
$ git reset --hard 07e0

向后恢复版本,首先要查找到对应版本的哈希id前4位,如果提交窗口找不到,可以使用以下命令

1
$ git reflog

这个命令记录了每一次版本相关的操作。git回退的速度非常快,因为在git内部有一个指向当前版本的HEAD指针,回退到某个版本,实际上是git把指针移动指向某个版本

工作区和暂存区

  • 工作区(Working Directory):.git所在的目录下,除了.git之外的其他文件都是在工作区内
  • 版本库(Repository):.git目录内所存的记录,有暂存区和Git为我们自动创建的第一个分支master,以及指向master的一个指针叫HEAD。
  • stage(或者叫index)的暂存区:用add命令放进来文件的位置

  • 如果文件在工作区被编辑,对应的status状态就是 Changes not staged for commit
  • 如果工作区新增文件,则对应的status状态就是 Untracked files
  • 如果文件被add后,对应的status状态就是 Changes to be committed
  • 多次add后的文件都放在暂存区,最后一次性全部提交。提交后的status状态就是 nothing to commit, working tree clean这时候工作区就是干净的,暂存区就没有任何内容了。

修改

管理修改

如果一个文件,修改一次后,add,再修改一次后直接commit,然后status则显示还有一次修改没有被提交,因为提交只对暂存区生效。所以要么每改动一次后都add,最后一次性提交;要么add一次就提交一次。
比较工作区与暂存区
  git diff 不加参数即默认比较工作区与暂存区
比较暂存区与最新本地版本库(本地库中最近一次commit的内容)
  git diff –cached […]
比较工作区与最新本地版本库
  git diff HEAD […] 如果HEAD指向的是master分支,那么HEAD还可以换成master

撤销修改

如果在工作区修改了文件后的status,会提示,下一步可以add到暂存区,或者从暂存区恢复修改:

1
2
3
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)

想要撤销,就用第3行的命令
$ git checkout – test.txt

如果已经add到暂存区了,这时想要撤销操作,这时可以从status中的提示——从HEAD中恢复修改。
$ git reset HEAD

但这时候暂存区的修改撤销了,工作区还是修改后的内容,此时再使用上面提交的 $ git checkout – test.txt 来撤销工作区修改,世界终于变得清净了!

删除文件

1
$ rm test2.txt

此命令可以从工作区删掉文件。如果要从版本库中删除,则add后提交即可,如果是误删了,则通过

1
$ git checkout -- test2.txt

从版本库里恢复。
如果已经将删除提交,则像前面一样先恢复版本库,然后在checkout出要恢复的文件。

分支管理

##概述

假设你准备开发一个新功能,但是需要两周才能完成,第一周你写了50%的代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能干活了。如果等代码全部写完再一次提交,又存在丢失每天进度的巨大风险。这种情况下需要分支来管理。自己在创建的新分支上进行开发,完成后一次性提交合并即可。
Git对于分支的创建、切换和删除都能非常快的实现,而SVN就很慢。

创建与合并分支

git单分支的结构是这样的,master是指向最新提交的指针,HEAD是指向master的指针,每做一次提交,指针就向前移动一步:

现在增加一个dev分支并切换到这个分支:

1
$ git -b checkout dev

这个代码可以写成两步,本别是创建新分支 $ git branch dev ,切换到目标分支 $ git checkout dev ,之后变成这样:

这时可以用命令查看分支

1
2
3
$ git branch
* dev
master

其中带星号的是当前所在分支。然后再新分支上做一些更改,再add并提交,这是结构变成了这样:

切换回master($ git checkout master )分支后,发现刚才所做的改动不见了,是因为改动在dev分支上。

这时使用合并命令:

1
$ git merge dev

即把目标分支合并到当前分支上。完成后提示 Fast-forward ,说明系统用了快进模式进行合并,此时的结构为:


master分支上也成了最新版。这时不需要dev分支了,可以删除:

1
$ git branch -d dev

这时再查看分支,已经没有dev了。

解决冲突

当两个分支上对同一个文件有修改并分别有提交,最后Git无法自动合并,就会产生冲突。

1
2
3
4
$ git merge 'feature2'
Auto-merging test2.txt
CONFLICT (content): Merge conflict in test2.txt
Automatic merge failed; fix conflicts and then commit the result.

如果你不服气再执行一次合并,就会看到:

1
2
3
4
5
$ git merge feature2
error: Merging is not possible because you have unmerged files.
hint: Fix them up in the work tree, and then use 'git add/rm <file>'
hint: as appropriate to mark resolution and make a commit.
fatal: Exiting because of an unresolved conflict.

这时候可以通过 git status 查看冲突信息,找到描述中冲突的文件:

1
2
3
4
5
<<<<<<< HEAD
f4 in master
=======
f4 is new
>>>>>>> f4

其中 <<<<<<< HEAD 和 ======= 之间是当前分支的最新版, ======= 和 >>>>>>> f4 之间是目标分支内容,手动修改后删掉这些符号,然后提交,结构如图:

可以用以下代码看到图形化流程:
$ git log –graph –pretty=oneline –abbrev-commit

其中, –graph 是图形化, –pretty=oneline 是一行显示, –abbrev-commit 是只显示每次提交id的前几位,显示效果如下:

分支管理策略

默认情况下,如果情况允许,Git会自动用快进模式合并分支,但这样合并后不会留下分支存在过的痕迹。删除分支后就会丢失相应信息。如果不想这样做,则在合并时加上参数 –no-ff ,Git则会生成一个提交,所以同时再加上一个提交信息(如果不加则会进入vim模式让编辑提交信息),代码如下:
$ git merge –no-ff -m “merge with no-ff” dev

这样合并后还能看到对应的分支信息,如图,

实际开发中的分支策略:
首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;
平时大家在dev分支上干活,需要发布时合并到master分支即可。

Bug分支

假设正在dev上开发,突然接到修复master上一个bug,就可以用如下命令把现场保存起来(这个命令比其他命令要执行的慢):
$ git stash

这时工作区就是干净的,刚才的改动不见了。然后把分支切换到master,并在此基础上新建并切换到bug分支issue-101,在这里修复bug。修复完成后回到master分支,进行非快速合并后删除bug分支,再切换回dev分支,可以通过加list参数看到 stash 的列表:
$ git stash list
stash@{0}: WIP on d3: 820373a 合并d2, Merge branch ‘d2’

通过如下命令恢复现场:
$ git stash apply

这时stash区的内容还存在,可以用list查看,如果要清理掉,就用
$ git stash drop

这时stash区什么都没了。如果将工作区内容多次保存到stash,则可以加 stash@{0} 这样的编号来指定恢复哪个(可用list参数查看编号)。
$ git stash apply stash@{0}

也可以用如下命令弹出最后一次保存的工作区内容,这个命令会将对应的stash内容清除掉。
$ git stash pop

Feature分支——强行删除分支

如果在master分支上删除一个已经提交但没有合并的其它分支,则会报错:
$ git branch -d f5
error: The branch ‘f5’ is not fully merged.
If you are sure you want to delete it, run ‘git branch -D f5’.

这时可以用参数 -D 强制删除:
$ git branch -D f5

需要注意的是,由于分支未合并,删除之后就没有任何记录了,分支上所有的修改也会丢失。

多人协作

先来两个命令:
$ git remote

查看远程仓库名称
$ git remote -v

查看远程仓库更详细的信息
场景:我本地有master和dev两个分支,但我只把master推送到远程仓库中。然后我的小伙伴从远程的master分支上克隆了一份,他是看不到我本地dev分支的。然后自己在本地新建dev分支进行开发,完了之后推送到远程仓库。同时我在本地 的dev分支修改了跟他一样的文件。这时我准备推送代码,就会报错,提示先pull同步代码, 但拉取的时候又报错,说没有指定本地dev和远程origin/dev之间的连接( There is no tracking information for the current branch. )。可通过以下代码进行关联:
$ git branch –set-upstream-to=origin/dev dev

然后再pull,解决冲突,再提交,再push,跟前面一样。

补充 :从远程git仓库里的指定分支拉取到本地(本地不存在的分支)
git checkout -b 本地分支名 origin/远程分支名

然后再 pull

Rebase

Rebase用来整理提交记录,把多条分叉合并成一条直线。

假设有代码:

1
2
3
4
5
6
7
8
9
10
11
$ git log --graph --pretty=oneline --abbrev-commit
* 582d922 (HEAD -> master) add author
* 8875536 add comment
* d1be385 (origin/master) init hello
* e5e69f1 Merge branch 'dev'
|\
| * 57c53ab (origin/dev, dev) fix env conflict
| |\
| | * 7a5e5dd add env
| * | 7bd91f1 add new env
...

Git用(HEAD -> master)和(origin/master)标识出当前分支的HEAD和远程origin的位置分别是582d922 add author和d1be385 init hello,本地分支比远程分支快两个提交。假设推送时发现有人改了同样文件导致冲突,pull下来解决后再提交,这时本地分支会比远程超前3个提交。

1
2
3
4
5
6
7
8
$ git log --graph --pretty=oneline --abbrev-commit
* e0ea545 (HEAD -> master) Merge branch 'master' of github.com:michaelliao/learngit
|\
| * f005ed4 (origin/master) set exit=1
* | 582d922 add author
* | 8875536 add comment
|/
* d1be385 init hello

如果觉得这种分叉的图形看起来乱,可以用如下命令整理一下:
$ git rebase

整理后查看到的记录为:

1
2
3
4
5
$ git log --graph --pretty=oneline --abbrev-commit
* 7e61ed4 (HEAD -> master) add author
* 3611cfe add comment
* f005ed4 (origin/master) set exit=1
* d1be385 init hello

发现Git把我们本地的提交“挪动”了位置,放到了f005ed4 (origin/master) set exit=1之后,这样,整个提交历史就成了一条直线。修改不再基于d1be385 init hello,而是基于f005ed4 (origin/master) set exit=1,但最后的提交7e61ed4内容是一致的。推送之后如图:

1
2
3
4
5
$ git log --graph --pretty=oneline --abbrev-commit
* 7e61ed4 (HEAD -> master, origin/master) add author
* 3611cfe add comment
* f005ed4 set exit=1
* d1be385 init hello

远程和本地都成了一条直线。
Rebase的缺点是会更改我们的本地提交,但合并后的内容是一致的。

标签管理

Git的标签就是版本库的快照,但其实它就是指向某个commit的指针。

创建标签

1
$ git tag v1.0

给当前的commit打上标签 v1.0

1
$ git tag

查看所有标签,经测试在dev分支上能看到master上所有标签,尽管dev上面的commit要少很多。
注意,标签不是按时间顺序列出,而是按字母排序的

1
$ git tag v0.9 f52c633

可以给历史commit打上标签,最后一个参数是commit的前几位(通过git log查看)

1
$ git tag -a v0.1 -m "version 0.1 released" 1094adb

创建带有注释的标签,-a后面是标签名,-m后面是注释内容

1
$ git show v0.1

查看标签名为 v0.1的详细内容

操作标签

1
$ git tag -d v0.1

删除本地标签

1
$ git push origin v1.0

将标签 v1.0 推送到远程仓库

1
$ git push origin --tags

将尚未推送的标签全部推送到远程仓库
如果要删除远程仓库的标签,有以下两个步骤:

  • 删除本地标签,见上
  • 删除远程仓库对应标签 $ git push origin :refs/tags/v0.9