• 首页
  • vue
  • TypeScript
  • JavaScript
  • scss
  • css3
  • html5
  • php
  • MySQL
  • redis
  • jQuery
  • Git 保存代码

    如果你是在一个已经存在文件的文件夹(而不是空文件夹)中初始化 Git 仓库来进行版本控制的话,你应该开始跟踪这些文件并提交。

    使用版本控制时,与团队保存和共享代码版本是最常见的操作。 Git 为这些任务提供了简单的三步工作流:

    • 创建新的工作分支。
    • 提交更改。
    • 推送分支以与团队共享。

    使用 Git 可以轻松地使用分支管理工作。每个 bugfix、新功能、添加了测试和更新的配置都以新分支开头。分支是轻量级和本地开发计算机,因此在推送分支之前,无需担心使用资源或协调更改。

    分支使你能够与开发中的其他更改隔离代码。一旦一切正常,分支及其更改就会与你的团队共享。其他人可以在自己的分支副本中试验代码,而不会影响其自己的分支中正在进行的工作。


    创建分支

    基于当前分支中的代码创建分支,例如 main,在开始新工作时。最好先检查在创建新分支之前使用git status哪个分支进行选择。在 Git 中创建分支:

    git branch 
    

    创建分支后,切换到该分支。

    git checkout branchname
    


    Git 有一个简写命令,用于创建分支并同时切换到该分支:

    git checkout -b branchname
    


    保存更改

    Git 不会在进行编辑时自动创建快照代码。 Git 必须准确告知要添加到下一快照的更改。这称为暂存。暂存更改后,创建提交以永久保存快照。

    Git 跟踪存储库中发生的文件更改。它将这些更改分为三个类别:

    • 自上次提交以来,未修改的文件尚未更改。
    • 自上次提交以来,已修改的文件发生了更改,但尚未暂存下一个提交。
    • 暂存文件具有将添加到下一个提交的更改。



    创建提交时,仅对快照使用暂存更改和未更改的文件。未标记的更改保留在文件系统上,但提交在其快照中使用未修改的文件。


    检查当前文件状态

    要查看哪些文件处于什么状态,可以用git status命令。如果在克隆仓库后立即使用此命令,会看到类似这样的输出:

    git status
    
    # 显示如下:
    On branch master
    nothing to commit, working directory clean
    

    这说明你现在的工作目录相当干净。换句话说,所有已跟踪文件在上次提交后都未被更改过。此外,上面的信息还表明,当前目录下没有出现任何处于未跟踪状态的新文件,否则 Git 会在这里列出来。最后,该命令还显示了当前所在分支,并告诉你这个分支同远程服务器上对应的分支没有偏离。现在,分支名是master,这是默认的分支名。我们在 Git 分支会详细讨论分支和引用。

    现在,让我们在项目下创建一个新的README文件。如果之前并不存在这个文件,使用git status命令,你将看到一个新的未跟踪文件:

    echo 'My Project' > README
    git status
    
    # 显示如下:
    On branch master
    Untracked files:
      (use "git add ..." to include in what will be committed)
    
        README
    
    nothing added to commit but untracked files present (use "git add" to track)
    

    在状态报告中可以看到新建的README文件出现在Untracked files下面。未跟踪的文件意味着 Git 在之前的快照(提交)中没有这些文件;Git 不会自动将之纳入跟踪范围,除非你明明白白地告诉它“我需要跟踪该文件”,这样的处理让你不必担心将生成的二进制文件或其它不想被跟踪的文件包含进来。不过现在的例子中,我们确实想要跟踪管理 README 这个文件。


    跟踪新文件

    使用命令git add开始跟踪一个文件。所以,要跟踪README文件,运行:

    git add README
    

    然后,再运行git status命令,会看到README文件已被跟踪,并处于暂存状态:

    git status
    
    # 显示如下:
    On branch master
    Changes to be committed:
      (use "git reset HEAD ..." to unstage)
    
        new file:   README
    

    只要在Changes to be committed这行下面的,就说明是已暂存状态。如果此时提交,那么该文件此时此刻的版本将被留存在历史记录中。你可能会想起之前我们使用git init后就运行了git add(files)命令,开始跟踪当前目录下的文件。git add命令使用文件或目录的路径作为参数;如果参数是目录的路径,该命令将递归地跟踪该目录下的所有文件。


    暂存已修改文件

    现在我们来修改一个已被跟踪的文件。如果你修改了一个名为CONTRIBUTING.md的已被跟踪的文件,然后运行git status命令,会看到下面内容:

    git status
    
    # 显示如下:
    On branch master
    Changes to be committed:
      (use "git reset HEAD ..." to unstage)
    
        new file:   README
    
    Changes not staged for commit:
      (use "git add ..." to update what will be committed)
      (use "git checkout -- ..." to discard changes in working directory)
    
        modified:   CONTRIBUTING.md
    

    文件CONTRIBUTING.md出现在Changes not staged for commit这行下面,说明已跟踪文件的内容发生了变化,但还没有放到暂存区。要暂存这次更新,需要运行git add命令。这是个多功能命令:可以用它开始跟踪新文件,或者把已跟踪的文件放到暂存区,还能用于合并时把有冲突的文件标记为已解决状态等。将这个命令理解为“添加内容到下一次提交中”而不是“将一个文件添加到项目中”要更加合适。现在让我们运行git add将"CONTRIBUTING.md"放到暂存区,然后再看看git status的输出:

    git add CONTRIBUTING.md
    git status
    
    # 显示如下:
    On branch master
    Changes to be committed:
      (use "git reset HEAD ..." to unstage)
    
        new file:   README
        modified:   CONTRIBUTING.md
    

    现在两个文件都已暂存,下次提交时就会一并记录到仓库。假设此时,你想要在CONTRIBUTING.md里再加条注释,重新编辑存盘后,准备好提交。不过且慢,再运行git status看看:

    vim CONTRIBUTING.md
    git status
    
    # 显示如下:
    On branch master
    Changes to be committed:
      (use "git reset HEAD ..." to unstage)
    
        new file:   README
        modified:   CONTRIBUTING.md
    
    Changes not staged for commit:
      (use "git add ..." to update what will be committed)
      (use "git checkout -- ..." to discard changes in working directory)
    
        modified:   CONTRIBUTING.md
    

    怎么回事?现在CONTRIBUTING.md文件同时出现在暂存区和非暂存区。这怎么可能呢?好吧,实际上 Git 只不过暂存了你运行git add命令时的版本,如果你现在提交,CONTRIBUTING.md的版本是你最后一次运行git add命令时的那个版本,而不是你运行git commit时,在工作目录中的当前版本。所以,运行了git add之后又作了修订的文件,需要重新运行git add把最新版本重新暂存起来:

    git add CONTRIBUTING.md
    git status
    
    # 显示如下:
    On branch master
    Changes to be committed:
      (use "git reset HEAD ..." to unstage)
    
        new file:   README
        modified:   CONTRIBUTING.md
    


    状态简览

    git status命令的输出十分详细,但其用语有些繁琐。如果你使用git status -s命令或git status --short命令,你将得到一种更为紧凑的格式输出。运行git status -s,状态报告输出如下:

    git status -s
    
    # 显示如下:
     M README
    MM Rakefile
    A  lib/git.rb
    M  lib/simplegit.rb
    ?? LICENSE.txt
    

    新添加的未跟踪文件前面有??标记,新添加到暂存区中的文件前面有A标记,修改过的文件前面有M标记。你可能注意到了M有两个可以出现的位置,出现在右边的M表示该文件被修改了但是还没放入暂存区,出现在靠左边的M表示该文件被修改了并放入了暂存区。例如,上面的状态报告显示: README 文件在工作区被修改了但是还没有将修改后的文件放入暂存区。lib/simplegit.rb 文件被修改了并将修改后的文件放入了暂存区。而 Rakefile 在工作区被修改并提交到暂存区后又在工作区中被修改了,所以在暂存区和工作区都有该文件被修改了的记录。



    忽略文件

    一般我们总会有些文件无需纳入 Git 的管理,也不希望它们总出现在未跟踪文件列表。通常都是些自动生成的文件,比如日志文件,或者编译过程中创建的临时文件等。在这种情况下,我们可以创建一个名为.gitignore的文件,列出要忽略的文件模式。来看一个实际的例子:

    cat .gitignore
    
    # 显示如下:
    *.[oa]
    *~
    

    第一行告诉 Git 忽略所有以.o.a结尾的文件。一般这类对象文件和存档文件都是编译过程中出现的。第二行告诉 Git 忽略所有以波浪符(~)结尾的文件,许多文本编辑软件(比如 Emacs)都用这样的文件名保存副本。此外,你可能还需要忽略 log,tmp 或者 pid 目录,以及自动生成的文档等等。要养成一开始就设置好.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`等。

    我们再看一个.gitignore 文件的例子:

    # no .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 the build/ directory
    build/
    
    # ignore doc/notes.txt, but not doc/server/arch.txt
    doc/*.txt
    
    # ignore all .pdf files in the doc/ directory
    doc/**/*.pdf
    

    Tip:GitHub 有一个十分详细的针对数十种项目及语言的.gitignore文件列表,你可以在 https://github.com/github/gitignore 找到它.


    查看已暂存和未暂存的修改

    如果git status命令的输出对于你来说过于模糊,你想知道具体修改了什么地方,可以用git diff命令。稍后我们会详细介绍git diff,你可能通常会用它来回答这两个问题:当前做的哪些更新还没有暂存?有哪些更新已经暂存起来准备好了下次提交?尽管git status已经通过在相应栏下列出文件名的方式回答了这个问题,git diff将通过文件补丁的格式显示具体哪些行发生了改变。

    假如再次修改 README 文件后暂存,然后编辑CONTRIBUTING.md文件后先不暂存,运行status命令将会看到:

    git status
    
    # 显示如下:
    On branch master
    Changes to be committed:
      (use "git reset HEAD ..." to unstage)
    
        modified:   README
    
    Changes not staged for commit:
      (use "git add ..." to update what will be committed)
      (use "git checkout -- ..." 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 --cached命令。(Git 1.6.1 及更高版本还允许使用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 status会看到暂存前后的两个版本。如果我们的环境(终端输出)看起来如下:

    git add CONTRIBUTING.md
    echo '# test line' >> CONTRIBUTING.md
    git status
    
    # 显示如下:
    On branch master
    Changes to be committed:
      (use "git reset HEAD ..." to unstage)
    
        modified:   CONTRIBUTING.md
    
    Changes not staged for commit:
      (use "git add ..." to update what will be committed)
      (use "git checkout -- ..." 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
    

    然后用git diff --cached查看已经暂存起来的变化:(--staged 和--cached 是同义词)

    git diff --cached
    
    # 显示如下:
    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
    

    Note:Git Diff 的插件版本在本书中,我们使用git diff来分析文件差异。但是,如果你喜欢通过图形化的方式或其它格式输出方式的话,可以使用git difftool命令来用 Araxis ,emerge 或 vimdiff 等软件输出 diff 分析结>果。使用git difftool --tool-help命令来看你的系统支持哪些 Git Diff 插件。


    提交更新

    现在的暂存区域已经准备妥当可以提交了。在此之前,请一定要确认还有什么修改过的或新建的文件还没有git add过,否则提交的时候不会记录这些还没暂存起来的变化。这些修改过的文件只保留在本地磁盘。所以,每次准备提交前,先用git status看下,是不是都已暂存起来了,然后再运行提交命令git commit

    git commit
    

    这种方式会启动文本编辑器以便输入本次提交的说明。(默认会启用 shell 的环境变量$EDITOR所指定的软件,一般都是 vim 或 emacs。当然也可以按照起步介绍的方式,使用git config --global core.editor命令设定你喜欢的编辑软件。)

    编辑器会显示类似下面的文本信息(本例选用 Vim 的屏显方式展示):

    # Please enter the commit message for your changes. Lines starting
    # with '#' will be ignored, and an empty message aborts the commit.
    # On branch master
    # Changes to be committed:
    #   new file:   README
    #   modified:   CONTRIBUTING.md
    #
    ~
    ~
    ~
    ".git/COMMIT_EDITMSG" 9L, 283C
    

    可以看到,默认的提交消息包含最后一次运行git status的输出,放在注释行里,另外开头还有一空行,供你输入提交说明。你完全可以去掉这些注释行,不过留着也没关系,多少能帮你回想起这次更新的内容有哪些。(如果想要更详细的对修改了哪些内容的提示,可以用-v选项,这会将你所做的改变的 diff 输出放到编辑器中从而使你知道本次提交具体做了哪些修改。)退出编辑器时,Git 会丢掉注释行,用你输入提交附带信息生成一次提交。

    另外,你也可以在commit命令后添加-m选项,将提交信息与命令放在同一行,如下所示:

    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 校验和是什么(463dc4f),以及在本次提交中,有多少文件修订过,多少行添加和删改过。

    请记住,提交时记录的是放在暂存区域的快照。任何还未暂存的仍然保持已修改状态,可以在下次提交时纳入版本管理。每一次运行提交操作,都是对你项目作一次快照,以后可以回到这个状态,或者进行比较。


    跳过使用暂存区域

    尽管使用暂存区域的方式可以精心准备要提交的细节,但有时候这么做略显繁琐。 Git 提供了一个跳过使用暂存区域的方式,只要在提交的时候,给git commit加上-a选项,Git 就会自动把所有已经跟踪过的文件暂存起来一并提交,从而跳过git add步骤:

    git status
    
    # 显示如下:
    On branch master
    Changes not staged for commit:
      (use "git add ..." to update what will be committed)
      (use "git checkout -- ..." 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 'added new benchmarks'
    [master 83e38c7] added new benchmarks
     1 file changed, 5 insertions(+), 0 deletions(-)
    

    看到了吗?提交之前不再需要git add文件“CONTRIBUTING.md”了。


    移除文件

    要从 Git 中移除某个文件,就必须要从已跟踪文件清单中移除(确切地说,是从暂存区域移除),然后提交。可以用git rm命令完成此项工作,并连带从工作目录中删除指定的文件,这样以后就不会出现在未跟踪文件清单中了。

    如果只是简单地从工作目录中手工删除文件,运行git status时就会在“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 ..." to update what will be committed)
      (use "git checkout -- ..." 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
    Changes to be committed:
      (use "git reset HEAD ..." to unstage)
    
        deleted:    PROJECTS.md
    

    下一次提交时,该文件就不再纳入版本管理了。如果删除之前修改过并且已经放到暂存区域的话,则必须要用强制删除选项-f(译注:即 force 的首字母)。这是一种安全特性,用于防止误删还没有添加到快照的数据,这样的数据不能被 Git 恢复。

    另外一种情况是,我们想把文件从 Git 仓库中删除(亦即从暂存区域移除),但仍然希望保留在当前工作目录中。换句话说,你想让文件保留在磁盘,但是并不想让 Git 继续跟踪。当你忘记添加.gitignore文件,不小心把一个很大的日志文件或一堆.a这样的编译生成文件添加到暂存区时,这一做法尤其有用。为达到这一目的,使用--cached选项:

    git rm --cached README
    

    git rm命令后面可以列出文件或者目录的名字,也可以使用glob模式。比方说:

    git rm log/\*.log
    

    注意到星号*之前的反斜杠\,因为 Git 有它自己的文件模式扩展匹配方式,所以我们不用 shell 来帮忙展开。此命令删除log/目录下扩展名为.log的所有文件。类似的比如:

    git rm \*~
    

    该命令为删除以~结尾的所有文件。


    移动文件

    不像其它的 VCS 系统,Git 并不显式跟踪文件移动操作。如果在 Git 中重命名了某个文件,仓库中存储的元数据并不会体现出这是一次改名操作。不过 Git 非常聪明,它会推断出究竟发生了什么,至于具体是如何做到的,我们稍后再谈。

    既然如此,当你看到 Git 的mv命令时一定会困惑不已。要在 Git 中对文件改名,可以这么做:

    git mv file_from file_to
    

    它会恰如预期般正常工作。实际上,即便此时查看状态信息,也会明白无误地看到关于重命名操作的说明:

    git mv README.md README
    git status
    
    # 显示如下:
    On branch master
    Changes to be committed:
      (use "git reset HEAD ..." to unstage)
    
        renamed:    README.md -> README
    

    其实,运行git mv就相当于运行了下面三条命令:

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

    如此分开操作,Git 也会意识到这是一次改名,所以不管何种方式结果都一样。两者唯一的区别是,mv是一条命令而另一种方式需要三条命令,直接用git mv轻便得多。不过有时候用其他工具批处理改名的话,要记得在提交前删除老的文件名,再添加新的文件名。


    git add

    • git add *.html:添加某个文件类型到暂存区,比如所有的.html 文件。
    • git add index/:添加整个文件夹到暂存区,比如 index 文件夹。
    • git add index/index.html:添加某个文件或者某个文件夹中的某个文件到暂存区,比如 index 下的 index.html 文件。


    Git Version 2.x
    命令长命令新文件被修改过的文件被删除的文件是否受当前目录限制说明
    git add -Agit add --all把当前整个工作区中所有的文件改动提交至暂存区。包括新增、修改、删除的文件,不受当前所在目录的限制。
    git add -ugit add --update将当前整个工作区中修改和删除的文件提交至暂存区。而新文件因为未被追踪(untrack),所以不能被提交至暂存区。
    git add *将当前工作区中当前目录(包括子目录)下的所有的文件提交至暂存区。包括新增、修改、删除的文件。但不包括文件名以.(点)开头的文件以及目录。并且忽略.gitignore配置。
    git add .将当前工作区中当前目录(包括子目录)下的所有的文件提交至暂存区。包括新增、修改、删除的文件。也包括文件名以.(点)开头的文件以及目录。并且会根据.gitignore配置做过滤。

    上篇:Git 存储库

    下篇:Git 撤消操作