前言

在版本管理时代之前,人们写软件的方式如下图1所示

图1 无版本管理的代码

其坏处就是软件版本随着时间越来越多,每个版本修改了什么内容,修改了哪些文件,如果没有详细记录也不知道。这样久会导致如果我们想回退到某个版本内容的时候就无法做到。git的出现就时为了解决这些问题的。在git出现之前,其实还有比较出名的SVN,当然SVN现在也有很多公司在用。

SVN与git的区别:SVN是集中式的管理方式,即就服务器一个仓库,git是分布式的管理方式,除了服务器仓库,本地也有一个仓库。而且这个本地仓库也可以与其他用户交互。当然本地仓库也可以与中央服务器交互。

版本管理的好处主要有以下几个方面

  • 版本管理有一个中央服务器,可以保存所有代码、文档
  • 每一次的修改都可以提交到版本库,修改有记录,可追踪
  • 不害怕某个同事离职了,代码没有入库
  • 本地的代码流失后,可以从版本库检出
  • 多人协作,每个同事完成的工作提交到版本库,方便进行集成
  • 当我们要开发需求或修复PR时,可以从版本库上拉出分支管理
  • 如果构建失败了,可以自动revert掉某次提交
  • 在大的企业,每次提交都可能触发一次构建,实时检查代码的质量

git原理

git区域

git主要有4个区域,如下图2所示

图2 git区域

  • 工作区,就是你平时存放项目代码的地方
  • Index / Stage: 暂存区,用于临时存放你的改动,事实上它只是一个文件,保存即将提交到文件列表信息
  • Repository: 仓库区(或版本库),就是安全存放数据的位置,这里面有你提交到所有版本的数据。其中HEAD指向最新放入仓库的版本
  • Remote: 远程仓库,托管代码的服务器,可以简单的认为是你项目组中的一台电脑用于远程数据交换
    引入Index/Stage的目的时因为我们可能经常的操作有些误操作,会将一些文件的记录就保存到了Repository,不过其实没有Index/Stage也是可以的,因为提交给Repository也可以回滚,如果有SVN的使用经验,Index/Stage就类似于SVN的选择要提交文件的页面。

工作流程

工作流程如下图
图3 git工作流程

1、在工作目录中添加、修改文件;2、将需要进行版本管理的文件add到暂存区域;3、将暂存区域的文件commit到git仓库;4、本地的修改push到远程仓库,如果失败则执行第55、git pull将远程仓库的修改拉取到本地,如果有冲突需要修改冲突。回到第三步因此,git管理的文件有三种状态:已修改(modified),已暂存(staged),已提交(committed)

文件四种状态


图4 git 文件状态

  • Untracked: 未跟踪, 此文件在文件夹中,但并没有加入到git库,不参与版本控制, 通过git add 状态变为Staged。
  • Unmodify: 文件已经入库且未修改, 即版本库中的文件快照内容与文件夹中完全一致,这种类型的文件有两种去处,如果它被修改, 而变为Modified,如果使用git rm移出版本库, 则成为Untracked文件。
  • Modified:文件已修改,仅仅是修改,并没有进行其他的操作,这个文件也有两个去处,通过git add可进入暂存staged状态,使用git checkout 则丢弃修改,返回到unmodify状态, 这个git checkout即从库中取出文件,覆盖当前修改
  • Staged:暂存状态,执行git commit则将修改同步到库中,这时库中的文件和本地文件又变为一致,文件为Unmodify状态。

git 操作

我们以windows 版本来演示git的操作,linux下命令一致。

配置git

当安装完 Git 应该做的第一件事就是设置你的用户名称与邮件地址。 这样做很重要,因为每一个 Git 的提交都会使用这些信息,并且它会写入到你的每一次提交中,不可更改,使用这些信息是为了别人可以看到这次提交是由什么人提交,这样就可以直接找到相应的人。使用如下命令配置用户名和邮箱

 git config --global user.name “liqiang”配置用户名git config --global user.email 552191868@qq.com 配置邮箱git config -l 查看配置信息


