菜鸟学git(一)

什么是Git

Git是目前世界上最先进的分布式版本控制系统(没有之一)
优势就是可以版本控制,不需要同时存储下多个文件。
网上看到的毕业论文版本管理不善的后果:
pic1
而用Git,不仅显得高大上,更重要的是,能记录你的改动,并且可以回撤到特定版本.
pic2
这样,你就结束了手动管理多个“版本”的史前时代,进入到版本控制的20世纪。

创建版本库

省略了安装Git,以及Git的原理介绍,正式进入创建版本库的介绍

什么是版本库?
其实就是仓库(repository),这个单词我们常常可以在Github上看到。可以简单理解成一个目录,这个目录里面的所有文件都可以被Git管理起来,每个文件的修改、删除,Git都能跟踪,以便任何时刻都可以跟踪历史,或者在将来的某个时刻可以”还原”.

创建一个版本库非常简单,选择一个合适的地方,创建一个空目录

1
2
3
4
$ mkdir learngit
$ cd learngit
$ pwd
/Users/michael/learngit

第二步,通过 git init 命令把这个目录变成Git可以管理的仓库.

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

这一步就是用来声明本文件夹与其他文件夹不同,这是一个仓库文件夹.可以发现当前目录下多了一个.git的目录,这个目录是Git来跟踪管理版本库的,没事千万不要修改这个目录里面的文件,不然改乱了,就把Git仓库给破坏了.
如果灭有看到.git目录,那是因为这个目录默认是隐藏的,用ls-ah命令就可以看见.

工作区和版本库

pic3
我的理解,工作区就是我们本地的文件夹,在我们电脑上能看到的目录.
而版本库则是我们需要提交文件上去的地方.

其他几个概念我觉得这张图展现的并不透彻,所以换一张图.
pic4
这里将上述三个区扩展成了五个区,更加详细的说明了其中的区别.

  1. 工作区还是上述定义
  2. 暂存区是本地仓库的一个暂存区域,一般通过git add将工作区文件提交到暂存区
  3. 本地仓库同样也是在本地,它是接收来自暂存区的文件提交,通过git commit.
  4. 本地远程仓库和远程仓库.本地远程仓库相当于远程仓库在本地的镜像,每次pull/fetch都将与远程保持同步。所以也可以简化成4个区。
    5种状态间的撤销修改
    pic5
    这部分会在后续讲

时光穿梭

之前介绍时提过,git可以帮助控制版本,所谓控制版本,也就是可以回退版本.

git status命令可以让我们时刻掌握仓库的状态.
上述命令的输出会告诉我们,哪些文件被修改过了,但还没有准备提交的修改.
如果你记不清了具体修改过文件中的什么内容,可以用git diff命令看看.

管理修改

记住一种说法:Git跟踪并管理的是修改,而非文件.
我们的git add只能从工作区取文件存入暂存区,而git commit也只能从暂存区取文件存入本地仓库。
所以有时会遇到问题,
本地的忘记传到暂存区,直接用了git commit
这种时候是本地工作区与本地仓库的不同,则可以通过
git diff HEAD —文件名 命令来查看工作区与仓库文件的不同.

撤销修改

pic5
这张图应该比较清晰了.
详细说一下各个命令

  1. git checkout:这个命令的作用很多。
    git checkout branchname是切换分支,
    git checkout -b branchname是创建并切换分支,
    git checkout —是从缓存区拉出文件替换工作区的文件。
  2. git clean:git clean 命令用来从你的工作区中删除所有没有tracked过的文件(tracked是跟踪的意思).删除刚刚新增的文件
  3. git reset :git reset —hard会重置暂存区和工作区的内容为和HEAD的新位置相同的内容,换句话说,就是你的没有commit的修改会被全部擦掉。
    git reset —hard HEAD^ ,Head和当前branch切到上一条commit同时工作目录里的新改动和已经add到stage区的新改动也一起全部消失了.
    git reset —soft:保留工作目录和暂存区,并重置HEAD
    git reset(不加参数,默认使用—mixed):保留工作目录,并且覆盖暂存区和HEAD
    git reset的实质是移动HEAD以及他所指向的branch
    git reset是对没有push的重置,也就说是从工作区开始到本地仓库那一段.

  4. git revert:用于反转提交,执行revert命令时要求工作树必须是干净的.git revert之后你的本地代码会回滚到指定的历史版本,这时你再git push即可以把线上的代码更新(这里不会像reset造成冲突的问题)

  5. git rm ,删除文件

  6. git push -f:除非只有自己一个人用,不然用push -force会被枪杀.

分支管理

创建与合并分支

master分支是一条线,每次提交master分支都会向前移动一步,这样,随着你不断提交,master分支的线越来越长.
而Head表示当前位置.
pic6
Git创建一个分支,因为除了增加一个dev指针,改改HEAD的指向,工作区的文件都没有任何变化.
pic7
Git合并分支也很快!就改改指针,工作区内容也不变!

1
2
$ git checkout -b dev
Switched to new branch 'dev'

git checkout命令加上-b参数表示创建并切换
等价于

1
2
3
$ git branch dev
$ git checkout dev
Switched to branch 'dev'

git branch 命令查看当前分支

1
2
3
$ git branch
* deve
master

