Git 基本操作

Git 基础

Git 的安装与配置

这里以 CentOS 8 举例。

首先使用 yum 安装 Git

1
sudo yum install git

检查是否安装成功并查看版本号

1
git --version

接着开始配置用户信息

1
2
3
4
5
# 配置用户名
git config --global user.name "yourname"

# 配置邮箱
git config --global user.email "yourname@email.com"

接着列出所有配置,查看是否配置成功

1
git config --list

获取仓库

获取 Git 项目仓库的方法有两种:

  1. 在现有项目或目录下导入所有文件到 Git 中;
  2. 从一个服务器克隆一个现有的 Git 仓库。

初始化新仓库

如果打算使用当前已有的项目来初始化一个新仓库,你只需要进入该项目目录并输入:

1
git init

该命令将创建一个名为 .git 的子目录,这个子目录含有你初始化的 Git 仓库中所有的必须文件。接下来使用 git add 来跟踪已有的文件,再使用 git commit 来进行提交,就可以完成当前仓库的构建。

克隆已有仓库

如果想获得一份已经存在了的 Git 仓库的拷贝,可以使用下面这个命令:

1
git clone [url] (可选,本地仓库名称)

其会在当前目录下创建一个与项目同名(默认)的目录,并在这个目录下初始化一个 .git 文件夹,从远程仓库拉取下所有数据放入 .git 文件夹,然后从中读取最新版本的文件的拷贝。此时所有的项目文件已经存放在本地仓库中,准备就绪等待后续的开发和使用。

Git 克隆的是该 Git 仓库服务器上的几乎所有数据,而不是仅仅复制完成你的工作所需要文件。 当你执行 git clone 命令的时候,默认配置下远程 Git 仓库中的每一个文件的每一个版本都将被拉取下来。

记录更新至仓库

查看文件状态

当我们要查看文件的状态时,可以使用 git status 命令。例如:

1
2
3
4
5
6
7
8
9
$ git status
On branch test
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   test.cpp

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

我们可以看到,其说明了我们当前所处的分支,以及展示了当前未被跟踪的文件,以及暂存区中待提交的文件。

我们还可以在后面加上 -s 或者 --short 参数来使输出更加简洁

1
2
3
4
$ git status -s
 M README.md
A  test.cpp
?? test2.cpp

左边的状态码的含义如下;

  • 新添加的未跟踪文件前面有 ?? 标记。

  • 新添加到暂存区中的文件前面有 A 标记。

  • 修改过的文件前面有 M 标记。出现在右边的 M 表示该文件被修改了但是还没放入暂存区,出现在靠左边的 M 表示该文件被修改了并放入了暂存区。

跟踪新文件

git add 是个多功能命令:可以用它开始跟踪新文件,或者把已跟踪的文件放到暂存区,还能用于合并时把有冲突的文件标记为已解决状态等。

我们可以使用命令 git add 来跟踪一个文件,例如:

1
git add test.cpp

git add 命令使用文件或目录的路径作为参数;如果参数是目录的路径,该命令将递归地跟踪该目录下的所有文件。

此时用 git status 就可以看到文件已经被跟踪,并放入暂存区中:

1
2
3
4
5
$ git status
On branch test
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   test.cpp

暂存已修改文件

如果我们修改了一个已经被跟踪的文件,此时它的状态如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$ git status
On branch test
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   test.cpp

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   README.md

此时文件内容发生了变化,但是还没有被放入暂存区中,此时就需要我们使用 git add 将其添加到暂存区

1
2
3
4
5
6
7
$ git add README.md
$ git status
On branch test
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   README.md
        new file:   test.cpp

此时文件已被加入到暂存区中,在下次使用 git commit 提交时就会被添加到仓库。但是,如果我们此时修改一个暂存区中的文件,会怎么样呢?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ git status
On branch test
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   README.md
        new file:   test.cpp

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   README.md

此时看起来像是文件同时出现在了暂存区和非暂存区,但事实是这样吗?答案是否定的,git 中有版本的概念,此时存在暂存区中的是我们上一次提交的那个版本,而此时在非暂存区中的即是最新的版本。

