动手学Git&GitHub

实际操作学习Git

读完本文章,你将会了解:

  1. Git 使用的基本操作,包括分支的基本操作
  2. 使用 Git 从远程仓库获取并推送至远程仓库
  3. 小白初学时遇到的部分问题解决(我在学习的时候遇到的问题基本都记录了下来,最开始的时候是看《Pro Git》这本书,但是发现自己能力有限,所以换了《 GitHub 入门与实践》这本书,本文也是基于这本书的部分章节所做的总结笔记)

前置条件:

  1. 已经下载了 Git 并配置了 Git 基本的 config 。如果没有下载,请在官网 Git (git-scm.com) 进行下载;如果没有配置 config ,请转到这篇文章 首次使用 Git 的配置操作
  2. 已经注册了 GitHub 账号,创建了自己的 SSH Key 并将公开密匙添加到 GitHub 中,同时创建了一个 GitHub 仓库,并将其 clone 到本地。如果没有 GitHub 账号,官网自己创建 GitHub (有梯子的直接进,没梯子打不开的多时间段多次尝试,会进去的);如果不知道 SSH Key 是什么,怎么创建的,不知道怎么创建 GitHub 仓库,及创建时各个选项的作用,请转到这篇文章 使用 Git&GitHub 的前期准备

1. 基本操作

git init——初始化仓库

使用 Git 进行版本管理,必须先初始化仓库:

# 新建一个文件夹 git-tutorial
$ mkdir git-tutorial
# 进入新建文件夹的目录下
$ cd git-tutorial
# 初始化仓库
$ git init
Initialized empty Git repository in C:/Users/杨柳/Desktop/git/git/git-tutorial/.git/

如果初始化成功,执行了 git init 命令的目录下就会生成 .git 目录。这个 .git 目录里存储着管理当前目录内容所需的仓库数据。该目录的内容被称为“附属于该仓库的工作树”。文件的编辑等操作在工作树中进行,然后记录到仓库中,以此管理文件的历史快照。

git status——查看仓库的状态

工作树和仓库在被操作的过程中,状态会不断发生变化。在 Git 操作过程中时常用 git status 命令查看当前状态:

$ git status
On branch master

No commits yet

nothing to commit (create/copy files and use "git add" to track)

结果显示了我们当前正处于 master 分支下,接着显示了没有可提交的内容,当前我们建立的这个仓库中还没有记录任何文件的任何状态。

提交(commit): 记录工作树中所有文件的当前状态

这里新建一个 README.md 文件作为管理对象,来进行第一次提交:

$ touch README.md
$ git status
On branch master

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        README.md

nothing added to commit but untracked files present (use "git add" to track)

结果显示在 Untracked files 中显示了 README.md 文件。类似地,只要对 Git 的工作树或仓库进行操作,git status命令的显示结果就会发生变化。

git add——向暂存区中添加文件

如果只是用 Git 仓库的工作树创建了文件,那么该文件并不会被记入 Git 仓库的版本管理对象当中。使用 git status 命令查看 README.md 文件时,它会显示在 Untracked files 里。要想让文件成为 Git 仓库的管理对象,就需要用 git add 命令将其加入暂存区(Stage 或者 Index)中。暂存区是提交之前的一个临时区域。

$ git add README.md
$ git status
On branch master

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
        new file:   README.md

将 README.md 文件加入暂存区后,git status 命令的结果为README.md 文件显示在 Changes to be committed 中

git commit——保存仓库的历史记录

git commit 命令可以将当前暂存区中的文件实际保存到仓库的历史记录中。通过这些记录,我们就可以在工作树中复原文件。

  1. 简洁提交(记录一行提交信息)

    $ git commit -m "First commit"
    [master (root-commit) 03c5685] First commit
     1 file changed, 0 insertions(+), 0 deletions(-)
     create mode 100644 README.md
    

    -m 参数后的 "First commit"称作提交信息,是对此次提交的描述,方便以后查看此次的提交更改的信息

  2. 记述详细提交信息

    直接执行 git commit 命令,不添加参数-m,执行后会自行启动编辑器,并显示下面的结果:

    # Please enter the commit message for your changes. Lines starting
    # with '#' will be ignored, and an empty message aborts the commit.
    # On branch master
    #
    # Initial commit
    #
    # Changes to be committed:
    # (use "git rm --cached <file>..." to unstage)
    #
    # new file: README.md
    #
    
    

    我们就可以自己在编辑器里添加此次的提交信息,一般的格式如下:

    • 第一行:用一行文字简述提交的更改内容
    • 第二行:空行
    • 第三行以后:记述更改的原因和详细内容

    在以 #(井号)标为注释的 Changes to be committed(要提交的更改)栏中,可以查看本次提交中包含的文件,如此次提交包含的文件有 README.md 。提交信息输入完毕,保存并关闭编辑器,然后就会在 Bash 窗口显示提交成功的信息。

  3. 中止提交

    如果在编辑器启动后想中止提交,将提交信息留空并直接关闭编辑器,提交就会被中止

  4. 查看提交后的状态

    提交完之后使用 git status 命令来查看当前状态:

    $ git status
    On branch main
    Your branch is up to date with 'origin/main'.
    
    nothing to commit, working tree clean
    

git log——查看提交日志

git log 命令可以查看以往仓库中提交的日志。包括可以查看什么人在什么时候进行了提交或合并,以及操作前后有怎样的差别:

$ git log
commit b32d1cb72107fae1fdbc6f745add5359c2eae312 (HEAD -> main, origin/main, origin/HEAD)
Author: leishen <liu096221.@gmail.com>
Date:   Mon Dec 25 20:16:32 2023 +0800

    edit hello-world.py

commit 568159c25e014620285fac1414380c98c84a129f
Author: hello-world-tmd <129686384+hello-world-tmd@users.noreply.github.com>
Date:   Mon Dec 25 19:27:12 2023 +0800

    Initial commit

由于当时为了练习不同的简洁和记述详细信息两种提交方式,所以提交了两次,这里也显示了两次提交记录,提交日志是按照时间的倒序排列的,即最近提交的记录在最上面。

