# git [git官网](https://git-scm.com/doc) [git reference](https://git-scm.com/docs) [git book](https://git-scm.com/book/en/v2) 一款优秀的版本管理工具, 不仅是代码管理, 更是版本管理. 我觉得不仅写代码的人要学会用这个, 所有的办公人员都应该学会 ```shell 忽略某个文件 git update-index --assume-unchanged config.php ``` ## 版本选择 [官网](https://git-scm.com/docs/gitrevisions) * 直接用sha1, 或者sha1前缀 * ## [lfs](https://git-lfs.github.com/) 处理大文件用. ### 原理 1. 每次add的时候,会计算文件的sha256sum, 保存到 `.git/lfs/objects/98/41/984132.....` 文件。`984132...`是sha256sum的结果 2. 每次commit的时候,git都会文件当做一个指针文件保存到 `.git/objects`, 文件格式:. 所以指针文件126到140字节左右 ``` version https://git-lfs.github.com/spec/v1 oid sha256:982144ca336922b975109ac8a8f77576265668cd966885701d8ac75b9c867802 size 6 ``` 3. 上传的时候,如果服务器的 `.git/lfs/objects/` 文件不存在,那就上传。 4. 下载的时候,根据lfs.fetchinclude配置,来判断拉取文件还是仅仅把文件设置成指针 ### 配置 * 编辑.gitattributes ``` .gitattributes filter= diff= merge= text * filter=lfs diff=lfs merge=lfs -text ``` * 初始化 ``` git lfs install git lfs track "*.jpg" "*.png" "*.exe" "*.JPG" "*.iso" git add . git commit -m '初始化' ``` ### 使用 * 输出pointer ``` $ git lfs clean < 123.txt version https://git-lfs.github.com/spec/v1 oid sha256:181210f8f9c779c26da1d9b2075bde0127302ee0e3fca38c9a83f5b1dd8e5d3b size 4 ``` * 还原特定文件 ``` git lfs checkout ``` * 拉取特定文件 ``` git lfs pull --include "windows软件/Git.exe" ``` * 克隆项目 ``` git lfs clone --exclude "*" # 所有的大文件都不拉 ``` * 拉去项目 ``` git config lfs.fetchexclude "*" # 只拉取文件hash, 不拉取整个文件 git pull origin master ``` * 迁移项目 ``` git lfs migrate import # 把git大对象变成lfs指针 ``` * 上传项目 ``` git push origin master # 上传git object和lfs对象 git push origin master --no-verify # 直接上传git object, 不上传lfs对象并且不校验, 因为我的服务器没有开启git-lfs https服务 ``` * 还原文件 ``` git lfs untrack git add --renormalize --all/ ``` [Files are still (not) in GIT LFS after changing .gitattributes](https://stackoverflow.com/questions/45350174/files-are-still-not-in-git-lfs-after-changing-gitattributes) ## 通用 ### [field names](https://git-scm.com/docs/git-for-each-ref#_field_names) * creatordate ## 配置 Setup and Config ### [config](https://git-scm.com/docs/git-config) * alias ``` git config alias.lg 'log --graph --pretty=format:"%Cgreen%h %Cred%s %Creset----%C(yellow)%cn %Creset@ %ad %C(green)%d" --date=format:"%Y-%m-%d %H:%M"' ``` * 设置用户名邮箱: ``` git config --global user.email "ramwin@qq.com" git config --global user.name "Xiang Wang" ``` * 设置默认的 pull 和 push ``` git branch --set-upstream-to=origin/origin master git config --global push.default matching | current # 默认推送当前分支 ``` * git中文不显示utf8编码而显示中文: `git config --global core.quotepath false` * 设置忽略文件权限修改: `git config core.filemode false` * 全局设定 ``` ~/.gitconfig [core] excludesfile = ~/.gitignore_global bigFileThreshold = 1m # 超过1M的文件不再当作文本去记录变化 ~/.gitignore_global` cat gitignore_global >> ~/.gitignore_global ``` * 服务器允许pull指定的commit uploadpack.allowReachableSHA1InWant=true * core.compression 和 core.looseCompression 只要设置了core.compression, 得到的pack就是一样的, 但是md5和原始文件不同 * test 文件夹, 默认, 一个windows10安装包 4.7G(5,010,587,648), 联想的普通机械硬盘, origin是同一个文件夹下面的另外一个文件夹 ```bash git add . # 耗时3分20秒 git commit -m "" # 立马结束 objects/pack-c36dxxx76f5.pack 4.6G 4953058178字节 git push origin master # 耗时6分21秒 objects/85/44441acexxxx3c21 4954989512字节 test_origin的配置 user.name=Xiang Wang user.email=ramwin@qq.com core.quotepath=false core.filemode=false core.excludesfile=~/.gitignore_global core.bigfilethreshold=200K pack.windowmemory=1g core.repositoryformatversion=0 core.filemode=true core.bare=true ``` * test1 文件夹, 都设置成0 ```bash git add . # 耗时1分25秒 git commit -m "" # 立马结束 pack-5854xxx2d65.pack 4.7G 5011352247字节 git push origin master # 耗时6分17秒 objects/85/44441acexxxx3c21 4954989512字节 test_origin1的配置 user.name=Xiang Wang user.email=ramwin@qq.com core.quotepath=false core.filemode=false core.excludesfile=~/.gitignore_global core.bigfilethreshold=200K pack.windowmemory=1g core.repositoryformatversion=0 core.filemode=true core.bare=true test_origin2的配置 user.name=Xiang Wang user.email=ramwin@qq.com core.quotepath=false core.filemode=false core.excludesfile=~/.gitignore_global core.bigfilethreshold=200K pack.windowmemory=1g core.repositoryformatversion=0 core.filemode=true core.bare=true core.compression=0 core.loosecompression=0 git push origin2 master # 耗时4分11秒 objects/85/44441acexxxx3c21 5011352225字节 ``` ### Core * sshCommand ``` git config core.sshCommand "ssh -i <自定义密钥>" ``` ## 获取或者创建项目 getting and creating projects ### init * [如何更改.git文件夹位置](https://stackoverflow.com/questions/40561234/can-you-change-git-folder-location) ``` git init # 初始化仓库 git init --separate-git-dir=/path/to/dot-git-directory . # 设置.git文件夹的地方 ``` * 创建自己的git服务器 ``` [root:~/] sudo adduser git [git:~/] git init --bare repository.git [root:~/] vim /etc/passwd # change git line to 'git:x:1001:1001:,,,:/home/git:/bin/bash' ``` ## 快照 Basic Snapshotting ### [add](https://git-scm.com/docs/git-add) 把文件变更添加进index * --renormalize 强制性得重新读取文件信息,不然git会根据文件的更新时间来决定,导致lfs或者`\r\n`配置的变更无法还原 ### commit * add 后查看修改: `git diff --cached` * 多次提交很简单的代码 `git commit --amend # 这样就能修改上次提交的信息,不创建新版本` * 提交了一次错误的版本 `git revert # 把那次commit之后的修改都reset掉,并生成一个新的commit` * [git如何生成sha1](https://gist.github.com/masak/2415865) ``` (printf "commit %s\0" $(git cat-file commit HEAD | wc -c); git cat-file commit HEAD) | sha1sum ``` ### diff ``` git diff --word-diff git diff HEAD HEAD^^ --stat # only see the different name ``` * `-w`, `--ignore-all-spaces`: 执行的时候忽略空格变化 ### difftool ``` git config diff.tool vimdiff git difftool HEAD^ HEAD # 用vimdiff横向比较文件 ``` * 对比一个文件在不同版本的数据 ``` git config --global diff.toll vimdiff git difftool -y master:README.md "HEAD^:README.md" ``` ### [notes](https://git-scm.com/docs/git-notes) 在原有的commit基础上添加备注,而不修改原有commit ``` git notes add -m "这个commit不可用" ``` * [服务器同步notes](https://stackoverflow.com/questions/18268986/git-how-to-push-messages-added-by-git-notes-to-the-central-git-server) ``` git push refs/notes/* git fetch origin refs/notes/*:refs/notes/* ``` ### restore * 还原删除的文件 ``` git restore :/ ``` ## 分支和合并 Branching and Merging ### branch ``` git branch [branchname] [startpoint] # 指定从哪个版本里开出一个新的分支 git branch --merged --no-contains master # 获取合入了master的分支的列表 ``` * `--no-contains ` 排除某个ref * `--merged ` 只展示能够从 ref (往前)走到的分支 ### checkout * 把文件还原到之前的某个版本 ``` git checkout versin -- file1/to/restore file2/to/restore ``` * 把已经add的文件还原到没add的状态 ``` git reset HEAD filename ``` * 强制丢弃已经修改的内容 ``` git checkout -f ``` ### [merge](https://git-scm.com/docs/git-merge) 合并分支 ### [mergetool](https://git-scm.com/docs/git-mergetool) 用来修复冲突 ``` git mergetool --tool=vimdiff ``` ### [tag](https://git-scm.com/docs/git-tag) [文档](https://git-scm.com/book/en/v2/Git-Basics-Tagging) * `--sort=` > 这里的排序使用的是和`git for-each-ref`一致的key > 使用`git config tag.sort`可以设置tag的默认排序 git config tag.sort -creatordate git tag -n | head -n 10 * Listing Your Tags git tag [-l] or [-list] git tag -l "v1.8.5*" 这时候的-l就必须存在了。 * Creating tags git支持2种tag, *lightweight* 和 *annotated* * Creating Annotated Tags annotated tag会保留谁在什么时候提交的tag git tag -a v1.4 -m "version 1.4" git show v1.4 * Creating Lightweight Tags 这个lightweight tag仅仅是保存一个标签。就没有谁创建的tag这种信息了 git tag v1.4-lw * Tagging Later git tag -a v1.2 给指定的某个提交添加tag * Sharing Tags 默认情况下`git push`不会上传tag到服务器 git push origin v1.5 git push origin --tags * Deleting Tags git tag -d v1.4-lw # 默认不会删除服务器的tag git push origin :refs/tags/v1.4-lw git push origin --delete * Checkouting out tags git checkout 2.0.0 * 其他 git tag -n git tag -m "新增公司信息存储功能" v2.0.0 git tag -l --format="%(tag) %(subject)" ### [worktree](https://git-scm.com/docs/git-worktree) 用于突然要维护一个旧分支, 又不想影响当前的工作区 git worktree add hotfix # 先用已有git checkout一次 cd hotfix # 进入分支目录修复bug ... git worktree prune # 修复后删除 ## Sharing and Updating Projects ### fetch * 拉取指定的commit `git fetch --depth=1 $SHA1` * 拉去指定的tag ``` git fetch origin refs/tags/1.0.0 ``` ### [push 推送](https://git-scm.com/docs/git-push) * 推送指定分支 ```bash git push origin : : git push origin 1.0.0:master ``` ### 子模块 submodule * [guide文档](https://git-scm.com/docs/gitsubmodules) * [命令参考](https://git-scm.com/docs/git-submodule) * 配置 ``` git config submodule.recurse true # 这样在外层git checkout会导致submodule也checkout ``` * add ``` git submodule add git@ramwin.com:~/small.git # 这样会clone整个small ``` * clone ``` git clone # submodule只会clone一个hash git clone --recurse-submodules # 每个submodule都会clone下来 git pull origin master --recurse-submodules # 每个submodule都pull git checkout --recurse-submodules # 每个submodule都checkout ``` * diff ``` git diff --submodule=diff # 每个submodule都diff ``` * init ``` git submodule init # 初始化small仓库 ``` * foreach 循环对每个submodule执行 ``` git submodule foreach git clean -df ``` * update ``` git submodule update small # 初始化后,可以clone ``` * [手动添加一个仓库](https://stackoverflow.com/questions/34562333/is-there-a-way-to-git-submodule-add-a-repo-without-cloning-it) ``` git update-index --add --cacheinfo 160000 d020b3a97f131ad11fb15bd8cce1774b0eb54c7b small git commit -m '先加上去再说' # 此时.git/index文件里显示有个submodule, 但是呢,工作区显示small文件夹不存在 ``` ## 查看和比较 Inspection and Comparison * [show](https://git-scm.com/docs/git-show) ``` git show # 查看某个版本的修改 git show : # 查看某个版本的文件 ``` ### [log](https://git-scm.com/docs/git-log) git log HEAD^ 是按照第一个parent依次往前找的,而不是按照时间顺序找的 git log 是按照时间顺序往前找的 * `-1, -, --max-count=`: 最多显示多少个commit * `--skip=`: 不显示最前面的多少个commit * [ ] --since #### [格式化 PRETTY FORMATS](https://git-scm.com/docs/git-log#_pretty_formats) * `git log --graph --pretty=format:"%Cblue%h %Cred%s %Creset----%cn @ %ad" --date=format:'%Y-%m-%d %H:%M' %d` * %h %H 简短/完整的哈希字符串 * %d %D ref的name, %D代表了不用括号括起来 * %an author name 作者名字 ## Patching ### [cherry-pick](https://git-scm.com/docs/git-cherry-pick) 把某次提交的功能应用当前版本 ``` git cherry-pick ``` ### rebase [官网](https://git-scm.com/docs/git-rebase) * 基础 ``` git rebase --onto git rebase --onto # 把branch从hash1开始(不包含)到结束的commit应用到newbase上 ``` * 合并几个commit ``` git rebase -i HEAD^^^ # 然后只pick第一个,squash后面所有的 ``` ### revert * 撤回上个版本 ``` git revert HEAD ``` * 连续撤回前2个版本,生成2个commit ``` git revert HEAD^^...HEAD # 注意前开后闭区间 ``` * 撤回但是不自动生成commit, 方便你连续revert多个commit时,只希望生成个commit ``` git revert -n/--no-commit HEAD^^...HEAD ``` * rebase的时候保留之前修复冲突的情况 ``` rebase_base - rebase_a - rebase_final(76187ed) \ / rebase_b [rebase_final] $ git rebase rebase_a 修复冲突时保留了rebase_b的版本 rebase_base - rebase_a - rebase_b 但是中间的C没改 ``` ## 排查 Debugging ### bisect 通过二分法找到出现bug的版本 ``` git bisect start # 开始寻找 git bisect bad # 当前版本报错 git bisect good 1.0.0 # 1.0.0版本不报错 ... git bisect reset # 找到报错版本后,推出bisect ``` ### blame ``` git blame filepath # 查看某个文件的修改记录 ``` ## Administration ### [clean](https://git-scm.com/docs/git-clean) 清理untracked文件 ``` git clean -dfxn git clean -dfx -n dry run -i 交互模式,每个都问你 -f 强制删除(一般需要,clean.requireForce默认开了) -d 多余的文件夹也删除 -x 包括ignore的文件也删除 ``` ### gc ``` git gc # 优化仓库 ``` ### [ ] fsck ### [bundle](https://git-scm.com/book/zh/v2/Git-%E5%B7%A5%E5%85%B7-%E6%89%93%E5%8C%85) 但是不支持lfs ```shell git bundle create master ^commit # 把从commit到master的版本打包 git bundle verify bundle # 查看打包的文件 git fetch master:other-master # 把bundle文件中的master分支复制到本地other-master分支 ``` ## 其他插件 Plumbing Commands ### cat-file 查看文件类型, 如果是commit可以看到对应的tree. 如果是文件, 可以直接看到内容 ``` git cat-file -p # hash文件用zlib直接打开可以看到内容 commit 227tree d47ac169be14144d8b80807645bf9d00497cb907 parent ef8d12680f2a5eec968320d7868985a1f9574f36 author Xiang Wang 1680442265 +0800 committer Xiang Wang 1680442265 +0800 terminal连续执行命令 ``` ### hash-object 生成一个文件的hash ```shell echo -e "blob 2\0A" > A_blob sha1sum A_blob >> f70f10e4db19068f79bc43844b49f3eece45c4e8 echo "A" > A git hash-object A >> f70f10e4db19068f79bc43844b49f3eece45c4e8 ``` * [merge-base](https://git-scm.com/docs/git-merge-base) 找到多个节点的共同祖先 ``` git merge-base commitA commitB ``` ## ls-tree 查看一个tree的文件内容(包含文件夹下的子文件夹的treenode和文件的node, 只有一层) ``` git ls-tree ``` ## ls-remote 展示远程仓库的分支和tag ## pull * 拉取远程分支 `git pull origin :` ## show 查看某个文件的版本 ``` git show ref:filepath > tmp ``` ## [sparse checkout](https://git-scm.com/docs/git-sparse-checkout) ``` git sparse-checkout init git sparse-checkout add ``` ## status * `git status -s, --short` *只显示文件名,而不显示其他多余的信息* ## [stash](https://git-scm.com/docs/git-stash) ``` git stash git stash list git stash pop = git stash apply; git stash drop git stash --all # 包含ignored和untracked git stash -- [] # 指定部分文件stash git stash -u/--include-untracked 包含untracked文件 ``` ## 其他 * [ ] working with remotes * 彻底删除某个文件 ``` git filter-branch --tree-filter 'rm -f ' HEAD git push -f ``` * 找到大文件 ``` git rev-list --objects --all | grep "$(git verify-pack -v .git/objects/pack/*.idx | sort -k 3 -n | tail -5 | awk '{print$1}')" ``` ## filter-repo ``` # 删除单个文件 git filter-repo --path path/to/your/file.txt --invert-paths # 删除文件夹(及内部所有文件历史) git filter-repo --path path/to/folder/ --invert-paths # 使用通配符删除某类文件(如所有 .log 文件) git filter-repo --path-glob '*.log' --invert-paths # 删除所有同名文件(如所有 .DS_Store) git filter-repo --path '.DS_Store' --invert-paths ```