此时,我们就需要再次使用 git add 来将最新的版本暂存起来

1
2
3
4
5
6
7
$ git add README.md
$ git status
On branch test
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   README.md
        new file:   test.cpp

忽略文件

文件 .gitignore 的格式规范如下:

  • 所有空行或者以 开头的行都会被 Git 忽略。
  • 可以使用标准的 glob 模式匹配。
  • 匹配模式可以以(/)开头防止递归。
  • 匹配模式可以以(/)结尾指定目录。
  • 要忽略指定模式以外的文件或目录,可以在模式前加上惊叹号(!)取反。

glob 模式是指 shell 所使用的简化了的正则表达式。

  1. 星号(*)匹配零个或多个任意字符;
  2. [abc] 匹配任何一个列在方括号中的字符;
  3. 问号(?)只匹配一个任意字符;
  4. 如果在方括号中使用短划线分隔两个字符,表示所有在这两个字符范围内的都可以匹配(比如 [0-9] 表示匹配所有 0 到 9 的数字)。
  5. 使用两个星号(*) 表示匹配任意中间目录,比如a/**/z 可以匹配 a/z, a/b/za/b/c/z等。

例如一个 C++ 的例子:

 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
# Prerequisites
*.d

# Compiled Object files
*.slo
*.lo
*.o
*.obj

# Precompiled Headers
*.gch
*.pch

# Compiled Dynamic libraries
*.so
*.dylib
*.dll

# Fortran module files
*.mod
*.smod

# Compiled Static libraries
*.lai
*.la
*.a
*.lib

# Executables
*.exe
*.out
*.app

GitHub 有一个十分详细的针对数十种项目及语言的 .gitignore 文件列表:https://github.com/github/gitignore 。

差异比较

git diff 用于比较已写入暂存区和已经被修改但尚未写入暂存区文件的区别。

例如此时我们修改了一个已写入暂存区的文件,此时结果如下:

1
2
3
4
5
6
7
8
$ git diff
diff --git a/README.md b/README.md
index d0e7d60..a261b14 100644
--- a/README.md
+++ b/README.md
@@ -1 +1 @@
-HELLO WORLD
+HELLO WORLD !!!

如果要查看已暂存的将要添加到下次提交里的内容,可以用下面这些命令:

1
2
3
git diff --cached	   // 老版本
或者
git diff --staged      // 新版本

请注意,git diff 本身只显示尚未暂存的改动,而不是自上次提交以来所做的所有改动。

提交更新

如果暂存区中的内容已经准备就绪了,此时就可以使用 git commit 来提交更新,例如:

1
2
$ git commit -m "commit"
# -m 用于将提交信息与命令放在同一行

提交完成后,它会告诉你当前是在哪个分支提交的,本次提交的完整 SHA-1 校验和是什么,以及在本次提交中,有多少文件修订过,多少行添加和删改过。

1
2
3
[test da8f5f4] commit
 1 file changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 test.md

跳过使用暂存区域

如果觉得每次提交前都要将已跟踪的文件存入暂存区过于麻烦,可以为 git commit 加上一个 -a 参数,例如:

1
2
3
4
5
6
7
$ git commit -a -m README.md
[test fa38827] README.md
 1 file changed, 1 insertion(+)
 
 $ git status
On branch test
nothing to commit, working tree clean

此时 Git 就会自动把所有已经跟踪过的文件暂存起来一并提交,从而跳过 git add 步骤。

删除文件

如果我们想要从 Git 中删除一个文件,就必须要将其从已跟踪文件清单移除,然后 commit 提交。

我们可以用 git rm 命令完成此项工作,并连带从工作目录中删除指定的文件,这样以后就不会出现在未跟踪文件清单中了。

1
2
3
4
5
6
7
8
$ git rm test.md
rm 'test.md'

$ git status
On branch test
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        deleted:    test.md

如果我们直接删除文件,会怎么样呢?

1
2
3
4
5
6
7
8
9
$ rm test.md
$ git status
On branch test
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        deleted:    test.md