commit栏显示的“b32d1……” 是指向这个提交的哈希值,可以通过这个值去查看指定的提交记录;Author栏显示的是我们给Git设置的用户名和邮箱地址;Date栏是提交执行的日期和时间;再往下就是此次提交的提交信息。

  1. 只显示提交信息的第一行

    如果只想让程序显示第一行简述信息,可以在 git log 命令后加上 --pretty=short。这样一下子看到多个提交。

    $ git log --pretty=short
    commit b32d1cb72107fae1fdbc6f745add5359c2eae312 (HEAD -> main, origin/main, origin/HEAD)
    Author: leishen <liu096221.@gmail.com>
    
        edit hello-world.py
    
    commit 568159c25e014620285fac1414380c98c84a129f
    Author: hello-world-tmd <129686384+hello-world-tmd@users.noreply.github.com>
    
        Initial commit
    
  2. 只显示指定目录、文件的提交日志

    只要在 git log命令后加上目录名,便会只显示该目录下的日志。如果加的是文件名,就会只显示与该文件相关的日志

    $ git log README.md
    commit 568159c25e014620285fac1414380c98c84a129f
    Author: hello-world-tmd <129686384+hello-world-tmd@users.noreply.github.com>
    Date:   Mon Dec 25 19:27:12 2023 +0800
    
        Initial commit
    
  3. 显示文件的改动

    如果想查看提交所带来的改动,可以加上- p参数,文件的前后差别就会显示在提交信息之后:

    $ git log -p
    commit b32d1cb72107fae1fdbc6f745add5359c2eae312 (HEAD -> main, origin/main, origin/HEAD)
    Author: leishen <liu096221.@gmail.com>
    Date:   Mon Dec 25 20:16:32 2023 +0800
    
        edit hello-world.py
    
    diff --git a/hello-world.py b/hello-world.py
    new file mode 100644
    index 0000000..5f8dd59
    --- /dev/null
    +++ b/hello-world.py
    @@ -0,0 +1,3 @@
    +# 2023年12月25日20:07:00
    +
    +print("Hello, world!")
    \ No newline at end of file
    
    commit 568159c25e014620285fac1414380c98c84a129f
    Author: hello-world-tmd <129686384+hello-world-tmd@users.noreply.github.com>
    Date:   Mon Dec 25 19:27:12 2023 +0800
    
        Initial commit
    
    diff --git a/README.md b/README.md
    new file mode 100644
    index 0000000..546f010
    --- /dev/null
    +++ b/README.md
    

    如果结果超过一页,则会自动隐藏起来,按一次回车键会显示一行隐藏的结果,出现(End)则显示完全部结果,按q键退出该结果。

git diff——查看更改前后的差别

git diff命令可以查看工作树、暂存区、最新提交之间的差别:

为了更好的演示,我们在刚刚提交的 README.md 文件中的原有内容删除(如果当时创建仓库时选中Add a README file,那 README.md 文件中应该有# git的内容,此处的git是仓库的名称),然后写点东西:

# Git教程
2023年12月27日16:14:49

在 Git Bash 界面运行code README.md命令(用编辑器打开 README.md 文件,如果是其他编辑器,如vim,则运行vim README.md命令),如果没有编辑器,在文件资源管理器中找到这个文件,手动打开它,然后将上面的内容(Markdown语法写的一行题目和内容)加进去。

  1. 查看工作树和暂存区的差别

    运行 git diff 命令,查看当前工作树与暂存区的差别:

    $ git diff
    diff --git a/README.md b/README.md
    index 546f010..12c15d0 100644
    --- a/README.md
    +++ b/README.md
    @@ -1 +1,2 @@
    -# git
    \ No newline at end of file
    +# Git教程
    +2023年12月27日16:21:27
    \ No newline at end of file
    

    我用 ChatGPT 询问了上面结果每一行的解释,如下:

    命令 $ git diff 用于显示自上次提交之后你的文件发生了哪些变化。这个命令生成了一个差异报告,也就是一个“diff”,它详细说明了文件的每一行有何变动。以下是对上述输出的每一行内容的解释:

    1. diff --git a/README.md b/README.md:这行表明 git diff 正在比较同一个文件的两个版本,即文件 README.md 的变动。通常来说,a/README.mdb/README.md 分别表示变动前后的文件状态。
    2. index 546f010..12c15d0 100644:这行显示了文件变化前后的blob对象的SHA-1校验和。546f010 是变化前的校验和,12c15d0 是变化后的校验和。100644 表明文件的权限,这里它表示一个普通文件,其中 644 是UNIX风格的文件权限(可读、可写、不可执行)。
    3. --- a/README.md:这行指出了变动前的文件版本。
    4. +++ b/README.md:这行指出了变动后的文件版本。
    5. @@ -1 +1,2 @@:这是一个hunk头部,它描述了改变发生的位置。-1 表示原始文件的第一行有改变。+1,2 表示改变发生在新文件的第一行和第二行。
    6. -# git:这行以负号(-)开头,表明原来 README.md 文件的第一行 # git 被删除或更改了。
    7. \ No newline at end of file:这表示在变动前,文件 README.md 的最后没有新行符号。
    8. +# Git教程:这行以正号(+)开头,表明在新文件中,第一行已经变为 # Git教程
    9. +2023年12月27日16:21:27:再次以正号(+)开头,表明在新文件中,新增了第二行,内容为 2023年12月27日16:21:27
    10. \ No newline at end of file:这再次表明在变动后,新的 README.md 文件最后也没有新行符号。

    总结来说,这个 git diff 报告表示 README.md 文件从单行的 “# git” 更改为了两行,分别是 “# Git教程” 和 “2023年12月27日16:21:27”,同时在两个版本的文件中都没有在文件的末尾添加新行符号。

    由于我们尚未用 git add命令向暂存区添加任何东西,所以程序 只会显示工作树与最新提交状态之间的差别,用 git add README.md 命令将 README.md 文件加入暂存区

  2. 查看工作树和最新提交的差别

    由于刚刚已经将 README.md 文件加入到暂存区,所以现在执行 git diff 命令不会显示任何结果。要查看工作树与最新提交的差别,可以执行 git diff HEAD 命令:

    $ git diff HEAD
    diff --git a/README.md b/README.md
    index 546f010..12c15d0 100644
    --- a/README.md
    +++ b/README.md
    @@ -1 +1,2 @@
    -# git
    \ No newline at end of file
    +# Git教程
    +2023年12月27日16:21:27
    \ No newline at end of file
    

    上一次提交之后,我们只更改了 README.md 这个文件,所以这个结果和上一个结果是一样的。

    我们不妨养成这样的好习惯:在执行git commit命令之前先执行git diff HEAD命令,查看本次提交与上次提交之间有什么差别,等确认完毕后再进行提交。这里的 HEAD 是指向当前分支中最新一次提交的指针

    运行git commit命令提交更改:

    $ git commit -m "Add index and time"
    [main 9183dc7] Add index and time
     1 file changed, 2 insertions(+), 1 deletion(-)
    

    保险起见,我们查看一下提交日志,确认是否提交成功:

    $ git log
    commit 9183dc7e3157797218d7aa2af62669f7f6d1e6c1 (HEAD -> main)
    Author: leishen <liu096221.@gmail.com>
    Date:   Wed Dec 27 16:57:10 2023 +0800
    
        Add index and time
    
    commit b32d1cb72107fae1fdbc6f745add5359c2eae312 (origin/main, origin/HEAD)
    Author: leishen <liu096221.@gmail.com>
    Date:   Mon Dec 25 20:16:32 2023 +0800
    
        edit hello-world.py
    
    commit 568159c25e014620285fac1414380c98c84a129f
    Author: hello-world-tmd <129686384+hello-world-tmd@users.noreply.github.com>
    Date:   Mon Dec 25 19:27:12 2023 +0800
    
        Initial commit
    

2. 分支的操作

如图 4.1 所示,从 master(以前的Git默认分支名称,现在改为 main,后面如果又看到 main 和 master,没有明确说明外,都是一个东西) 分支创建 feature-A 分支和 fix-B 分支后,每个分支中都拥有自己的最新代码。master 分支是 Git 默认创建的分支,因此基本上所有开发都是以这个分支为中心进行的。

在这里插入图片描述

不同分支中,可以同时进行完全不同的作业。等该分支的作业完成之后再与 master 分支合并。比如 feature-A 分支的作业结束后与 master合并,如图 4.2 所示:

在这里插入图片描述

git branch——显示分支一览表

git branch命令可以将分支名列表显示,同时可以确认当前所在分支:

$ git branch
* main

可以看到 main 分支左侧标有“*”(星号),表示这是我们当前所在的分支。也就是说,我们正在 main 分支下进行开发。结果中没有显示其他分支名,表示本地仓库中只存在 main 一个分支

git chechout -b——创建、切换分支

如果想以当前的 main 分支为基础创建新的分支,我们需要用到git checkout -b命令

  1. 创建并切换到 feature-A 分支并进行提交

    执行下面的命令,创建并切换到 feature-A 分支:

    $ git checkout -b feature-A
    Switched to a new branch 'feature-A'
    

    上面的一条命令等同于下面的两条命令:

    $ git branch feature-A
    $ git checkout feature-A
    

    查看分支列表:

    $ git branch
    * feature-A
      main
    

    结果显示我们在 feature-A 分支下。在这个状态下像正常开发那样修改代码、执行git add命令并进行提交的话,代码就会提交至 feature-A 分支。像这样不断对一个分支(例如 feature-A)进行提交的操作,我们称为“培育分支”。

    实际操作一下。在 README.md 文件中添加一行:

    # Git教程
    2023年12月27日16:21:27
    
    - feature-A
    

    然后进行提交:

    $ git add README.md
    $ git commit -m "Add feature-A"
    [feature-A 3141f4f] Add feature-A
     1 file changed, 3 insertions(+), 1 deletion(-)
    
  2. 切换到 main 分支

    现在我们再来看一看 master 分支有没有受到影响。首先切换至 main 分支:

    $ git checkout main
    Switched to branch 'main'
    Your branch is ahead of 'origin/main' by 1 commit.
      (use "git push" to publish your local commits)
    

    然后查看 README.md 文件(在 Git Bash 窗口用编辑器打开 README.md 文件),会发现 README.md 文件仍然保持原先的状态,并没有被添加文字。feature-A 分支的更改不会影响到 master 分支,这正是在开发中创建分支的优点。只要创建多个分支,就可以在不互相影响的情况下同时进行多个功能的开发。

  3. 切换回上一个分支

    现在,我们再切换回 feature-A 分支:

    $ git checkout -
    Switched to branch 'feature-A'
    

    像上面这样用“-”(连字符)代替分支名,就可以切换至上一个分支。当然,将“-”替换成 feature-A 同样可以切换到 feature-A 分支

特性(Topic)分支

特性分支顾名思义,是集中实现单一特性(主题),除此之外不进行任何作业的分支。在日常开发中,往往会创建数个特性分支,同时在此之外再保留一个随时可以发布软件的稳定分支。稳定分支的角色通常由 master 分支担当(图 4.3)

在这里插入图片描述

之前我们创建了 feature-A 分支,这一分支主要实现 feature-A,除 feature-A 的实现之外不进行任何作业。即便在开发过程中发现了 BUG,也需要再创建新的分支,在新分支中进行修正。

基于特定主题的作业在特性分支中进行,主题完成后再与 master 分支合并。只要保持这样一个开发流程,就能保证 master 分支可以随时供人查看。这样一来,其他开发者也可以放心大胆地从 master 分支创建新的特性分支。

主干分支

主干分支是刚才我们讲解的特性分支的原点,同时也是合并的终点。通常人们会用 master 分支作为主干分支。主干分支中没有开发到一半的代码,可以随时供他人查看

有时我们需要让这个主干分支总是配置在正式环境中,有时又需要用标签 Tag 等创建版本信息,同时管理多个版本发布。拥有多个版本发布时,主干分支也有多个

git merge——合并分支

接下来,我们假设 feature-A 已经实现完毕,想要将它合并到主干分支 main 中。首先切换到 main 分支:

$ git checkout main
Switched to branch 'main'
Your branch is ahead of 'origin/main' by 1 commit.
  (use "git push" to publish your local commits)

然后合并 feature-A 分支。为了在历史记录中明确记录下本次分支合并,我们需要创建合并提交。因此,在合并时加上--no-ff参数:

$ git merge --no-ff feature-A
Merge made by the 'ort' strategy.
 README.md | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

ChatGPT 对上述结果的解释:

这段文本表明已经完成了一次合并(merge)操作,且合并策略使用的是’ort’。每一行的意思如下:

  1. Merge made by the 'ort' strategy.:这表示使用了 ort 合并策略来合并两个分支。ort 代表 “Ostensibly Recursive’s Twin”,这是 Git 用于合并的一个较新策略。它是以经典的 “recursive” 策略为基础,对其作出改进以优化性能和处理一些边缘情况。
  2. README.md | 4 +++-:这是合并结果的一个概要。README.md 是被合并的文件的名字。后面的 4 +++- 指出在这次合并中,README.md 文件总共有 4 行发生了变更,其中 3 行是插入(用 + 表示),1 行是删除(用 - 表示)。
  3. 1 file changed, 3 insertions(+), 1 deletion(-):这是变更的详细统计。它表明在这次合并中,有 1 个文件发生了变更。在这个文件中,有 3 行内容被添加,1 行内容被删除。

这些信息通常在你使用命令行工具进行合并后得到的输出,它提供了一个高度概括的变更报告,有助于快速了解合并操作的结果。

git log --graph——以图表形式查看分支

git log --graph命令进行查看的话,能很清楚地看到特性分支(feature-A)提交的内容已被合并。除此以外,特性分支的创建以及合并也都清楚明了:

$ git log --graph
*   commit e5556cf65ebd02c42fa768506c7d6981c81659e5 (HEAD -> main)
|\  Merge: 9183dc7 3141f4f
| | Author: leishen <liu096221.@gmail.com>
| | Date:   Wed Dec 27 17:59:10 2023 +0800
| |
| |     Merge branch 'feature-A'
| |
| * commit 3141f4fd1d26eaccd513b6a42e2a0ec5c141552c (feature-A)
|/  Author: leishen <liu096221.@gmail.com>
|   Date:   Wed Dec 27 17:29:16 2023 +0800
|
|       Add feature-A
|
* commit 9183dc7e3157797218d7aa2af62669f7f6d1e6c1
| Author: leishen <liu096221.@gmail.com>
| Date:   Wed Dec 27 16:57:10 2023 +0800
|
|     Add index and time
|
* commit b32d1cb72107fae1fdbc6f745add5359c2eae312 (origin/main, origin/HEAD)
| Author: leishen <liu096221.@gmail.com>
| Date:   Mon Dec 25 20:16:32 2023 +0800
|
|     edit hello-world.py
|
* commit 568159c25e014620285fac1414380c98c84a129f
  Author: hello-world-tmd <129686384+hello-world-tmd@users.noreply.github.com>
  Date:   Mon Dec 25 19:27:12 2023 +0800

      Initial commit

git log --graph命令可以用图表形式输出提交日志,非常直观,务必记住

3. 更改提交的操作

git reset——回溯历史版本

Git 的另一特征便是可以灵活操作历史版本。借助分散仓库的优势,可以在不影响其他仓库的前提下对历史版本进行操作。为了熟悉对历史版本的操作,先回溯历史版本,创建一个名为 fix-B 的特性分支(图 4.4)

在这里插入图片描述

  1. 回溯到创建 feature-A 分支前

    让我们先回溯到上一节 feature-A 分支创建之前,创建一个名为 fix-B 的特性分支。要让仓库的 HEAD、暂存区、当前工作树回溯到指定状态,需要用到 git reset --hard命令。只要提供目标时间点的哈希值(在git log中去找到 feature-A 创建之前的状态的哈希值,这里是提交信息为Add index and time的提交的哈希值),就可以完全恢复至该时间点的状态:

    $ git reset --hard 9183dc7e3157797218d7aa2af62669f7f6d1e6c1
    HEAD is now at 9183dc7 Add index and time
    

    已经成功回溯到特性分支(feature-A)创建之前的状态。由于所有文件都回溯到了指定哈希值对应的时间点上,README.md 文件的内容也恢复到了当时的状态。

  2. 创建 fix-B 分支

    创建特性分支(fix-B):

    $ git checkout -b fix-B
    Switched to a new branch 'fix-B'
    

    我们在 README.md 文件中添加一下内容:

    # Git教程
    2023年12月27日16:21:27
    
    - fix-B
    

    然后直接提交 README.md 文件:

    $ git add README.md
    $ git commit -m "Fix B"
    [fix-B 378a45a] Fix B
     1 file changed, 3 insertions(+), 1 deletion(-)
    

    现在的状态如图 4.5 所示。接下来我们的目标是图 4.6 中所示的状态,即主干分支合并 feature-A 分支的修改后,又合并了 fix-B 的修改

    在这里插入图片描述

    在这里插入图片描述

  3. 推进至 feature-A 分支合并后的状态

    首先恢复到 feature-A 分支合并后的状态。不妨称这一操作为“推进历史”

    git log命令只能查看以当前状态为终点的历史日志。所以这里要使用git reflog命令,查看当前仓库的操作日志。在日志中找出回溯历史之前的哈希值,通过git reset --hard命令恢复到回溯历史前的状态:

    $ git reflog
    378a45a (HEAD -> fix-B) HEAD@{0}: commit: Fix B
    9183dc7 (main) HEAD@{1}: checkout: moving from main to fix-B
    9183dc7 (main) HEAD@{2}: reset: moving to 9183dc7e3157797218d7aa2af62669f7f6d1e6c1
    e5556cf HEAD@{3}: merge feature-A: Merge made by the 'ort' strategy.
    9183dc7 (main) HEAD@{4}: checkout: moving from feature-A to main
    3141f4f (feature-A) HEAD@{5}: checkout: moving from main to feature-A
    9183dc7 (main) HEAD@{6}: checkout: moving from feature-A to main
    3141f4f (feature-A) HEAD@{7}: commit: Add feature-A
    9183dc7 (main) HEAD@{8}: checkout: moving from main to feature-A
    9183dc7 (main) HEAD@{9}: commit: Add index and time
    b32d1cb (origin/main, origin/HEAD) HEAD@{10}: commit: edit hello-world.py
    568159c HEAD@{11}: clone: from github.com:hello-world-tmd/git.git
    

    在日志中,我们可以看到 commit、checkout、reset、merge 等 Git 命令的执行记录。只要不进行 Git 的 GC(Garbage Collection,垃圾回收),就可以通过日志随意调取近期的历史状态。即便开发者错误执行了 Git 操作,基本也都可以利用git reflog命令恢复到原先的状态,所以请务必牢记本部分。

    上面执行记录为 “merge feature-A” 的部分为 feature-A 特性分支合并后的状态,对应哈希值为 e5556cf 。我们将 HEAD、暂存区、工作树恢复到这个时间点的状态:

    $ git checkout main
    Switched to branch 'main'
    Your branch is ahead of 'origin/main' by 1 commit.
      (use "git push" to publish your local commits)
    
    $ git reset --hard e5556cf
    HEAD is now at e5556cf Merge branch 'feature-A'
    

    之前我们使用git reset --hard命令回溯了历史,这里又再次通过它恢复到了回溯前的历史状态。当前的状态如图 4.7 所示

    在这里插入图片描述

消除冲突

合并 fix-B 分支,就可以得到我们想要的状态:

$ git merge --no-ff fix-B
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
Automatic merge failed; fix conflicts and then commit the result.

这时,系统告诉我们 README.md 文件发生了冲突(Conflict)。系统在合并 README.md 文件时,feature-A 分支更改的部分与本次想要合并的 fix-B 分支更改的部分发生了冲突。不解决冲突就无法完成合并,所以我们打开 README.md 文件,解决这个冲突。

  1. 查看冲突部分并将其解决

    用编辑器打开 README.md 文件,会发现其内容变成下面这样:

    # Git教程
    2023年12月27日16:21:27
    
    <<<<<<< HEAD
    - feature-A
    =======
    - fix-B
    >>>>>>> fix-B
    

    上面部分是当前 HEAD 的内容,下面部分是要合并的 fix-B 分支中的内容。我们在编辑器中将其改成想要的样子:

    # Git教程
    2023年12月27日16:21:27
    
    - feature-A
    - fix-B
    

    如上所示,本次修正让 feature-A 与 fix-B 的内容并存于文件之中。但是在实际的软件开发中,往往需要删除其中之一,所以在处理冲突时,要仔细分析冲突部分的内容后再行修改

  2. 提交解决后的结果

    冲突解决后,执行git addgit commit命令:

    $ git add README.md
    
    $ git commit -m "Fix conflict"
    [main 29c28ee] Fix conflict
    

    本次更改解决了冲突,我们将提交信息记为"Fix conflict"

git commit --amend——修改提交信息

要修改上一条提交信息,可以使用git commit --amend命令:

我们将上一条提交信息修改为 “Merge branch ‘fix-B’” :

$ git commit --amend

执行完上面的命令,编辑器会自动启动,显示下面的内容:

Fix conflict

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Wed Dec 27 20:56:39 2023 +0800
#
# On branch main
# Your branch is ahead of 'origin/main' by 5 commits.
#   (use "git push" to publish your local commits)
#
# Changes to be committed:
#	modified:   README.md
#

将提交信息的部分修改为 Merge branch ‘fix-B’ ,然后保存文件,关闭编辑器。显示下面的结果,代表提交信息已被更改:

[main 484c488] Merge branch 'fix-B'
 Date: Wed Dec 27 20:56:39 2023 +0800

**如果按照上面步骤,显示的结果是[main ec85e63] Fix conflict,则未修改,那么可能是由于编辑器的原因,我们可以使用git commit --amend "New commit message"跳过编辑器来修改提交信息。

执行git log --graph命令可以看到提交日志中的相应内容也已经被修改:

$ git log --graph
*   commit 484c488f28c9dadb1c12243722053a8f35c99cb8 (HEAD -> main)
|\  Merge: e5556cf 378a45a
| | Author: leishen <liu096221.@gmail.com>
| | Date:   Wed Dec 27 20:56:39 2023 +0800
| |
| |     Merge branch 'fix-B'
| |
| * commit 378a45a67280af488b8e6944001af4a865f41443 (fix-B)
| | Author: leishen <liu096221.@gmail.com>
| | Date:   Wed Dec 27 18:27:06 2023 +0800
| |
| |     Fix B
| |
* |   commit e5556cf65ebd02c42fa768506c7d6981c81659e5
|\ \  Merge: 9183dc7 3141f4f
| |/  Author: leishen <liu096221.@gmail.com>
|/|   Date:   Wed Dec 27 17:59:10 2023 +0800
| |
| |       Merge branch 'feature-A'
| |
| * commit 3141f4fd1d26eaccd513b6a42e2a0ec5c141552c (feature-A)
|/  Author: leishen <liu096221.@gmail.com>
|   Date:   Wed Dec 27 17:29:16 2023 +0800
|
|       Add feature-A
|
* commit 9183dc7e3157797218d7aa2af62669f7f6d1e6c1
| Author: leishen <liu096221.@gmail.com>
| Date:   Wed Dec 27 16:57:10 2023 +0800
|
|     Add index and time
|
* commit b32d1cb72107fae1fdbc6f745add5359c2eae312 (origin/main, origin/HEAD)
| Author: leishen <liu096221.@gmail.com>
| Date:   Mon Dec 25 20:16:32 2023 +0800
|
|     edit hello-world.py
|
* commit 568159c25e014620285fac1414380c98c84a129f
  Author: hello-world-tmd <129686384+hello-world-tmd@users.noreply.github.com>
  Date:   Mon Dec 25 19:27:12 2023 +0800

      Initial commit

git rebase -i——压缩历史

在合并特性分支之前,如果发现已提交的内容中有些许拼写错误等,我们可以提交一个修改,然后将这个修改包含到前一个提交之中,压缩成一个历史记录。

  1. 创建 feature-C 分支

    首先,新建一个 feature-C 特性分支:

    $ git checkout -b feature-C
    Switched to a new branch 'feature-C'
    

    作为 feature-C 的功能实现,我们在 README.md 文件中添加一行文字,并且故意留下拼写错误,以便之后修正:

    # Git教程
    2023年12月27日16:21:27
    
    - feature-A
    - fix-B
    - faeture-C
    

    提交这部分内容,使用git commit -am命令可以一次完成git addgit commit两步操作:

    $ git commit -am "Add feature-C"
    [feature-C ac66615] Add feature-C
     1 file changed, 2 insertions(+), 1 deletion(-)
    
  2. 修正拼写错误

    打开编辑器,将 README.md 文件中的 “faeture-C” 修改为 “feature-C”,执行git diff命令查看修改后的差别:

    $ git diff
    diff --git a/README.md b/README.md
    index 46c6cd3..56b7552 100644
    --- a/README.md
    +++ b/README.md
    @@ -3,4 +3,4 @@
    
     - feature-A
     - fix-B
    -- faeture-C
    \ No newline at end of file
    +- feature-C
    \ No newline at end of file
    

    然后提交,错字漏字等失误称作 “typo” :

    $ git commit -am "Fix typo"
    [feature-C 12c9096] Fix typo
     1 file changed, 1 insertion(+), 1 deletion(-)
    

    实际上,我们不希望在历史记录中看到这类提交,因为健全的历史记录并不需要它们。如果能在最初提交之前就发现并修正这些错误,也就不会出现这类提交了。

  3. 更改历史

    因此,我们来更改历史。将" Fix typo"修正的内容与之前一次的提交合并,在历史记录中合并为一次完美的提交。为此,我们要用到git rebase命令:

    $ git rebase -i HEAD~2
    

    上述命令,可以选定当前分支中包含 HEAD(最新提交)在内的两个最新历史记录为对象,并在编辑器中打开:

    pick ac66615 Add feature-C
    pick 12c9096 Fix typo
    
    # Rebase 484c488..12c9096 onto 484c488 (2 commands)
    #
    # Commands:
    # p, pick <commit> = use commit
    # r, reword <commit> = use commit, but edit the commit message
    # e, edit <commit> = use commit, but stop for amending
    # s, squash <commit> = use commit, but meld into previous commit
    # f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
    #                    commit's log message, unless -C is used, in which case
    #                    keep only this commit's message; -c is same as -C but
    #                    opens the editor
    # x, exec <command> = run command (the rest of the line) using shell
    # b, break = stop here (continue rebase later with 'git rebase --continue')
    # d, drop <commit> = remove commit
    # l, label <label> = label current HEAD with a name
    # t, reset <label> = reset HEAD to a label
    # m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
    #         create a merge commit using the original merge commit's
    #         message (or the oneline, if no original merge commit was
    #         specified); use -c <commit> to reword the commit message
    # u, update-ref <ref> = track a placeholder for the <ref> to be updated
    #                       to this position in the new commits. The <ref> is
    #                       updated at the end of the rebase
    #
    # These lines can be re-ordered; they are executed from top to bottom.
    #
    # If you remove a line here THAT COMMIT WILL BE LOST.
    #
    # However, if you remove everything, the rebase will be aborted.
    #
    

    我们将 12c9096 的 Fix typo 的历史记录压缩到 ac66615 的 Add feature-C 里。按照下面,将 12c9096 左侧的 pick 部分删除,改写为 fixup :

    pick ac66615 Add feature-C
    fixup 12c9096 Fix typo
    

    保存编辑器里的内容,关闭编辑器,Git Bash 显示:

    Successfully rebased and updated refs/heads/feature-C.
    

    查看提交日志:

    这里不知道为什么,我的已经显示了Successfully rebased and updated refs/heads/feature-C.,并且 .git 文件夹中的 git-rebase-todo 文件已经更改为 fixup 12c9096 Fix typo 但在查看提交日志的时候还是和以前一样,并没有将此次提交信息删除,原因我目前还没有找到,等找到之后在来写上。

    下面是正确的结果,我在书中截取的,跟我自己操作的有些哈希值和内容有些差别,但不影响观察到正确的结果:

    在这里插入图片描述

    这样一来,Fix typo 就从历史中被抹去,也就相当于 Add feature-C 中从来没有出现过拼写错误。算是一种良性的历史改写

  4. 合并到 main 分支

    feature-C 分支的使命告一段落,我们将它与 main 分支合并:

    $ git checkout main
    Switched to branch 'main'
    Your branch is ahead of 'origin/main' by 8 commits.
      (use "git push" to publish your local commits)
    
    $ git merge --no-ff feature-C
    Merge made by the 'recursive' strategy.
     README.md | 1 +
     1 file changed, 1 insertion(+)
    

    main 分支整合了 feature-C 分支。

4. 推送至远程仓库

Git 是分散型版本管理系统,前面所学习的,都是针对单一本地仓库的操作。下面尝试将其余远程仓库建立联系。远程仓库是与我们本地仓库相对独立的另一个仓库,所以我们先在GitHub上新建一个仓库。

为防止与其他仓库混淆,将仓库名设为 git_remote 。创建时请不要勾选 Initialize this repository with a README 选项。因为勾选了该选项,GitHub 一侧的仓库就会自动生成 README.md 文件,与本地仓库失去了整合性。虽然到时也可以强制覆盖,但还是建议不要勾选该选项。

git remote add——添加远程仓库

在 GitHub 上创建的仓库路径为 “git@github.com:用户名 / git-remote.git”。现在我们用git remote add命令将它设置成本地仓库的远程仓库:

$ git remote add new_remote git@github.com:hello-world-tmd/git_remote.git

现在的 Git 在 clone 仓库的时候会自动将此仓库设为名为 origin 的远程仓库,所以,我们此处将 git_remote 这个远程仓库命名为 new_remote。

既然已经有了远程仓库,那还这样操作有什么意义呢? 当你没有 clone 仓库的时候,即你只是在本地写的文档代码,那么你可以通过该操作,将写的这些文档代码上传到远程仓库中

git push——推送至远程仓库

  1. 推送至 main 分支

    git push命令可以将当前分支下的本地仓库内容推送给远程仓库,现在我们在 main 分支下进行:

    $ git push -u new_remote main
    Enumerating objects: 26, done.
    Counting objects: 100% (26/26), done.
    Delta compression using up to 16 threads
    Compressing objects: 100% (22/22), done.
    Writing objects: 100% (26/26), 2.66 KiB | 1.33 MiB/s, done.
    Total 26 (delta 7), reused 3 (delta 0), pack-reused 0
    remote: Resolving deltas: 100% (7/7), done.
    To github.com:hello-world-tmd/git_remote.git
     * [new branch]      main -> main
    branch 'main' set up to track 'new_remote/main'.
    

    像这样执行git push命令,当前分支的内容就会被推送给远程仓库 origin 的 master 分支。-u参数可以在推送的同时,将 new_remote 仓库的 main 分支设置为本地仓库当前分支的 upstream(上游)。添加了这个参数,将来运行git pull命令从远程仓库获取内容时,本地仓库的这个分支就可以直接从 new_remote 的 main 分支获取内容,省去了另外添加参数的麻烦。

    执行该操作后,当前本地仓库 main 分支的内容将会被推送到 GitHub 的远程仓库中。在 GitHub 上也可以确认远程 main 分支的内容和本地 main 分支相同。

  2. 推送至 main 以外的分支

    除了 main 分支之外,远程仓库也可以创建其他分支。举个例子,我们在本地仓库中创建 feature-D 分支,并将它以同名形式 push 至远程仓库:

    $ git checkout -b feature-D
    Switched to a new branch 'feature-D'
    
    $ git push -u new_remote feature-D
    Total 0 (delta 0), reused 0 (delta 0), pack-reused 0
    remote:
    remote: Create a pull request for 'feature-D' on GitHub by visiting:
    remote:      https://github.com/hello-world-tmd/git_remote/pull/new/feature-D
    remote:
    To github.com:hello-world-tmd/git_remote.git
     * [new branch]      feature-D -> feature-D
    branch 'feature-D' set up to track 'new_remote/feature-D'.
    

    现在,在远程仓库的 GitHub 页面就可以查看到 feature-D 分支了

5. 从远程仓库获取

假如现在有一名新开发者来共同开发,他需要在他的电脑上获取远程仓库的内容进行开发,先新建一个文件夹,我们在另一个目录下新建一个文件夹

git clone——获取远程仓库

  1. 获取远程仓库

    先换到刚新建的文件夹目录下,右击文件夹,单击 Open Git Bash here 打开 Bash 窗口,将 GitHub 上的仓库 clone 到本地,注意不要与之前操作的仓库在同一目录下:

    $ git clone git@github.com:hello-world-tmd/git_remote.git
    Cloning into 'git_remote'...
    remote: Enumerating objects: 26, done.
    remote: Counting objects: 100% (26/26), done.
    remote: Compressing objects: 100% (15/15), done.
    remote: Total 26 (delta 7), reused 26 (delta 7), pack-reused 0
    Receiving objects: 100% (26/26), done.
    Resolving deltas: 100% (7/7), done.
    
    $ cd git_remote
    

    执行git clone命令后我们会默认处于 main(以前是master,前面有说过) 分支下,同时系统 会自动将 origin 设置成该远程仓库的标识符。也就是说,当前本地仓库的 main 分支与 GitHub 端远程仓库(origin)的 main 分支在内容上是完全相同的

    我们可以用git branch -a命令查看当前分支的相关信息,参数-a可以同时显示本地仓库和远程仓库的分支信息:

    $ git branch -a
    * main
      remotes/origin/HEAD -> origin/main
      remotes/origin/feature-D
      remotes/origin/main
    

    结果显示了 remotes/origin/feature-D ,证明远程仓库中已经有了 feature-D 分支

  2. 获取远程的 feature-D 分支

    将 feature-D 分支获取至本地仓库:

    $ git checkout -b feature-D origin/feature-D
    Switched to a new branch 'feature-D'
    branch 'feature-D' set up to track 'origin/feature-D'.
    

    - b参数的后面是本地仓库中新建分支的名称。为了便于理解,我们仍将其命名为 feature-D,让它与远程仓库的对应分支保持同名。新建分支名称后面是获取来源的分支名称。例子中指定了 origin/feature-D,就是说以名为 origin 的仓库(这里指 GitHub 端的仓库)的 feature-D 分支为来源,在本地仓库中创建 feature-D 分支

  3. 向本地的 feature-D 分支提交更改

    在新 clone 的这个本地仓库中的 README.md 文件中添加- feature-D的内容,查看更改:

    $ git diff
    diff --git a/README.md b/README.md
    index 56b7552..1da1c68 100644
    --- a/README.md
    +++ b/README.md
    @@ -3,4 +3,5 @@
    
     - feature-A
     - fix-B
    -- feature-C
    \ No newline at end of file
    +- feature-C
    +- feature-D
    \ No newline at end of file
    

    提交:

    $ git commit -am "Add feature-D"
    [feature-D c7effbd] Add feature-D
     1 file changed, 2 insertions(+), 1 deletion(-)
    
  4. 推送 feature-D 分支

    $ git push
    Enumerating objects: 5, done.
    Counting objects: 100% (5/5), done.
    Delta compression using up to 16 threads
    Compressing objects: 100% (3/3), done.
    Writing objects: 100% (3/3), 319 bytes | 319.00 KiB/s, done.
    Total 3 (delta 1), reused 0 (delta 0), pack-reused 0
    remote: Resolving deltas: 100% (1/1), completed with 1 local object.
    To github.com:hello-world-tmd/git_remote.git
       9b25faa..c7effbd  feature-D -> feature-D
    

git pull——获取最新的远程仓库分支

回到原先的目录下,原先的仓库中只创建了 feature-D 分支,并没有在该分支下进行任何提交。而远程仓库的 feature-D 分支中已经有了我们刚刚推送的提交。这时我们就可以使用git pull命令,将本地的 feature-D 分支更新到最新状态。当前分支为 feature-D 分支:

$ git branch
  feature-A
  feature-C
* feature-D
  fix-B
  main

$ git pull new_remote feature-D
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 1), reused 3 (delta 1), pack-reused 0
Unpacking objects: 100% (3/3), 299 bytes | 13.00 KiB/s, done.
From github.com:hello-world-tmd/git_remote
 * branch            feature-D  -> FETCH_HEAD
   9b25faa..c7effbd  feature-D  -> new_remote/feature-D
