Git

本文最后更新于:2024年8月20日 下午

Git

简介

  • 分布式版本控制系统
  • Linux之父Linus Torvalds两周用C写了一个分布式版本控制系统,一个月内Linux 系统的源码已经由Git管理了。这就是鸡欧帝的实力吗?亏贼!
  • 集中式版本控制系统和分布式版本控制系统的区别
    • 本质区别:你的本地是否有完整的版本库历史。
      • 假设SVN服务器消失了,你失去了所有的历史信息,因为你的本地只有当前版本以及部分历史信息。
      • 假设Github服务器消失了,你不会丢掉任何git历史信息,因为你的本地有完整的版本库信息,你可以把本地的git库重新上传到其他的git服务器上去。
    • 集中式版本控制器很依赖中央服务器,需要网络,当网络不好时,每次拉取,提交文件非常缓慢。

安装

  • linux:sudo apt-get install git
  • mac:安装一个Xcode
  • windows:安装一个gitbash

基本使用

  • 创建一个目录

    1
    2
    3
    4
    $ mkdir learngit
    $ cd learngit
    $ pwd
    /Users/michael/learngit
  • git init 通过git init命令把这个目录变成Git可以管理的仓库:

    1
    2
    $ git init
    Initialized empty Git repository in /Users/michael/learngit/.git/
  • 之后可以在此目录下创建文件

  • 把一个文件放入git仓库分为两步

    • git add 命令,把文件添加到仓库

    • git commit 命令,把文件提交到仓库 -m “注释”。-m””后面输入本次提交的说明

      1
      2
      3
      4
      5
      $ git add readme.txt
      $ git commit -m "wrote a readme file"
      [master (root-commit) eaadf4e] wrote a readme file
      1 file changed, 2 insertions(+)
      create mode 100644 readme.txt

版本管理

  • git status 命令可以查看仓库当前的状态(可以知道文件是否有修改)

  • git diff + 文件名 命令可以查看当前文件和仓库中文件的差异(查看修改的内容)

版本回退

  • git log 命令显示从近到最远的提交日志
    • git log –pretty=oneline 加上参数后,会精简输出内容
  • git reset 回退版本
    • git reset –hard HEAD^ 表示回退到前一个版本,HEAD^代表前一个版本,HEAD^^代表前两个版本,以此类推
    • git reset –hard HEAD~100 回退版本,数字代表回退到前多少个版本
    • git reset –hard 1094a 加上版本号的前几位即可,会回退到指定的版本
  • git reflog 查看命令历史
    • 可以通过查看命令历史,重返未来的某个版本。(对于当前版本来说是未来,因为当前版本是从未来某个版本回退回来的)

工作区和暂存区

  • 工作区

    • 在git仓库中目前的文件,就是工作区
  • 版本库

    • 工作区目录下有一个隐藏文件**.git**,是git的版本库

    • git版本库中最重要的就是stage(index)暂存区,git自动创建的一个master,以及指向master的一个指针叫HEAD

      版本库

    • git add命令就是把提交的所有修改放到暂存区(stage),git commit 命令就是一次性把暂存区的所有修改提交至版本库(master)

    • 查看工作区和版本库中最新版本的区别:

      1
      git diff HEAD -- readme.txt
    • ````bash

      git reset命令既可以回退版本,也可以把暂存区的修改回退到工作区。当我们用HEAD时,表示最新的版本。

      git reset HEAD readme.txt

      丢弃工作区的修改

      git checkout – readme.txt
      #git checkout其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。

      删除文件,然后commit文件就删除了

      rm test.txt

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24

      ### 远程仓库

      * 采用github作为远程在线的git服务器

      * 注册一个github账号,本地git仓库和github仓库之间的传输是通过SSH加密的

      * 步骤

      ````bash
      #第1步:创建SSH Key。在C盘当前用户的主目录下,看看有没有.ssh目录,如果有,再看看这个目录下有没有id_rsa和#id_rsa.pub这两个文件。有的话也可以删掉重新创建ssh key。如果没有,打开Shell(Windows下打开Git #Bash),创建SSH Key:
      ssh-keygen -t rsa -C "youremail@example.com"

      #第二步:在github中将公钥id_rsa.pub的内容添加

      #为什么GitHub需要SSH Key呢?因为GitHub需要识别出你推送的提交确实是你推送的,而不是别人冒充的,而Git支持SSH协议,所以,GitHub只要知道了你的公钥,就可以确认只有你自己才能推送。

      #当然,GitHub允许你添加多个Key。假定你有若干电脑,你一会儿在公司提交,一会儿在家里提交,只要把每台电脑的Key都添加到GitHub,就可以在每台电脑上往GitHub推送了。

      #最后友情提示,在GitHub上免费托管的Git仓库,任何人都可以看到喔(但只有你自己才能改)。所以,不要把敏感信息放进去。

      #如果你不想让别人看到Git库,有两个办法,一个是交点保护费,让GitHub把公开的仓库变成私有的,这样别人就看不见了(不可读更不可写)。另一个办法是自己动手,搭一个Git服务器,因为是你自己的Git服务器,所以别人也是看不见的。这个方法我们后面会讲到的,相当简单,公司内部开发必备。

      #确保你拥有一个GitHub账号后,我们就即将开始远程仓库的学习。

