进阶学习资料

本文主要介绍Git的一些进阶技巧命令的使用。

定制专属的快捷命令

掌握Git小技巧,设置别名,简化操作。  通过git config命令为常用Git指令设定简短别名。 

# 如设置co别名用于checkout
git config --global alias.co checkout 
git config --global alias.br branch 
git config --global alias.ci "commit -s"
git config --global alias.st status

比如,输入git ci即可代替冗长的git commit -s。随着Git使用深入,不妨为常用命令创建别名。例如,为简化取消暂存文件的操作,可自定义一个Git别名。

git config --global alias.unstage 'reset HEAD --'

以下是两种常用的方法,主要解决将文件从暂存区移除:

# 方法一: 适用于单个文件或多文件的情况。
git reset HEAD -- fileA
# 方法二: 虽然Git没有直接的unstage命令,可通过别名来简化操作。
git unstage fileA 

您可以使用git last来代替git log -1 HEAD命令,以查看最近一次的提交记录。

git config --global alias.last 'log -1 HEAD'

这样,可以轻松地看到最后一次提交信息:

# 这个命令是为Git配置一个全局别名last,使每次输入git last就能显示最近一次的提交信息。
git last   
commit 66938dae3329c7aebe598c2246a8e6af90d04646 Author: Josh Goebel  <dreamer3@example.com> Date:   Tue Aug 26 19:48:51 2008 +0800      test for current head      Signed-off-by: Scott Chacon <schacon@example.com>

代码合并与变基

在 Git 中整合来自不同分支的修改主要有两种方法:合并(merge) 以及变基(rebase)。

  • 合并:对于两个分支,如下图:

    整合分支最容易的方法是 Merge 命令。 它会把两个分支的最新快照(C3和 C4)以及二者最近的共同祖先(C2)进行三方合并,合并的结果是生成一个新的快照(并提交)

  • Rebase合并

    通过提取在 C4 中引入的补丁和修改,然后在 C3的基础上应用一次。 在 Git 中,这种操作就叫做变基。 您可以使用 rebase命令将提交到某一分支上的所有修改都移至另一分支上。

    在上面这个例子中,运行:

    git checkout experiment 
    git rebase master 
    First, rewinding head to replay your work on top of it...
    Applying: added staged command

    它的原理是首先找到这两个分支(即当前分支 Experiment、变基操作的目标基底分支 Master)的最近共同祖先 C2,然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件,然后将当前分支指向目标基底 C3, 最后以此将之前另存为临时文件的修改依序应用。

    现在回到master分支,进行一次快进合并。

    # 切换到Git仓库的master分支。
    git checkout master 
    
    # 合并当前分支到已检出的分支。在执行git merge之前,确保您已经切换到了想要合并到的目标分支。
    git merge experiment

  • 优雅地合并分支代码并提交

    对比两种合并方法,rebase能让提交历史更整洁,呈现串行趋势,因此备受青睐。它不仅能优雅地合并代码,还能修改提交历史。通过rebase,您可以将本地多个开发提交合并,再整合到目标分支,使历史记录更加清晰。

代码暂存

工作时,当您忙碌于项目某部分时,而这时想要切换到另一个分支做一点别的事情。 若需临时转至另一分支,当下半成品提交显得多余, 这时执行git stash命令可以将未完成的修改保存到一个栈上,而您可以随时重新应用这些改动切换分支。

以下为例,进入项目查看之前改动的文件,显示结果:

git status 
Changes to be committed:   
(use "git reset HEAD <file>..." to unstage)  	
modified:   index.html  
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:   lib/simplegit.rb

现在想要切换分支,但是还不想要进行提交,运行git stashgit stash save

git stash 
Saved working directory and index state \   
"WIP on master: 049d078 added the index file" 
HEAD is now at 049d078 added the index file 
(To restore them type "git stash apply")

工作目录变干净了,并且刚刚的修改都不存在了:

git status 
# On branch master nothing to commit, working directory clean

在这时,能够轻易地切换分支并在其他工作投入,此时修改将被存储在栈上, 想要查看栈存的东西,可以执行git stash list

git stash list 
stash@{0}: WIP on master: 049d078 added the index file 
stash@{1}: WIP on master: c264051 Revert "added file_size" 
stash@{2}: WIP on master: 21d80a5 added number to log

在本例中,有两个之前做的暂存,所以接触到了三个不同的暂存工作。 可以通过原来stash命令的帮助提示的命令将刚刚暂存的工作重新应用:git stash apply。 

如果想要应用其中一个更旧的暂存,可以通过名字指定它,比如:git stash apply stash@{2}, 如果不指定一个暂存,Git认为指定的是最近的暂存:

