Git的基本操作和 git workflow

一、扯扯Git的诞生

    在没有Git之前,大家大部分都是靠SVNCVS等集中式的版本控制系统,集中式版本控制系统不但速度慢,而且必须联网才能使用,所有的版本信息都放在服务器上,万一服务器宕机一个小时,基本上这一个小时大家就不用工作了。这个时候分布式代码版本控制就横空出世了,优点很明显,分布式管理,没有网络的情况下仍然可以查看日志、提交代码、创建分支,而且支持local branch, 缺点可能算是有一定的学习曲线,但是作为程序员算是一个基本技能。

二、Git的基本操作

1、创建版本库

    版本库其实就是一个仓库,仓库里面的所有文件都可以被Git管理起来,对文件的每次修改,删除,新增都可以进行追踪。新建一个仓库非常简单只需要新增一个文件夹,然后init一下就可以。

1
2
3
4
5
// 新建一个文件夹
$ mkdir gitDemo

// 初始化Git仓库
$ git init

    然后你就会发现在文件夹下多了一个.git的文件夹,这个文件夹就是来跟踪管理版本库的,一般不需要改动。如果没有看到.git文件夹那应该是隐藏了可以通过 ls -ah 命令查看。

2、将文件添加到版本库

    当我们对文件进行改动需要提交到仓库时,只需要做二步就可以,假如我需要把readme.text加入到仓库

1
2
3
4
5
6
// 用 add 命令告诉 git 我准备把文件添加到仓库, 暂时先放在你这(暂存区)
$ git add readme.text

// 用 commit 命令提交到本地仓库,Git是分布式,commit后本地就会有提交记录
// commit命令是有一个 -m 参数后面跟随的是此次提交的说明,这个说明很重要,不能乱写,不然后面找改动记录就很麻烦了
$ git commit -m 'init readme.text file'

    我们现在已经成功提交了一个文件,如果我们想随时知道当前版本库的状态可以使用 git status 命令进行查看,这个命令会告诉我们当前的状态几个文件add, 几个commit都可以清楚的看到,但是如果我们想知道这次对文件做了哪些修改,就可以使用 git diff命令进行查看,这个命令在我们实际开发中每次提交前通过diff去查看本次的改动还是很有必要的。

3、远程仓库

    我们知道一个项目不可能只有我们自己在开发,那么协作开发就需要用到一个中间服务器,角色有点类似SVN的中央存储但是又有本质的区别,在git中可以理解成交换中心,我们自己写完代码推送到远程交换中心,别人想要看到我们的最新代码就需要就远程就拉取。远程库的创建有两种方式,一种是本地已有仓库,和远程关联,一种是先创建远程,直接通过命令克隆到本地然后进行开发。如果一个项目从头开始,个人还是比较建议先创建远程仓库,然后从远程克隆库。
      I、添加远程库
          假设我们现在已经创建了一个远程仓库,现在流行的git仓库管理地址也就差不多是Github Gitee GitLab等平台,创建仓库的方式都大同小异也很简单。创建仓库后会提示我们克隆新的,还是关联已有的,如果关联已有的话我们需要在本地仓库运行命令:

1
$ git remote add origin git@github.com:userName/gitDemo.git

          以 GitHub 为例,上面的userName需要替换成你自己的账户名称,这个命令其实就是将本地仓库和远程关联了起来,下一步就需要把本地的内容全部推送上去。

1
$ git push -u origin master

          git push命令就是把本地库的内容推送到远程,由于远程库是空的,我们第一次推送master分支时,加上了-u参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。
      II、从远程克隆
          从远程克隆的话其实就很简单了,远端创建好仓库后,只需要执行命令:

1
$ git clone git@github.com:userName/gitDemo.git

          如果有多人开发只需要每个人各自克隆一下就好了。

4、分支管理

    几乎每一种版本控制系统都以某种形式支持分支,分支的功能大致就是将不同功能的代码剥离出来,同时不影响主线的开发。实际应用中,一般采用一个master主分支,为线上稳定版本分支,此分支不可直接提交,只能通过develop或者hotfix分支进行合并。一个为develop开发分支,分支为最新完成开发通过开发自测的开发分支,在我们项目中,给到测试的代码就是直接从develop中打包,而没有采用release分支。第三个为feature分支,也就是功能分支,当我们接到一个新功能需要开发时,需要从develop分支切一个属于自己的功能开发分支,当功能开发完成需要交付测试时,将代码合并至develop分支。最后还需要一个hotfix分支,此分支用来解决线上爆出的bug需要紧急修复,此分支需要从线上版本master分支切出,通过测试上线后合并至masterdevelop分支。这是现在大多数包括我们自己开发团队在使用的Git分支管理规范和流程,也是前人经过探索总结的,来保证开发过程更好的进行沟通协作。这种git flow模式下的项目会一直保留两个长期分支,master存放对外发布的稳定版,develop存放日常开发的最新版。另外master每次发布后需要对当前节点打上标签(Tag)进行记录,一般可以用于版本号记录,方便以后版本追溯。
      I、基本管理
          创建分支很简单,速度也很快,主要因为创建分支时,Git只是新建了一个指针,指向切出分支相同的那次提交,再把HEAD指向新分支,就表示在新建分支上,工作区文件实质上没有变化。分支操作的基础命令如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 创建分支
