一些不常使用但有趣的 Git 命令
发布于 2022-11-23
使用指定的私钥克隆仓库
当具有多个私钥时, git clone
时可以指定私钥克隆:
git clone REMOTE_GIT_URL --config core.sshCommand="ssh -i ~/.ssh/id_rsa.other"
不同的目录下执行 git 使用不同的配置文件
includeIf
是 Git
配置中的一个特性,用于在不同的情境下包含不同的配置文件。
假设我们有两个不同的 Git 配置文件,一个用于工作,一个用于个人项目。 我们可以根据工作目录的路径加载不同的配置文件。
# ~/.gitconfig [user] name = Your Name email = your.email@example.com [includeIf "gitdir:~/work/"] path = ~/work/gitconfig-work [includeIf "gitdir:~/personal/"] path = ~/personal/gitconfig-personal
在这个例子中:
如果 Git 仓库位于 ~/work/
目录下,将加载 ~/work/gitconfig-work
文件。
如果 Git 仓库位于 ~/personal/
目录下,将加载 ~/personal/gitconfig-personal
文件。
无论在哪个目录下,都会使用主配置文件的用户姓名和邮箱。
如果我们工作和个人项目使用的 ssh 私钥不同,则可以分别在两个配置文件中配置 core.sshCommand
配置项。
判断分支是否已经合并到主干
# 判断第一个分支是其他分支的祖先吗 git merge-base --is-ancestor your/feature-brance origin/master
该命令不会有任何输出, 如果 “是”,返回值为 “0”; 如果不是,返回值为 “1”.
删除所有已合并到主干的分支
# 删除本地分支 git branch --merged | xargs git branch -d # 删除远程分支 git branch -r --merged | sed 's/origin\///' | xargs -I {} git push origin :{}
忽略项目中某些文件, 但不放在 .gitignore 文件中
在团队合作的项目中, 你使用了某些大家不常用的工具等,可能会在项目根目录下生成一些配置文件等。
因为不常用,所以极大概率没有加入到 .gitignore
文件中。
为了避免提交代码时受到这些文件的困扰(误提交),我们可以在本地忽略掉这些文件。
echo "/your-some-file" >> .git/info/exclude
参考:gitignore
比较本地文件和某[远程]分支上的文件差异
git diff origin/dev -- path/to/filename
使用 git-hooks
设置受保护的分支(禁止推送)
在团队开发中, 一般都有权限管理的, 比如 master
分支只允许 Team Leader 点合并。
开发者是没有权限直接推送到 master
分支的。
但作为 Team Leader, 即便有权限直接推送到 master
分支, 最佳实践还是不要这么干的好。
更可怕的是,今天由于误操作,将还在开发中的代码直接 push 到了 master
分支, 惊出一身冷汗。
为了避免再次发生这种情况,可通过 .git/hooks/pre-push
来实现禁止推送到某些受保护的分支。
#!/bin/sh # An example hook script to verify what is about to be pushed. Called by "git # push" after it has checked the remote status, but before anything has been # pushed. If this script exits with a non-zero status nothing will be pushed. # # This hook is called with the following parameters: # # $1 -- Name of the remote to which the push is being done # $2 -- URL to which the push is being done # # If pushing without using a named remote those arguments will be equal. # # Information about the commits which are being pushed is supplied as lines to # the standard input in the form: # # <local ref> <local oid> <remote ref> <remote oid> remote="$1" url="$2" protected_branch="main" protected_remote_ref="refs/heads/${protected_branch}" while read local_ref local_oid remote_ref remote_oid do # echo "$local_ref" "$local_oid" "$remote_ref" "$remote_oid" if test "$remote_ref" = "$protected_remote_ref" then echo >&2 "分支 ${protected_branch} 为受保护的分支, 不允许推送" exit 1 fi done exit 0
本地忽略已提交文件的后续变更
对于新创建的文件, 加入 .gitignore
中即可实现忽略。
但这种方式仅针对新创建的(从未提交过的)文件生效。
比如,项目中的 Dockerfile
文件,本身是需要跟着版本库走的,
且开发,测试,预发布和生产四个环境下的配置可能略有差异(需要指定当前开发环境)。
每次开始开发新需求,都需要从 master
分支拉取最新代码,然后修改 Dockerfile
将 --env=prod
修改为 --env=dev
.
开发完成需要发布到测试环境时同样需要修改 Dockerfile
将 --env=dev
修改为 --env=test
.
发布到预发布和生产时,同样需要执行这些操作,非常不便。
尤其是到预发布后发现的问题,需要重新回到 开发,测试 等环节,再经历这么一遍。
为了解决这个问题,思路很简单,就是本地开发环境对 Dockerfile
文件的修改仅保留在本地,不提交到远程。
这样本地对该文件的修改就不会提交到远程的开发分支,当合并到 测试,预发布,生产等环境时,也就不存在覆盖的问题,各个环境保留各个环境原有的配置即可。
为了达到这个目的,可以使用:
# 告诉Git忽略对文件的修改,即使文件已经被修改,Git也不会将其标记为已修改 git update-index --assume-unchange Dockerfile
这个时候再执行 git status
命令,会发现该文件并没有被修改的标记。
当然,如果后续确实有修改 Dockerfile
的需求,
可以再执行:
git update-index --no-assume-unchange Dockerfile
来恢复对 Dockerfile
文件变动的检查。
切换到上一个分支
git checkout -
移除指定源文件中行尾的空白字符
sed -i -E 's/\s+$//' $(git ls-files '*.cpp' '*.h') # 或者使用更强大的 git grep 来搜索文件 sed -i -E '...' $(git grep -lw Foo '*.cpp' '*.h')
查看分支的最后一次提交时间
# 所有分支, 按照最后提交时间正序排列 git for-each-ref --sort=committerdate refs/heads/ \ --format='%(committerdate:short) %(refname:short)' # 获取最近更新的5个分支名 git for-each-ref --sort=committerdate refs/heads/ \ --format='%(committerdate:short) %(refname:short)' | tail -5 | cut -c 12- # 列出最近更新的 5 个分支名 git for-each-ref --sort=-committerdate --count=5 --format='%(refname:short) %(committerdate:relative)' refs/heads/
git for-each-ref
命令可用于列出和显示各种类型的引用,例如分支、标签、远程跟踪分支等。
同时可以使用不同的选项来过滤和格式化输出。
以下是一些常用的参数:
--format=<format>
: 指定输出的格式。可以使用占位符来引用不同的字段。例如%d
表示引用的类型,%H
表示引用的完整哈希值,%s
表示引用的简短描述等。--sort=<key>
: 指定排序的键。可以使用不同的键来按照不同的方式排序引用。例如refname
按引用名称排序,committerdate
按提交时间排序等。可以使用-
来表示降序排序。--count=<n>
: 限制输出的数量,只显示前 n 个引用。--merged=<commit>
: 仅显示已合并到指定提交的引用。--no-merged=<commit>
: 仅显示未合并到指定提交的引用。--contains=<commit>
: 仅显示包含指定提交的引用。--points-at=<object>
: 仅显示指向指定对象的引用。--merged-with=<commit>
: 仅显示与指定提交合并的引用。--no-merged-with=<commit>
: 仅显示与指定提交未合并的引用。--format=<format>
: 指定输出的格式。可以使用占位符来引用不同的字段。例如%d
表示引用的类型,%H
表示引用的完整哈希值,%s
表示引用的简短描述等。--points-at=<object>
: 仅显示指向指定对象的引用。--contains=<commit>
: 仅显示包含指定提交的引用。--merged-with=<commit>
: 仅显示与指定提交合并的引用。--no-merged-with=<commit>
: 仅显示与指定提交未合并的引用。
遍历历史所有提交, 删除指定文件的所有痕迹
应用场景:
之前把个人密码文件放在了 git 仓库中,而且一直有对该文件的更新。 后面想想这种方式还是不太安全,所以要把该文件从 git 仓库移除。 但这个文件已经存在于 git 历史中了,这时就需要遍历所有提交,抹除所有关于该文件的痕迹。
git filter-branch -f --index-filter 'git rm -rf --cached --ignore-unmatch YOUR_PRIVATE_FILE_PATH' HEAD
该命令的作用是在每个提交中执行 git rm -rf --cached --ignore-unmatch path_to_file
,
将指定的文件从 Git 的索引中移除。这样,在重写历史后,该文件将不再存在于 Git 的历史记录中。
注意, git filter-branch
是一个强大而危险的命令,它会重写 Git 的历史记录。
在使用该命令之前,请务必备份你的代码库,并确保你了解该命令的影响和风险。
遍历历史所有提交, 修改提交人的姓名和邮箱
应用场景:
更换了邮箱地址, 想把 git 历史提交记录中的邮箱地址变更为新的邮箱地址。
git filter-branch --env-filter ' OLD_EMAIL="旧的邮箱地址" CORRECT_NAME="正确的作者名字" CORRECT_EMAIL="正确的邮箱地址" if [ "$GIT_COMMITTER_EMAIL" = "$OLD_EMAIL" ]; then export GIT_COMMITTER_NAME="$CORRECT_NAME" export GIT_COMMITTER_EMAIL="$CORRECT_EMAIL" fi if [ "$GIT_AUTHOR_EMAIL" = "$OLD_EMAIL" ]; then export GIT_AUTHOR_NAME="$CORRECT_NAME" export GIT_AUTHOR_EMAIL="$CORRECT_EMAIL" fi ' --tag-name-filter cat -- --branches --tags
该命令会遍历历史提交,检查每个提交的作者信息。
如果发现与 OLD_EMAIL
匹配的邮箱地址,就会将作者信息替换为 CORRECT_NAME
和 CORRECT_EMAIL
。
通过设置环境变量,将正确的作者信息应用于每个匹配的提交。
注意, git filter-branch
是一个强大而危险的命令,它会重写 Git 的历史记录。
在使用该命令之前,请务必备份你的代码库,并确保你了解该命令的影响和风险。