git stash apply 
# On branch master 
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed) 
# 
#      modified:   index.html 
#      modified:   lib/simplegit.rb #

可以看到Git重新修改了当您暂存时撤消的文件。 也可以运行git stash pop来应用暂存并从栈上扔掉它。

暂存的丢弃可以运行git stash drop加上将要移除的暂存的名字来移除它:

git stash list 
stash@{0}: WIP on master: 049d078 added the index file 
stash@{1}: WIP on master: c264051 Revert "added file_size" 
stash@{2}: WIP on master: 21d80a5 added number to log 
git stash drop stash@{0} 
Dropped stash@{0} (364e91f3f268f0900bc3ee613f9f733e82aaed43)

从暂存创建一个分支

如果暂存了一些工作,在后续重新应用工作时可能会有问题。 如果应用尝试修改刚刚修改的文件,您会得到一个合并冲突并不得不解决它。 如果想要一个轻松的方式来再次测试储藏的改动,可以运行git stash branch创建一个新分支,检出暂存工作时所在的提交,重新在那应用工作,然后在应用成功后扔掉:

git stash branch testchanges 
Switched to a new branch "testchanges" 
# On branch testchanges
# Changes to be committed: 
#   (use "git reset HEAD <file>..." to unstage) 
#      modified:   index.html 

# Changed but not updated: 
#   (use "git add <file>..." to update what will be committed) 
#      modified:   lib/simplegit.rb # Dropped refs/stash@{0} (f0dfc4d5dc332d1cee34a634182e168c4efc3359)

这是在新分支轻松恢复暂存工作并继续工作的一个很不错的途径。

修改代码历史

