Better

Ethan的博客,欢迎访问交流

Git 与 GitHub 深入

使用 Git 和 GitHub 也有好长一段时间了,这次我们来总结一些常见用法,解锁更多花式操作,同时注意在集成环境下的禁忌。

发展

VCS(version control system)出现以前

  1. 目录拷贝区分不同版本
  2. 公共文件容易被覆盖
  3. 成员沟通成本很高,代码集成效率低下

集中式 VCS

  1. 集中的版本管理服务器
  2. 具备文件版本管理和分支管理能力
  3. 客户端必须时刻和服务器连接

分布式 VCS

  1. 服务器和客户端均有完整的版本库
  2. 脱离服务端,客户端照样可以管理版本
  3. 查看历史和版本比较等多数操作,都不需要访问服务器,比集中式更能提高版本管理效率

最小化配置

官网(git-scm.com)中可以找到基础的中文教程

最小配置,配置 user 信息,目的就是提交可追溯

git config --global user.name 'your_name'
git config --global user.email 'your_email'

除了配置 global 外,还可以配置 local 和 system。可以通过 list 参数显示配置

git config --list --local # 只对某一个仓库有效
git config --list --global # 所有仓库有效
git config --list --system # 对系统所有登录的用户有效

--local 参数必须在特定仓库下才能执行

.git 目录

.git 目录常用文件或目录有

  • HEAD 文件:内容是 refs/heads/current_branch_name
  • config 文件:存放本地相关的配置信息
  • refs 文件夹(本质内容是 hash 值的存储)
    • heads:分支信息
    • remotes:远程信息
    • tags:标签信息
  • objects
    • 双字符松散文件:文件夹名称 + 内部文件名构成 hash 值
    • pack 打包文件
  • log:操作日志

commit、tree、blob 关系

  • 一个 commit 对应一个 tree(tree 表示当时这个 commit 下项目文件目录和文件的快照)
  • tree 里面可以包含 tree(因为目录也是树)
  • blob 只和文件内容相关,和文件名无关

命令进阶

git init 建立 git 仓库,两种场景

  • 把已有的项目代码纳入 git 管理:git init
  • 新建的项目直接用 git 管理: git init your_project,会在当前路径下创建和项目名称同名的文件夹

关于重命名和删除,git 提供内置命令

  • git rm
  • git mv

git log 常用参数,注意:参数通常可以搭配使用

  • 无参:查看当前分支的历史
  • 加上分支名称:表示查看指定分支的历史
  • --all:查看所有分支的历史
  • --graph:查看图形化历史
  • --oneline:查看单行的简洁历史
  • -n + number:查看最近的指定number条数的历史
  • 更多:git help --web log 打开网页版指南

git branch -v 查看本地分支信息,-av 查看远程和本地的所有分支信息

图形化查看版本演进:通过 gitk 命令打开图形界面

git cat-file 查看 git 文件类型

  • git cat-file -t hash 查看类型(commit、tag、tree、blob)
  • git cat-file -p hash 查看内容

git checkout -b branch-name。默认值基于当前分支的最近一次提交,或者你可以指定具体哪个分支,也可以是哪次提交。

通过 HEAD 可以帮助我们实现简写。比如我们要 diff 两次提交。git diff commitIDA commitIDB。但是 commitID 通常需要自己额外去找,此时我们就可以使用 HEAD。如下:

git diff HEAD HEAD^ # HEAD 的父亲
git diff HEAD HEAD^^ # 父亲的父亲
git diff HEAD HEAD~1 # 往前第一次提交

git reset 三个参数

  • --soft:只是把 HEAD 指向的 commit 恢复到你指定的 commit,暂存区和工作区不变
  • --hard:把HEAD、暂存区和工作区修改为你指定的 commit 的时候的文件状态
  • --mixed:默认参数,把HEAD、暂存区修改为你指定的 commit 的时候的文件状态,工作区保持不变

加塞紧急任务:使用 git stash 会将当前工作区和暂存区存储起来,恢复到当前 HEAD 的状态。然后你可以在此基础上新建 bug 修复分支,完毕后就可以通过 apply 或 pop 恢复现场。

指定不需要 git 管理的文件注意点

  • 是否加/的差别,有的话表示该文件夹下所有文件都不纳入管理,但如果存在同名文件,则会纳入管理。如果没有则均不会纳入管理。
  • 如果提交 commit 后,想忽略一些已经提交的文件,需要通过 git rm --cached name_of_file 的方式删除掉 git 仓库里面无需跟踪的文件。

