Git使用简易指南

一.创建版本库
       创建版本库有两种方法,第一种方法就是直接在本地选择或创建一个干净的文件夹(比如创建文件夹guo mkdir guo),然后使用命令git init初始化版本库。初始化完成,提示已初始化空的 Git 仓库于 /Users/zephyr/Desktop/guo/.git/;
       第二种方法就是clone一个远程库,git会在本地自动创建一个版本库,并且这个版本库会自动和远程库的master分支建立关联。
       然后我们将需要跟踪的文件test.txt放到文件夹guo下,需要注意的是,我们必须将需要跟踪的文件放到.git所在的文件夹或者其子文件下才能被git管理,对于其它存储区域的文件git无能为力。然后使用命令git add将文件添加到暂存区,最后使用命令git commit -m "第一次提交"将文件提交到本地版本库,其中 -m用来添加提交时的说明,这个说明非常必要,因为有的修改可能时间比较久远了,如果不添加说明的话我们没办法看出这次修改的目的或者产生了什么作用。

二.几个重要的概念

  • 版本库
    当我们执行git init命令以后,在当前文件夹下会生成一个隐藏的.git文件夹,我们可以把这个.git目录理解为我们的版本库,由于它位于我们本地计算机上,所以也叫本地版本库(Local Repository),它将用来跟踪工作区中内容的修改。
  • 工作区
    .git目录所在的文件夹或者其子文件夹就是我们的工作区,平常我们开发,修改都是针对工作区中的内容进行的,所以它的代码是最新的
  • 暂存区
    暂存区(index/stage)是git中非常重要的一个概念,顾名思义,暂存区是暂时存放文件的区域,但是它不是保存的文件实体,而是通过id指向文件实体。通过git add添加的文件都会保存到暂存区中,我们可以通过git status查看暂存区的状态。在git中,只有通过git add将文件添加到暂存区后才会被git管理。当通过git commit将暂存区的内容全部提交到本地版本库以后,暂存区就没有内容了,git status会提示“无文件要提交,干净的工作区”

       举个栗子,我们把test.txt放到工作区的时候,使用git status查看暂存区状态,
       Git使用简易指南

       当我们将文件放到工作区的时候,git用红色字体提醒我们有未跟踪的文件,可以通过git add来建立跟踪, 从这句话我们也能看出,git跟踪文件修改是从暂存区开始的,而不是从工作区开始的。 根据提示,我们使用git add test.txt将文件添加到暂存区,然后再使用git status查看暂存区状态,
       Git使用简易指南
不再提示我们有未跟踪的文件了,这时候文件就已经添加到暂存区了。我们只需要使用git commit -m "提交说明"就可以将文件修改提交到本地版本库,然后我们再使用git status查看暂存区状态,会提示无文件要提交,干净的工作区
现在test.txt已经加入到git的版本库了,假如我们现在修改这个文件,然后使用git status看看会发生什么,
首先使用git add test.txt添加到暂存区,然后git status查看状态
       Git使用简易指南
提示我们有修改尚未提交,从这里我们可以看出git跟踪的不是文件本身,而是文件的修改

三.撤销操作
       通过前边的操作,我们的文件已经顺利的被git跟踪到了,然后我们开始修改文件,修改完以后又后悔了,想还原回去,这时候我们还没有将这次修改保存到暂存区,该怎么办那?我们可以使用git checkout -- test.txt,用版本库中的最新版本覆盖工作区中的内容,我们再使用git status查看暂存区状态,提示我们工作区是干净的,这样就还原回去了。假如我们修改了文件,并且已经添加到暂存区了,该怎么办?我们可以使用git checkout HEAD test.txt还原到上一次的提交操作,其中的HEAD是一个指针,指向了当前分支,这个后边会讲到;使用git reset HEAD test.txt也可以达到同样的目的,那这两个命令有什么区别那?前者实际上是一次覆盖操作,用版本库中的最新版本覆盖了工作区中未提交的版本,后者是重置操作,最近的一次git add操作被取消,但是工作区中的修改还存在,这时候我们可以再通过一次git checkout test.txt来还原到上一个版本。如果我们修改了很多的文件,现在这些修改都不要了,要都改回去,如果我们一个一个的还原的话讲非常麻烦,这时我们可以使用git reset --hard HEAD将所有未提交的修改还原,所有通过git add添加到暂存区的修改都将被取消。