no changes added to commit (use "git add" and/or "git commit -a")

此时这个操作会被加入未暂存清单中,我们仍然需要使用 git rm 来进行删除。

如果只是想从仓库中删除文件(或者暂存区),而不想在本地删除,那该怎么做呢?

此时可以使用 –cached 参数:

1
git rm --cached [file]

如果在删除前,这个文件就已经在暂存区中,那该怎么办呢?

1
2
3
4
5
$ git status
On branch test
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   test.md

此时文件已经存储在暂存区了,我们尝试使用 git rm 删除它:

1
2
3
4
$ git rm test.md
error: the following file has changes staged in the index:
    test.md
(use --cached to keep the file, or -f to force removal)

此时我们发现其无法直接被删除,原因是 Git 为了防止误删还没有添加到快照中的数据(这种数据无法被 Git 恢复),为其添加了安全策略,需要使用强制删除的选项 -f 才行。

1
git rm -rf test.md 

移动/重命名文件

当我们想要将一个文件移动到其他目录时,可以使用下面命令:

1
git mv [源文件] [新路径]

同样,我们也可以使用 git mv 来完成文件的重命名,例如:

1
git mv [原文件名] [新文件名]

那它是如何做到既能移动,又能够重命名呢?其实运行 git mv 就相当于运行了下面三条命令:

1
2
3
mv [原文件名] [新文件名]
git rm [原文件名]
git add [新文件名]

此操作必须要在暂存区或者文件commit之后才能进行

查看提交记录

当我们想要查看提交记录时,可以使用 git log 命令,例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ git log
commit af1248beeb9de2b58d339441c5fccc2cbd652374 (HEAD -> master, origin/master, origin/HEAD)
Author: HONGYU-LEE <756687451@qq.com>
Date:   Mon Apr 11 14:43:39 2022 +0800

    update

commit 1801c1003b9250e7854eec43dd53c4974b5b04b2
Author: HONGYU-LEE <756687451@qq.com>
Date:   Sat Apr 9 18:13:42 2022 +0800

    C++ 20 done

commit 11ed22aab5099608427492711c560a2c967ba7d3
Author: HONGYU-LEE <756687451@qq.com>
Date:   Fri Apr 8 21:34:39 2022 +0800

    Cpp 17 done 20 doing

commit 9d519fa0ada5991fc8791012d98c736704ee3b68
Author: HONGYU-LEE <756687451@qq.com>
Date:   Thu Apr 7 21:49:42 2022 +0800

我们可以看到,其默认以提交时间降序排序,同时列出了每个提交的 SHA-1 校验和、作者和邮箱、提交时间以及提交说明。

其常用选项如下:

选项 说明
-p 按补丁格式显示每个更新之间的差异。
–stat 显示每次更新的文件修改统计信息。
–shortstat 只显示 –stat 中最后的行数修改添加移除统计。
–name-only 仅在提交信息后显示已修改的文件清单。
–name-status 显示新增、修改、删除的文件清单。
–abbrev-commit 仅显示 SHA-1 的前几个字符,而非所有的 40 个字符。
–relative-date 使用较短的相对时间显示(比如,“2 weeks ago”)。
–graph 显示 ASCII 图形表示的分支合并历史。
–pretty 使用其他格式显示历史提交信息。可用的选项包括 oneline,short,full,fuller 和 format(后跟指定格式)。

其常用的限制输出的参数如下:

选项 说明
-(n) 仅显示最近的 n 条提交
–since, –after 仅显示指定时间之后的提交。
–until, –before 仅显示指定时间之前的提交。
–author 仅显示指定作者相关的提交。
–committer 仅显示指定提交者相关的提交。
–grep 仅显示含指定关键字的提交
-S 仅显示添加或移除了某个关键字的提交

撤销操作

补充文件/信息

如果我们已经 git commit 提交之后,才发现可能有几个文件忘记提交,又或者是提交的信息写错了。这时可以使用下列命令来尝试重新提交

1
git commit --amend

