本文首发于稀土掘金。该平台的作者 逐光而行 也是本人。
(注:图截取自青训营官方账号 Android 客户端专场学习资料一 git专题,原出处不详)
Git是什么
之前一直觉得vcs的v是指version,直到看到一篇参考文章(见文末),说git是distributed revision control system(分布式修改控制系统),感觉这个说法也很有意思。
同时,我认为和传统的vsc相比,git最大的优势在于 分布式 (支持远程协作)
Git仓库及其创建
git clone xxx
之后所在目录会生成一个文件夹,里面有所clone项目的代码,组成了工作树,.git包含了该项目的所有历史信息。
Git分支
Git的历史管理就像一棵树,有不同的分支(branch),一个commit创建一个新结点,每一个分支都可以通过一个head指针追踪到,每个head指针指向的是当前分支的末端(最新)(提前猜测版本回退的本质是指针前移)
刚新建的项目只有一个head分支(树的根),称为master。 有tag的说法,开发者用标签指明是哪个分支的哪个结点。
以下这行代码: ```shell git switch -c new v2.6.13 ``` 指的是新建一个分支(new v2.6.13)并切换过去(switch)并且查看 (-c) (我觉得切换的本质是指针跳转)
``` git branch ``` 查看所有分支,当前分支会有*
``` git reset --hard v2.6.17 ``` 强制切换当前分支至v2.6.17
``` git show ``` 展示最近的代码提交记录 commit后面那段是一个唯一标识的id,通过**哈希**方法生成,和分支名字是唯一对应的。 (事实上,.git里文件数据和内容也是以hash形式存储的)
``` - `git branch` - list all branches. - `git branch ` - create a new branch named ``, referencing the same point in history as the current branch. - `git branch ` - create a new branch named ``, referencing ``, which may be specified any way you like, including using a branch name or a tag name. - `git branch -d ` - delete the branch ``; if the branch is not fully merged in its upstream branch or contained in the current branch, this command will fail with a warning. - `git branch -D ` - delete the branch `` irrespective of its merged status. - `git switch ` - make the current branch ``, updating the working directory to reflect the version referenced by ``. - `git switch -c ` - create a new branch `` referencing ``, and check it out. ``` 我总结一下用法: git branch 默认在当前结点分叉,如果补上参数,就是在参数所在结点分叉; branch 后接-d是检查性删除,-D是强制性删除; git switch是切换分支,`git switch -c `是在末尾参数所在结点分叉并且切换过去,并且检查。 .git文件架下的HEAD记录了谁是当前分支。
个人理解: ```` git switch --detach v2.6.17 ```` 指的是把该结点 分离 出来,单独查看其状态,在其上做的变动没有影响。 加上--detach的用处有两个: - 查看一些特殊版本。默认情况下switch会切到分支的head上,但是--detach可以让人很方便地查看某个任意的结点。 - 谨慎一点,避免错误。--detach是分离的意思,也就是将要修改的结点先和主树切割开来,慢慢折腾,感觉没问题了可以使用 ``` git switch -c new_branch_name ``` 新建一个分支,将当前更改转移到新分支去。 ## Git远程分支 在`git branch`命令后加上 -r 选项可以查看远程分支(remote)。 `origin`是指代远程仓库(即该仓库未被我们拉取的时候的状态)。 我的理解来自各个地方的提交信息会被集中记录,因此可以查到。 因为同一个仓库每时每刻都可能有来自全球各地的提交信息,因此可以通过“抓取”获得最新信息。它会更新关于其他远程提交的信息,但不会更改个人的本地仓库信息(即使原来的master在远程已经被改了也不会影响个人clone下来的master)。 `git fetch` 也可以通过拉取的方式`git pull`,这两者应该是等同的。 当个人推送上去之后,中央仓库记录也变多了,也能更新 。`git push`
也可以直接往远程仓库添加分支,这些记录也会被同步更新。 ``` $ git remote add 分支名 ``` ## 巧用二进制搜索(bisect)解决问题 ### 问题背景 本地的某个分支版本运行正常,代码仓库里面的master出现了问题(说明是该分支版本之后的(某些)提交引起),如何快速通过代码提交历史定位差异? ### 解决方案 ``` $ git bisect start $ git bisect good v2.6.18 $ git bisect bad master ``` 步骤: 开始搜索;告知哪个版本是好的,哪个版本是坏的。(good 和bad对应二分查找的首末位置) 系统是将代码版本结点隔离了再进行检查的。 检查完成后,会输出打印引起冲突的commit id号 ### 后续 `git bisect reset` 回到未启动搜索前的分支 ### 让bisect忽略某个版本的错误 - 方法一 ``` git bisect visualize git reset --hard fb47ddb2db ``` 这段代码的意思是先让这个版本对搜索程序显式可见,再 强行 让搜索程序将其认定为另一个安全且近似的版本fb47ddb2db - 方法二: 废话不多说,直接跳过 ``` git bisect skip ``` ## Git日志——查看历史commit 比如如下代码的意思是:查看2.5 以后 的版本中涉及Makefile文件 或 在fs目录下的 ``` git log v2.5.. Makefile fs/ ``` git log 和gitk都是很好的查看详情命令 ## 找不同:diff 找xx和yy之间的不同 ``` git diff xx..yy ``` 找两者的祖先到yy的不同 ``` git diff xx..。yy ``` 找补丁 ``` git format-patch master..test ``` ## Git merge xx合并分支 有冲突的部分会被单独拎出来,当冲突解决了之后再次提交,就能合并成功,该结点记属于原分支,也属于合并后的分支 放弃合并(中断合并): `git merge --abort` 撤销已经提交上去的合并请求(注意,使用要慎重,特别是该版本也合并了其他分支的情况下可能会出问题) `git reset --hard xxx(原来分支的名字)` 上述情况适用于不同的开发者独立开辟的分支的合并;如果一个分支从主枝延伸出来,现在要把它放回去,则称为“fast-forward”,内部实现过程没有上述那么复杂。 ## 修改错误 两种方法:(推荐第一种) - 新建一个提交,相当于在原树上往前生成一个节点。 创建一个恢复旧版本的新结点 `git revert xx` - 回到原版本,在原版本上修改(!!如果代码已经公开了就不要再这么做!!)
`git fsck` (file system check) 一个有效检查仓库文件状态的命令
## 一些命令的运行流程 ``` you push your personal repo ------------------> your public repo ^ | | | | you pull | they pull | | | | | they push V their public repo <------------------- their repo ``` 这样可以把私人提交和团队成果区分开,所以如果是这种方式的话,每次记得pull,不要埋头硬改。 ## push失败由什么引起?怎么解决? - 失败的原因是提交不是fast-forward的,有可能是因为之前强制修改了已经公开的提交。 - 解决方案:强制push,以下两种任选其一 ``` git push ssh://yourserver.com/~you/proj.git +master git push -f ssh://yourserver.com/~you/proj.git master ``` ## 和merge很像的rebase rebase这个单词字面意思是重置,这里其实也是把分支树进行合并等变换 比如像下面这样: ``` H---I---J topicB / E---F---G topicA / A---B---C---D master ``` 输入命令 ``` git rebase --onto master topicA topicB ``` 会使树的结构变成这样: ``` H'--I'--J' topicB / | E---F---G topicA |/ A---B---C---D master ``` (感觉是个高级用法,它让我知道记录真的是可以随意修改的,不过我还是不要乱用比较好) # 后记 之前对git的了解仅限于在Linux服务器命令行下创建了git仓库,学了几行简单命令(add、commit)这些方便提交lab;而正式在本机写代码时,由于之前写的东西体量小,不太需要版本控制,所以一般都是肉眼debug+凭借记忆,极少数情况会去用idea默认的vcs,翻翻上一次的修改版本。 但是现在慢慢开始要完成一些比较大的项目,而且需要团队合作,因此开始使用idea的git集成。 说实话,我感觉对着图标点点点是方便了,但是集成和封装好的东西反而让我有种很懵的感觉。所以想趁着有空,深入了解一下git,虽然它只是一个工具,但它的思想是很值得学习和研究的。认真了解一些细节也是为了更好地使用吧。 # 参考资料 [Git User Manual](https://mirrors.edge.kernel.org/pub/software/scm/git/docs/user-manual.html#repositories-and-branches)