图5 git 配置

常用命令

git init

安装git之后,我们在D盘的git目录下创建3个目录,远程仓库remote,和2个本地仓库(local-a,local-b).,我们先进入remote目录,然后右键Git Bash Here,创建一个远程的仓库。使用命令git init 创建 或者 git init –bare创建。使用git init 创建的仓库可以存储具体的代码,使用git init –bare创建的仓库不存储具体的代码,一般在远程服务器创建为裸仓库即可。创建远程仓库之后如下图6所示

图 6 创建远程服务器裸仓库

git clone

git clone 命令用于从远程服务器仓库拷贝到本地,并且后续可以通过在本地添加文件之后,并上传到服务器。进入到local-a目录,然后使用git clone命令从remote目录中拷贝文件如图7所示,同理,进入到local-b目录,使用git clone 将远程目录拷贝到本地。

图7 使用git clone将远程仓库拷贝到本地

git status

git status可以查看当前仓库文件的各自状态,在下面命令中进行说明。

git log

git log可以查看提交的版本信息,本地仓库的版本,远程仓库的版本等信息,在后续的命令中进行讲解。

git add

git add 是将工作区的代码提交给暂存区

git add . :将当前文件夹下的所有添加或者更新文件都一并提交

我们在本地目录local-a中添加一个main.c源文件,并添加一行内容。随后保存,然后使用git add . 将工作区的main.c提交给暂存区,并使用git status 查看文件状态 如图8所示

图8 git add将文件添加到暂存区,并使用git status查看状态

git commit

git commit 将暂存区提交给本地仓库

git commit main.c -m “first commit” 将main.c文件提交给本地仓库,提交的日志信息为first commit

我们使用上述命令将暂存区的main.c提交到本地仓库,并使用git log查看当前的提交信息。如图9 所示.

图9 使用git commit提交和git log查看状态
我们对git log命令中的一些信息进行说明如下:
HEAD:本地仓库当前最新的提交指针
master:当前提交的分支(后续会说明分支的作用)
Author:里面包含用户名和邮箱信息(在git config中已经进行了描述)
Date:本次的提交时间
提交的信息:如first commit就是本次提交的日志信息
Hash字符串(40位):每次提交都使用hash算法生成的唯一 字符串,用来唯一描述某次提交。可以使用前面几位来描述某次提交。

git push

使用git add和git commit之后,代码已存储到本地仓库,但是其他用户却不能查看到最新的代码信息,使用git push命令,可以将代码推送到远程服务器。使用如下命令

git push origin master: 其中origin代表远程仓库的简写,master代表要提交本地仓库的master分支到远程仓库。


图10 推送代码到远程服务器
对于图10来说,使用git log 可以看到这次的日志信息比上次多了 origin/master,其实这是在本地查看到远程服务器的状态信息,这里HEAD->master,origin/master在同一行里,说明远程服务器和本地仓库的版本状态是一样的。通常如果进行了多次git commit,而没有git push到远程仓库,那么就会发现HEAD->master一般是提前于origin/master的,origin/master表示远程仓库也是使用的master分支。

git pull

可以使用git pull从远程服务器拉取代码,在我们的例子中,repo-a的代码已经更新到远程仓库中,如果repo-b想从远程服务器中获取到最新代码,使用git pull即可。

图11 从远程服务器拉取代码

git reflog

不像git log那样,git reflog不会显示那么完整的信息,但是使用git reflog会将自己每次提交的日志记录都记录下来了,即使使用git命令将提交信息进行了合并(后续会讲到),图12显示了git reflog的使用。

图12 git reflog的使用

有时候我们需要我们提交日志的内容,比如想把前几次的内容进行合并位一次提交,或者将值卡提交的版本删除,主要就是为了更容易阅读每次提交版本所做的工作。主要有git commit –amend 和git rebase命令

git commit –amend