这个命令会将暂存区中的文件提交。 如果自上次提交以来你还未做任何修改,那么快照会保持不变,而你所修改的只是提交信息。而如果进行了修改,则第二次提交将代替第一次提交的结果。

撤销暂存的文件

如果我们不小心使用 git add 将不必要的文件加入暂存区,如何将这些文件撤销呢?这时候就要用到下述命令:

1
2
3
git reset HEAD <file>...	   // 老版本
或者
git restore --staged <file>... // 新版本

在调用时加上 –hard 选项会令 git reset 成为一个危险的命令,即可能导致工作目录中所有当前进度丢失。

撤销对文件的修改

当我们对一个文件进行修改后,如果不满意刚才的修改,像将其回滚到上次提交的状态时,可以使用如下命令:

1
2
3
git checkout -- <file>...	   // 老版本
或者
git restore <file>... 		   // 新版本

git checkout -- [file] 是一个危险的命令。 你对那个文件做的任何修改都会消失, 除非确定不想要那个文件,否则不要使用这个命令。

远程仓库

查看远程仓库

如果想查看你已经配置的远程仓库服务器,可以运行 git remote 命令。 它会列出你指定的每一个远程服务器的简写。也可以指定选项 -v,会显示需要读写远程仓库使用的 Git 保存的简写与其对应的 URL。例如:

1
2
3
$ git remote -v
origin  https://gitee.com/HONGYU-LEE/git-tes.git (fetch)
origin  https://gitee.com/HONGYU-LEE/git-tes.git (push)

如果还想要查看某一个远程仓库的更多信息,可以使用 git remote show [remote-name] 命令。例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ git remote show origin
* remote origin
  Fetch URL: https://gitee.com/HONGYU-LEE/git-tes.git
  Push  URL: https://gitee.com/HONGYU-LEE/git-tes.git
  HEAD branch: master
  Remote branch:
    master tracked
  Local branch configured for 'git pull':
    master merges with remote master
  Local ref configured for 'git push':
    master pushes to master (local out of date)

它同样会列出远程仓库的 URL 与跟踪分支的信息,并且告诉你正处于 master 分支,并且如果运行 git pull,就会抓取所有的远程引用,然后将远程 master 分支合并到本地 master 分支。 它也会列出拉取到的所有远程引用。

添加远程仓库并拉取数据

可以通过以下命令来添加一个新的远程 Git 仓库:

1
git remote add <shortname> <url>

如果你使用 clone 命令克隆了一个仓库,命令会自动将其添加为远程仓库并默认以 “origin” 为简写。

从远程仓库中拉取数据

使用下面这个命令,就可以从远端仓库中获得数据:

1
git fetch [remote-name]

这个命令会访问远程仓库,从中拉取所有你还没有的数据。 执行完成后,你将会拥有那个远程仓库中所有分支的引用,可以随时合并或查看。

虽然 git fetch 命令会将数据拉取到你的本地仓库,但是它并不会自动合并或修改你当前的工作,所以在准备好时必须手动将其合并入你的工作。

如果你有一个分支设置为跟踪一个远程分支,可以使用 git pull 命令来自动的抓取然后合并远程分支到当前分支。

推送到远程仓库

如果我们想分享项目时,必须将其推送到上游。此时可以使用下面这个命令:

1
git push [remote-name] [branch-name]

只有当你有所克隆服务器的写入权限,并且之前没有人推送过时,这条命令才能生效。 当你和其他人在同一时间克隆,他们先推送到上游然后你再推送到上游,你的推送就会毫无疑问地被拒绝。 你必须先将他们的工作拉取下来并将其合并进你的工作后才能推送。

远端仓库移除/重命名

如果想要修改一个远程仓库的简写名,可以使用以下命令:

1
git remote rename [old_name] [new_name]

值得注意的是这同样也会修改你的远程分支名字。 例如那些过去引用 old_name/master 的现在会引用 new_name/master

如果因为一些原因想要移除一个远程仓库,可以使用以下命令:

1
git remote rm [remote-name]

标签(Tag)

与其他版本控制系统一样,Git 可以给历史中的某一个提交打上标签,以示重要。 Git 使用两种主要类型的标签:轻量标签(lightweight)附注标签(annotated)