基于某分支做变基:git rebase branch-name

分离头指针

分离头指针(detached HEAD):直接 git checkout commitID,就会提示你处在分离头指针的状态,因为没有和任何分支进行关联,仅仅是基于某个 commit。此时你可以做一些尝试性的修改,如果觉得可行,可以提交他们。你也可以通过另一个 checkout 从而丢弃在这种状态下做的所有提交。

git 很聪明,当你在分离头指针的状态下做了提交,此时切换到其他分支,会给你个友情提示,会提示有提交没有关联到任何分支,如果你觉得是有用的,你可以通过创建一个分支保留他们,命令为:git branch new-branch-name commitID;否则过段时间,会被 git 清理。

HEAD 正常都是指向某分支的最近一个 commit,在分离头指针的情况下,直接指向某个 commit。

Q & A

Q:如果修改最新 commit 的 message?

A:git commit --amend。本质上不只是修改 message,而是会创建一个将暂存区的内容生成一个 commit,再将当前最新的 commit 替换成新生成的那一个。

Q:如何修改老旧 commit 的 message?

A:git rebase -i commitID(很关键,这是被修改的 commit 的父 id),然后按照交互操作(选择 reword)即可。

这种操作仅限在没提交到远程分支的情况下,如果已提交,不建议如此操作,因为会影响到团队的其他成员

思考为何 rebase -i 会分离头指针呢?因为 git rebase 的工作就是利用的分离头指针,rebase 意味着基于新 base 的 commit 来变更部分 commits。处理的时候把 HEAD 指向 base 的 commit,此时如果该 commit 没有对应的 branch,就会处于分离头指针状态,然后重新一个一个生成新的 commit,当 rebase 创建完最后一个 commit 后,结束分离头状态,git 让变基完成的分支名指向 HEAD。

Q:怎么把连续的多个 commit 整理成一个 commit 呢?

A:同样使用 git rebase -i commitID,交互操作选择 squash 模式将指定的 commit 合并 pick 的 commit 中。

Q:怎么把间隔的几个 commit 整理成一个 commit 呢?

A:同样使用 git rebase -i commitID,其实和处理连续的类似,只不过我们要手动将不连续的调整成连续的,同样使用 squash 模式。

我们知道变基ID的选择是我们要修改的前一个,如果我们要修改最开始的祖先ID该如何处理呢。此时我们可以手动 pick ID 来达到目的。

Q:如果有多个分支的情况下,我们对其中某个分支进行了变基整理,因此对于当前分支而言,之前很多 commit 都没有了,此时基于之前 commit 创建的 branch 和 tag 该如何自处呢?

A:此时就有可能出现多个独立的树结构出来。

Q:如何比较暂存区和 HEAD 所含文件的差异?

A:git diff --cached 或者 git diff --staged。

Q:如何比较工作区和暂存区所含文件的差异

A:git diff 默认功能,git diff -- filename 只对指定文件看差异。

-- 是为了让 git 命令读取命令参数的时候消除歧义用的,双连字符后面的是路径和文件

Q:如何让暂存区的文件恢复成和HEAD的一样?

A:git reset HEAD,会把暂存区所有文件取消掉,如果需要指定某一个,在后面添加文件即可

Q:如何让工作区的文件恢复为和暂存区一样?

A:git checkout -- file

Q:如何消除最近的几次提交?

A:git reset --hard commitID(想回退到的指定状态的commit)

Q:如何查看不同提交的指定文件的差异

A:git diff commitID1 commitID2 path-of-filename

GitHub

Git 备份成本地或远程仓库,通过 git remote add name remote_url 的方式进行关联,后面我们就可以通过 push 进行同步,这里看常用的传输协议

哑协议和智能协议区别

  • 哑协议传输进度不可见,智能协议传输可见
  • 智能协议传输速度比哑协议块

GitHub 配置公私钥(mac)

  1. 检查是否已经有公私钥:ls -al ~/.ssh,没有则创建
  2. ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
  3. 将公钥配置在 GitHub 的 SSH and GPG keys 中

添加公私钥的好处就是 push 代码的时候能够智能识别用户,无需额外输入用户名和密码