可以修改提交记录,让提交版本的log更好看,比如我们之前提交的日志不足以说明提交的信息情况,如果想修改,则可以使用这条命令,提前可以使用git log查看日志状态,修改过后(vim修改,会vim操作就可以),然后再次使用git log就可以查看日志已经修改。
git commit –amend还可以将这次源文件修改之后和上次进行合并,既是说这次修改代码之后,不需要提交版本。我们将local-a的代码在提交1次,最终的状态如下所图13所示。

图13 提交2次记录到本地服务器

我们使用 git commit –amend命令之后,如图14所示

图14 git commit –amend
使用vim将second commit 2 修改为second commit ,然后保存,再次使用git log,就可以看到上次提交的日志信息已经被修改为second commit,但是使用git reflog命令却显示了3次修改。如图15所示

图15 显示git commit –amend修改后的日志情况

git rebase

使用git commit –amend只能对最近一次的日志进行修改,如果相对多次提交记录进行修改,需要使用git rebase 命令,get rebase操作是在操作本地仓库,用于将多次日志进行合并,git rebase -i 开始hash值,结束hash值也可以使用git rebase -i,如果不带hash值, 那么就是默认从最新版本到没有提交到远程服务器之前的办吧 。在修改之前,提交版本如图16所示

图16 提交了4次版本的本地仓库
如图16所示,我们使用git log可以看到有4次提交版本到本地仓库,但是只有第一次提交到远程服务器,如果我们使用git rebase -i 命令,会出现如图17所示。

图17 git rebase -i整理界面
git rebase 有很多命令可供操作(如注释说明),以下做个简单的说明
pick ( p ): 使用该条提交,不进行任何修改,比如我们这里退出vim,不进行任何修改,那么相当做不对任何操作。
r: 使用r命令修改第三次提交和第4次提交的日志,但是不修改提交的内容,这个命令的作用就是用于让别人更容易看懂每次提交代码的作用,有时候我们提交过后,才发现日志对本次代码的用途说明的不是很清楚,我们就可以使用这个命令。如图18所示

图 18 使用r命令修改第3次和第4次提交日志
然后保存vim会自动提出修改第3次和第4次的日志信息,保存为我们想修改的日志信息即可。然后使用git log查看,如图19所示。

图19 所用 git rebase的r命令修改提交的日志
e: e命令不仅会修改提交的日志,还会记录提交时的代码状态,比如我们使用e修改第4次提交的内容,如图20所示

图20 使用e命令修改日志和代码
然后在图20使用wq保存vim之后,出现如图21所示

图21 e命令修改之前的提示
图21说明了e命令可以对代码进行修改,并且git会记录这次提交之前的源文件的状态,修改代码之后,在使用git commit –amend 修改本次要提交的日志,然后使用git rebase –continue完成本次提交的修改,但是如果需要一次性修改多条提交,需要重复多次,以便完成所有的修改操作。
对于git rebase的其他命令,如果有兴趣,请自行查阅相关资料,这里就不过多说明了。

有时候,我们需要进行git的逆向操作,即将暂存区的内容回退到工作区,或者将本地服务器的版本回退到上一个版本等等。

git restore

使用git restore -S . 命令将暂存区的内容回滚到工作区,如图22所示

图22 暂存区回滚到工作区

git reset

我们使用local-b仓库来讲解,我们将local-a仓库的所有更新到服务器(git push origin master),然后在local-b使用git pull像远程服务器拷贝最新的内容,初始状态如图23所示。

图23 ,local-b的初始状态

我们将main.c的内容删除3行,保留hello world和return 0 2行内容。然后使用git add 和git commit 将内容提交到本地服务器,使用git reflog命令,然后查看main.c和状态如图24所示

图24 ,使用 git reset之前的状态
git reset –hard head~1该命令直接硬件回滚到HEAD~1(79983ed), 把上次提交(head~0)的内容直接给抹掉,就好像从来没有提交过一样。即没有在服务器上有过记录。
我们查看main.c和使用git log查看状态,就好像前面所做的操作没有一样,如图25所示

图25 硬件回滚到前一个版本

git reset –soft head~1:回退到index暂存区,将内容回到HEAD 1版本,使用git status可以看到main.c的内容还没有提交到本地服务器,如图26所示

