git的工作目录|暂存区|版本库|远程仓库

  • 工作目录:是用户在本地进行文件操作的实际目录,即项目所在的目录。用户可以在工作目录中创建、修改、删除文件等,这些操作都是在本地进行的,尚未被 Git 跟踪和管理。
  • 暂存区:也称为索引(Index),是工作目录和版本库之间的中间区域。当使用git add命令将文件添加到暂存区后,这些文件就准备好被提交到版本库中了。暂存区可以让用户将多次的文件修改组合成一个逻辑上的提交。
  • 版本库:也叫 Git 仓库,包含了项目的所有版本历史和相关的元数据,存储在隐藏的.git目录中。当执行git commit命令时,暂存区中的文件会被提交到版本库中,形成一个新的提交记录。
  • 远程仓库:是位于网络上的共享版本库,用于团队成员之间共享和协作开发项目。远程仓库可以是自己搭建的服务器,也可以是像 GitHub、GitLab 这样的代码托管平台上的仓库。本地仓库可以与远程仓库进行同步,将本地的提交推送到远程仓库,或者从远程仓库拉取最新的提交。

git的文件状态

git中文件有以下常见的4种状态:未跟踪、未修改、已修改、已暂存

  • 未跟踪(Untracked)

    • 含义:文件在工作目录中,但尚未被 Git 纳入版本控制范围,Git 不知道有这个文件的存在。

    • 场景:当在工作目录中新建一个文件,还没有执行git add命令将其添加到暂存区时,该文件就处于未跟踪状态。比如在项目目录下新建了一个new_file.txt文件,此时git status命令的输出会显示该文件为未跟踪文件。

  • 已跟踪(Tracked)

    • 含义:文件已经被 Git 纳入版本控制,Git 会对其进行跟踪,记录文件的变更历史。已跟踪文件又可细分为以下三种状态。
    • unmodified(未修改)
      • 含义:文件在工作目录中,且自从上次提交或更新后,其内容没有发生任何改变,与版本库中的对应版本保持一致。
      • 示例:如果项目中有一个styles.css文件,在上次提交后没有对其进行任何编辑操作,那么它就处于unmodified状态。
    • modified(已修改)
      • 含义:文件在工作目录中被修改了,其内容与版本库中的对应版本不一致,Git 检测到了这种变化,但这些修改还未被添加到暂存区。
      • 示例:对styles.css文件进行了样式调整,保存后,该文件就处于modified状态,git status命令会显示该文件被修改,提示可以选择将这些修改添加到暂存区或者丢弃。
    • staged(已暂存)
      • 含义:已修改的文件通过git add命令被添加到了暂存区,准备好被提交到版本库中,此时文件的修改会被包含在即将进行的提交中。
      • 示例:在对styles.css文件修改后,执行git add styles.css,该文件就从modified状态变为staged状态,git status命令会显示该文件已被暂存,等待提交。

以下所有操作都在实际工作目录下进行

Git本地配置

在正式演示git的添加提交撤回等操作前,我们需要对git进行简单的配置。

Git配置分为系统--system全局--global本地--local三个不同级别,优先级依次递减,其中如果存在重复的配置,那么低级别配置会覆盖高级别配置

  • local配置:存在每个git仓库的.git/config文件中
  • global配置:存在用户目录下的.gitconfig文件中
  • system配置:存在${profile}/etc/gitconfig中,${prefix}为git的安装目录

git的提交会引用用户的用户名以及邮箱(可以使用github提供的匿名邮箱),因此这里需要执行命令进行设置:

1
2
git config --global user.name "DopamineNone"
git config --global user.email 123919976+DopamineNone@users.noreply.github.com

相应的在配置文件中就会出现:

1
2
3
[user]
name = DopamineNone
email = 123919976+DopamineNone@users.noreply.github.com

除此之外,还可以为git命令设置别名,比如:

1
git config --global alias.ad "add ."

相当于为git add .起了别名git ad

本地工作目录|暂存区|版本库操作