添加远程库

  • 如果你在本地建好了一个git仓库后,想在github上新建一个git仓库,并且可以实现两个仓库之间的远程同步

    • 第一步:在github创建一个仓库

    • 第二步:和新创建的远程github仓库进行关联

      • ````bash
        git remote add origin git@github.com:zt3019/learngit.git
        1
        2
        3
        4
        5
        6
        7

        - 第三步:将本地库的所有内容推送到远程库上

        - ````bash
        #添加关联后,远程库的名字叫origin,这是git的默认叫法,也可以修改
        git push -u origin master
        #由于远程库是空的,我们第一次推送master分支时,加上了-u参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。
    • 在这之后,需要提交代码到远程库只需要执行push命令

      • ```bash
        git push origin master
        #查看远程库信息
        git remote -v
        #删除远程库
        git remote rm origin

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52

        #### 从远程库克隆

        * 如果是先创建远程库,然后从远程库克隆

        - 先登录远程库,如github,创建一个新的仓库

        - 远程库创建好之后,用git clone克隆一个本地库

        ````bash
        git clone git@github.com:zt3019/gitskills.git
        ````

        - 之后就可以在本地看到创建的gitskills仓库了

        * Git支持多种协议,包括`https`,但`ssh`协议速度最快。

        ### 分支管理

        * 分支在实际中有什么用呢?假设你准备开发一个新功能,但是需要两周才能完成,第一周你写了50%的代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能干活了。如果等代码全部写完再一次提交,又存在丢失每天进度的巨大风险。

        现在有了分支,就不用怕了。你创建了一个属于你自己的分支,别人看不到,还继续在原来的分支上正常工作,而你在自己的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,这样,既安全,又不影响别人工作。

        * 每次提交,git都把他们串成一条时间线,这条时间线就是一个分支。主分支即**master**分支,**HEAD**严格来说不是指向提交,而是指向**master**,**master**才是指向提交的,所以,**HEAD**指向的就是当前分支。

        * 例如:git新增一个dev分支,git创建了一个指针叫dev,指向master相同的提交,再把`HEAD`指向`dev`,就表示当前分支在dev上。如果在dev上操作完成之后,将`dev`合并到`master`,将`master`指向`dev`当前的提交,就完成了合并。所以git合并分支非常快,修改一下指针的指向。合并完成后,我们可以删除`dev`分支,而删除`dev`分支就是把`dev`指针给删掉,删掉后,我们就剩下了一条`master`分支

        * [廖雪峰的官方网站中的git教程](https://www.liaoxuefeng.com/wiki/896043488029600/900003767775424)

        * 实操:

        - 第一步:创建`dev`分支

        - ````bash
        git switch -c dev
        #git checkout命令加上-b参数表示创建并切换,相当于下面两条语句:
        git branch dev
        git switch dev
        #使用git branch命令查看当前分支
        git branch
        ````

        - 第二步:切换到`dev`分支后,在`dev`分支修改文件内容

        - ```bash
        #在dev分支修改git文件内容之后,提交修改的文件
        git add readme.txt
        git commit -m "branch test"

        # 切换回master分支,我们无法看到刚才修改的内容,因为刚才的添加提交是在dev分支上的
        git switch master

    • 第三步:把dev分支的工作成果合并到master分支上

      • ````bash
        #git merge命令用于合并指定分支到当前分支。合并后,再查看readme.txt的内容,就可以看到,和dev分支的最新提交是完全一样的。
        git merge dev
        #合并完成后,我们可以在master分支上看到刚才修改的内容,然后就可以删除dev分支了
        git branch -d dev
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18

        - 因为创建、合并和删除分支非常快,所以Git鼓励你使用分支完成某个任务,合并后再删掉分支,这和直接在master分支上工作效果是一样的,但过程更安全。

        - 建议使用git switch来切换分支,更易于理解

        #### 解决分支冲突

        * 分支合并是有可能会出现分支冲突的,比如下面的一个案例

        - 创建一个新的分支,修改文件内容,进行add并commit

        - ````bash
        #创建feature分支并切换到该分支
        git switch -c feature
        #修改readme.txt文件内容,添加一行:creating a new branch named feature
        #然后添加到暂存区并提交
        git add readme.txt
        git commit -m "new a branch named feature1"
    • 切换到master分支,也修改文件内容,进行add并commit

      • ````bash
        #切换分支
        git switch master
        #修改readme.txt文件内容,添加一行:分支冲突吧你
        #然后添加到暂存区并提交
        git add readme.txt
        git commit -m “add chinese”

        1
        2
        3
        4
        5
        6

        - 在master进行合并,将feature分支合并进来

        - ````bash
        #合并分支
        git merge feature
      • 两个分支都对readme.txt文件内容进行了不同的修改,无法进行快速合并,git会发出报错信息,readme.txt文件存在冲突,必须手动解决冲突之后再提交。git status可以查看到冲突的文件。

      • 分支冲突

    • 出现冲突之后,我们需要解决冲突

      • ````bash
        vim readme.txt
        #Git用<<<<<<<,=======,>>>>>>>标记出不同分支的内容
        #重新编辑冲突了的文件,保留你需要的内容,去除冲突的部分
        #然后重新add,commit
        git add readme.txt
        git commit -m “conflict fixed”
        #git会显示”conflict fixed”

        1
        2
        3
        4
        5
        6
        7
        8
        9

        - 最后工作

        - ````bash
        #从git提交的分支图可以看到我们解决分支冲突的过程
        git log --graph --pretty=oneline --abbrev-commit

        #最后,我们可以将feature分支删除
        git branch -d feature

