Recording Changes to the Repository
source:git-scm.com/book/en/v2/...
当前在你的电脑上应该就有了一个git仓库。现在你应该很想对文件进行一些修改,并在完成一阶段的修改工作之后,对这些修改进行快照并提交到git仓库。
所有在你的工作目录下的文件都只会存在两种状态:tracked
或者untracked
。Tracked
文件就是能在最新的快照中存在的文件或新加入暂存区的文件,有unmodified modified staged
集中状态,简而言之啊,tracked
file就是Git进行管理的文件'
Untracked
file就是除tracked
文件之外的文件。即不在你最新快照中,也不在暂存区中。当你首次clone一个仓库,你的所有文件都是tracked
且unmodified
的,因为git只是checkout 而你也没对文件进行任何修改。
当你编辑文件时,git就会将这些编辑过的文件视为modified
,因为在最近的一次commit之后,这些文件发生了变化。随后,你可以将这个修改后的文件选择性地进行暂存,并进行提交,然后就重新进入了状态循环。
Checking the Status of Your Files
你用来确定文件处于什么状态的工具就是命令git status
。如果你在clone之后直接运行该命令,你会得到下面的输出:
shell
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working tree clean
这代表你有一个干净的工作目录;换句话说,你的tracked
文件没有一个被修改的。还代表着当前目录不存在untracked
的文件。最后,这个命令告诉了你 你当前在哪个分支上,并且告知你它与服务器上的该分支没有偏离。当前我们在默认的master
分支上,关于分支的东西会在Git Branching中进行学习。
Note GitHub在2020年中奖默认的分支名由master
改为main
,并且其他的git host也都进行了效仿。你会发现一些新创建的仓库的默认分支为master
而不是main
。除此之外,默认的分支名可以进行修改(可参阅 Your default branch name)。而git自身依旧使用master
作为其默认分支。
假如你在目录下添加了一个新文件README
。如果该文件先前并不存在,当你运行git status
时,你会看到你的untracked
文件如下所示:
console
$ echo 'My Project' > README
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
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)
可以看到README
是untracked
。Untracked
代表git在最新的快照中没有该文件,而且当前还没有被暂存。Git不会将其纳入管理除非你显式告诉git。这样你就不必担心生成的二进制文件或者你不想假如git的文件 进行提交。
Tracking New Files
为了对一个新文件进行跟踪,你需要使用命令git add
。例如,你想跟踪README
文件。你应该运行
csharp
$ git add README
此时重新运行status
命令,就会发现README
文件已经被跟踪了并且已被暂存。
console
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: README
可以看到它当前在Changes to be committed
heading下。如果你此时进行提交,提交的这个文件的版本就是你运行git add
时的版本。git add <files>
用来开始根据你文件夹下的文件,git add
可以接受一个文件或者一个文件夹的路径名做为参数,如果是文件夹,命令会让git递归地跟踪其中的每个文件。
Staging Modified Files
让我们对其中已经跟踪的文件进行修改。例如,对之前跟踪的CONTRIBUTING.md
进行修改,然后运行git status
查看,就会得到下面的输出
console
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: README
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: CONTRIBUTING.md
CONTRIBUTING.md
文件在Changes not staged for commit下,这意味着该已被跟踪的文件已被修改,但是还未被暂存。要将其暂存,你应该运行git add
命令。git add
是一个多用途指令--你可以使用它去跟踪新文件,暂存文件 或者 标记 冲突解决。你可以将其看做 "将这个content添加到下次提交中" 而不是"添加文件到这个项目中"。现在让我们运行git add
,现在我们就暂存了CONTRIBUTING.md
,此时 运行git status
:
console
$ git add CONTRIBUTING.md
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: README
modified: CONTRIBUTING.md
现在这两个文件都被暂存了,并且会进入你的下一次提交。此时,你如果想在提交该文件之前 对CONTRIBUTING.md
进行一点小修改,你打开文件,然后又修改了一些东西并保存,此时当你运行git status
时,
console
$ vim CONTRIBUTING.md
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: README
modified: CONTRIBUTING.md
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: CONTRIBUTING.md
这是怎么回事, CONTRIBUTING.md
同时在staged
和unstaged
下。这实际上是因为,git暂存中的文件实际时你运行git add
当时的文件。如果你现在进行提交CONTRIBUTING.md
的版本就是你执行git add
时的版本而不是执行git commit
时的版本。如果你在运行git add
之后修改了文件,你需要再次执行git add
来暂存这个文件的最新版本。
console
$ git add CONTRIBUTING.md
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: README
modified: CONTRIBUTING.md
Short Status
git status
的输出是非常综合的,同样也显得很啰嗦。所以git提供了一个短写的status标志来让你可以以一种更紧凑的方式看到你的修改。如果你运行git status -s
或者git status --short
,你就可以看到这种简化的输出。
bash
$ git status -s
M README
MM Rakefile
A lib/git.rb
M lib/simplegit.rb
?? LICENSE.txt
没有被跟踪的新文件会标记为??
。被添加到暂存区的新文件会标记为A
,修改获得被标记为M
。这个标志位其实由两个flag组成,左边的代表暂存区的状态,右边的代表工作目录的状态。以上面输出为例,README
在目录中被修改但还未被暂存,文件lib/simplegit.rb
已修改也被暂存,Rakefile
被修改 暂存,然后又被再次修改。所以其同时有处于暂存区和非暂存区两种状态。
Ignoring Files
通常,你会有一些文件 你即不想git自动添加,甚至不想将其展示为untracked
。这通常是一些自动生成的文件,如日志文件或者构建系统所产生的一些文件。在这种情况下,你可以创建一个名为.gitignore
的文件来对其进行匹配。下面是.gitignore
文件的一个示例:
shell
$ cat .gitignore
*.[oa]
*~
第一行让git忽略以.o
或者.a
结尾的文件。第二行让git忽略文件名以~
结束的文件。在你要使用git进行管理工作目录前 先添加一个.gitignore
文件是一个好主意,这样你就不会意外向git添加一些你不想让git管理的文件。
.gitignore
中的规则如下所示:
- 空行或者以
#
开头的行会被忽略 - 支持标准的glob pattern,会递归地应用到整个工作目录
- 你可以在pattern前添加
/
来避免递归 - 你可以在pattern后添加
/
来制定一个文件夹 - 你可以在pattern前添加
!
来否定一个pattern
Glob pattern就像是shell使用的简化的正则表达式,*
匹配0个或多个字符,[abc]
匹配中括号中的任何字符(该例中为 a,b,c);?
匹配单个字符;[0-9]
匹配在它们之间的字符(本例为0到9)。你还可以使用两个*
来匹配嵌套的文件夹 a/**/z
会匹配a/z a/b/z a/b/c/z
。
下面是一个.gitignore
文件的例子
yaml
# ignore all .a files
*.a
# but do track lib.a, even though you're ignoring .a files above
!lib.a
# only ignore the TODO file in the current directory, not subdir/TODO
/TODO
# ignore all files in any directory named build
build/
# ignore doc/notes.txt, but not doc/server/arch.txt
doc/*.txt
# ignore all .pdf files in the doc/ directory and any of its subdirectories
doc/**/*.pdf
Tip
github维护了很多.gitignore
文件的例子, github.com/github/giti...
Note 在这个简单的示例中,一个仓库可能在其根目录下有个.gitignore
文件,它会递归作用于整个仓库,然后,是可以在其子文件夹下添加其他的.gitignore
文件。这个嵌套的.gitignore
文件的规则之应用于它们所在的文件夹下。Linux kernel source 仓库就有206个.gitignore
文件。对于多个.gitignore
文件的细节超出了本书的范围。可以通过man gitignore
来获取更多详情。
Viewing Your Staged and Unstaged Changes
如果你想知道具体你修改了什么东西,你可以使用git diff
命令。我们后面会介绍更多关于git diff
的详情。使用git diff
你可以知道你修改了什么但还未被暂存,以及你暂存了什么将要提交的文件.
如果我们修改并暂存了README
文件,以及修改了 CONTRIBUTING.md
但没有暂存。如果你运行git status
命令,你可能会得到下面的输出:
console
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: README
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: CONTRIBUTING.md
要看你修改了什么但还未暂存,使用git diff
console
$ git diff
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8ebb991..643e24f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -65,7 +65,8 @@ branch directly, things can get messy.
Please include a nice description of your changes when you submit your PR;
if we have to read the whole diff to figure out why you're contributing
in the first place, you're less likely to get feedback and have your change
-merged in.
+merged in. Also, split your changes into comprehensive chunks if your patch is
+longer than a dozen lines.
If you are starting to work on a particular area, feel free to submit a PR
that highlights your work in progress (and note in the PR title that it's
这个命令对比了你工作目录和你暂存区的差别。告诉了你 你修改了什么但还未暂存。
使用git diff --staged
来比较暂存区文件和最后一次提交之间的区别。
css
$ git diff --staged
diff --git a/README b/README
new file mode 100644
index 0000000..03902a1
--- /dev/null
+++ b/README
@@ -0,0 +1 @@
+My Project
你需要知道git diff
并不会展示从你最后提交以来的所有修改,它展示的只是还未暂存的修改。如果你暂存了你所有的修改,git diff
将没有输出。
举另一个例子,如果你暂存了CONTRIBUTING.md
后续又修改了它,你可以使用git diff
,你会看到暂存区和未暂存的区别。如果你的环境像下面一样
console
$ git add CONTRIBUTING.md
$ echo '# test line' >> CONTRIBUTING.md
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: CONTRIBUTING.md
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: CONTRIBUTING.md
现在你可以使用git diff
来查看什么是还未暂存的
console
$ git diff
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 643e24f..87f08c8 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -119,3 +119,4 @@ at the
## Starter Projects
See our [projects list](https://github.com/libgit2/libgit2/blob/development/PROJECTS.md).
+# test line
Committing Your Changes
请记住对于你所创建或修改,如果你未运行git add
,这些修改就不会进入提交。当你准备提交你的修改时,最简单的方式就是使用git commit
:
console
$ git commit
运行该命令后会启动你的编辑器
Note
这取决于你的shell的EDITOR
环境变量,通常是vim或emacs,但是你可以使用git config --global core.editor
命令进行修改
编辑器会展示如下的内容
bash
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# Your branch is up-to-date with 'origin/master'.
#
# Changes to be committed:
# new file: README
# modified: CONTRIBUTING.md
#
~
~
~
".git/COMMIT_EDITMSG" 9L, 283C*
当退出编辑器的时候,git就会按照这个提交信息来创建提交(过滤掉备注和diff)
你还可以和使用commit
命令的时候同时指定提交信息
sql
$ git commit -m "Story 182: fix benchmarks for speed"
[master 463dc4f] Story 182: fix benchmarks for speed
2 files changed, 2 insertions(+)
create mode 100644 README
现在 你就进行了第一次提交。可以看到这个提交输出了一些信息:你提交到了哪个分支(master
),SHA-1
是多少,多少文件被修改了,以及新增 删除的行数。
请记住提交的是你在暂存区的文件。其他未被暂存的未被提交。每当你进行一次提交,你就为你的项目创建了一个快照,后边你可以进行回滚或者对比。
Skipping the Staging Area
有时候暂存这一步显得很多余,你可以使用git commit
的时候添加一个-a
来跳过执行git add
,git会在提交之前自动暂存被跟踪的文件。
console
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: CONTRIBUTING.md
no changes added to commit (use "git add" and/or "git commit -a")
$ git commit -a -m 'Add new benchmarks'
[master 83e38c7] Add new benchmarks
1 file changed, 5 insertions(+), 0 deletions(-)
使用-a
很方便,但注意该flag可能会让你提交一些你不想要的修改。
Removing Files
要从git中删除一个文件,你必须将其从你的跟踪文件中删除它,更准确的说,将它从你的暂存区中删除并提交,git rm
就是做这件事的,但是也会将文件从你的工作目录中删除。
如果你只是简单的将文件从你的工作目录中删除,它会展示"Changes not staged for commit"(即 未暂存)
console
$ rm PROJECTS.md
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
deleted: PROJECTS.md
no changes added to commit (use "git add" and/or "git commit -a")
但如果你执行的是git rm
,它就会暂存这个文件的删除
console
$ git rm PROJECTS.md
rm 'PROJECTS.md'
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
deleted: PROJECTS.md
当你下次提交的时候,这个文件就会消失并且不会再被跟踪。如果你编辑了这个文件或已经将其加入到了暂存区,你必须添加-f
来强制删除,这是一种保护措施,因为当你做了修改未被提交到git时,删除可能会造成你的修改无法恢复。
另一种情况是 你想保存文件在你的工作目录下,但是将其从你的暂存区删除。换句话说,我们想让文件还在我们的工作目录下,但是不让git继续跟踪。这是非常有用 当你忘记添加一些文件到你的.gitignore
文件,并不小心暂存了他,为达成这个目标,使用--cached
选项。
console
$ git rm --cached README
你可以传递文件 目录 或者glob pattern到git rm
命令,即你可以做类似下面的事情
shell
$ git rm log/\*.log
请注意*
前的\
是必要的,因为git有自己的文件名扩展方式,这样可以避免shell帮我们进行文件名扩展。这个命令会移除所有在log/
文件夹下的扩展名为.log
文件。
Moving Files
与其他VCS不同,git不会显式根据文件的移动,如果你对git中的一个文件重命名,git不会知道你重命名了这个文件。但是git时非常聪明的。
但git也有一个mv
命令,这就很令人困惑,如果你想要对git中的一个文件重命名,你可以执行下面的命令
shell
$ git mv file_from file_to
事实上,如果你执行这个命令并查看status,你会发现git知道它是一个重命名文件。
console
$ git mv README.md README
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
renamed: README.md -> README
但是这与下面的命令也是一致的
shell
$ mv README.md README
$ git rm README.md
$ git add README
git会隐式地直到这是一个重命名。实际上git mv
和上面的区别只是git mv
替代了这三条命令。这就非常方便。