本地仓库同步到远程

  • git remote add name remote_url
  • git push name --all(all 表示推送所有分支,name 为 origin 才可缺省)

通常我们建立远程仓库的时候,可能预先就有文件了,比如readme.md、.gitignore 或 LICENCE,此时本地和远程同步,往往会遇到问题,因为本地和远程是没有上下游关系的,是互相独立的版本树,直接 git merge 会报错。我们可以先通过 git fetch,然后再单独执行 git merge 操作查看哪个步骤报错(git pull 等同于 git fetch + git merge),此时可以通过 git merge --allow-unrelated-histories origin/master 来达到合并目的。

解锁 git fetch 和 git pull 更多用法

git fetch <远程主机名> # 这个命令将某个远程主机的更新全部取回本地
git fetch <远程主机名> <分支名> # 取回特定分支的更新
git pull <远程主机名> <远程分支名>:<本地分支名> # 将远程主机的某个分支的更新取回,并与本地指定的分支合并
git pull <远程主机名> <远程分支名> # 远程分支是与当前分支合并,则冒号后面的部分可以省略

git clone remote_url:默认使用远程项目名作为本地文件夹名称,如果需要自行指定名称,直接在后面加上即可。

如果远程有新的分支,默认本地是没有的,因此需要基于远程分支建立本地分支,这个基于很关键,不然就等同于在本地建立一个分支。命令:git checkout -b local-branch-name remote-branch-name

通常,合并分支时,如果可能,Git 会用 Fast-forward 模式,但这种模式下,删除分支后,会丢掉分支信息。因为 Fast-forward 表示快进模式合并,即直接将当前分支指针指向要合并的分支,此时不会生成新的 commit,因此一旦删除分支或者分支指针往前走,很难体现该出提交是合并自某个分支的。如果禁用掉 Fast-forward,就可以生成新的 commit 从而反映这件事。

git merge --no-ff -m "commit描述" <分支名>

如果不能采用 Fast-forward 模式,此时会触发 auto-merge,如果自动合并没有冲突,则会提示用户输入描述信息,从而创建一个新的 commit。如果自动合并失败,则需要手动解决冲突后,自行创建一个 commit 提交更改。

禁止向集成分支执行 push -f 操作。

禁止向集成分支执行变更历史的操作。如果公共的 commit 做了改变,会给协同开发带来麻烦。

GitHub 核心功能

  • 代码审查
  • 项目管理
  • 项目集成
  • 团队管理
  • 社交编程
  • 文档(pages,wikis)
  • 代码托管

如何高效在 GitHub 搜索项目

  • 直接搜索框回车,然后选择 advanced search,可进行高级搜索
  • 输入框关键词空格隔开,默认匹配项目标题和项目描述,十分局限
  • 方便起见,可以直接在输入框使用关键字
    • in:readme
    • stars:>1000
    • language:JavaScript
    • …… 和 advanced search 是对应的

组织类型帐号

  • 创建:settings - Organizations - New organization
  • 组织帐号可以有仓库,有成员,有小组
  • 小组可以管理仓库,管理成员(读写权限),子小组
  • 拥有团队后,在新建 repo 时就可以 Owner 选择对应组织
  • 建完团队项目后,就可以在项目下设置下的 Collaborators & teams 选择归属小组与对应的权限了

挑选合适的分支集成策略

  • Allow merge commits:只要 Git 能自行处理,就会自行 merge,在 base 上创建一个新的 commit,被合并分支和新的 commit 建立关联(叫做 merge commit)。
  • Allow squash merging:不改变被合分支指向,把分支的多个提交,合成一个 base 上新的 commit。可用来形成线性分支树。
  • Allow rebase merging:不改变被合分支指向,在 base 创建多个新的 commit 一一对应分支上的提交。可用来形成线性分支树。

为规范 issue,GitHub 支持为 issue 定制模版,具体的模版文件是存在 repo 中的,这样他们 new issue 时,就可以选择模版了。

使用 Project 管理 issue ?

  • 可以自己定义,也支持各种各样的模版,比如 Bug 管理,进度管理等,简直就是个任务管理器
  • 支持手动添加,或者 issue & PR 设置 Project

如何实施内部 code review ?通过添加保护分支规则,settings - branches

  • 必须通过内部 PR
  • 状态检查
  • 签名提交
  • 仅管理员等

怎么保证集成的质量:Marketplace 选择需要的工具实现自动化检查与发布



留言