分支管理策略

  • 一般情况下,合并分支时,git会使用fast forward模式,这种模式下,删除分支后,会丢掉分支信息

  • 如果要强制禁用Fast forward模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。

  • 关于分支管理策略的参考博客

  • 通过一个案例来演示:

    • ````bash
      #创建并切换dev分支
      git switch -c dev
      #修改readme.txt文件内容并提交
      git add readme.txt
      git commit -m “add merge”
      #切换回master
      git switch master
      #回到master分支之后,合并dev分支,使用–no-ff参数,表示禁用Fast forward
      git merge –no-ff -m “merge with no-ff” dev
      #再查看git提交的分支树
      git log –graph –pretty=oneline –abbrev-commit

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44

      - ![](D:\图片\分支策略.png)

      #### Bug分支

      - 我们在开发过程中,遇到了bug,我们可以通过创建一个新的临时分支来修复,修复之后,合并分支,然后将临时分支删除。举个栗子:

      - 当我们收到一个修复bug的任务,我们创建一个`issue-3208`来修复它,但是我们在dev分支上进行的工作只完成了一半,不能够提交。而且我们必须先解决bug

      - ````bash
      #stash功能,可以把当前工作现场储存起来,等后续恢复现场后继续工作
      git status
      #执行status命令之后,再用git status查看工作区,就是干净的
      #首先要确定是在哪个分支上修复bug,例如需要在master分支上修复
      git switch master

      #在master分支上创建临时修复bug的分支
      git switch -c issue-3208


      #在新建的分支修复bug,修复完成之后,进行add&commit
      git add readme.txt
      git commit -m "fix bug 3208"

      #修复完成之后,切换到master分支,并完成合并,最后删除issue-3208分支
      git switch master

      git merge --no-ff -m "merged bug fix 3208" issue-101

      #完成bug修复之后,继续回到dev分支干活
      git switch dev

      #通过git status 命令发现工作区是干净的
      #通过stash list 命令查看,隐藏的工作区
      git stash list

      #一是用git stash apply恢复,但是恢复后,stash内容并不删除,你需要用git stash drop来删除;
      #另一种方式是用git stash pop,恢复的同时把stash内容也删了:
      git stash pop

      #可以进行多次stash,恢复的时候,先查看git stash list 的列表,然后恢复指定的stash
      git stash apply stash@{0}

      #在master分支上修复的bug,想要合并到当前dev分支,可以用git cherry-pick <commit>命令,把bug提交的修改“复制”到当前分支,避免重复劳动。

      -

