好久没更新了,上一次还是22年8月. 因为我换工作啦,一直处于 ramp up 的状态…
之前用 SVN 比较多,现在终于纯纯的用 git 了,总结一下常用且容易忘的~

git commit

git commit --allow-empty -m "empty commit" 可以产生一个空的提交, 可以用来进行一些测试 git commit --amend 最新提交有问题需要修改并且不想产生一个新的提交可以用 amend 参数

git stash

场景:当本地某Bug/Feature(下面称其为A)正在处理时,你又接到新的Bug/Feature(下面称其为B)需要先紧急处理,此时你也许可以这样来做:先提交前面未完成的A, 处理好B之后再来继续A, 但是这样一来关于A的提交其实是不完整的.那么应该怎么办?其实有个 git stash 的命令可以解决这个问题. git stash会把所有未提交的修改(包括暂存的和非暂存的)都保存起来,用于后续恢复当前工作目录.

下面简单列一下流程:

  1. git stash list 查看当前stash的列表
  2. git stash 暂存当前未提交的修改(注意与git add是不一样的)
  3. 切换其他分支处理Bug/Feature,或是git pull等操作
  4. git stash pop 恢复暂存的修改

Note1: 上面其实是省略了 push 这个子命令, 加上这个子命令可以做更多事情, 比如:

  1. git stash push -m <message> 暂存当前内容并添加一个 <message> 的描述, 这样当有很多 stash 时可以更容易知道某个 stash 是做什么的
  2. git stash push [file1] [file2] 可以只 stash 特定的文件

Note2: git stash pop stash@{version} stash list 中多条暂存内容时可以选择某一条 pop 出来, 否则会pop最新的

Note3: git stash drop stash@{version} 可以丢弃某个 stash

git rebase

basic

rebase 跟 merge 都能合并分支, 只是 rebase 会修改「历史记录」. 按我目前的理解 rebase 最常用的使用场景有两个:

  1. 当你本地修改落后于远程仓库时(已经产生本地的 commit 而不是仍处于工作区的(working directory), 处于工作区的可以用 git stash),你想在最新代码上继续本地修改,你可以用 rebase 而非 merge 来更新最新代码,因为 merge 会产生一个合并的记录,而这种类型的合并记录在历史记录中其实没有什么价值。
    另外 git pull 默认是 git fetch + git merge, 我们可以把 git merge 改成 git rebase, 方法是 git config --global pull.rebase true

  2. 本地有多个 commit 准备推送到远程仓库, 但是此时你发现前面某个 commit 需要修改(不是最新的那个, 最新那个可以通过 git commit --amend 来修改), 此时可以用 git rebase -i <previous_commit_id>. 这里的 <previous_commit_id> 就是想要修改的 commit 的前一个-i 参数是打开交互模式, 此时会弹出编辑器, 将想要修改的「pick commitID」 改成「edit commitID」, 保存退出编辑器后就可以修改对应代码。代码修改完成后执行 git add . git commit --amend(可选) git rebase --continue. 然后git会为后续提交执行 rebase, 如果有冲突还需要手动解决冲突.

合并多个 commit

git rebase -i [start] [end] 其中 [start] 不会被包含, [end] 会被包含。 [end] 也可以省略, 表示到最新。
假设你的 start 和 end 指定了 ABCDE 五个 commit(E是最新的):
pick A
pick B
pick C
pick D
pick E
你想要把 BCD 合并成一个 B’, 那么你需要修改 CD 前面的 pick 为 squash 或者 fixup (squash 会保留 commit msg 而 fixup 不会) 并保持退出就可以了。

用 rebase 丢弃 commit

如果某次 commit 提交错了, 你又不想通过新的提交来修复这次的错误, 而是想直接从「历史记录」中丢弃这次修改, 那就可以使用 git rebase 中的 drop commit 功能. (git reset HEAD~ --hard 可以丢弃最新的一个, 或者 git reset HEAD~~ --hard 丢弃最新的两个, 以此类推. 但是不能丢弃中间的某一个, git rebase 可以)

大致流程:

  1. 保证工作区没有修改(如果有可以使用stash暂存)
  2. git rebase -i commitID (这个id是你想要删除的的commitID的前一个, -i 是进入交互模式)
  3. 此时会弹出编辑器, 将你想要丢弃的「pick commitID」改成「drop commitID」
  4. 保存退出

PS: 其实上面说的「想要删除的的commitID的前一个」, 不一定要这样, 只要保证是更早之前的提交就行, 只是这样一来后面弹出的编辑器里面就会有多条, 注意将对应的那次提交的「pick」改成「drop」就行.

git clean

git clean -fd <path> remove untracked files/folders

git cherry-pick

将一个分支的某次提交应用到当前分支,会在当前分支产生一个新的提交,其内容就是前面的那次。加 -x 参数可以在提交信息中自动加上这次提交是从哪个提交pick过来的。

有时有这样一个需求:只想把某分支的某个commit的修改内容拿过来,但是不想在当前分支上产生一次commit,也就是仅仅把修改的内容拿过来,后面可能需要在修改或者应用到当前的commit上(使用git commit --amend)。
达到这个目的可以先 git cherry-pick <commitID>git reset。这是很快的两步操作,当然正规做法似乎是先 git diff <commit1> <commit2> > some.patchgit apply some.patch (当然还要 rm some.patch), 但是如果有冲突 apply patch 会失败, cherry-pick 则可以手动解决冲突.