查看标签

在 Git 中列出已有的标签是非常简单直观的, 只需要输入 git tag,就会以字典序排列出所有的标签:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ git tag
1.21
1.22
1.23
v1.10
v1.11
v1.12
v1.13
v1.14
v1.15
v1.16
v1.17
v1.18
v1.19
v1.20
v1.3
v1.4
v1.5
v1.6
v1.7
v1.8
v1.9

也可以使用特定的模式查找标签,例如想查找 v1.10 ~ v1.13 版本的标签,则可以使用正则表达式:

1
2
3
4
5
$ git tag -l 'v1.1[0-3]'
v1.10
v1.11
v1.12
v1.13

附注标签

附注标签是存储在 Git 数据库中的一个完整对象。 它有自身的校验和信息,包含着标签的名字,电子邮件地址和日期,以及标签说明,标签本身也允许使用 GNU Privacy Guard (GPG) 来签署或验证。

通常建议创建附注标签,这样你可以拥有以上所有信息;但是如果你只是想用一个临时的标签,或者因为某些原因不想要保存那些信息,轻量标签也是可用的。

它的创建也非常简单,只需要加上参数 -a 即可:

1
$ git tag -a v1.0 -m 'version 1.0'

如果是想要对过去的提交打上标签,只需要在末尾指定已提交的校验和(部分校验和也可以,会自动识别)即可:

1
$ git tag -a v0.8 fa38827eb9d27a2559e8178aa67189105d882fca

此时查看 git tag,发现已经给那个位置追加上了标签

1
2
3
4
$ git tag
v0.8
v1.0
v1.0-test

通过使用 git show 命令可以看到标签信息与对应的提交信息:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$ git show
commit da8f5f463cad799940543c7304df4d436a9efb2d (HEAD -> test, tag: v1.0)
Author: HONGYU-LEE <756687451@qq.com>
Date:   Mon Apr 18 17:35:08 2022 +0800

    commit

diff --git a/test.md b/test.md
deleted file mode 100644
index e69de29..0000000

输出显示了打标签者的信息、打标签的日期时间、附注信息,然后显示具体的提交信息。

轻量标签

轻量标签就像是个不会变化的分支,实际上它就是个指向特定提交对象的引用。 本质上是将提交校验和存储到一个文件中(没有保存任何其他信息)。

创建一个轻量标签时只需要使用 git tag,而不需要加别的参数,例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ git tag v1.0-test
$ git show
commit da8f5f463cad799940543c7304df4d436a9efb2d (HEAD -> test, tag: v1.0-test, tag: v1.0)
Author: HONGYU-LEE <756687451@qq.com>
Date:   Mon Apr 18 17:35:08 2022 +0800

    commit

diff --git a/test.md b/test.md
deleted file mode 100644
index e69de29..0000000

此时不会看到额外的标签信息, 命令只会显示出提交信息。

发布标签

默认情况下,git push 命令并不会传送标签到远程仓库服务器上。 在创建完标签后你必须显式地推送标签到共享服务器上。

1
git push origin [tagname]

如果想要一次性推送很多标签,也可以使用带有 --tags 选项的 git push 命令。 这将会把所有不在远程仓库服务器上的标签全部传送到那里。

1
git push origin --tags

Tag 与 Commit

Git 的标签虽然是版本库的快照,但其实它就是指向某个 commit 的指针,但是它与 commit 又有些不同(分支可以移动,标签不能移动)。

那么为什么有了 commit 后,还要引入 tag 呢?

“请把上周一的那个版本打包发布,commit号是6a5819e…”

“一串乱七八糟的数字不好找!”

如果换一个办法:

“请把上周一的那个版本打包发布,版本号是v1.2”

“好的,按照tag v1.2查找commit就行!”

所以,tag就是一个让人容易记住的有意义的名字,它跟某个commit绑在一起。

Licensed under CC BY-NC-SA 4.0
最后更新于 May 23, 2022 18:44 CST
Built with Hugo
主题 StackJimmy 设计