Git
https://tom.preston-werner.com/2009/05/19/the-git-parable.html
Git基于SHA-1的对象存储机制会创建
其中3b是SHA1的第一个字节, 之所以为第一个字节创建单独目录, 是因为一些文件系统在单个目录处理大量文件的效率很差,
该方法可以均匀创建256路分区缓解此问题.
.git/objects/3b/18e12dba79e4c8300dd08e728b8dad 这样的文件路径.其中3b是SHA1的第一个字节, 之所以为第一个字节创建单独目录, 是因为一些文件系统在单个目录处理大量文件的效率很差,
该方法可以均匀创建256路分区缓解此问题.
HEAD: 当前分支的最近提交.ORIG_HEAD: 合并(merge)和复位(reset)时, 会用 ORIG_HEAD 指向旧版本的 HEAD.FETCH_HEAD: 最近抓取(fetch)分支的 HEAD, 该引用仅在抓取之后短暂有效.MERGE_HEAD: 合并(merge)进行时, 指向合并的分支(ORIG_HEAD 是master, MERGE_HEAD 是branch).
C~ / C~1: 上一代提交(在只有一个父提交的情况下与 C^1 结果相同).C~~ / C~1~1 / C~2: 向上两代提交, 即 C~1 的 C~1.合并的提交可能拥有多个父提交, 这种情况下可以用
^ 按顺序选择父提交:C^ / C^1: 第一个父提交.C^^ / C^1^1 / C^2: 第二个父提交.提交范围的本质是"图的可达性"
{commit1}..{commit2}: 指从commit1到commit2的所有提交(不包含commit1, 语义上commit1应该是commit2的祖先).^{commit}: 在范围内排除特定提交.{branch1}..{branch2}: 技术上和commit范围一样, 语义上表示在branch2却不在branch1上的所有提交...{commit}: HEAD..{commit} 的缩写.{commit}..: {commit}..HEAD 的缩写.{commit1}...{commit2}:取commit1和commit2的对称差(极少使用).
相当于取
commit1可达但commit2不可达的祖先提交 + commit2可达但commit1不可达的祖先提交git config --global user.name "用户名"git config --global user.email "电子邮箱"配置文件为
~/.gitconfiggit config --global alias.show-graph 'log --graph --abbrev-commit --pretty=online'该命令添加了show-graph别名
git clone --bare existing_repo bare_repo_path: 克隆为裸仓库.git clone --bare existing_repo: 克隆裸仓库到当前目录.git remote add github git://github.com/username/something.git: 添加远程仓库.git fetch github: 从上游获取最新代码.git merge github/master: 合并上游master代码至当前分支.git fetch origin pull/{id}/head:{name}: 获取某个PR的分支, 并命名.这种push的缺点在于push完后不会自动更新local/master, 需要用fetch重新拉取local/master.
如果项目需要同步推送到多个上游, 说明项目很可能处于一种不良模式, 应尽可能避免使用到此技巧.
如果项目需要同步推送到多个上游, 说明项目很可能处于一种不良模式, 应尽可能避免使用到此技巧.
# 让push的时候push到origin(不可省略, 否则不会push到origin)git remote set-url --add --push origin $(git remote get-url origin) # 让push的时候push到localgit remote set-url --add --push origin $(git remote get-url local)git fetch --allgit pull: git fetch 和 git merge 的组合.git pull --rebase: 以rebase方式拉取, 本地commit会变基到最新的远程commit上.注: rebase时, 需要用
git rebase --continue 而不是 git commit 提交冲突第一行: 简单描述提交内容.
第二行: 空行.
第三方以后: 详细陈述提交内容.
第二行: 空行.
第三方以后: 详细陈述提交内容.
中止提交: 提交信息留空并退出编辑器.
git commit --amend: 重新编辑上一次提交(如果使用 git add, 则可以将新的修改并入此次提交).git commit --amend --no-edit: 重新提交.git branch {分支名}: 创建分支.git checkout {分支名}: 切换分支.git checkout -: 切换到上一个分支.git checkoub -b {分支名}: 创建并切换分支.git log --graph: 以图表形式查看分支.git branch -a: 查看包括本地和远程仓库的所有分支.git branch -m <oldname> <newname>: 重命名本地分支.git branch -m <newname>: 重命名本地当前分支.git merge-base {commit1} {commit2}: 返回两个提交的共同祖先.git stash: 暂存当前修改.git stash --include-untracked: 暂存当前未提交的跟踪文件和未跟踪的文件(常用).git stash apply: 恢复最后一次暂存的文件.git stash pop: 恢复最后一次暂存的文件并删除暂存(常用).git stash list: 查看全部的暂存列表.git stash drop {name}: 删除暂存.git stash clear: 删除全部暂存.不要在主分支等公共分支上使用rebase, 这会破坏历史.
git rebase {branch}: 以变基的形式将分支并入当前分支.git rebase -i {commit}: 以修改历史的形式变基到 {commit}, 例如从分支变基到最新的master.git rebase -i HEAD~5: 以修改历史的形式变基最后5次提交.squash意味着此提交将合并到先前的提交中, 包括此提交的message.
fixup意味着此提交将合并的先前的提交中, 但不包括此提交的message.
fixup意味着此提交将合并的先前的提交中, 但不包括此提交的message.
git commit --fixup HEAD: 创建提交作为HEAD的fixup, 为autosquash作准备.git commit --squash HEAD: 创建提交作为HEAD的squash, 为autosquash作准备.git rebase -i --autosquash {commit}: 在交互式编辑器里, 将自动处理先前提交的fixup和squash.为交互式rebase默认开启autosquash:
git config --global rebase.autosquash truegit merge --no-ff {分支名}: 将对应分支合并至当前分支, 但需要手动填写合并提交信息.git merge --no-commit --no-ff {分支名}: 手动进行合并.git merge --squash branch:将分支里所有commit对文件的修改添加到当前stage, 执行
git commit 以完成合并.最终, 这相当于"将分支里的commit压缩(squash)为一个commit进行合并",
该commit默认情况下会以squasher为作者, 所有被压缩的commit为父commit.
git show {commit}: 查看commit包含的修改.git show next:{filename}: 查看filename在next分支上的样子.git log {filename}: 查看目录或文件的日志.git log -p {filename}: 查看包含diff的日志.git log --follow {filename}:查看该文件的历史日志.
不使用follow的情况下, 由于重命名等原因会不显示重命名之前的日志.
git log --oneline: 单行显示日志.git log --oneline --graph: 单行显示日志并打印节点图.git diff HEAD: 将工作树与HEAD(最后一个提交)进行比较.git clean -fd: 删除本地所有未追踪文件和目录.git reset --hard {commit}: 将当前分支(HEAD)退回指定commit, 也用于撤销合并等操作.git restore .: 与上一条作用相同.git clean -fd && git restore .: 恢复至上一次提交(放弃提交内容).git reset --soft HEAD~: 撤销上一次提交(保留改动内容).git checkout {commit} -- path/to/file: 从指定提交中恢复指定文件.git tag {tagName}: 创建轻量级, 无注释(non-annotated)/轻量级标签.git tag --annotate {tagName}: 创建有注释(annotated)的标签.git tag -n99: 查看所有标签及它们携带的消息(non-annotated标签会显示commit消息).使用annotated标签被认为是最佳实践,
因为这种标签会生成对象, 记录标签的创建者, 创建日期和额外的标签消息
(在实际使用中, 标签消息没什么实际用处, annotated标签主要功能主要是为了记录标签创建者和创建时间).
因为这种标签会生成对象, 记录标签的创建者, 创建日期和额外的标签消息
(在实际使用中, 标签消息没什么实际用处, annotated标签主要功能主要是为了记录标签创建者和创建时间).
由于这个原因, non-annotated标签被认为适合私人和临时性使用, 而annotated标签被用于发布.
两者的区别可以通过
而non-annotated标签需要额外加上
git describe 体现出来, annotated标签能够被直接查询,而non-annotated标签需要额外加上
--tags flag.git push --follow-tags: 在push的同时向远程发送 有注释 标签.git push origin :{tagName}: 删除远程tag, 与下一条等同.git push --delete origin {tagName}: 删除远程tag, 与上一条等同.git revert HEAD: 创建一个反转上一个提交的提交.git revert {commit}..HEAD: 反转从特定提交开始至今的所有修改.查看git的历史提交缓存, 从中可以抢救出那些被误删的提交.
git reset HEAD@{index}: 前往reflog列出的指定提交.cherry-pick可将一个或多个提交的 更改 抓取到当前分支上.
通常用于跨分支打补丁的场景, 当不想合并整个分支的时候, 使用cherry-pick.
通常用于跨分支打补丁的场景, 当不想合并整个分支的时候, 使用cherry-pick.
自Git 2.23加入的新命令, 用于切换分支.
功能与checkout基本一样, 专门分离出一个命令是因为checkout承载了太多的功能.
功能与checkout基本一样, 专门分离出一个命令是因为checkout承载了太多的功能.
Git 2.23加入的新命令.
git restore path/to/file相当于
git checkout HEAD -- path/to/file恢复指定提交的文件(缩写为
-s):git restore --source commit path/to/file此子命令允许将分支检出到单独的目录里, 从而避免切换分支带来的麻烦或同时查看多个分支中的内容.
git worktree add ../path/to/directory branch: 创建一个工作树目录, 该目录检出当前存储库的某个分支.git worktree list: 列出当前存储库的工作树, 可删除的工作树将被标为prunable(意味着工作树目录无法访问).git worktree prune: 移除被标记为prunable的工作树.和subtree不同, submodule将仓库作为引用加入到当前仓库中.
submodule会向仓库添加
.gitmodules 文件记录子模块仓库的元数据, 此文件应该被纳入版本控制.添加子模块:
git submodule add 子模块的仓库地址 子模块路径克隆具有子模块的项目:
git clone --recurse-submodules 项目地址# 以上命令等价于以下步骤git clone 项目地址cd 项目地址git submodule update --init# 以上命令等价于以下步骤git clone 项目地址cd 项目地址git submodule init # 作用是将`.gitmodules`复制到`.git/config`里git submodule update获取同时更新任何嵌套的子模块:
git submodule update --init --recursive- 期望仓库是嵌套的(一个仓库里有另一个仓库), 而不是多个仓库合并成的仓库.
- 希望能更容易切换子模块的提交(子模块只跟踪提交而不是分支/标签).
- 希望直接在当前仓库里贡献代码到子模块的仓库.
- 子模块可能频繁的创建和删除.
- 子模块永远具有可访问性.
和submodule不同, subtree将仓库作为副本加入到当前仓库中.
默认情况下会包括仓库原有提交, 这很适合将polyrepo迁移至monorepo:
git subtree add --prefix=子树路径 子树仓库的仓库地址 子树仓库的提交(或分支)使用--squash选项可以在不包含仓库原有提交的情况下, 添加仓库为子树:
git subtree add --prefix=子树路径 --squash 子树仓库的地址 子树仓库的提交(或分支)从源仓库拉取提交:
注意, 由于子树对Git是透明的, 因此每次pull实际上都是直接对比远程仓库和本地仓库, 然后进行merge.
git subtree pull --prefix=子树路径 子树仓库的地址 子树仓库的提交(或分支)注意, 由于子树对Git是透明的, 因此每次pull实际上都是直接对比远程仓库和本地仓库, 然后进行merge.
子树是来自社区的名为git-subtree的脚本, 而非Git的核心功能.
因此当一个项目被作为子树添加时, Git是不知道子树这个概念的, 它对于Git来说是透明的.
这也是为什么每次调用子树命令都需要使用额外的参数, 它其实更像是子模块的手动版本.
因此当一个项目被作为子树添加时, Git是不知道子树这个概念的, 它对于Git来说是透明的.
这也是为什么每次调用子树命令都需要使用额外的参数, 它其实更像是子模块的手动版本.
- 基本上不需要向外部项目反馈代码时
- 希望能够很方便地编辑引用的外部项目的内容
- 希望将两个项目的源代码彻底合并(即希望将外部项目废弃)
- 不需要其他使用者掌握额外的知识(子模块会引入很多复杂的新知识)
- 1.
git checkout -b someone-patch-1 master为它人的PR创建本地分支并切换至此分支. - 2.
git pull https://github.com/someone/repo.git patch-name
将他人仓库的patch-name分支下载(fetch)并合并(merge)进此分支.
经过试验, 这里使用git fetch替换git pull的效果是一样的. - 3.
git checkout master切换回主分支, 准备合并. - 4.
git merge --no-ff someone-patch-1合并. - 5.
git push origin master合并完成后上传.
操作者有原始仓库的权限, 则Github这样的服务会自动为Pull Request的分支添加权限, 使操作者可以向PR分支推送提交.
- 1.
git checkout -b someone-patch-1 master为它人的PR创建本地分支并切换至此分支. - 2.
git pull https://github.com/someone/repo.git patch-name拉取PR分支到本地. - 3.创建新的提交.
- 4.
git remote add someone https://github.com/someone/repo.git添加PR所有者的上游仓库. - 5.
git push someone someone-patch-1:patch-name将本地分支推送到上游仓库.
情况: 操作者对该案例的原始仓库A没有控制权, 对Pull Request的仓库B也没有控制权.
目标: 操作者需要修改他人PR里的分支, 发送新的PR给仓库B, 以便原始PR能够被成功合并仓库A.
目标: 操作者需要修改他人PR里的分支, 发送新的PR给仓库B, 以便原始PR能够被成功合并仓库A.
- 1.
git clone 仓库B的URL克隆仓库B. - 2.
git remote add target 仓库A的URL将仓库A作为远程名称target加入到当前仓库. - 3.
git fetch --all下载仓库A和仓库B的所有远程分支. - 4.
git checkout PR分支签出仓库B里的PR分支. - 5.
git checkout -b patch在PR分支的基础上创建一个patch分支. - 6.
git merge target/master
合并来自仓库A的最新代码.
此处不能使用变基, 因为仓库B没有仓库A的最新代码.
另一种方案是直接fork仓库A, 用cherry-pick的方式将仓库B的分支捡进来.
虽然这种方案不方便发送PR给仓库B, 但直接发送PR给仓库A在结果上是一样的,
尤其是对那些很久都没有合并的PR而言, 原来的提交者可能早就不关心这件事了.
虽然这种方案不方便发送PR给仓库B, 但直接发送PR给仓库A在结果上是一样的,
尤其是对那些很久都没有合并的PR而言, 原来的提交者可能早就不关心这件事了.
git tag | xargs git push --delete origin: 清除所有远程tag.git tag | xargs git tag -d: 清除所有本地tag.git grep <regexp> $(git rev-list --all): 搜索该仓库从创建至今的所有历史文本.