Git的强大之处在于它允许您随时调整提交历史。无论是调整顺序、修改信息、编辑文件,还是合并、拆分、删除提交,都可以在工作前轻松完成。

  • 修改最后一次提交

    最常见的操作是修改最近一次提交,您可能想更改提交信息或更新文件。

    如果,只是想修改最近一次提交的提交信息,那么很简单:

    git commit --amend

    想要精简提交信息或修改已提交的快照,只需进入文本编辑器修改最近一条提交信息,然后保存关闭即可。若已提交却忘记添加文件,可通过git addgit rm修改文件,再运行git commit --amend更新快照,但请注意,修正会改变提交的SHA-1校验和,类似小变基,推送后请勿修正。

  • 修改多个提交信息

    要修改历史中较早的提交,需使用更复杂的工具。Git无直接改变历史工具,但可用交互式变基。通过git rebase -i,可在任意提交后停止,修改信息、添加文件等。要修改最后三次提交,使用HEAD~3作为参数传递给git rebase -i命令。记住,这实际上指定了以前的四次提交中的父提交。

    git rebase -i HEAD~3

    需要重点注意的是相对于正常使用的 log 命令,这些提交显示的顺序是相反的。 运行一次 log 命令,会看到类似这样的东西:

    git log --pretty=format:"%h %s" HEAD~3..HEAD 
    a5f4a0d added cat-file 
    310154e updated README formatting and added blame 
    f7f3f6d changed my name a bit

    反序变基脚本, 它从指定提交(如HEAD~3)逆序重演每个变更,要停在某次变更上修改,编辑脚本,将想改的‘pick’换成‘edit’。

    例如,只调第三次提交

    edit f7f3f6d changed my name a bit 
    pick 310154e updated README formatting and added blame 
    pick a5f4a0d added cat-file

    当保存并退出编辑器时,Git将您带回到列表中的最后一次提交,把送回命令行并提示以下信息:

    git rebase -i HEAD~3 
    Stopped at f7f3f6d... changed my name a bit 
    You can amend the commit now, with
             git commit --amend  
    Once you’re satisfied with your changes, run
             git rebase --continue

    这些指令准确地告诉您该做什么。 

    git commit --amend

    修改提交信息,然后退出编辑器。 然后,运行

    git rebase --continue

    这个命令将会自动地应用另外两个提交,然后就完成了。 如果需要将不止一处的 pick 改为 edit,需要在每一个修改为 edit 的提交上重复这些步骤。 每一次,Git 将会停止,让您修正提交,然后继续直到完成。

    想重塑您的提交历史,可以通过变基命令操作,但需注意,此操作针对的是HEAD~3..HEAD范围内的提交,且会彻底重写它们,关键要点如下:

    • ‌重写提交‌:变基不仅修改内容,还重写提交信息。

    • ‌避免冲突‌:别碰已推送的提交,否则会造成版本混乱。

    • ‌编辑器操作‌:运行命令后,文本编辑器将展示待重写的提交列表。

    pick f7f3f6d changed my name a bit  # 使用这个提交。
    pick 310154e updated README formatting and added blame 
    pick a5f4a0d added cat-file  
    # Rebase 710f0f8..a5f4a0d onto 710f0f8 
    # 
    # Commands: 
    #  p, pick = use commit 
    #  r, reword = use commit, but edit the commit message 
    #  e, edit = use commit, but stop for amending 
    #  s, squash = use commit, but meld into previous commit 
    #  f, fixup = like "squash", but discard this commit's log message 
    #  x, exec = run command (the rest of the line) using shell 
    # 
    # These lines can be re-ordered; they are executed from top to bottom. 
    # 
    # If you remove a line here THAT COMMIT WILL BE LOST. 
    # 
    # However, if you remove everything, the rebase will be aborted. 
    # 
    # Note that empty commits are commented out
  • 重新排序提交

    也可以使用交互式变基来重新排序或完全移除提交。 如果想要移除 “added cat-file” 提交然后修改另外两个提交引入的顺序,可以将变基脚本从这样:

    pick f7f3f6d changed my name a bit 
    pick 310154e updated README formatting and added blame pick 
    a5f4a0d added cat-file

    改为这样:

    pick 310154e updated README formatting and added blame 
    pick f7f3f6d changed my name a bit

    当保存并退出编辑器时,Git 将您的分支带回这些提交的父提交,应用 310154e,然后应用 f7f3f6d,最后停止。 事实修改了那些提交的顺序并完全地移除了 “added cat-file” 提交。

  • 压缩提交

    通过交互式变基工具,也可以将一连串提交压缩成一个单独的提交。 在变基信息中脚本给出了有用的指令。

    # Commands: 
    #  p, pick = use commit 
    #  r, reword = use commit, but edit the commit message 
    #  e, edit = use commit, but stop for amending 
    #  s, squash = use commit, but meld into previous commit 
    #  f, fixup = like "squash", but discard this commit's log message 
    #  x, exec = run command (the rest of the line) using shell 
    # 
    # These lines can be re-ordered; they are executed from top to bottom. 
    # 
    # If you remove a line here THAT COMMIT WILL BE LOST. 
    # 
    # However, if you remove everything, the rebase will be aborted. 
    # 
    # Note that empty commits are commented out

    如果,指定 “squash” 而不是 “pick” 或 “edit”,Git将应用两者的修改并合并提交信息在一起。 所以,如果想要这三次提交变为一个提交,可以这样修改脚本。

    pick f7f3f6d changed my name a bit squash 
    310154e updated README formatting and added blame squash 
    a5f4a0d added cat-file

    当保存并退出编辑器时,Git 应用所有的三次修改然后将你放到编辑器中来合并三次提交信息。

    # This is a combination of 3 commits. 
    # The first commit's message is: changed my name a bit  
    # This is the 2nd commit message:  updated README formatting and added blame  
    # This is the 3rd commit message:  added cat-file

    当你保存之后,你就拥有了一个包含前三次提交的全部变更的提交。

  • 拆分提交

    拆分一个提交会撤消这个提交,然后多次地部分地暂存与提交直到完成你所需次数的提交。 例如,假设想要拆分三次提交的中间那次提交。 想要将它拆分为两次提交:第一个 “updated README formatting”,第二个 “added blame” 来代替原来的 “updated README formatting and added blame”。 可以通过修改rebase -i的脚本来做到这点,将要拆分的提交的指令修改为 “edit”。

    pick f7f3f6d changed my name a bit edit 
    310154e updated README formatting and added blame pick 
    a5f4a0d added cat-file

    然后,当脚本将你进入到命令行时,重置那个提交,拿到被重置的修改,从中创建几次提交。 当保存并退出编辑器时,Git带您到列表中第一个提交的父提交,应用第一个提交(f7f3f6d),应用第二个提交(310154e),然后让你进入命令行。 那里,可以通过git reset HEAD^ 做一次针对那个提交的混合重置,实际上将会撤消那次提交并将修改的文件未暂存。 现在可以暂存并提交文件直到有几个提交,然后当完成时运行git rebase --continue

    git reset HEAD^ 
    git add README 
    git commit -m 'updated README formatting' 
    git add lib/simplegit.rb 
    git commit -m 'added blame' 
    git rebase --continue

    Git在脚本中应用最后一次提交(a5f4a0d),历史记录看起来像这样:

    git log -4 --pretty=format:"%h %s" 
    1c002dd added cat-file 
    9b29157 added blame 35cfb2b updated README formatting 
    f3cc40e changed my name a bit

    再次强调,如果您正打算推送提交到共享仓库,请先暂停。我们有一个重要更新:所有在列表中的提交的SHA-1校验和已经更改。为确保数据的完整性和避免冲突,请务必确认这些提交尚未被推送到共享仓库。