美文网首页
『Git 干货』#2 本地仓库使用(简明)

『Git 干货』#2 本地仓库使用(简明)

作者: Hwcoder | 来源:发表于2021-10-19 09:38 被阅读0次

    访问博客查看 本文 最新内容,排版更美观ヾ(•ω•`)o 如有错误欢迎指出~

    Git 系列学习笔记:

    接上文,本文在命令行的基础上介绍了常用指令的在本地的使用情景

    本文大部分内容参考了 RCY 同学的教程,部分参考了 廖雪峰教程-Git菜鸟教程-Git,以及 Git 官网文档 Git-Documentation

    本地的 Git

    预先准备

    在正式的操作前,你需要先配置你的用户信息,这件事情通常在你的机器上只需要干一次即可,因为我们使用了全局配置。在任意⼀个目录打开你的终端,配置你的用户名和邮箱:

    $ git config --global user.name <username> # 替换为 GitHub 用户名
    $ git config --global user.email <email>   # 替换为绑定的邮箱
    # username 中如果有空格,需要加双引号
    

    基本操作

    在想用 Git 管理的项目路径下右键进入 Bash 终端,然后将当前目录初始化为 Git 仓库。

    $ git init
    

    手动新建一个文件,随便写点什么,譬如一个 hello.md,此时文件存在于工作区。下面将其跟踪并提交:

    $ git add hello.md      # 如果有更多文件,也可直接追加在后面
    $ git commit -m "Init"  # -m: 表示 message,备注本次提交信息
    

    注意到,add 后面是文件名,也可以是目录通配符,因此也支持以下写法:

    $ git add .          # 相对路径中 . 表示当前目录,因此这一选项会添加当前目录下所有文件
    $ git add *.cpp      # * 代表任意字符串,所有 .cpp后缀的文件被添加
    $ git add test?.cpp  # ? 代表单个字符,不得为空,因此如 test1.cpp 将被添加,而 test.cpp 等不会
    $ git add dir/       # 添加路径下的 d 文件夹内全部文件, / 有没有皆可
    

    此外,为了体现三个区,我们可以使用一些指令来对比:

    $ git status         # 查看工作区、暂存区的文件,一般前者是红色,后者是绿色
    $ git diff           # 查看工作区和暂存区的差异,适用于暂存后又修改的内容
    $ git diff --cached  # 查看暂存区与 Git 仓库的差异,适用于提交前查看
    $ git diff HEAD      # 同时查看其他两个区和 Git 仓库的差异
    
    Diff用法图解

    在工程中,通常我们不用命令行查看差异,而是用 VSCode、GitHub Desktop、GitLab 等可视化工具。

    如果对于已暂存的文件后悔了,也可以取消暂存:

    $ git reset <filename>  # 对于该命令,如果不带参数则会清空暂存区
    $ git restore --stage <filename>
    

    在完成了几次提交后,可以查看提交历史:

    $ git log
    

    提交历史中,每个提交记录都有对应的 commit hash 值,唯一标识了这次提交,这是 Git 用 SHA-1 hash 生成的加密字符串。

    注:如果 log 比较长或者窗口比较小,这会触发「导航」模式,很多人第⼀次见到可能不知所措,不会退出该页面,此时可以:

    • 上下键移动或 Page Up / Down 翻页;
    • 输入 \ 接字符来全局查找 ;
    • 输入 q 退出,与其他系统中的导航模式类似;
    • 其他操作可以通过查询关键字「Linux less 导航」来查到。

    分支 (Branch)

    有了前面的知识,我们已经在脑海中想象⼀副快照变更图了,本节中我们将快照称作「结点」,若干结点组织成了版本树——Git 本身正是使用了红黑树对结点进行高效管理。

    以下内容强烈推荐结合 Git 分支在线教程 来学习!

    目前我们的结点树基本是串行的(除了回退、重新提交会导致分叉),那么所谓的并行开发如何体现呢?

    注意到了反复有⼀个单词 main 出现在命令行,这即是 Git 默认的分支:主分支(旧版本叫 master)。main 即是这个分支的名字,也是一个指针,指向了该分支的最新结点。

    和它⼀起的还有⼀个单词 HEAD,这表示头指针,指向当前所处的结点。在你做分支相关的操作前,会有 HEAD -> main -> 最新结点,直到你将它们分开。

    创建分支

    与初始化仓库类似,创建分支也很方便:

    $ git branch test  # 创建⼀个名为 test 的分⽀
    

    注意:创建完后分支的即会指向当前所在的结点,因此当前最新结点同时被 main、test、HEAD 指向,即 HEAD -> main(test) -> 最新结点

    对于已有的分支,也可以通过以下命令查看、切换:

    $ git branch         # 查看本地分支,带 * 的为 HEAD 指向的当前分支
    $ git checkout test  # 检出,表示切换到 test 分支
    
    # 上述的创建、切换操作也可以被缩减为一步操作
    $ git checkout -b test # 创建⼀个名为 test 的分⽀并且切换过去
    

    切换分支后,可以看到出现在命令行右侧的 main 已经变成了 test。查看 log 也可发现 HEAD -> test(main) -> 最新结点

    合并分支

    如果此时我们已经在不同的分支提交了不同修改 C2 和 C3,那么如何将分支合并到一起,使得并行开发结果汇总呢?

    $ git checkout main  # 切换到 main 分⽀
    $ git merge test     # 让 main 分⽀合并 test 分⽀的结点
    

    对于分支的合并,Git 有专门的图形化输出命令来进行查看版本树(也可以用其他可视化工具):

    $ git log --graph --oneline --all
    # oneline 表示用行来显示记录,最上方的是最新的提交
    # all 表示显示所有分支,如果没有则只显示当前分支及其祖先
    
    # 当然,还有其他许多参数可以美化版本树,这里贴一个大神的版本
    $ git log --graph --all --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --date=relative
    

    为展示方便,这里使用在线教程里的图例:

    main分支合并test分支

    可以看到,合并后产生了一个新结点 C4,该结点具有双父结点,指向原来的 C2 和 C3。需要注意的是,该结点属于 main 分支,是 main 分支的最新结点(被指针 main 所指),而 test 仍指向旧的 C2。

    如果要把 test 分支也同步到新结点,只需要让 test 分支合并 C4,也就是合并当前的 main 分支:

    $ git checkout test  # 切换到 test 分⽀
    $ git merge main     # 让 test 分⽀合并 main 分⽀的结点
    

    而由于 main 分支的最新结点 C4 继承自 C2,此时的 Git 不会有任何操作,只是简单地将指针 test 移动到指针 main 所在位置,即快速前移(fast-forward):

    test分支合并main分支

    多数情况下,我们会先用主分支合并从分支,如果之后需要再从分支继续开发,才会把从分支快速前移

    冲突的合并

    如果 C2 和 C3 修改的代码不在同一文件的同一处,上述的 merge 是没有问题的,但是一旦发生冲突,git merge test 命令时就会提示错误。

    此时如果我们用 git status 查看,会发现工作区里有一个新的状态「未合并的路径」,里面就是冲突的文件。打开该文件,会发现 Git 已经在里面标记出了双方修改的内容(用 VSCode 等 IDE 将看得更清楚)。

    而我们只需要手动维护冲突,将该文件手动加入暂存区,最后再提交,就会生成一个新的结点,该结点无异于直接使用 git merge test 命令得到的。

    变基 (Rebase)

    一个来回穿插的版本树是有点凌乱的,此时不得不提到第二种合并分支的办法:变基 (rebase) 操作。Rebase 实际上就是取出从分支的提交记录,「复制」它们,然后在主分支逐个的放下去。

    Rebase 的优势就是可以创造更线性的提交历史。如果两个分支没有冲突,如上文提到的第一种情况,直接 Merge 会出现一个新的结点(实际上该结点并没有做出实质的修改,反而使版本树变得冗长)。

    此时如果我们用 Rebase 操作,则可以简化版本树:

    $ git checkout test  # 切换到 test 分⽀
    $ git rebase main    # 让 test 分⽀以 main 为基,因此 test 成为 main 的后代
    

    注意,Rebase 操作通常是让从分支变基到主分支,这与 Merge 操作是相反的!

    test分支变基到main

    观察该图,我们可以发现 test 分支上的工作在 main 的最顶端,同时我们也得到了一个更线性的提交序列。

    而提交记录 C2 依然存在(树上那个半透明的节点),而 C2’ 是我们 Rebase 到 main 分支上的 C2 的副本,它们具有不同的 hash 值,可以通过 log 查看。

    之后我们也可以通过类似的操作,把 main 快速前移到最新的结点:

    $ git checkout main  # 切换到 main 分⽀
    $ git rebase test    # 让 main 分⽀以 test 为基,快速前移
    # 当然,z也可以用前面的 merge 操作
    

    Rebase 操作在没有冲突时将非常舒适,可以避免没有意义的合并结点(尤其是涉及到远程仓库时),但是一旦发生了冲突,操作将十分繁琐!这里不再赘述,具体工程中如果遇到了请根据 Git 的自动提示逐步操作。

    而对于 Rebase 后的从分支上的结点,就变成了所谓的「悬垂结点」,这些结点的访问将十分复杂。此外,如果这个从分支被废弃,我们也可以用以下指令将其删去:

    $ git branch -d test  # 删除名为 test 的分⽀,用于合并后废弃的分支
    

    分叉 (Branch Diverged)

    分叉是分支的一种特殊情况,往往是因为某些「不友好」操作而产生,最终被废弃掉。对于单人操作的仓库,其产生原因可能是:

    • Reset 后旧分支:版本回退后重新提交,这种情况下往往是要弃用旧分支。但如果旧的分支仍有需要保留的更改,则需要 Cherry-Pick 等操作。
    • Rebase 后从分支:上文介绍到,Rebase 将创造更线性的主分支,但这样做的代价是从分支将被废弃,成为一个无用的分叉。

    上述情形的发生往往可以人为进行控制,而对于多人操作的仓库,如果不同开发者同时对一个结点进行了更改,将会造成「不可控」的分叉,具体情形及解决方法将在下一节介绍。

    相关文章

      网友评论

          本文标题:『Git 干货』#2 本地仓库使用(简明)

          本文链接:https://www.haomeiwen.com/subject/lczooltx.html