$ git branch (branchname)

// 切换分支(-b 参数表示创建并切换)
// 或者 git switch (-c 参数表示创建并切换)
$ git checkout (branchname)
$ git checkout -b (branchname)

$ git switch (branchname)
$ git switch -c (branchname)

// 合并分支
$ git merge

// 列出所有分支
$ git branch

// 删除分支
$ git branch -d (branchname)

      II、标签管理
          上面我们其实也有提到针对master的每次发布进行打标签,其实标签就是来定义一个重要阶段,并希望永远记住那个时刻,比如我们版本发布了1.0版本,就可以在master的最新一次提交加上V1.0得标签。

1
2
3
4
5
6
7
8
9
10
11
// 创建一个带注解标签
$ git tag -a v1.0

// 查看所有标签
$ git tag

// 删除标签
$ git tag -d v1.0

// 对历史提交打标签
$ git tag v1.0 commitID

          需要注意的一点是标签总是与某个commit挂钩的,如果这个commit既出现在master分支又出现在dev分支那么这两个分支都是可以看到标签的。

5、Git Stash 和 Git WorkTree 场景和用法

      I、Git Stash
          我们在开发时经常会遇到这样的场景,正在 feature 分支开发功能,线上突然出现了高优先级bug要马上解决,但是新功能才写了一半不想提交,这个时候就可以用stash命令将当前进度保存起来,然后切换至另一个分支修改bug,修改完成后切回 feature 恢复之前进度继续开发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 保存当前工作进度,可以添加save增加注释,可以多次使用
$ git stash save <message>

// 显示保存进度列表
$ git stash list

// 恢复最新的进度
$ git stash pop

// 删除一个存储进度,不指定stash_id,默认删除最新
$ git stash drop [stash_id]

// 删除所有存储进度
$ git stash clear

      II、Git WorkTree
          git stash 已经可以基本上帮助我们解决多个分支切换工作的问题,但是工作中更多的时候会有需要并行开发的情况,我需要同时多开编辑器,而且更多时候因为分支不同,我有可能依赖的node_modules版本也是有差异的,但是因为我们配置ignore的原因,切换分支并不会重新安装依赖会导致打包依赖时的各种问题,git 2.6以上开始提供了worktree 功能,可以解决这样的问题。git worktree 从一个仓库中可以创建多个工作目录,方便多开编辑器并行开发。git worktree的命令只有几行非常容易记住。

1
2
3
4
5
6
7
8
9
10
11
12
// 增加新的worktree
$ git worktree add [路径] [分支]

// 新分支创建
$ git worktree add -b [新分支名] [新路径] [从此分支创建]
// eg. git worktree add -b myNewBranch ../testDir master

// worktree 详细情况, 可以增加 --porcelain 选项,用来改变显示风格
$ git worktree list --porcelain

// 删除worktree(删除关联目录后,清除worktree信息
$ git worktree prune

6、撤销和回滚

          在我们开发过程中肯定会遇到这样的场景,一、我刚把不想要的代码,点错了给commit,但是还没有push,二、线上炸了,需要还原这次代码。类似于这几种情况我们就需要用到git的撤销与回滚。
          场景一、未push之前的所有提交操作,可以统称为撤销.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 文件被修改,但是还没 add
$ git checkout fileName
$ git checkout .

// 对多个文件执行了add,但是只想提交一部分
$ git add *
$ git status
# 取消暂存
$ git reset HEAD <filename>

// 文件执行了add 但是不想要这些改动
# 取消暂存
$ git reset HEAD fileName
# 撤销修改
$ git checkout fileName

          场景二、已进行push,需要回滚。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 回滚到tag标记处
$ git checkout <tag>

// 撤销指定文件到指定版本
# 查看指定文件的历史版本
git log <filename>
# 回滚到指定commitID
git checkout <commitID> <filename>

// 删除最后一次远程提交
#使用revert
$ git revert HEAD
$ git push origin master
# 使用reset
$ git reset --hard commitID^
$ git push origin master -f
#区别: revert 放弃提交但是会生成一次新的提交,reset直接重置到指定提交

// 回滚某次提交
# 找到要回滚的commitID
git log
git revert commitID