(GitBook) 如何将你的修改记录到仓库中

3 阅读7分钟

Recording Changes to the Repository

source:git-scm.com/book/en/v2/…

当前在你的电脑上应该就有了一个git仓库。现在你应该很想对文件进行一些修改,并在完成一阶段的修改工作之后,对这些修改进行快照并提交到git仓库。

所有在你的工作目录下的文件都只会存在两种状态:tracked或者untrackedTracked文件就是能在最新的快照中存在的文件或新加入暂存区的文件,有unmodified modified staged集中状态,简而言之啊,trackedfile就是Git进行管理的文件‘

Untracked file就是除tracked文件之外的文件。即不在你最新快照中,也不在暂存区中。当你首次clone一个仓库,你的所有文件都是trackedunmodified的,因为git只是checkout 而你也没对文件进行任何修改。

当你编辑文件时,git就会将这些编辑过的文件视为modified,因为在最近的一次commit之后,这些文件发生了变化。随后,你可以将这个修改后的文件选择性地进行暂存,并进行提交,然后就重新进入了状态循环。

The lifecycle of the status of your files

Checking the Status of Your Files

你用来确定文件处于什么状态的工具就是命令git status。如果你在clone之后直接运行该命令,你会得到下面的输出:

$ 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文件如下所示:

$ 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)

可以看到READMEuntrackedUntracked代表git在最新的快照中没有该文件,而且当前还没有被暂存。Git不会将其纳入管理除非你显式告诉git。这样你就不必担心生成的二进制文件或者你不想假如git的文件 进行提交。

Tracking New Files

为了对一个新文件进行跟踪,你需要使用命令git add。例如,你想跟踪README文件。你应该运行

$ git add README

此时重新运行status命令,就会发现README文件已经被跟踪了并且已被暂存。

$ 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查看,就会得到下面的输出

$ 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:

$ 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时,

$ 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 同时在stagedunstaged下。这实际上是因为,git暂存中的文件实际时你运行git add当时的文件。如果你现在进行提交CONTRIBUTING.md 的版本就是你执行git add时的版本而不是执行git commit时的版本。如果你在运行git add之后修改了文件,你需要再次执行git add来暂存这个文件的最新版本。

$ 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,你就可以看到这种简化的输出。

$ 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文件的一个示例:

$ 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文件的例子

# 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命令,你可能会得到下面的输出:

$ 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

$ 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来比较暂存区文件和最后一次提交之间的区别。

$ 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,你会看到暂存区和未暂存的区别。如果你的环境像下面一样

$ 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来查看什么是还未暂存的

$ 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:

$ git commit

运行该命令后会启动你的编辑器


Note

这取决于你的shell的EDITOR环境变量,通常是vim或emacs,但是你可以使用git config --global core.editor命令进行修改


编辑器会展示如下的内容

# 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命令的时候同时指定提交信息

$ 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会在提交之前自动暂存被跟踪的文件。

$ 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”(即 未暂存)

$ 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,它就会暂存这个文件的删除

$ 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选项。

$ git rm --cached README

你可以传递文件 目录 或者glob pattern到git rm命令,即你可以做类似下面的事情

$ git rm log/\*.log

请注意*前的\是必要的,因为git有自己的文件名扩展方式,这样可以避免shell帮我们进行文件名扩展。这个命令会移除所有在log/文件夹下的扩展名为.log文件。

Moving Files

与其他VCS不同,git不会显式根据文件的移动,如果你对git中的一个文件重命名,git不会知道你重命名了这个文件。但是git时非常聪明的。

但git也有一个mv命令,这就很令人困惑,如果你想要对git中的一个文件重命名,你可以执行下面的命令

$ git mv file_from file_to

事实上,如果你执行这个命令并查看status,你会发现git知道它是一个重命名文件。

$ 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

但是这与下面的命令也是一致的

$ mv README.md README
$ git rm README.md
$ git add README

git会隐式地直到这是一个重命名。实际上git mv和上面的区别只是git mv替代了这三条命令。这就非常方便。