Feature分支

  • 在开发中,有新功能进来的时候,创建一个feature分支,在上面开发完成之后合并,最终删除该分支

    • ````bash
      #开发一个新需求
      git switch -c feature-vulcan

      #开发完成后进行add&commit

      #之后应该切换到dev并进行合并,然后删除新建的分支

      #新需求砍掉了
      git branch -D feature-vulcan

      #-D参数强行删除

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15

      - 如果要丢弃一个没有被合并过的分支,可以通过`git branch -D <name>`强行删除。

      #### 多人协作

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

      - ````bash
      # 查看远程库的详细信息
      git remote -v
      #上面显示了可以抓取和推送的origin的地址。如果没有推送权限,就看不到push的地址。
      #推送分支,就是把该分支上的所有本地提交到远程库,推送时,要指定本地分支,git就会把该分支推送到远程库对应的远程分支上
      git push origin master
      git push origin dev

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

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

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

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

  • 因此,多人协作的工作模式通常是这样:

    1. 首先,可以试图用git push origin <branch-name>推送自己的修改;
    2. 如果推送失败,则因为远程分支比你的本地更新,需要先用git pull试图合并;
    3. 如果合并有冲突,则解决冲突,并在本地提交;
    4. 没有冲突或者解决掉冲突后,再用git push origin <branch-name>推送就能成功!

    如果git pull提示no tracking information,则说明本地分支和远程分支的链接关系没有创建,用命令git branch --set-upstream-to <branch-name> origin/<branch-name>

    这就是多人协作的工作模式,一旦熟悉了,就非常简单。

Rebase

标签管理

创建标签

  • 发布一个版本时,通常现在版本库中打一个标签(tag),这样就确定了打标签时刻的版本。标签就是版本库的一个快照。commit号太长了,不方便寻找,使用标签号进行提交发版,更加清晰明了。

  • 打标签

    • ````bash
      #切换到需要打标签的分支上
      git switch master
      #git tag 命令打上标签,不加名字可以查看标签
      git tag v1.0
      #默认标签是打在最新提交的commit上的,要为之前的commit打上标签,需要找到commit id
      git tagv0.9 5f23208

      git 命令查看标签,不是按时间顺序列出,而是按照字母排序的

      git tag
      #使用git show 查看标签详细信息
      git show v1.0#创建带有说明的标签,用-a指定标签名,-m指定说明文字
      git tag -a v0.1 -m “version 0.1 released” 4d39864
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22

      - 标签总是和某个commit挂钩。如果这个commit既出现在master分支,又出现在dev分支,那么在这两个分支上都可以看到这个标签。

      #### 操作标签

      - 可以删除标签,推送标签至远程

      - ````bash
      #删除标签
      git tag -d v0.1

      #推送某个标签到远程
      git push origin v1.0
      #一次性推送全部尚未推送到远程的本地标签
      git push origin --tags

      #如果标签已经推送至远程库,删除远程标签需要两步,先删除本地,再删除远程
      git tag -d v0.9

      git push origin -d tag v0.9
      #之后可以去远程库查看标签是否已经删除

远程仓库

  • 在GitHub上,可以任意Fork开源仓库;
  • 自己拥有Fork后的仓库的读写权限;
  • 可以推送pull request给官方仓库来贡献代码。
  • gitee国内的git托管服务商,有中文,不容易出现网络问题

