关于Git的使用,《一个小时学会Git》介绍的极其详细,且通俗易懂,所以相比于提供官网教程,个人觉得这篇博客更适合入门~
这里贴个图,区别下常用操作:
Git Examples
从仓库中拆分子目录为独立项目
比如你有一个叫做 big-project 的仓库,目录如下:
big-project
├── codes-a
├── codes-b
└── codes-eiyo
如何把 codes-eiyo
拆出来做为一个独立仓库?又如何把 codes-eiyo
清理掉,只保留剩下的代码在仓库中?
function split_to_independence(){ sub_dir=$1 test -z $2 && tmp_branch="tmp_"${sub_dir} || tmp_branch=$2 test -z $3 && path_new=${tmp_branch} || path_new=$3 path_old=$(pwd) # 将 sub_dir 目录下的所有提交整理为新的分支 git subtree split -P ${sub_dir} -b ${tmp_branch} # 初始化新仓库 if [ -d ${path_new} ]; then echo "There has been a same-name dir. Make sure it's empty?" else mkdir ${path_new} git init ${path_new} fi # 拉取原仓库的 tmp_branch 分支到 path_new 的 master 分支 cd ${path_new} git pull ${path_old} ${tmp_branch} # 清理原项目中的临时分支 cd ${path_old} git branch -D ${tmp_branch} }
从仓库中清理子目录 commit 痕迹
情景同上,只是变为:从当前commit列表中清除关于 sub-dir 的所有记录。
function clean_up_subdir(){ sub_dir=$1 current_branch=$(git symbolic-ref --short HEAD) # $(git branch | grep '*' | awk '{print $2}') # there are some interesting ways: # git symbolic-ref --short HEAD # git describe --contains --all HEAD test -z $2 && target_branch=${current_branch} || target_branch=$2 # 重写提交记录,且不保留空提交 git filter-branch --index-filter "git rm -rf --cached --ignore-unmatch ${sub_dir}" --prune-empty ${target_branch} # 前步操作会清理 sub_dir 中所有已提交文件,但会忽略垃圾文件及文件夹 # rm -rf ${sub_dir} }
从每一个提交移除一个文件
- 例如,有人粗心地通过 git add . 提交了一个巨大的二进制文件,你想要从所有地方删除它。
- 可能偶然地提交了一个包括一个密码的文件,然而你想要开源项目。
-
# 从每一个快照中移除了一个叫作 passwords.txt 的文件,无论它是否存在 $ git filter-branch --tree-filter 'rm -f passwords.txt' HEAD # 移除所有偶然提交的编辑器备份文件 $ git filter-branch --tree-filter 'rm -f *~' HEAD
最后将可以看到 Git 重写树与提交然后移动分支指针。
使一个子目录做为新的根目录
- 例如,从另SVN中导入项目,并且存在几个没意义的子目录(trunk、tags 等等)。
如果想要让 trunk 子目录作为每一个提交的新的项目根目录: -
$ git filter-branch --subdirectory-filter trunk HEAD
现在新项目根目录是 trunk 子目录了。 Git 会自动移除所有不影响子目录的提交。
全局修改邮箱地址
- 在你开始工作时忘记运行 git config 来设置你的名字与邮箱地址
- 或者你想要开源一个项目并且修改所有你的工作邮箱地址为你的个人邮箱地址。
-
$ git filter-branch --commit-filter ' if [ "$GIT_AUTHOR_EMAIL" = "schacon@localhost" ]; then GIT_AUTHOR_NAME="Scott Chacon"; GIT_AUTHOR_EMAIL="schacon@example.com"; git commit-tree "$@"; else git commit-tree "$@"; fi' HEAD
这会遍历并重写每一个提交来包含你的新邮箱地址。
注意:因为提交包含了它们父提交的 SHA-1 校验和,这个命令会修改你的历史中的每一个提交的 SHA-1 校验和,而不仅仅只是那些匹配邮箱地址的提交。
子项目管理
参考:《Git Subtree 双向同步子项目,附简明使用手册》
首先,在当前项目中添加子项目的方式有两种:
- 从本地仓库拉取子项目: git subtree add --prefix=<subdir_相对路径> <local_repo_相对路径> <分支> --squash
注意,此模式可以获取到 local_repo 的 subtree_branch 的当前commit版本,而不是最新版本 -
function subtree_add_local(){ subtree_repo=$1 # it's a local repo test -z $2 && subtree_branch="master" || subtree_branch=$2 test -z $3 && sub_dir=${subtree_repo##*/} || sub_dir=$3 # 将 subtree_repo 当前的commit作为 subtree 复制到 sub_dir 并合并提交 # --squash 用于将 subtree_repo 的历史提交合并为一个commit,否则将合并(备份)其全部commit git subtree add --prefix=${sub_dir} ${subtree_repo} ${subtree_branch} --squash } # subtree_add_local ../test branch_test ./subdir
从远程分支拉取子项目:
-
function subtree_add_remote(){ remote_repo_url=$1 test -z $2 && subtree_branch="master" || subtree_branch=$2 temp=${remote_repo_url##*/} # ugly?? remote_repo_alias=${temp%%.*} test -z $3 && sub_dir=${remote_repo_alias} || sub_dir=$3 # 添加远程仓库的地址 git remote add -f ${remote_repo_alias} ${remote_repo_url} # 将 remote_repo_url clone到本地并pull其最新版本作为 subtree git subtree add --prefix=${sub_dir} ${remote_repo_alias} ${subtree_branch} --squash } # subtree_add_remote ssh://git@phab.i5cnc.com/diffusion/NEMO/nemo.git
以上操作完成子项目目录的创建和子项目代码的拉取。
经由 Git Subtree 维护的子项目代码,对于父项目来说是透明的,所有的开发人员看到的就是一个普通的目录,以同样的方式去commit和reset就行了。
关于 push 和 pull
对于整体项目来说,git push/pull 时不会同步子项目——它稳定在某一个版本号,以免子项目对接口的更新导致依赖原接口的实现失效。
故而更新和推送子项目是手动进行的:
function subtree_remote(){ # pull from remote_repo push_or_pull=$1 remote_repo_url=$2 # or the remote_repo_alias test -z $3 && subtree_branch="master" || subtree_branch=$3 temp=${remote_repo_url##*/} # ugly?? remote_repo_alias=${temp%%.*} test -z $4 && sub_dir=${remote_repo_alias} || sub_dir=$4 git subtree ${push_or_pull} --prefix=${sub_dir} ${subtree_repo} ${subtree_branch} --squash } # subtree_remote pull iocheck dev_branch
撤销 add 操作
# 从stage中删除文件,工作区则保留物理文件(针对untracked files) git rm --cached <file> # 移除所有未跟踪文件(针对untracked files) # 常用参数-df,-d表示包含目录,-f表示强制清除。 git clean [options] # 针对 modified files,stage区被重写,但不影响工作区 git reset HEAD <file>...
Git 核弹级选项 filter-branch
--tree-filter
选项在检出项目的每一个提交后运行指定的命令然后重新提交结果。
为了让 filter-branch
在所有分支上运行,可以给命令传递 --all
选项。
通常可以在一个测试分支中执行该选项,当你确定最终结果是正确的,再硬重置 master 分支。
***
以下示例引自官网:
- 从每一个提交移除一个文件
- 例如,有人粗心地通过 git add . 提交了一个巨大的二进制文件,你想要从所有地方删除它。
- 可能偶然地提交了一个包括一个密码的文件,然而你想要开源项目。
-
# 从每一个快照中移除了一个叫作 passwords.txt 的文件,无论它是否存在 $ git filter-branch --tree-filter 'rm -f passwords.txt' HEAD # 移除所有偶然提交的编辑器备份文件 $ git filter-branch --tree-filter 'rm -f *~' HEAD
最后将可以看到 Git 重写树与提交然后移动分支指针。
- 使一个子目录做为新的根目录
- 例如,从另SVN中导入项目,并且存在几个没意义的子目录(trunk、tags 等等)。
如果想要让 trunk 子目录作为每一个提交的新的项目根目录: -
$ git filter-branch --subdirectory-filter trunk HEAD
现在新项目根目录是 trunk 子目录了。 Git 会自动移除所有不影响子目录的提交。
- 例如,从另SVN中导入项目,并且存在几个没意义的子目录(trunk、tags 等等)。
- 全局修改邮箱地址
- 在你开始工作时忘记运行 git config 来设置你的名字与邮箱地址
- 或者你想要开源一个项目并且修改所有你的工作邮箱地址为你的个人邮箱地址。
-
$ git filter-branch --commit-filter ' if [ "$GIT_AUTHOR_EMAIL" = "schacon@localhost" ]; then GIT_AUTHOR_NAME="Scott Chacon"; GIT_AUTHOR_EMAIL="schacon@example.com"; git commit-tree "$@"; else git commit-tree "$@"; fi' HEAD
这会遍历并重写每一个提交来包含你的新邮箱地址。
注意:因为提交包含了它们父提交的 SHA-1 校验和,这个命令会修改你的历史中的每一个提交的 SHA-1 校验和,而不仅仅只是那些匹配邮箱地址的提交。