四.差别比较操作
       我们已经知道了git存在工作区,暂存区,版本库的概念,在工作中,这三个空间中的内容必然会存在不同步的时候,比如我们将test.txt添加到了版本库,然后又修改了test.txt并且没有添加到暂存区,这时候就会产生了不同步,我们可以通过git提供的diff命令来查看这种差别,因为git中存在工作区,暂存区,版本库三个重要的空间,所以我们可以分别比较三个空间的差别,使用如下命令;

  1. git diff 比较的是工作区和暂存区的差别
  2. git diff –cached 比较的是暂存区和版本库的差别
  3. git diff HEAD 比较的是工作区和版本库的差别
    Git使用简易指南
    执行diff命令后,如果没有差别,则不提示,如果有差别,则显示如上图,其中红色减号开头的行为减少的内容,绿色加号开头的行为增加的内容。

五.版本回退
       在了解版本回退前,首先了解一个概念

  • HEAD
    HEAD是git中的一个指针,它指向了当前的分支指针,比如当前分支是主分支,那么HEAD就指向了master指针,默认情况下,master指针指向了主分支中最新一次的提交操作。假如我们需要回退版本,其实就是在当前分支的各个提交操作点之间切换master指针的指向。
  • git log
    git loggit提供的日志命令,可以用来查看文件修改的日志信息
  • git reflog
    git refloggit提供的命令日志命令,可以用来查看之前执行过什么命令

       当我们进行一次git commit操作的时候,会相应的产生一个commit id,我们可以使用git log命令查询到每次提交操作以及对应的commit id,如下;
       Git使用简易指南
然后,我们可以使用git reset --hard <commit id>命令来回退到这个commit id所对应的版本,其实就是让当前的分支指针指向了commit id所对应的提交节点;由于HEAD指针指向了分支指针,分支指针指向了提交节点,所以我们也可以使用HEAD来代替commit id来进行版本回退,git reset --hard HEAD表示当前的版本,git reset --hard HEAD^表示上一个版本,git reset --hard HEAD^^表示更早一个的版本,如果是更早100个版本那,我们使用^的话就太麻烦了,可以使用git reset --hard HEAD~100来进行版本回退。
       我们使用git reset回退到了以前的版本,我们又后悔了,想回去怎么办?前边说过git reset --hard <commit id>命令只要知道了commit id就可以将当前分支指针移动到其对应的提交节点,它不但可以用来回退,也可以回到更新的提交节点,只要这个commit id是存在的。我们可以利用git reflog查询到commit id,然后使用git reset --hard <commit id>就可以回到你想去的任何节点,需要注意一点,这个commit id比较长,我们只需要输入前几位就可以了。

6.删除操作

  • 当某个文件已经被废弃的时候,我们就要删除这个文件,但是这还不够,如果这个文件已经加入了版本库,我们还需要在版本库中删除掉这个文件。首先可以使用rm test.txt命令或者手动删除文件,然后使用git rm test.txt在版本库中删除文件,由于删除也是一种修改,我们可以使用git status查看一下暂存状态,如下;
           Git使用简易指南
    最后,使用git commit命令提交这次删除操作。

  • 假如我们只是误删了文件,版本库中还有,我们想要恢复文件怎么办?可以使用git checkout --test.txt来恢复,也就是用本地版本库来覆盖工作区

7.远程库的调用

  • 在终端输入以下内容ssh-****** -t rsa -C "[email protected]
    在本地生成RSA公钥和私钥,用于和github服务器进行加密交互.
  • 将生成的id_rsa.pub即公钥信息复制到github的SSH Keys中,然后添加,这样就可以和github进行安全交互了。如果你有多台电脑需要和github交互,提交代码,你就得将每台电脑的ssh key都添加到github上,这样每台电脑就可以往github上推送了。
  • 添加远程库,在github上创建一个仓库,然后我们需要将本地库和远程库关联,使用如下的命令git remote add origin [email protected]:path/repo-name.git,例如git remote add origin [email protected]:zephyr90/learnGit.git,这里远程库的名字是origin,这是git默认的叫法,也可以改成别的。
  • 下一步,就可以将本地库的所有内容推送到远程库上,使用命令git push -u origin master,其中-u参数不但可以使本地master分支内容推送到远程新的master分支,还可以把本地的master分支和远程的master分支关联起来,简化以后的推送或者拉取命令。 以后只要本地有新内容提交,就可以通过git push origin master推送到github上,不需要再加-u了。
  • 如果没有本地库,一切从零开始,那么可以创建一个远程库,然后git clone。