Updating 9b25faa..c7effbd
Fast-forward
 README.md | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

GitHub 端远程仓库中的 feature-D 分支是最新状态,所以本地仓库中的 feature-D 分支就得到了更新。今后只需要像平常一样在本地进行提交再 push 给远程仓库,就可以与其他开发者同时在同一个分支中进行作业,不断给 feature-D 增加新功能。

如果两人同时修改了同一部分的源代码,push 时就很容易发生冲突。所以多名开发者在同一个分支中进行作业时,为减少冲突情况的发生,建议更频繁地进行 push 和 pull 操作

6. Git资料

  • [Pro Git](Git - Book (git-scm.com)):GitHub公司的 Scott Chacon 写的一部零基础(我不觉得是零基础啊,也可能是我比较菜,反而是这本《GitHub入门与实践》更符合我的口味)的 Git 书籍(开源)
  • Learn Git Branching:学习Git基本操作的网站,有点像小游戏,把上面的内容看完可以通关一下,加强记忆
  • Git - GitHub 文档:文档就不多说了

7. .gitignore的使用介绍

本小节是自己当时没学懂,以为不重要,但是后来自己使用 Git 的时候,发现这个很重要,因为我用 VScode 写代码,VScode 的设置文件.vscode会在你更改设置之后就出现,但是,在将代码推送到远程仓库的时候,也不能将.vscode文件也给推送过去吧,所以就找了些资料,然后了解了部分,做一下记录