查看文件状态

  • git status: 使用git status命令可以查看当前工作目录和暂存区的状态,了解哪些文件被修改、新增或删除,以及哪些文件已被暂存准备提交。

查看提交历史

  • git log可以查看版本库的提交日志
  • 使用--graph选项可以查看git提交图谱

初始化仓库

  • git init: 会将当前的目录初始化化为git仓库,生成一个隐藏文件夹.git,用于存储与版本控制相关的信息

这里实践一下:

1
2
3
4
5
6
7
8
9
10
11
DopamineNone@DM MINGW64 ~/Projects/git_test
$ git init
Initialized empty Git repository in C:/Users/DopamineNone/Projects/git_test/.git/

DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git status
On branch master

No commits yet

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

可见刚初始化后的仓库什么都没有

添加至暂存区(untracked, modified -> staged)

  • git add [file]
    • 这个操作的目的是为了将未跟踪或已修改状态的文件推至暂存区中
    • 可以用git add .将仓库中所有未跟踪或已修改状态的文件推至暂存区

实践一下:

1. 创建一个新文件file1.txt,查看状态(untracked)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ touch file1.txt

DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git status
On branch master

No commits yet

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

nothing added to commit but untracked files present (use "git add" to track)
  1. 将file1.txt推送至暂存区,查看状态(staged,这里提示词为changes to be committed,即将要提交的变化); 状态变化:untracked -> staged
1
2
3
4
5
6
7
8
9
10
11
12
DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git add file1.txt

DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git status
On branch master

No commits yet

Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: file1.txt
  1. 修改file1.txt,查看状态(modified);这里to be committed的是暂存区中file1.txt的快照;状态变化:staged -> modified
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ echo "hello git" > file1.txt

DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git status
On branch master

No commits yet

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

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: file1.txt
  1. 将file1.txt添加至暂存区,查看状态(staged); 状态变化(modifed -> staged);状态变化:modified -> staged
1
2
3
4
5
6
7
8
9
10
11
12
13
DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git add file1.txt
warning: in the working copy of 'file1.txt', LF will be replaced by CRLF the next time Git touches it

DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git status
On branch master

No commits yet

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

提交暂存区文件(staged -> unmodified)

git commit -m "message"提交暂存区内容至版本库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git commit -m "init project"
[master (root-commit) 1384bf0] init project
1 file changed, 1 insertion(+)
create mode 100644 file1.txt

DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git status
On branch master
nothing to commit, working tree clean

DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git log
commit 1384bf0b39111d1f33ffb501f609d0c88797f422 (HEAD -> master)
Author: DopamineNone <c20160419raytheon@163.com>
Date: Wed Jan 8 13:39:46 2025 +0800
init project

查看不同区域/分支的差异

  • git diff可以显示不同区域/分支中的内容差异,包括

查看工作目录与暂存区的差异

1
2
3
4
5
6
7
8
9
10
11
12
13
DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ echo "hello world" > file1.txt

DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git diff file1.txt
warning: in the working copy of 'file1.txt', LF will be replaced by CRLF the next time Git touches it
diff --git a/file1.txt b/file1.txt
index 8d0e412..3b18e51 100644
--- a/file1.txt
+++ b/file1.txt
@@ -1 +1 @@
-hello git
+hello world

查看暂存区与版本库的差异

需要加上选项--staged--cached

1
2
3
4
5
6
7
8
9
10
11
12
13
DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git add file1.txt
warning: in the working copy of 'file1.txt', LF will be replaced by CRLF the next time Git touches it

DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git diff --staged file1.txt
diff --git a/file1.txt b/file1.txt
index 8d0e412..3b18e51 100644
--- a/file1.txt
+++ b/file1.txt
@@ -1 +1 @@
-hello git
+hello world

查看不同提交版本间的差异

git diff <commit1> <commit2>

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
DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git commit -m "replace git by world"
[master 407586a] replace git by world
1 file changed, 1 insertion(+), 1 deletion(-)

DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git log
commit 407586abfd0b5a0239e63dc582abbd5d752edaa0 (HEAD -> master)
Author: DopamineNone <xxx>
Date: Thu Jan 9 21:05:26 2025 +0800

replace git by world

commit 1384bf0b39111d1f33ffb501f609d0c88797f422
Author: DopamineNone <xxx>
Date: Wed Jan 8 13:39:46 2025 +0800

init project

DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git diff 407586abfd0b5a0239e63dc582abbd5d752edaa0 1384bf0b39111d1f33ffb501f609d0c88797f422
diff --git a/file1.txt b/file1.txt
index 3b18e51..8d0e412 100644
--- a/file1.txt
+++ b/file1.txt
@@ -1 +1 @@
-hello world
+hello git

查看两个分支之间的差异

git diff <branch1> <branch2>,暂不做演示

撤销工作目录的修改(modified -> unmodified | staged)

这里撤销的只是还没提交至暂存区中的修改

  1. 将file1.txt的内容清空,查看状态(modified)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ echo "" > file1.txt

DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git status
On branch master

No commits yet

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

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: file1.txt
  1. 使用git restore <file>命令撤回修改
1
2
3
4
5
6
7
8
9
10
11
12
DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git restore file1.txt

DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git status
On branch master

No commits yet

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

撤销暂存区的修改

这里分两种情况:

  1. 仅将暂存区中文件内容恢复至最近的提交版本,但不修改工作区的文件内容
  2. 将暂存区和工作区中的文件内容恢复至最近的提交中

仅撤销暂存区的修改

使用git reset <file>git restore --staged <file>

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
DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ echo "" > file1.txt

DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git add file1.txt
warning: in the working copy of 'file1.txt', LF will be replaced by CRLF the next time Git touches it

DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git diff --staged file1.txt
diff --git a/file1.txt b/file1.txt
index 3b18e51..8b13789 100644
--- a/file1.txt
+++ b/file1.txt
@@ -1 +1 @@
-hello world
+

DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git reset file1.txt
Unstaged changes after reset:
M file1.txt

DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git diff --staged file1.txt

DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git diff file1.txt
warning: in the working copy of 'file1.txt', LF will be replaced by CRLF the next time Git touches it
diff --git a/file1.txt b/file1.txt
index 3b18e51..8b13789 100644
--- a/file1.txt
+++ b/file1.txt
@@ -1 +1 @@
-hello world
+

撤销工作目录和暂存区的修改

使用git checkout HEAD <file>命令

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
DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git add file1.txt
warning: in the working copy of 'file1.txt', LF will be replaced by CRLF the next time Git touches it

DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git diff file1.txt

DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git diff --staged file1.txt
diff --git a/file1.txt b/file1.txt
index 3b18e51..8b13789 100644
--- a/file1.txt
+++ b/file1.txt
@@ -1 +1 @@
-hello world
+

DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git checkout HEAD file1.txt
Updated 1 path from 8242445

DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git diff --staged file1.txt

DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git diff file1.txt

撤销提交记录

撤销提交可以由两种git命令实现: git resetgit revert,但两者的效果不同

git reset [option] <commit> 分为三种情况:

  1. 仅将目标版本之后commit移出,不改变暂存区和工作目录的内容
  2. 将目标版本之后commit移出,恢复暂存区内容为当前版本,但不改变工作目录内容
  3. 将版本库、暂存区、工作目录的内容恢复至目标提交版本。

git revert <commit>表示,提交一个与目标commit的效果相反的commit,从而实现仅撤回某一版本的变化的效果(例如:commit1为在之前版本上新增了func1,那么git revert commit1作用为提交一个删除了func1的commit),撤回的影响包括版本库,暂存区和工作目录

仅撤销版本库提交

使用git reset --soft <commit>表示撤回到哪个提交记录,如果仅撤回最近的一次提交,可以使用git reset --soft HEAD~1, HEAD指向本分支最新的提交,~1表示HEAD的前一次提交