通用配置

  • 有时候一些隐私文件也在git工作目录中,但是不能提交他们。比如保存了数据库密码的配置文件

  • 可以在Git工作区的根目录下创建一个特殊的.gitignore文件,然后把要忽略的文件名填进去,Git就会自动忽略这些文件。

  • 忽略文件的原则是:

    1. 忽略操作系统自动生成的文件,比如缩略图等;
    2. 忽略编译生成的中间文件、可执行文件等,也就是如果一个文件是通过另一个文件自动生成的,那自动生成的文件就没必要放进版本库,比如Java编译产生的.class文件;
    3. 忽略你自己的带有敏感信息的配置文件,比如存放口令的配置文件。
  • 不需要从头写.gitignore文件,GitHub已经为我们准备了各种配置文件,只需要组合一下就可以使用了。所有配置文件可以直接在线浏览:https://github.com/github/gitignore

  • ````bash
    #强制添加文件到git
    git add -f App.class
    #或者你发现,可能是.gitignore写得有问题,需要找出来到底哪个规则写错了,可以用git check-ignore命令检查
    git check-ignore -v App.class

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22

    * 把指定文件排除在`.gitignore`规则外的写法就是`!`+文件名,所以,只需把例外文件添加进去即可。

    * 忽略某些文件时,需要编写`.gitignore`

    * `.gitignore`文件本身要放到版本库里,并且可以对`.gitignore`做版本管理!

    * 可以给git命令设置简称

    - ````bash
    git config --global alias.st status
    git config --global alias.co checkout
    git config --global alias.ci commit
    git config --global alias.br branch
    #--global参数是全局参数,也就是这些命令在这台电脑的所有Git仓库下都有用
    git config --global alias.unstage 'reset HEAD'
    #执行上面的命令之后
    git unstage test.py == git reset HEAD test.py

    git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"

    git lg
    • 每个仓库的Git配置文件都放在.git/config文件中

    • 别名就在[alias]后面,要删除别名,直接把对应的行删掉即可。

    • 而当前用户的Git配置文件放在用户主目录下的一个隐藏文件.gitconfig

    • 配置别名也可以直接修改这个文件,如果改错了,可以删掉文件重新通过命令配置。

利用git分支,多端更新博客

需求:想在不同地方的电脑更新博客

解决原理

  • hexo博客目录结构

    • 目录结构
  • hexo生成的静态页面文件默认放在master分支上,是_config.yml配置文件配置的

    • pFYzbsH.png
  • 每次写完博客执行hexo deploy的时候,hexo会帮我们把生成好的静态页面文件推到master分支上。

  • 第一次部署好博客时,github给我们创建的唯一一个分支就是master分支,同时也会是github的默认分支。每次git clone 或 git pull时拉取的都是默认分支的代码

  • hexo delpoy执行时,推送静态页面文件到github,和是否是默认分支是无关的。这是由_config.yml配置文件决定的,写着什么分支就推送到什么分支

  • 根据hexo提交的特性,现在hexo生成的静态博客文件都放在master分支上。所以我们可以新创建一个hexo分支,然后把hexo分支设置为默认分支。

解决办法:

  • 把新创建的hexo设置为默认分支,用于存放博客的所有需要的源文件,master分支依然存放静态文件
  • 在旧电脑上,把必要的博客源文件上传git push到hexo分支。在新电脑上git clone +”仓库地址”,把hexo分支的文件下载下来,剩下的就是安装好hexo环境,随后就可以在新电脑上hexo deploy推送生成的静态页面到master分支上。(因为克隆下来的博客源文件中的_config.yml配置文件写的是master分支)
  • 这种方式创建的两个分支,实际上是完全独立的两个分支。一个是静态页面的文件,一个是博客的源文件。通过git add . git commit -m "" git push 来更新源文件分支,hexo deploy来更新静态页面

具体步骤

  1. 在github创建一个hexo分支,并设置其为默认分支

  2. 打包将要推送到github上的文件

    • clone该仓库到本地(clone的是hexo默认分支)

    • clone的文件夹里仅留下.git 文件夹,其他的文件都删除

    • 找见我们hexo原位置,将hexo文件夹内除.deploy_git 以外都复制到clone下来的文件夹中

    • 可以配置一下.gitignore文件,减少一些非必要文件的上传

      • ````bash
        .DS_Store
        Thumbs.db
        db.json
        .log
        node_modules/
        public/
        .deploy
        /
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19

        * 如果已经clone过主题文件,那么需要把theme主题文件夹里的 .git 也删除。因为git不能嵌套上传,最好是显示隐藏文件,检查一下有没有,否则上传的时候会出错,导致你的主题文件无法上传,这样你的配置在别的电脑上就用不了了。
        * 最后将clone并修改之后的文件夹推送到远程库

        ## 问题记录

        * 网络不好的时候用http提交或者拉取代码会失败,可以考虑使用ssh的方式

        - 设置 远程仓库的提交链接:
        - git remote set-url origin git@github.com:zt3019/zt3019.github.io.git

        - ````bash
        -- ssh方式
        git pull git@github.com:zt3019/zt3019.github.io.git
        git push git@github.com:zt3019/zt3019.github.io.git
        -- https方式
        git pull https://github.com/zt3019/zt3019.github.io
        git push https://github.com/zt3019/zt3019.github.io


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!