7.1 什么是.gitignore?

我们在使用 Git 的时候,会有部分文件不想、同时也不需要将它添加到版本控制中,就比如 VScode 的配置文件.vscode,或者是 Git 的配置文件.git(准确说,.vscode.git都是文件夹,但文件也是一样)。这个时候我们就可以创建一个.gitignore文件,将我们不想添加到版本控制的文件或文件夹以指定的语法规则添加到.gitignore文件里面,然后使用git add .gitignore命令,就可以忽略这些文件了。

注意,这里的忽略是指在运行git add命令的时候,可以将他们忽略。如果你已经将其添加到版本控制中了,那就不行了,解决办法往后看。

7.2 .gitignore使用方法

此处以我当时遇到的问题进行描述。

先说我当时遇到的问题:我在本地 clone 了一个仓库(名称为DataStructure),然后在仓库目录下新建了一个文件夹 chapter1 ,在 chapter1 文件夹中新建了一个 .c 文件,然后对 VScode 进行了配置,结果在 chapter1 文件夹中出现了 .vscode 文件夹,我想要在git add的时候不将 .vscode 文件夹添加到暂存区,或者说我不想把 .vscode 文件夹同步到远程仓库,于是我进行了一下操作:

  1. 打开 Git Bash ,并转到本地仓库的目录,即包含 chapter1 文件夹的目录

  2. 如果不存在.gitignore文件,则新建一个.gitignore文件:

    touch .gitignore
    
  3. 使用文本编辑器打开 .gitignore 文件,随便什么都可以,也可以直接在文件资源管理器双击该文件,选择用记事本打开。这里我们通过 Bash 用记事本打开:

    notepad .gitignore
    
  4. .gitignore 文件中添加以下内容:

    # 只忽略 chapter1 文件夹中的 .vscode 文件夹
    chapter1/.vscode/
    
    # 如果你以后还会创建 chapter2 ,该文件夹中的 .vscode 你也想忽略,那你可以添加以下内容
    */.vscode/
    
  5. 保存并关闭 .gitignore 文件。

  6. .gitignore 文件的更改添加到 Git 中:

    git add .gitignore
    
  7. 提交更改:

    git commit -m "Add .vscode to .gitignore"
    