把dev分支的工作成果合并到master分支

1
2
3
4
5
$ git merge dev
Updating d46f35e..b17d20e
Fast-forward
readme.txt | 1+
1 file changed,1 insertion(+)

git merge命令用于合并指定分支到当前分支。
注意到上面的Fast-forward信息,Git告诉我们,这次合并是“快进模式”,也就是直接把master指向dev的当前提交,所以合并速度非常快。

当然,也不是每次合并都能Fast-forward,我们后面会讲其他方式的合并。

合并完成后,就可以放心地删除dev分支了

1
2
$ git branch -d dev
Deleted branch dev (was b17d20e)

删除后,查看branch,就只剩下master分支了:

1
2
$ git branch
* master

解决冲突

分支冲突的情况,Git无法执行”快速合并”,通过手动解决冲突后再提交.
git status可以告诉我们冲突的文件。
用带参数的git log也可以看到分支的合并情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ git log --graph --pretty=oneline --abbrev-commit
* cf810e4 (HEAD -> master) conflict fixed
|\
| * 14096d0 (feature1) AND simple
* | 5dc6824 & simple
|/
* b17d20e branch test
* d46f35e (origin/master) remove test.txt
* b84166e add test.txt
* 519219b git tracks changes
* e43a48b understand how stage works
* 1094adb append GPL
* e475afc add distributed
* eaadf4e wrote a readme file

pic8

Bug分支

软件开发中,bug就像家常便饭一样。有了bug就需要修复,在Git中,由于分支是如此的强大,所以,每个bug都可以通过一个新的临时分支来修复,修复后,合并分支,然后将临时分支删除。

当你接到一个修复一个代号101的bug的任务时,很自然地,你想创建一个分支issue-101来修复它,

但是,当前正在dev上进行的工作还没有提交:

1
2
3
4
5
6
7
8
9
10
11
12
$ git status
On branch dev
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

new file: hello.py

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

并不是你不想提交,而是工作只进行到一半,还没法提交.Git 提供了一个stash功能,可以把当前工作现场”储藏”起来,等以后恢复现场后继续工作.

1
2
$ git stash
Saved working directory and index state WIP on dev: f52c633 add merge

当我们修改好bug后,切回到原来的工作线上时.由于工作区之前用过了git stash,这时是干净的。我们可以用git stash list查看。
如果要恢复工作现场,可以用 git stash apply恢复,但恢复后stash内容并不删除,需要用git stash drop来删除;
另一种方式是 git stash pop,恢复的同时把stash内容也删了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ git stash pop
On branch dev
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

new file: hello.py

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

Dropped refs/stash@{0} (5d677e2ee266f39ea296182fb2354265b91b3b2a)

多人协作

当你从远程仓库克隆时,实际上Git自动把本地的master分支和远程的master分支对应起来了,并且,远程仓库的默认名称是origin。

要查看远程库的信息,用git remote:

1
2
$ git remote
origin

或者,用git remote -v 显式更详细的信息:

1
2
3
$ git remote -v
origin git@github.com:michaelliao/learngit.git (fetch)
origin git@github.com:michaelliao/learngit.git (push)

上面显示了可以抓取和推送的origin的地址。如果没有推送权限,就看不到push的地址。

推送分支

推送分支,就是把该分支上的所有本地提交推送到远程库。推送时,要指定本地分支,这样,Git就会把该分支推送到远程库对应的远程分支上:

1
$ git push origin master

如果要推送其他分支,比如dev,就改成:

1
$ git push origin dev

但是,并不是一定要把本地分支往远程推送,那么,哪些分支需要推送,哪些不需要呢?

master分支是主分支,因此要时刻与远程同步;

dev分支是开发分支,团队所有成员都需要在上面工作,所以也需要与远程同步;

bug分支只用于在本地修复bug,就没必要推到远程了,除非老板要看看你每周到底修复了几个bug;

feature分支是否推到远程,取决于你是否和你的小伙伴合作在上面开发。

总之,就是在Git中,分支完全可以在本地自己藏着玩,是否推送,视你的心情而定!

Rebase

用git log命令可以查看所有分支的情况:

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 rebase.
pic9
rebase 黄金定律(必须遵守的一条准则):
不要对在你的仓库外有副本的分支执行变基。

merge vs rebase

到底该使用 merge 方式开发还是使用 rebase 方法开发是有争议的

有一种观点认为,仓库的提交历史即是 记录实际发生过什么。 它是针对历史的文档,本身就有价值,不能乱改。 从这个角度看来,改变提交历史是一种亵渎,你使用谎言掩盖了实际发生过的事情。 如果由合并产生的提交历史是一团糟怎么办? 既然事实就是如此,那么这些痕迹就应该被保留下来,让后人能够查阅。

另一种观点则正好相反,他们认为提交历史是 项目过程中发生的事。 没人会出版一本书的第一版草稿,软件维护手册也是需要反复修订才能方便使用。 持这一观点的人会使用 rebase 及 filter-branch 等工具来编写故事,怎么方便后来的读者就怎么写。

总的原则是,只对尚未推送或分享给别人的本地修改执行变基操作清理历史,从不对已推送至别处的提交执行变基操作,这样,你才能享受到两种方式带来的便利。

-------------本文结束感谢您的阅读-------------