下面将git记录回溯至“hello git”

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
DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git log
commit 407586abfd0b5a0239e63dc582abbd5d752edaa0 (HEAD -> master)
Author: DopamineNone <xxx>
Date: Thu Jan 9 21:05:26 2025 +0800

replace git by world

commit 1384bf0b39111d1f33ffb501f609d0c88797f422
Author: DopamineNone <xxx>
Date: Wed Jan 8 13:39:46 2025 +0800

init project

DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git reset --soft HEAD~1

DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git diff --staged file1.txt
diff --git a/file1.txt b/file1.txt
index 8d0e412..3b18e51 100644
--- a/file1.txt
+++ b/file1.txt
@@ -1 +1 @@
-hello git
+hello world

仅撤销版本库提交和恢复暂存区

使用git reset <commit>即可,等同于git reset --mixed <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
DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git commit -m "replace git by world 2"
[master b166262] replace git by world 2
1 file changed, 1 insertion(+), 1 deletion(-)

DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git log
commit b166262647db5d50e1ea59f644f59845f6fc002b (HEAD -> master)
Author: DopamineNone <xxx>
Date: Thu Jan 9 21:34:08 2025 +0800

replace git by world 2

commit 1384bf0b39111d1f33ffb501f609d0c88797f422
Author: DopamineNone <xxx>
Date: Wed Jan 8 13:39:46 2025 +0800

init project

DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git reset HEAD~1
Unstaged changes after reset:
M file1.txt

DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git diff --staged file1.txt

DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git diff file1.txt
diff --git a/file1.txt b/file1.txt
index 8d0e412..3b18e51 100644
--- a/file1.txt
+++ b/file1.txt
@@ -1 +1 @@
-hello git
+hello world

撤销所有区域的变化至上个提交

使用git reset --hard <commit>,下例中工作区的内容最后也被回溯至”hello git”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git add file1.txt

DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git commit -m "replace git by world 3"
[master e2f0737] replace git by world 3
1 file changed, 1 insertion(+), 1 deletion(-)

DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git reset --hard HEAD~1
HEAD is now at 1384bf0 init project

DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git diff file1.txt

DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ cat file1.txt
hello git

仅撤回某一版本的变化

通过git revert <commit1>实现,如:

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
DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ echo " world" >> file1.txt

DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ cat file1.txt
hello git
world

DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git add file1.txt
warning: in the working copy of 'file1.txt', LF will be replaced by CRLF the next time Git touches it

DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git commit -m "add world"
[master bc2c3ea] add world
1 file changed, 1 insertion(+)

DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git revert HEAD
[master a52db43] Revert "add world"
1 file changed, 1 deletion(-)

DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ git log
commit a52db43677856b9e37e880308c3581bdb53f34eb (HEAD -> master)
Author: DopamineNone <xxx>
Date: Thu Jan 9 22:38:33 2025 +0800

Revert "add world"

This reverts commit bc2c3ea660eea6c38d5d3c8ca03f51935a1028ec.

commit bc2c3ea660eea6c38d5d3c8ca03f51935a1028ec
Author: DopamineNone <xxx>
Date: Thu Jan 9 22:33:29 2025 +0800

add world

commit 1384bf0b39111d1f33ffb501f609d0c88797f422
Author: DopamineNone <xxx>
Date: Wed Jan 8 13:39:46 2025 +0800

init project

DopamineNone@DM MINGW64 ~/Projects/git_test (master)
$ cat file1.txt
hello git

git reset 和 git revert的区别:

  1. git reset的作用是撤回至先前的某个版本,而git revert是通过提交一个相反的commit,仅“撤回”某个版本的变化
  2. 当撤回一个公共分支(多人合作使用的分支)的某个commit时,用于公共分支的commit只增不减(若删除了公共分支的commit,会导致与其他人的本地分支产生冲突),因此这时候使用git revert最稳妥;但如果时个人分支(即该分支不会被他人直接引用),可以使用git reset 配合 git push -f实现提交撤回,并使日志更加简洁

更多git基本使用方法请见Git 基本用法(2)