1. 获取 Git 仓库
通常有两种获取 Git 项目仓库的方式:
- 将尚未进行版本控制的本地目录转换为 Git 仓库;
- 从其它服务器 克隆 一个已存在的 Git 仓库。
两种方式都会在本地机器上得到一个工作就绪的 Git 仓库。
1.1 在已存在目录中初始化仓库
如果你有一个尚未进行版本控制的项目目录,想要用 Git 来控制它,那么首先需要进入该项目目录中。 不同系统上的做法不同:
在 Linux 上:
$ cd /home/user/my_project
在 macOS 上:
$ cd /Users/user/my_project
在 Windows 上:
$ cd /c/user/my_project
之后执行:
$ git init
该命令将创建一个名为 .git
的子目录,这个子目录含有初始化的 Git 仓库中所有的必须文件,这些文件是 Git 仓库的骨干。 但在此时,仅仅是做了一个初始化的操作,项目里的文件还没有被跟踪。 可以通过 git add
命令来指定所需的文件来进行追踪,然后执行 git commit
:
$ git add .
$ git commit -m 'initial project version'
1.2 克隆现有的仓库
使用git clone <url>
命令获得一份已经存在了的 Git 仓库的拷贝,默认配置下远程 Git 仓库中的每一个文件的每一个版本都将被拉取下来。比如,要克隆 Git 的链接库 libgit2,并自定义本地仓库的名字为“mylibgit”,可以用下面的命令:
$ git clone https://github.com/libgit2/libgit2 mylibgit
2. 记录每次更新到仓库
2.1 记录每次更新到仓库
工作目录下的每一个文件只有以下两种状态:
- 已跟踪: 被纳入了版本控制的文件( Git 已经知道的文件),在上一次快照中有它们的记录,在工作一段时间后, 它们的状态可能是未修改,已修改或已放入暂存区。
- 未跟踪: 除已跟踪文件外的所有文件都属于未跟踪文件,它们既不存在于上次快照的记录中,也没有被放入暂存区。
编辑过某些文件之后,,Git 将它们标记为已修改文件。 可以选择性地将这些修改过的文件放入暂存区,然后提交所有已暂存的修改。下图描述了文件的状态变化周期:
2.2 检查当前文件状态
可以用 git status
命令查看哪些文件处于什么状态。 在克隆仓库mylibgit
后,没有修改任何文件,使用此命令,会看到类似这样的输出:
$ git status
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
在项目下创建一个新的 README 文件,使用 git status
命令,可以看到一个新的未跟踪文件 README 出现在 Untracked files 下面,且字体为红色:
$ echo 'mylibgit' > README
$ git status
On branch main
Your branch is up to date with 'origin/main'.
Untracked files:
(use "git add <file>..." to include in what will be committed)
README
nothing added to commit but untracked files present (use "git add" to track)
2.3 跟踪新文件
使用命令 git add
开始跟踪一个文件:
$ git add README
此时再运行 git status
命令,会看到 README 文件已被跟踪(绿色字体),并处于暂存状态:
$ git status
On branch main
Your branch is up to date with 'origin/main'.
Changes to be committed: # 只要在 Changes to be committed 这行下面的,就说明是已暂存状态
(use "git restore --staged <file>..." to unstage)
new file: README
2.4 暂存已修改的文件
在README文件添加一行,然后运行 git status 命令,会看到下面内容:
$ git status
On branch main
Your branch is up to date with 'origin/main'.
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: README
# Changes not staged for commit 这行下面,说明已跟踪文件的内容发生了变化,但还没有放到暂存区
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
文件 README 同时出现在暂存区(绿色字体)和非暂存区(红色字体)。Git 只暂存了运行 git add 命令时的版本, 如果现在提交,README的版本是最后一次运行 git add 命令时的那个版本,而不是运行 git commit 时,在工作目录中的当前版本。所以,运行了 git add 之后又作了修订的文件,需要重新运行 git add
把最新版本重新暂存起来,然后再看看 git status 的输出:
$ git add README
$ git status
On branch main
Your branch is up to date with 'origin/main'.
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: README
2.5 状态简览
git status
命令的输出十分详细,但其用语有些繁琐。 Git 有一个选项-s
(或--short
)可以缩短状态命令的输出,以简洁的方式查看更改:新添加的未跟踪文件前面有 ??
标记,新添加到暂存区中的文件前面有 A
标记,修改过的文件前面有 M
标记。
$ git status -s
A README
2.6 忽略文件
有些文件无需纳入 Git 的管理,也不希望它们总出现在未跟踪文件列表。通常都是些自动生成的文件,比如日志文件,或者编译过程中创建的临时文件等。在这种情况下,可以创建一个名为 .gitignore 的文件,列出要忽略的文件的模式。 文件 .gitignore
的格式规范如下:
- 所有空行或者以 # 开头的行都会被 Git 忽略。
- 可以使用标准的 glob 模式匹配,它会递归地应用在整个工作区中。
- 匹配模式可以以(/)开头防止递归。
- 匹配模式可以以(/)结尾指定目录。
- 要忽略指定模式以外的文件或目录,可以在模式前加上叹号(!)取反。
所谓的 glob 模式是指 shell 所使用的简化了的正则表达式:
- (*) 匹配零个或多个任意字符;
- [abc] 匹配任何一个列在方括号中的字符 (这个例子要么匹配一个 a,要么匹配一个 b,要么匹配一个 c);
- 问号(?)只匹配一个任意字符;
- 如果在方括号中使用短划线分隔两个字符,表示所有在这两个字符范围内的都可以匹配(比如 [0-9] 表示匹配所有 0 到 9 的数字)。
- 使用两个星号(**)表示匹配任意中间目录,比如 a/**/z 可以匹配 a/z 、 a/b/z 或 a/b/c/z 等。
# 忽略所有的 .a 文件
*.a
# 但跟踪所有的 lib.a,即便你在前面忽略了 .a 文件
!lib.a
# 只忽略当前目录下的 TODO 文件,而不忽略 subdir/TODO
/TODO
# 忽略任何目录下名为 build 的文件夹
build/
# 忽略 doc/notes.txt,但不忽略 doc/server/arch.txt
doc/*.txt
# 忽略 doc/ 目录及其所有子目录下的 .pdf 文件
doc/**/*.pdf
2.7 查看已暂存和未暂存的修改
git status
命令的输出过于简略,可以用 git diff
命令显示具体修改了什么地方,git diff
本身只显示尚未暂存的改动,而不是自上次提交以来所做的所有改动。
在 README 文件末尾添加一行,先不暂存, 运行 status 命令将会看到:
$ git status
On branch main
Your branch is up to date with 'origin/main'.
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: README
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
要查看尚未暂存的文件更新了哪些部分,不加参数直接输入 git diff
:
$ git diff
warning: LF will be replaced by CRLF in README.
The file will have its original line endings in your working directory
diff --git a/README b/README
index 41fd9ffc7..d9b17b358 100644
--- a/README
+++ b/README
@@ -1,2 +1,3 @@
mylibgit
hello
+world
此命令比较的是工作目录中当前文件和暂存区域快照之间的差异。也就是修改之后还没有暂存起来的变化内容。
若要查看已暂存的将要添加到下次提交里的内容,可以用 git diff --staged
或git diff --cached
命令。这条命令将比对已暂存文件与最后一次提交的文件差异:
$ git diff --staged
diff --git a/README b/README
new file mode 100644
index 000000000..41fd9ffc7
--- /dev/null
+++ b/README
@@ -0,0 +1,2 @@
+mylibgit
+hello
2.8 提交更新
使用git commit
命令将暂存区的文件提交。 提交前,需要确认还有什么已修改或新建的文件还没有 git add 过,否则提交的时候不会记录这些尚未暂存的变化。所以,每次准备提交前,先用 git status 看下,所需要的文件是不是都已暂存起来了, 然后再运行提交命令 git commit。
$ git commit -m "readme"
[main 82e74ac39] readme
1 file changed, 3 insertions(+)
create mode 100644 README
提交后会显示,当前在main分支提交,本次提交的完整 SHA-1
校验和是 82e74ac39,在本次提交中,有1个文件修订过,3行添加。
2.9 跳过使用暂存区域
尽管使用暂存区域的方式可以精心准备要提交的细节,但有时候这么做略显繁琐。Git 提供了一个跳过使用暂存区域的方式,只要在提交的时候,给 git commit
加上 -a
选项,Git 就会自动把所有已经跟踪过的文件暂存起来一并提交,从而跳过 git add 步骤:
git commit -a -m 'no add'
2.10 移除文件
要从 Git 中移除某个文件,就必须要从已跟踪文件清单中移除(确切地说,是从暂存区域移除),然后提交。可以用 git rm
命令完成此项工作,并连带从工作目录中删除指定的文件。
# 从暂存区域移除 README
$ git rm -rf README
rm 'README'
$ git status
On branch main
Your branch is ahead of 'origin/main' by 1 commit.
(use "git push" to publish your local commits)
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
deleted: README
# 提交
$ git commit -m "rm readme"
[main c5de4544a] rm readme
1 file changed, 3 deletions(-)
delete mode 100644 README
$ git status
On branch main
Your branch is ahead of 'origin/main' by 2 commits.
(use "git push" to publish your local commits)
nothing to commit, working tree clean
2.11 移动文件
要在 Git 中对文件改名,可以这么做:
$ git mv file_from file_to
上述 git mv
相当于运行了下面三条命令:
$ mv README.md README
$ git rm README.md
$ git add README
3 查看提交历史
3.1 git log
简介
使用一个非常简单的 simplegit
项目作为示例。运行下面的命令获取该项目:
$ git clone https://github.com/schacon/simplegit-progit
在此项目中运行 git log
命令,可以看到下面的输出:
$ git log
commit ca82a6dff817ec66f44342007202690a93763949 (HEAD -> master, origin/master, origin/HEAD)
Author: Scott Chacon <schacon@gmail.com>
Date: Mon Mar 17 21:52:11 2008 -0700
changed the verison number
commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
Author: Scott Chacon <schacon@gmail.com>
Date: Sat Mar 15 16:40:33 2008 -0700
removed unnecessary test code
commit a11bef06a3f659402fe7563abf99ad00de2209e6
Author: Scott Chacon <schacon@gmail.com>
Date: Sat Mar 15 10:31:28 2008 -0700
first commit
不传入任何参数的默认情况下,git log
会按时间倒序列出所有的提交,这个命令会列出每个提交的 SHA-1
校验和、作者的名字和电子邮件地址、提交时间以及提交说明。
3.2 git log
常用选项
-p
或--patch
它会显示每次提交所引入的差异(按补丁的格式输出)。可以限制显示的日志条目数量(不仅仅是-p
选项),例如使用 -1 选项来只显示最近的一次提交:
$ git log -p -1
commit ca82a6dff817ec66f44342007202690a93763949 (HEAD -> master, origin/master, origin/HEAD)
Author: Scott Chacon <schacon@gmail.com>
Date: Mon Mar 17 21:52:11 2008 -0700
changed the verison number
diff --git a/Rakefile b/Rakefile
index a874b73..8f94139 100644
--- a/Rakefile
+++ b/Rakefile
@@ -5,7 +5,7 @@ require 'rake/gempackagetask'
spec = Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
s.name = "simplegit"
- s.version = "0.1.0"
+ s.version = "0.1.1"
s.author = "Scott Chacon"
s.email = "schacon@gmail.com"
s.summary = "A simple gem for using Git in Ruby code."
--stat
显示每次提交的简略统计信息,使用 -1 选项来只显示最近的一次提交:
$ git log --stat -1
commit ca82a6dff817ec66f44342007202690a93763949 (HEAD -> master, origin/master, origin/HEAD)
Author: Scott Chacon <schacon@gmail.com>
Date: Mon Mar 17 21:52:11 2008 -0700
changed the verison number
Rakefile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
--pretty
这个选项可以使用不同于默认格式的方式展示提交历史。 这个选项有一些内建的子选项。 比如oneline
会将每个提交放在一行显示,在浏览大量的提交时非常有用;format
可以定制记录的显示格式。
$ git log --pretty=oneline -1
ca82a6dff817ec66f44342007202690a93763949 (HEAD -> master, origin/master, origin/HEAD) changed the verison number
$ git log --pretty=format:"%h - %an, %ar : %s" -1
ca82a6d - Scott Chacon, 14 years ago : changed the verison number
--since
和--until
# 列出最近两周的所有提交
$ git log --since=2.weeks
commit 759b615e6e1192742976cae2c16589811aa0d814 (HEAD -> master)
Author: zhangsan <12345@qq.com>
Date: Sat Apr 30 17:58:38 2022 +0800
initial project version
# 列出2022-04-30之前的提交,包括当日
$ git log --until="2022-04-30"
commit 759b615e6e1192742976cae2c16589811aa0d814 (HEAD -> master)
Author: zhangsan <12345@qq.com>
Date: Sat Apr 30 17:58:38 2022 +0800
initial project version
4. 撤消操作
- 提交后,发现漏掉了几个文件没有添加,或者提交信息写错了,可以像下面这样操作:
$ git commit -m 'initial commit' # 上一次提交
$ git add forgotten_file # 暂存forgotten_file文件
$ git commit --amend # 提交forgotten_file文件,代替上一次提交的结果,最终只有一次提交
git commit --amend
命令会将暂存区中的文件提交。在上次提交后,如果未做任何修改,那么快照会保持不变,修改的只是提交信息。第二次提交将代替第一次提交的结果,最终只有一次提交。
- 使用
git add
命令暂存a.txt
文件后,可以使用下面的命令取消暂存的a.txt
文件:
git reset HEAD a.txt
- 提交
a.txt
后,对a.txt
进行修改,可以使用下面的命令撤消对文件的修改,还原成上次提交时的样子:
$ git checkout -- a.txt # 一个危险的命令,对a.txt在本地的任何修改都会消失
5. 打标签
Git 支持两种标签:
- 附注标签(annotated): 是存储在 Git 数据库中的一个完整对象,它们是可以被校验的,其中包含打标签者的名字、电子邮件地址、日期时间,此外还有一个标签信息,并且可以使用 GNU Privacy Guard(GPG)签名并验证。通常会建议创建附注标签,这样可以拥有以上所有信息。
- 轻量标签(lightweight): 轻量标签很像一个不会改变的分支——它只是某个特定提交的引用。如果只是想用一个临时的标签,或者因为某些原因不想要保存附注标签中的信息,可以用轻量标签。
5.1 列出标签
使用git tag
命令以字母顺序列出已有的标签(可带上可选的 -l
选项 --list
):
$ git tag
v0.1.0
v0.10.0
v0.11.0
v0.12.0
......
按照特定的模式查找标签:
$ git tag -l "v0.27*"
v0.27.0
v0.27.0-rc1
v0.27.0-rc2
v0.27.0-rc3
v0.27.1
v0.27.10
v0.27.2
v0.27.3
v0.27.4
v0.27.5
v0.27.6
v0.27.7
v0.27.8
v0.27.9
5.2 创建附注标签
在运行 tag
命令时指定 -a
选项创建附注标签,-m
选项指定了一条将会存储在标签中的信息(如果没有为附注标签指定一条信息,Git 会启动编辑器要求你输入信息):
$ git tag -a v1.0 -m 'version 1.0'
通过使用 git show 命令可以看到 打标签者的信息、打标签的日期时间、附注信息、具体的提交信息。
$ git show v1.0
tag v1.0
Tagger: zhangsan <12345@qq.com> # 打标签者的信息
Date: Sun May 1 22:18:35 2022 +0800 # 打标签的日期时间
version 1.0 # 附注信息
# 具体的提交信息
commit 0dc3e6247ed13aa0d568bd1ec31ac6e00172c01a (HEAD -> master, tag: v1.0)
Author: zhangsan <12345@qq.com>
Date: Sun May 1 21:14:51 2022 +0800
new a.txt
diff --git a/a.txt b/a.txt
new file mode 100644
index 0000000..577fcb1
--- /dev/null
+++ b/a.txt
@@ -0,0 +1 @@
+this is git demo
v1.0
5.3 创建附注标签
轻量标签本质上是将提交校验和存储到一个文件中——没有保存任何其他信息。创建轻量标签,不需要使用 -a
、-s
或 -m
选项,只需要提供标签名字:
$ git tag -a v1.0-lw -m 'version 1.0-lw'
$ git tag
v1.0
v1.0-lw
这时,如果在标签上运行 git show
,不会看到额外的标签信息,只会显示出提交信息:
$ git show v1.0-lw
tag v1.0-lw
Tagger: zhangsan <12345@qq.com>
Date: Sun May 1 22:35:51 2022 +0800
version 1.0-lw
commit 0dc3e6247ed13aa0d568bd1ec31ac6e00172c01a (HEAD -> master, tag: v1.0-lw, tag: v1.0)
Author: zhangsan <12345@qq.com>
Date: Sun May 1 21:14:51 2022 +0800
new a.txt
diff --git a/a.txt b/a.txt
new file mode 100644
index 0000000..577fcb1
--- /dev/null
+++ b/a.txt
@@ -0,0 +1 @@
+this is git demo
5.4 后期打标签
可以对过去的提交打标签。 假设提交历史是这样的:
$ git log --pretty=oneline
15027957951b64cf874c3557a0f3547bd83b3ff6 Merge branch 'experiment'
a6b4c97498bd301d84096da251c98a07c7723e65 beginning write support
0d52aaab4479697da7686c15f77a3d64d9165190 one more thing
6d52a271eda8725415634dd79daabbc4d9b6008e Merge branch 'experiment'
0b7434d86859cc7b8c3d5e1dddfed66ff742fcbc added a commit function
4682c3261057305bdd616e23b64b0857d832627b added a todo file
166ae0c4d3f420721acbb115cc33848dfcc2121a started write support
9fceb02d0ae598e95dc970b74767f19372d61af8 updated rakefile
964f16d36dfccde844893cac5b347e7b3d44abbc commit the todo
8a5cbc430f1a9c3d00faaeffd07798508422908a updated readme
假设在 v1.2 时(在 “updated rakefile” 提交),忘记给项目打标签,可以在命令的末尾指定提交的校验和,补上标签:
$ git tag -a v1.2 9fceb02
$ git tag
v0.1
v1.2
v1.3
v1.4
v1.4-lw
v1.5
$ git show v1.2
tag v1.2
Tagger: Scott Chacon <schacon@gee-mail.com>
Date: Mon Feb 9 15:32:16 2009 -0800
version 1.2
commit 9fceb02d0ae598e95dc970b74767f19372d61af8
Author: Magnus Chacon <mchacon@gee-mail.com>
Date: Sun Apr 27 20:43:35 2008 -0700
updated rakefile
...
5.5 删除标签
可以使用以下命令删除一个本地标签:
$ git tag -d v1.0-lw
Deleted tag 'v1.0-lw' (was b81a375)