8.分支管理和使用
        git将每次提交操作串成一个时间线,这就是一个分支,默认情况下,只有一个时间线,也就是只有一个主分支,即master分支。我们前边说了HEAD是指向当前版本的指针,实际上,严格的说,HEAD指向的是master,然后master指向提交,所以HEAD指向的就是当前分支。每次提交,master就前进一步,HEAD始终指向master。当我们创建分支时,就是创建了一个新的指针,比如dev,它也和master一样,指向提交。如果我们要切换分支的话,只需要将HEAD指针指向dev就可以了,这样当前分支就切换成了dev,整个过程中只有指针的创建和HEAD指向的变化, 所以速度非常快。这时候我们再对工作区文件进行修改,然后提交就是会提交到dev分支上了,dev指针会前进一步,但是master不变。如果我们切换回master分支,会发现文件没有发生变化。
       如果我们再dev分支上完成了工作,如何合并到master分支上那?我们只需要把master指向dev的当前提交即可完成合并。这时候dev可能已经没有用了,我们可以删掉直接。
操作:

  1. 创建新的分支并切换 git branch dev, git checkout dev或者直接git checkout -b dev,这样我们就创建了分支dev并切换到此分支。
  2. 现在切换到新的分支了,也就是dev指针创建完成并指向了当前提交点,并且HEAD指向了dev,我们可以通过git branch命令来查看当前的所有分支,
           Git使用简易指南
    其中带*号的表示当前的分支,然后我们就可以正常的git addgit commit操作,这样修改的内容就提交到了当前的分支也就是dev分支上了。
  3. 我们切换回master,发现工作区内容没有发生改变,因为修改都提交到了dev分支上,然后我们可以使用git merge dev将dev分支和当前分支合并。然后我们再查看工作区内容,发现发生了改变。
           Git使用简易指南
  4. 现在分支已经合并完成了,dev已经没有用了,我们可以通过git branch查看当前的所有分支,然后可以通过git branch -d dev将dev分支删除掉。合并分支的操作就完成了。

       这里需要考虑一个问题,假如我们在master分支上对文件做了修改,然后切换到dev分支上对同一个文件做了修改,这样就会产生冲突,当我们在master分支上使用git merge dev的时候会显示冲突如下;
       Git使用简易指南
其中会显示冲突的文件,使用git status也可以显示冲突的文件,这个时候我们只需要打开冲突的文件,冲突的地方会使用<<<<<< HEAD , ======, >>>>>>>dev这样的字符串,这是git用来表示不同分支内容的标志;
       Git使用简易指南
其中HEAD和等号包括的内容是当前分支的内容,等号和dev包括的内容是dev分支的内容,我们只需要按照实际情况删除掉不需要的内容即可解决冲突。最后,我们需要执行git addgit commit命令将这次修改提交到版本库,合并完成。
       另外,需要注意的是,我们开头讲的第一个合并的例子的图示中,我们发现合并方式为FAST-FORWARD,
       Git使用简易指南

这种方式会使分支信息丢失,我们可以使用git log --graph查看分支的图示,如下;
       Git使用简易指南