git show

git show <commit-id> <filename> 显示某次提交的某个文件的改动内容

git show <tag> show detailed info about this tag

git tag

git tag --contains <commit> 可以查看包含某个 commit 的所有 tag

git log

git log -- <filename> 查看某个文件的修改历史。特别的,这里将 <filename> 改成子模块的路径就可以查看子模块的更新历史,我的意思不是查看子模块的全部记录(这完全可以进入子模块然后 git log 查看)而是查看父仓库中在何时使用了子模块的新的 commitID。接下来看具体修改内容(其实就是commitID的变更)就参考上面的 git show 就好了

git branch

分支重命名 git branch -m {{old_branch_name}} {{new_branch_name}}

分支删除 git branch -D {{branch_name}}

改变本地分支跟踪的远程分支 git branch -u <upstream/your_branch> <your_branch>

git push <远程主机名> <本地分支名>:<远程分支名>

git push origin -d <remote_branch_name> 删除远程分支

git bisect

git 提供的二分查找某个问题是在哪次提交中被引入的。 用法:

  1. git bisect start <new commit> <old commit>
  2. after step1 git will checkout to the middle commit between new and old, we should check good or bad in current state and type git bisect bad or git bisect good
  3. repeat step2 until we find the first bad commit
  4. after we find the first bad commit, we can type git bisect reset to reset the bisect state.

git fomat-patch && git apply

git format-patch -1 and git apply --whitespace=fix --reject xxx.patch

git reflog

This command manages the information recorded in the reflogs.
Reference logs, or “reflogs”, record when the tips of branches and other references were updated in the local repository.

当你执行 git reset HEAD~ --hard 丢掉了某些记录后, 可以通过 git reflog 来查看前面的记录:

1
2
1a5e804 HEAD@{1}: commit: update git and c_c++ nodes
35d09d2 HEAD@{2}: rebase (finish): returning to refs/heads/main

接着可以使用 git reset 1a5e804 来恢复之前的提交

git submodule

git submodule update --init --recursive fetch submodules

git submodule status 可以查看所有 submodule 的状态,包括每个 submodule 当前所在的 commit id
其中 ‘-’ 前缀表示这个子仓库还没有初始化, 也就是子仓库的目录存在, 里面的内容不存在.
‘+’ 前缀表示子仓库当前所在的 commit id 与父仓库中存的 commit id 不一样.
‘U’ 前缀表示子仓库存在冲突.

submodule 的 commit id 存在在父项目中的 database 中。 参考:Where does Git store the SHA1 of the commit for a submodule?

submodule 一般情况下由其他人维护,若 submodule 本身有了一些更新,这时你想应用某个更新(也就是某个 commit 时),你可以在子项目路径下 checkout 到某个 commit id, 然后到父项目路径中提交并推送到远程.(注意这里不是 submodule 所在仓库的提交, 是父仓库更新 submodule 的 commit id) 而作为父仓库使用者,当 submodule 的 commit id 更新后, 可以使用git pull && git submodule update进行更新。

git submodule update --remote 是拉取submodule远端的更新并checkout到最新, 可能会存在与父仓库存储的 submodule 的 commit id 不一样的情况(上面说的 ‘+’ 前缀)

git log <filename> 一样, git log <submodule path> 也可以直接查看某个 submodule 的 commit id 在父仓库中的更新历史

当 submodule 的 uri 需要更新时

  1. 修改 .gitmodules 中的 uri
  2. 修改 .git/config 中的 uri
  3. 删除 .git/modules/ 下 submodule 文件夹
  4. 删除 ./ 下 submodule 文件夹
  5. git submodule update –init

有多个 submodule 只想初始化其中一个时

有时会有很多 submodule, 全部初始化并下载代码非常费时, 只想查看一个或几个子仓库时, 可以这样:

  1. git submodule init {{path/to/submodule}}
  2. git submodule update --recursive

git remote

git remote 是用来操作远程仓库相关的命令.
下面用一个例子来说明相关命令

  1. 你在 Github 上 fork 了一个仓库(用于自己修改代码或者提交 pull request)并clone到了本地
  2. 此时 git remote -vv 的结果是一条: origin https://...
  3. 你在本地的这个仓库中输入此命令 git remote add upstream https://...upstream 是一个名称, 你可以起任意名字, 最后那个链接是你fork仓对应的原始仓库。
  4. 这时你再输入 git remote -vv 的结果就是两条:origin https://... upstream https://...
  5. git remote update 可以拉取最新的全部的远程仓库的更新(等同于 git fetch origingit fetch upstream)
  6. 基于最新的 upstream/master 创建开发分支(比如 dev), 然后 git push origin dev. 之后就可以在 web 上创建PR
  7. 工作一段时间后, 远程仓库(origin和upstream都)有可能删除了一些分支,但是你本地还存在这些分支,你就可以 git remote prune origin 或者 git remote prune upstream 来删除本地的这些远程分支