现在,.vscode 文件夹和它的内容将会被 Git 忽略,即不会被包含到版本控制中。如果之前已经跟踪了 .vscode 文件夹(即对.vsccde文件夹或者包含.vscode文件夹的父目录执行了git add命令,也就是前面所说的问题),需要先从 Git 仓库中移除它:

git rm -r --cached chapter1/.vscode
git commit -m "Remove .vscode from version control"

使用 --cached 选项会将 .vscode 文件夹从 Git 跟踪中移除,但它还会保留在你的本地文件系统中。然后你可以提交这个更改,这样 .vscode 文件夹就完全从版本控制中移除了。

7.3 .gitignore语法规则

  1. 忽略特定文件

    # 忽略配置文件
    config.json
    
  2. 忽略特定类型的文件

    # 忽略所有.txt文件
    *.txt
    
  3. 忽略除特定文件之外的所有文件

    # 忽略所有文件
    *
    # 但不忽略README.md
    !README.md
    
  4. 忽略特定目录下的文件

    # 忽略logs目录下的所有文件
    logs/
    # 忽略bin目录及其子目录下的所有 .o 和 .a 文件
    bin/**/*.o
    bin/**/*.a
    
  5. 注释

    # 这是一个注释
    
  6. 忽略特定模式以外的文件或目录

    # 忽略所有文件
    *
    # 但不忽略 .md 文件和 doc/ 目录
    !*.md
    !doc/
    
  7. 忽略所有文件夹

    # 忽略子目录下的所有 .vscode 文件夹
    */.vscode/
    
    # 忽略所有层级的 .vscode 文件夹
    **/.vscode/
    