图26 ,回退到暂存区
git reset head~1的全写是:git reset –mixed head~1 ,这里是直接退到workspace了。内容没有提交到服务器,但是本地workspace还是存在最近一次提交的内容。

解决冲突

在日常开发中,难免会遇到多个开发者同时修改同一个文件,而且修改文件的同一个地方,那么就不可避免的出现冲突,我们这里先说明冲突的git状态,然后在说明如何解决冲突。
有2个本地仓库,其中一个本地仓库修改源文件之后提交给服务器(git push),另一个本地仓库去下载(git pull),并修改了其中一个文件,并提交到本地(git commit),然后提交到服务器(git push).第一个本地仓库也去修改源文件,并提交到暂存区(index),然后去git commit到本地仓库。然后使用git pull拉取服务器上的文件操作,就会发生冲突(修改了同一个源文件导致)。
首先我们将local-a的main.c代码更新到远程服务器(git push),随后local-b去拉取代码(git pull),然后local-a和local-b同时去更新main.c,然后local-a提交到服务器(git push),local-b提交到本地服务器,然后使用git pull去拉取到本地,这时候就出现冲突了,如图27所示

图27,local-b出现冲突
使用vim打开main.c,如图28所示,然后并修改如图29之后,提交就能解决冲突。

图28,冲突版本的代码

图28 ,冲突信息,<<<<<<<<>>>>>>>> 3e4d20…是远程服务器的版本信息,然后我们想让远程的自己的代码一起使用,我们保留这2份代码即可。

图29 修改后的代码
然后修改完成后,按照正常操作即可。

标准工作流程

git 分支

在企业开发中,不可能只在一个分支上进行开发,比如前面提到的master分支。在说明工程流程之前,先说下一般有哪些分支,分支如图30所示

图30 企业git分支情况

master:主分支,通常用于线上版本
hotfix:热更新版本,可以线上更新的版本
release:开发完成之后,并测试通过之后,积极发布的版本
develop:开发分支版本,一般由主程进行管理
feature:功能模块版本
bug:存在bug的版本分支

查看分支

git branch:用于查看当前处于什么分支,不过一般不需要,因为在命令中就已经显示了当前处于什么分支

创建和切换分支

一般刚建的仓库仅仅只有master分支,我们创建develop分支:git branch develop,并且切换到develop分支,如图31所示。

图31 创建和切换分支

切换到develop分支之后,在打开main.c,发现里面的内容和master分支一致。然后我们修改main.c的部分内容。并git add ,git commit ,并git push 到服务器,然后我们切换到master分支,会发现develop修改的内容并没有更新到master分支,这就是说,master分支一般作为稳定的线上版本,develop修改的内容不会影响到master分支。
git checkout -b bug/timer: 首选切换到bug/timer,-b参数表示如果没有bug/timer分支,则先创建该分支。

合并分支

前面提到,develop用于开发版本,修改的内容不会及时更新到master分支,但是我们确实想将develop分支的内容更新到master分支该怎么办呢,那么使用git merge develop将develop分支合并到master分支(要保证当前处于master分支)

正常流程

通常主程会生成一个develop分支,然后弄创建一些功能模块和BUG的分支,交由不同的人员去开发,开发完成之后,检查功能模块和BUG分支是否合格,如果合格,就将其合并到develop分支,有时候我们需要查看当前有哪些分支,可以使用 git branch –all ,还有很多关于分支的命令,如下所示

git帮助

git –help:会线上帮助命令,假如我们想知道git branch 支持的参数,可以使用git branch –然后按tab键就可以知道git branch的所有支持的参数。

.gitignore

有时候我们不想提交一些文件,那么可以将这些忽略的文件列表写入到这个文件中,比如我们想忽略所有的exe文件,我们可以在文件里面写 *.exe即可。

总结

本文说了git的一些常见用法,基本上这些命令能解决工作上的大多数问题,如果想更加深入的学习git,需要自行参考git语法。这里就不说了,希望本文能够帮助初学git的人有一个帮助。