这是使用普通模式合并之后的图示,我们可以清楚的看到有两个分支,在最上边两个分支发生了合并,如果我们使用FAST-FORWARD模式的话,我们就只能看到当前分支,而dev分支的信息就丢失了,看不出曾经发生过分支的合并,那么如何解决这个问题那?
所以为了保留分支信息,我们可以禁用fast forward,使用普通模式。使用命令git merge --no-ff -m “使用普通模式合并” dev, 使用普通模式合并,会保存分支信息,并且git会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。

       再考虑一个问题,在实际开发中,我们经常要开发新功能,通常我们并不会在主分支上进行开发,这样会导致主分支不稳定,风险太大,我们应该新创建一个分支,在新分支上开发新功能,开发完成以后,使用git merge --no-ff -m “提交说明”来合并分支。如果功能开发过程中,这个新功能不需要了,不用继续了,我们可以使用git branch -d branch-name来删除分支,这时候git会提醒我们当前的分支没有被合并,我们只需要使用git branch -D branch-name就可以强制删除当前分支了。
       另一个场景,当我们开发过程中,需要紧急修改一个master分支上的bug,但是我们现在有许多新功能的代码没有提交,而且新功能没有完成,提交了会导致编译不通过,这个时候怎么办?git为我们提供了一个保护现场的功能,我们只需要使用git stash命令就可以将现场保存起来,我们还可以通过git stash list查询stash中的内容,如下;
       Git使用简易指南
显示stash中有一条记录。保存完成以后,我们就可以切换到需要修改bug的分支,然后在那个分支上新建一个新的分支,在上边修改bug,然后合并分支。bug修改完成!我们可以继续之前的工作了,可以使用git stash pop将现场恢复出来就可以继续工作了!在这里git stash pop有两个左右,首先恢复现场,然后直接把stash删除掉了,我们也可以分步完成,先执行git stash apply恢复现场,然后执行git stash drop删除掉stash,这里可以看出来,stash有点类似于暂时保存文件的空间,用完就直接删除掉。

       以上两实际场景中,我们都是新建了新的分支用来开发新功能或者修改bug,为什么要这么做?这么做的目的就是为了保持主分支的稳定,主分支应该用来发布新版本。实际上,在工作中,多个开发人员将代码通过clone的方式从远程库克隆到本地,git clone这种方式会自动建立本地master分支和远程master分支之间的关联,并且本地只能看到master分支,如果我们要在dev分支上开发怎么办?我们需要创建远程origin的dev分支到本地,用以下的命令git checkout -b dev origin/dev,通过这条命令在本地创建了dev分支,并且和远程库的dev建立了关联,我们就可以在dev上开发,并通过命令git push origin dev提交到origin的dev分支上了。这时候又出现一个问题,我们前边知道本地库中不同分支修改同一个文件会导致冲突,那么当我们多个开发人员对同一个文件做了修改,然后提交到了origin的dev上的时候,同样会产生冲突。这是我们就需要解决冲突,首先通过git pull将远程库的最新代码拉取下来,然后解决冲突,在本地提交,再git push origin dev,这个时候git pull也可能失败,由于你的dev和远程dev没有建立连接,可以使用git branch --set-upstream dev origin/dev创建连接,然后git pull,然后解决冲突,本地提交,推送远程。

9.标签管理
       在git中commit id可以唯一的代表一次commit操作,我们可以通过git reset --hard <commit id>来实现版本的回退,但是commit id太长了,并且没有具体含义,如果开发中,我们需要将半年前的某个版本打包的话,通过commit id可以实现,但是会非常困难,我们需要通过git log翻看,找到那个commit操作,如果这个操作没有通过-m添加说明,并且忘记具体时间的话,那就完蛋了,那么有什么好办法嘛?我们可以给某个提交节点添加标签,唯一的表示这个版本。可以通过git tag V1.0 <commit id>为某个具体commit id代表的版本添加标签,或者使用git tag V1.0为最新的版本添加标签,我们还可以通过git tag来查看所有所有的标签。添加标签也可以和commit操作一个添加一个说明,使用命令git -a tag-name -m “标签说明”。另外,可以使用命令git show <tag-name>来查看标签详情。
       标签代表了一个提交节点,也就代表了这个节点所代表的版本,我们可以将这些标签所代表的版本推送的远程库上,使用命令git push origin <tag-name>,也可以将所有的标签都推送到远程库,使用命令git push origin --tags,如果这个标签不需要了我们可以通过git tag -d <tag-name>删除这个标签,如果要删除远程库中的标签的话首先需要git tag -d <tag-name>删除本地标签,然后使用命令, git push origin:refs/tags/<tag-name>就可以删除远程标签了。

10.忽略文件
在.gitignore中加入需要忽略的文件名,那么这些文件的修改就不会被git跟踪了