re` 文件。

  1. .gitignore 文件的更改添加到 Git 中:

    git add .gitignore
    
  2. 提交更改:

    git commit -m "Add .vscode to .gitignore"
    

现在,.vscode 文件夹和它的内容将会被 Git 忽略,即不会被包含到版本控制中。如果之前已经跟踪了 .vscode 文件夹(即对.vsccde文件夹或者包含.vscode文件夹的父目录执行了git add命令,也就是前面所说的问题),需要先从 Git 仓库中移除它:

git rm -r --cached chapter1/.vscode
git commit -m "Remove .vscode from version control"

使用 --cached 选项会将 .vscode 文件夹从 Git 跟踪中移除,但它还会保留在你的本地文件系统中。然后你可以提交这个更改,这样 .vscode 文件夹就完全从版本控制中移除了。

7.3 .gitignore语法规则

  1. 忽略特定文件

    # 忽略配置文件
    config.json
    
  2. 忽略特定类型的文件

    # 忽略所有.txt文件
    *.txt
    
  3. 忽略除特定文件之外的所有文件

    # 忽略所有文件
    *
    # 但不忽略README.md
    !README.md
    
  4. 忽略特定目录下的文件

    # 忽略logs目录下的所有文件
    logs/
    # 忽略bin目录及其子目录下的所有 .o 和 .a 文件
    bin/**/*.o
    bin/**/*.a
    
  5. 注释

    # 这是一个注释
    
  6. 忽略特定模式以外的文件或目录

    # 忽略所有文件
    *
    # 但不忽略 .md 文件和 doc/ 目录
    !*.md
    !doc/
    
  7. 忽略所有文件夹

    # 忽略子目录下的所有 .vscode 文件夹
    */.vscode/
    
    # 忽略所有层级的 .vscode 文件夹
    **/.vscode/
    
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值