自荐
俊建 Kevin
2021.9
由于本文,促成了蓝桥与我的合作网课:12 个实验教你轻松进阶 Git,算是作为本文入门系列的延伸吧,网课依然是我的写作风格:由浅入深、逐步上难度、行文随性……但是比本文肯定正式和规范多了,蓝桥的编辑要求也很严格,希望能够帮到有需要的同学。网课与本文几乎没有重复,甚至最好阅读完本文的前 4 章后再读才好。
另外,我还为本文和网课单独申请了一个微博:GiterClub,有什么 git 相关的交流和问题都可以 @、私信我,我不保证及时回复啊,但如果有空且碰巧我会的话,非常乐意与大家共同探讨。
自序
俊建 Kevin
2016.2
读一本 Git 的书和读一篇 Git 的文章给人的知识含量是不同的,但给人的愉悦感也是迥异的。本文不想让您有读书的感觉、不追求知识全面、架构完整,但也不想只罗列一些 git 的知识点,搞的像一个 Tip 集。本文会从实际使用的角度,以聊天问答的形式,从浅入深,逐步展露 git 的若干方面。
本文分 Round 进行,每局并不以 git 命令的逻辑来分类,而是以使用 git 完成任务的水平来分解阶段。一些问题原本来自于我和朋友们的聊天,闲聊之余,记录成集,不要期待有什么高深的理论,让你每读完一个 Round,可以小试牛刀,然后烹茶小饮、若有所思……是我最想要的。
git 有自己的 user manunal,和官方宝书(英文版、中文版、国内备份),如果你读来无碍,请忽视本文,本文与它们相比只是个小人书、连环画,或者作为它们的一个补充。我在写每个 Topic 的时候也都在想:是不是书里已经有了?我是不是重复了?是不是删掉算了?—— 经常在知识点完整和拾遗之间反复权衡,但想到碎片化阅读越来越普及、学东西主要靠百度的今天,我写点东西发出来应该也是有益的。
git --version
,就在那里了一路“下一步”安装完即可。
对比一下 1 和 4:
所以建议您直接用 4:直接双击或在 cmd 中打开 ${安装目录}/bin/sh.exe 来运行 MinGW 环境和 git
先单机玩玩吧,理由如下:
git 和 svn 最大的区别:
所以说我们还是先练习一下除了 sync 之外的基础命令吧,会了这些之后,至少你能在本机快乐的写日记了。
我看到有些小伙伴还在用类似 EDiary 等日记本软件或 PIM 软件来写每天的日记,积累了这么多年的日记,一旦 win10 上运行不了 EDiary 可咋整?还是赶紧试试用纯文本+Markdown 来写日记,用 git 本地做版本管理吧!
下文我使用个人日记的小项目来演示本机的 git 操作
OK,让我们从头开始,跟着做一遍吧,Good Luck ……
$ git config --global user.name wkevin
$ git config --global user.emal wkevin27@gmail.com
MBP:demo wangkevin$ mkdir mydiary
MBP:demo wangkevin$ cd mydiary
$ cat >diary.md
# Diary
## 2016.1.31
回家过年^C
$ ls
diary.md
$ cat diary.md
# Diary
## 2016.1.31
回家过年
git init
:在文件夹中创建 git 库$ git init
Initialized empty Git repository in /Users/wangkevin/workspace/kproject/demo/mydiary/.git/
$ ls -a
. .. .git diary.md
$ ls .git
HEAD config hooks objects
branches description info refs
$ cat .git/config
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
git status
:显示一个未被管控的文件(Untracked files) diary.md$ git status
On branch master
Initial commit
Untracked files:
(use "git add <file>..." to include in what will be committed)
diary.md
nothing added to commit but untracked files present (use "git add" to track)
git add filename
:将文件纳入管理,filename 支持通配符,最常用的就是点(.)表示所有文件$ git add diary.md
git status
显示此文件待提交(to be committed),此时文件已经开始被 git 管理了,文件进入一种暂存状态(stage),如果想反悔可以用git rm --cached
使其进入 unstage 状态$ git status
On branch master
Initial commit
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: diary.md
git status -s
-short 短模式$ git status -s
A diary.md
git status -b
-branch 显示分支,git status
不带参数默认就是-b 的,所以常和短模式合作,合并为一个 sb,哈哈$ git status -sb
## Initial commit on master
A diary.md
git commit
: 将文件从暂存态提交入库 —— 暂存就像回收站(删除前给你一个 check 的机会,多次操作放入回收站的文件可以一次清空),多次操作放入暂存,最后考虑成熟了,check OK 了,再 commit 提交$ git commit
aster (root-commit) 14dd781] create mydiary
1 file changed, 4 insertions(+)
create mode 100644 diary.md
git commit
后会自动打开一个编辑器(编辑器是可配置的,以后再说怎么配置),比如 vi,进行提交 log 的撰写,保存退出即提交成功,不保存退出即放弃提交 1
2 # Please enter the commit message for your changes. Lines starting
3 # with '#' will be ignored, and an empty message aborts the commit.
4 # On branch master
5 #
6 # Initial commit
7 #
8 # Changes to be committed:
9 # new file: diary.md
10 #
git status
,都已经提交干净了$ git status
On branch master
nothing to commit, working directory clean
$ git status -s
$ git log
commit 14dd7815fcf56c961e11c52e96e2fc3fbd7d0543
Author: wkevin <wkevin27@gmail.com>
Date: Sun Jan 31 11:39:55 2016 +0800
create mydiary
$ git lg
b81373d | 2016-01-31 15:49:08 +0800 | 2016-01-31 15:49:08 +0800 | wkevin add .gitignore file
67840e1 | 2016-01-31 12:20:26 +0800 | 2016-01-31 12:20:26 +0800 | wkevin 2.2日记
bf36ab9 | 2016-01-31 12:19:33 +0800 | 2016-01-31 12:19:33 +0800 | wkevin 2.1的日记
14dd781 | 2016-01-31 11:39:55 +0800 | 2016-01-31 11:39:55 +0800 | wkevin create mydiary
$ git log 6784
commit 67840e1813af1084abd5d07d2e2a2e185c679f09
Author: wkevin <wkevin27@gmail.com>
Date: Sun Jan 31 12:20:26 2016 +0800
2.2日记
git add
、适时git commit
,经过一段时间,你的 diary 库就越来越让你爱不释手了$ git log
commit 67840e1813af1084abd5d07d2e2a2e185c679f09
Author: wkevin <wkevin27@gmail.com>
Date: Sun Jan 31 12:20:26 2016 +0800
2.2日记
commit bf36ab9b0d489a2eda911be9e01bddc395fc29e0
Author: wkevin <wkevin27@gmail.com>
Date: Sun Jan 31 12:19:33 2016 +0800
2.1的日记
commit 14dd7815fcf56c961e11c52e96e2fc3fbd7d0543
Author: wkevin <wkevin27@gmail.com>
Date: Sun Jan 31 11:39:55 2016 +0800
create mydiary
git log
默认显示的内容不爽?想看更详细的、或更简略的?——别急,统统没问题,各种参数全方位满足你的各种需求,但这里先不说,后文慢慢来,先不要用这些复杂的参数来打击自己吧,不过来个一步简洁到位的的命令:git shortlog
—— 什么?太简洁了?哈哈,别急,从简洁到纸到复杂到翔全都有,慢慢来。$ git shortlog
wkevin (3):
create mydiary
2.1的日记
2.2日记
如果只是让 git 管理个日记本,自己写、自己看、绝不给别人看、绝不上网……这些命令就差不多够了!
哇!好累啊,可以休息一下了,就这些命令,玩几天,把日记写上一个礼拜,然后我们再继续。如果你不打算继续了,也没关系,这些命令就写日记–够用了!
第一局,Over!
欢迎回来,能回来接着读说明你是个积极追求上进的好同学,我们继续聊!
用 git 写了一些日记,你肯定有了新需求,最令你恼火的可能有:
有这样的问题说明你已经是 git 的初级用户了,并且听了我的建议:“使用命令行,远离 GUI” —— 我一点都不奇怪,绝大部分程序猿一旦用上 git 都会上瘾的,会频繁的git commit
,然后在git log
中寻觅自己的成就感,否则吃不好饭、睡不好觉……呵呵
言归正传。
别名(alias)是 linux 系统的基本概念,在 git 中也如鱼得水:
$ git config --global alias.st "status"
$ git st
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: diary.md
no changes added to commit (use "git add" and/or "git commit -a")
$ git config --global alias.st "status -sb"
$ git st
## master
M diary.md
$ git config --global alias.ci "commit"
问:markdown 写的 diary.md ,会在本地生成 diary.html 检查和欣赏一下,但其实是不需要 commit 的,如何在 git commit
的时候忽略它们。
答:git commit
的时候已经不能忽略了,要忽略需要在git add
的时候,通过编辑.gitignore 文件让 add 命令忽略它们。
$ git st
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: diary.md
Untracked files:
(use "git add <file>..." to include in what will be committed)
diary.html
no changes added to commit (use "git add" and/or "git commit -a")
$ cat >.gitignore
*.html
^C
$ cat .gitignore
*.html
$ git st
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: diary.md
Untracked files:
(use "git add <file>..." to include in what will be committed)
.gitignore
no changes added to commit (use "git add" and/or "git commit -a")
$ git log 6784
commit 67840e1813af1084abd5d07d2e2a2e185c679f09
Author: wkevin <wkevin27@gmail.com>
Date: Sun Jan 31 12:20:26 2016 +0800
2.2日记
$ git log --author=wkevin
commit 67840e1813af1084abd5d07d2e2a2e185c679f09
Author: wkevin <wkevin27@gmail.com>
Date: Sun Jan 31 12:20:26 2016 +0800
2.2日记
……
$ git log --author=wke
commit 67840e1813af1084abd5d07d2e2a2e185c679f09
Author: wkevin <wkevin27@gmail.com>
Date: Sun Jan 31 12:20:26 2016 +0800
2.2日记
……
$ git log --author=wken
$
$ git log --author=wke.*n
commit 67840e1813af1084abd5d07d2e2a2e185c679f09
Author: wkevin <wkevin27@gmail.com>
Date: Sun Jan 31 12:20:26 2016 +0800
2.2日记
$ git log --pretty=medium --date=short
commit 67840e1813af1084abd5d07d2e2a2e185c679f09
Author: wkevin <wkevin27@gmail.com>
Date: 2016-01-31
2.2日记
commit bf36ab9b0d489a2eda911be9e01bddc395fc29e0
Author: wkevin <wkevin27@gmail.com>
Date: 2016-01-31
2.1的日记
commit 14dd7815fcf56c961e11c52e96e2fc3fbd7d0543
Author: wkevin <wkevin27@gmail.com>
Date: 2016-01-31
create mydiary
默认的git log
一条 commit log 至少需要 6 行来显示,一页顶多看个 5、6 条,很不方便。如果单条达到 10 行的话,一页也就看个 2、3 条。像 linux 这样的项目,经常遇到长篇大论的 log,内容倒是详实了,但很难做一览表式的查询。
下面我们祭出 git log 的必杀技: –pretty 或 –format
git log --pretty=xxx
等价于 git log --format=xxx
, xxx 可以是这些:
其中oneline
能够帮你精简 log
$ git log --pretty=oneline
67840e1813af1084abd5d07d2e2a2e185c679f09 2.2日记
bf36ab9b0d489a2eda911be9e01bddc395fc29e0 2.1的日记
14dd7815fcf56c961e11c52e96e2fc3fbd7d0543 create mydiary
其实 --oneline
也是一个单独的参数
$ git log --oneline
67840e1 2.2日记
bf36ab9 2.1的日记
14dd781 create mydiary
那需要用上 –pretty=formate:“……”参数了
format 参数很多,没必要逐一掌握,除了你是强迫症患者 – 凑巧本文不使用强迫症的视角,哈哈
我常用的有:
示例:
$ git log --pretty=format:'%ad %an %s'
Sun Jan 31 15:49:08 2016 +0800 wkevin add .gitignore file
Sun Jan 31 12:20:26 2016 +0800 wkevin 2.2日记
Sun Jan 31 12:19:33 2016 +0800 wkevin 2.1的日记
Sun Jan 31 11:39:55 2016 +0800 wkevin create mydiary
$ git log --pretty=format:'%ad %an %s' --date=short
2016-01-31 wkevin add .gitignore file
2016-01-31 wkevin 2.2日记
2016-01-31 wkevin 2.1的日记
2016-01-31 wkevin create mydiary
$ git log --pretty=format:'%ad %an %s' --date=local
Sun Jan 31 15:49:08 2016 wkevin add .gitignore file
Sun Jan 31 12:20:26 2016 wkevin 2.2日记
Sun Jan 31 12:19:33 2016 wkevin 2.1的日记
Sun Jan 31 11:39:55 2016 wkevin create mydiary
$ git log --pretty=format:'%h %ad %an %s' --date=local
b81373d Sun Jan 31 15:49:08 2016 wkevin add .gitignore file
67840e1 Sun Jan 31 12:20:26 2016 wkevin 2.2日记
bf36ab9 Sun Jan 31 12:19:33 2016 wkevin 2.1的日记
14dd781 Sun Jan 31 11:39:55 2016 wkevin create mydiary
最后,你还需这样:
$ git config --global --replace-all alias.lg "log --pretty=format:'%h %ad %an %s' --date=local"
那就是颜色了,加上颜色让字段分的更加清晰
git config --global --replace-all alias.lg "log --pretty=format:'%C(auto) %h | %ai | %Cred %an %Cgreen %s'"
为什么改用%ai
,不用%ad
了?
%ad
会受到--date=xxx
的影响,%ai
不会。所以限制了%ad
的使用,如果常从 github 上拿代码,会看到世界各地的提交人和提交时间,我还是希望分一下时区的,所以用了%ai
。
比如我们来查看 linux 的源码:
必须要说了,git 的设计者的设计思路是:希望提交人(执行git commit
的人)能够把 author 写明白,而不是据为己有。所以 git 的作者(author)和提交人(commit)可以不是同一个人。
$ git commit --author=wkevin --date='2016-01-30 22:04:04 +0800'
上面的命令可以在 commit 的同时指定提交内容的 author 和 AUTHOR_DATE,这个恐怕要靠提交者(committer)的记忆力和公德心了,把这段代码真实 author 的名字和写就时间录进去,而不是让 git 默认的把自己的 name 和提交时间(COMMITTER_DATE)录入库中。
在没有 github 之前,一个开源项目通常还是只设置几个有权限的提交人,大家想贡献代码就发 patch 给有权限的人,然后有权人 commit。但自从有了 github,发明了 fork(fork 并不属于 git,而是 github 的独创哦)和 PR(Pull-Request),让这个过程更加的轻便,也让项目的发展更加《失控》,有能力的人可以在自己的领地 fork 并发展一个项目,PR 或不 PR 给原作者全凭个人喜好,原作者如果“懒政”,其他人完全可以独立发展。—— 每个人都在自己的库里 commit,使得 committer 和 author 通常都是一个人,大家都是通过 PR 给其他人,而不是发送 patch 了。—— 所以 --author
这个参数已经很久不用一次了。
$ git log --stat
git tag
貌似是完成不了这个任务,只能拜托git log
了。
关键是 --simplify-by-decoration
参数, refs/heads 和 refs/tags 都算一种 decoration,再联合 –tags 就可以了:
$ git log --tags --simplify-by-decoration --pretty="format:%ci %d"
下面这句可以按常规 log 来显示,每个 hash 后面跟的就是 tag
$ git log --decorate=full --simplify-by-decoration
或者用 for-each-ref 命令也是极好的:
$ git for-each-ref --format="%(creatordate) %(refname:short) " refs/tags/*
这个必须有!
git 和 TortoiseSVN 相比是不恰当的,git 要和 subversion 比较,它们两个是协议;TortoiseGit 才是和 TortoiseSVN 比较,这两个是前端。Subversion 的前端并不多,除了 TortoiseSVN 并没有更多的选择,git 的前端却不少:TortoiseGit、GitForWindows、Github for Desktop……
前端对协议进行了封装(比如默认安装的 TortoiseSVN 都已经找不到
svn
等命令,所以也不能运行svn log
、svn commit
)和更多的图形化工作(图标重绘、文本比较工具……)的事情留在后面慢慢说,回到比较工具上来:除非你是要制作补丁包,或者改动很小,否则你几乎不会想直接查看git diff
,配置好第三方比较工具的调用方法是必须要做的 —— 这个懒偷不得。
git 调用第三方工具是灵活的,当然 TortoiseSVN 调用第三方 diff/Merge 工具也是可定制的,并且用户不指定第三方工具的话,TortoiseSVN 项目自己做了一个比较工具 TortoiseMerge 来作为默认,TortoiseGit 也是有默认的。git 则需要手工设置。
git 中查看差异有两个命令:
git diff
: 在 Terminal 中按照 Linux 的传统方式生成 patchgit difftool
: 使用第三方工具显示差异git difftool
命令能够调用的第三方比较工具有很多,列几个本人用过的:
用哪个呢?这是萝卜白菜的事情,不要纠结,你用惯了哪个就是哪个(我相信你的电脑上肯定已经有了一个文本比较工具,用它就是了,本着开放、开源、和跨平台的原则,我个人推荐 Meld)。git 调用它们的方法配置是大同小异。我不能每种软件在每个系统中都试一遍,所以只能条目列在这里,但我本人没搞过的就空着了,看官自己百度一下吧,照葫芦画瓢能力强的话也用不着百度。
vi ~/.gitconfig
,加入:difftool.prompt=false
diff.tool=araxis
merge.tool=araxis
mergetool.araxis.path=/Applications/Araxis Merge.app/Contents/Utilities/compare
difftool.araxis.path=/Applications/Araxis Merge.app/Contents/Utilities/compare
$ git config --global diff.tool bc3
$ git config --global difftool.bc3.path "c:/program files/beyond compare 3/bcomp.exe"
$ git config --global diff.tool diffmerge
$ git config --global difftool.diffmerge.cmd 'diffmerge "$LOCAL" "$REMOTE"'
除此之外,还可以配置一项:
$ git config --global difftool.prompt false
OK,弄好了吧,我们来总结一下其知识点,如果不想看,可以跳过去看下条了。
git config ...
命令vi ~/.gitconfig
直接修改 git 的配置文件,方法 1 最终也是落实到 2 上肯定还是有些完美主义者,一台电脑上安装了多个比较软件,想要不断切换 —— 也是没问题的。
$ git config --global difftool.bc.cmd 'beyondcompare "$LOCAL" "$REMOTE"'
$ git config --global difftool.am.cmd 'araxismerge "$LOCAL" "$REMOTE"'
$ git config --global difftool.dm.cmd 'diffmerge "$LOCAL" "$REMOTE"'
$ git config --global diff.tool bc
或
$ git config --global diff.tool am
或
$ git config --global diff.tool dm
git 在 git commit
之前首先要 git add
,从 svn 转移过来的同学会对这点有一些疑惑和质疑。
git add
将文件放入到暂存区(stage),并生成对象 —— 参见本文的 git 的对象(object)
理解 git 需要理解文件的 5 种状态和 3 个区(area):
5 种状态:
3 个区:
1、2、3 状态在本地工作目录,4 状态属于暂存区,5 状态属于 git 库。
如果我修改了一下 README.md,git add
了一下,然后又修改了一下,用git st
的打印是这样的:
$ git st
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: README.md
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: README.md
很多地方把暂存和 stage 混在一起,不查字典的话还以为 stage 的中文翻译就是暂存。其实 stage 就这个单词的本身的意思是:
stage 本身并没有暂存的意思,git 中可以理解为把文件放到一个舞台上上演一下,进而文件进入到一个新的阶段。——用这个词可以说是一箭三雕。
你可以输入git help stage
看看
GIT-STAGE(1) Git Manual GIT-STAGE(1)
NAME
git-stage - Add file contents to the staging area
SYNOPSIS
git stage args...
DESCRIPTION
This is a synonym for git-add(1). Please refer to the documentation of that command.
git stage
是同义于 git add
的:将文件加入到 staging area(舞台区、阶段区、进而翻译为暂存区,下文我还是尽量不去翻译这个词汇,而直接用英文吧,或搞个缩写:SA —— 如果让我翻译,我会译为:检视区)。
把前面已经提到过暂存区像回收站,把文件放在回收站是给操作人一个检视的机会和反悔的机会,操作妥当后再彻底删除,彻底删除后再想反悔就要费劲了。git 的 staging area 也是给用户一个检视的机会和反悔的机会,用户可以:
git add
或git stage
命令随时向 SA 增加文件,和回收站不同的是后进入 SA 的文件会覆盖前面进入的git checkout
命令随时从 SA 反悔,文件会从 SA 移除,是否覆盖git commit
会自动打开系统默认的编辑器来让你写 log,如下修改:
ubuntu$ update-alternatives --config editor
这个问题不属于 git 的范畴,而是 linux 的。
到这里,你应该能够优雅地使用 git 管理自己的日记本了:log 清清爽爽、命令简简单单、变更一目了然 —— 如果只是管理自己的日记项目,已然可以游刃有余了。
如果你是工程师或程序员,还会有多版本、多分支并行开发的需求,下面我们开始讨论分支、合并等操作。如果不是程序员,基本上可以刀枪入库、马放南山,结束阅读了。
第二局,Over!
并发:并行开发,将会涉及分支(创建、查询、删除……)、标签等。
好了,这里要提到一个非常重要的概念了,很多 git 书籍都会强调的一点:git 的 branch 只是个指针 —— 也常被称作“git 的必杀技”。
下面这几步非常有必要:
git branch
$ git help branch
我来概要的描述一些要点
git init
后会自动创建一个 master 分支,它并不是一个多么特殊的分支,跟其他分支没什么区别。曾经我也愤怒过这个事情:
git remote add ...
,增加一个 branch 却是:git branch ...
—— 为啥不要 add?git remote remove ...
,删除一个 branch 却是:git branch -d ...
—— 为啥要用-d
?git branch delete xyz
: 结果增加了两个分支:delete 和 xyzgit branch remove delete xyz
: 以为用错了,再试 remove,结果又增加了第 3 个分支:removegit branch del remove delete xyz
: 结果可想而知git help branch
git branch -d del remove delete xyz
在用参数还是用子命令的问题上,其实也不要纠结,子命令还能加参,所以子命令相当于对参数进行了一级分类,或者纯粹是开发者的个人偏好。
git <command> <subcommand> <option>
git bisect <subcommand>
git bundle <subcommand>
git credential <subcommand>
git notes <subcommand>
git p4 <subcommand>
git remote <subcommand>
git stash <subcommand>
git subtree <subcommand>
git svn <subcommand>
git worktree <subcommand>
git <command> <option>
git merge
= diff
+ patch
假定:本地工作目录是 b1(HEAD)的,希望从 b2 合并过来,b1 和 b2 从同一个节点 C3 继承
初始状态:
b1(HEAD)
|
C1 --- C2 --- C3
|
b2
git merge b2
后,b1 都不发生任何变化 b1(HEAD)
|
C1 --- C2 --- C3 --- C6 --- C7
|
b2
b1(HEAD)
|
C1 --- C2 --- C3
\
C4 --- C5
|
b2
git merge b2
后,b1 快速前移(fast-forward)到 b2 b1(HEAD)
|
C1 --- C2 --- C3 --- C4 --- C5
|
b2
git merge --no-ff b2
后,b1 即使可以快速前移(fast-forward),也会生成一个 commit,就像 b1 有提交似得,其实 C6 里面没有任何文件的修改,只是为给自己和团队人员提个醒,此处做过一次合并 —— merge 一个 anotated tag 的时候用此默认参数 b1(HEAD)
|
C1 --- C2 --- C3 ------ C6
\ /
C4 --- C5
|
b2
b1(HEAD)
|
C1 --- C2 --- C3 -- C6
\
C4 --- C5
|
b2
git merge b2
后,
--commit/
,--edit/
,即:会产生一次 commit,自动生成 log 但会弹出编辑器给用户编辑,并且git log b1
的时候会列出 C1/2/3/4/5/6/7 所有的提交信息 b1(HEAD)
|
C1 --- C2 --- C3 -- C6 -- C7
\ /
C4 --- C5
|
b2
git merge --no-edit b2
可以不弹出编辑器给用户编辑 loggit merge --no-commit b2
可以避免自动生成 commit,而只是把 b2 的差异合并到本地文件,并 add 到暂存区,后续由开发者自己提交。提交后的新节点 C7 仍是有两个父节点的(C5、C6) 本地文件 -> 暂存区
/ / \
/ / 手动commit b1(HEAD)
/ / \ |
C1 --- C2 --- C3 -- C6..../.................C7
\ / /
C4 --- C5 .............../
|
b2
git merge -squash b2
:sqush 单词的意思是挤压、压扁。此命令可以把 b2 中的差异提交合并成 diff,patch 到本地文件并 add,用户 commit 后生成的新节点和 b2 没有任何关系(git log
是看不到 b2 的所有提交的),可以理解为纯粹从 b2 拿差异过来,但又不和 b2 发生关系。—— 常用于在主干上合并一个没有完成的特性分支,或者两个相互依赖的分支不定时的互相合并。 本地文件 -> 暂存区
/ / \
/ / 手动commit b1(HEAD)
/ / \ |
C1 --- C2 --- C3 -- C6..../.................C7
\ /
C4 --- C5
|
b2
本地文件(diff文件)
/ /
b1(HEAD) /
| /
C1 --- C2 --- C3 -- C6 /
\ /
C4 --- C5
|
b2
git mergetool confilctfile
两种方式之一修改冲突文件,手工修改完毕后还需git add
,git mergetool
退出时 git 默认会把冲突文件 add 到暂存区,最后git commit
,此时会发现默认的 log 已经被自动加上了 本地文件(diff文件) -- "手工+git add"或"git mergetool"
/ / \
/ / 手动commit b1(HEAD)
/ / \ |
C1 --- C2 --- C3 -- C6 --- / -----------------------------------------------C7
\ / /
C4 --- C5 ------------------------------------------------/
|
b2
我想你所表达的所谓“垃圾”只是针对分支要合并到主干了,这些 log 显得琐碎而多余,针对主干来说是“垃圾”,在分支开发过程中这些肯定不是垃圾,而是有效的防护网,也是向领导汇报工作时的“烂笔头”。
我建议你在分支开发过程中可以适当的多提交一些、提交细一些,不但可以省去单独写“工作日志”,也利于回忆和追溯,如果领导在关注这个 git 库,也能让领导感觉每天都在努力,挣一些情感分。
但毕竟好又多的 log 信息合入主干的话还是要认真思考一下是不是要保留所有 log
git merge featurebranch master
git merge --squash featurebranch master
—— 具体原理参考 分支的合并(git merge)有哪几种场景 已描述这个问题问的好,很多一开始接触 git 的同学基本意识不到这个问题。这已经不是 git 本身的问题了,而是使用 git 的团队之间的工作流规范了。
git branch featurebranch
再开一个就是了,名字都可以相同git rebase master
git pull --rebase
如果有冲突会停住,然后git mergetool
解决冲突,git rebase --continue
继续和 git difftool
类似,也有 git mergetool
,但 mergetool 不是用来 merge 的,而是用来处理 merge 后的冲突文件的。
$ git config --global mergetool.diffmerge.trustExitCode true
$ git config --global merge.tool bc3
$ git config --global mergetool.bc3.path "c:/program files/beyond compare 3/bcomp.exe"
$ git mergetool
$ git config --global merge.tool diffmerge
$ git config --global mergetool.diffmerge.cmd 'diffmerge --merge --result="$MERGED" "$LOCAL" "$(if test -f "$BASE"; then echo "$BASE"; else echo "$LOCAL"; fi)" "$REMOTE"'
$ git log --pretty=oneline --graph
* 94688f21cc5d8bc85f1783b4c8b98b3288d712cb improve readme
* 693f2c48421d9218e057340bf29de75e0d5ba8d2 Merge pull request #377 from PhrozenByte/patch-1
|\
| * 9545a295cf4cfda6e728ebf0948a12bc5530e42d README.md: Add PHP 5.3+ requirement
| * 3d649081e58c9fed5ff11aeede1be2dd2e0ee153 Update composer.json requirements
|/
* 32de2cedcc98ffb3476f5a413f47bb482691c807 Merge pull request #373 from getgrav/master
|\
| * e7443a2bd868e78946ae6a01a1b07d477ce6f4cc Fixed really sorry spelling errors
| * 10a7ff776c3f16b1b3aa41c176c48150fc091065 Left as-is
| * 5ad15b87faa2ab10f7cda7593e2e92696fafadd2 Break out method_exists checks into extendable methods to allow for better pluggability
| * b166cab9a252f4093af1f33cb178a86f6047d08a Make `lines` protected to allow for extendability
|/
* 0f974bf34fdc420c3a7dc0a6c5c5fc620fa9dd89 improve readme
* 3d7cdeec5f90a16934a2cfd35a089c78aa0e4816 remove duplicate item in: who uses it
关于 git branch 之间的图示,有一些软件做出了不同的展示,这里 有一篇文章对比展示了 gitk、gitkraken、smartgit、sourcetree……多个软件的效果,并且自己也开发了 gitamine 来图示。
关于分支的命名,可以用一条 git 命令来检查: git check-ref-format —— 它的返回值为 0 表示 git 接受此命名,否则不接受。比如:
kevin@T410:~$ git check-ref-format "refs/heads/zte"
kevin@T410:~$ echo $?
0
kevin@T410:~$ git check-ref-format "refs/heads/z.t.e"
kevin@T410:~$ echo $?
0
kevin@T410:~$ git check-ref-format "refs/heads/zt..e"
kevin@T410:~$ echo $?
1
kevin@T410:~$ git check-ref-format "refs/heads/@zte"
kevin@T410:~$ echo $?
0
kevin@T410:~$ git check-ref-format "refs/heads/z~t^e"
kevin@T410:~$ echo $?
1
kevin@T410:~$ git check-ref-format "refs/heads/z:t?e"
kevin@T410:~$ echo $?
1
先解释一下 ref/hedas :分支和 tag 都是指针,ref 就是指针的意思,所有 branch 和 tag 在.git/config 中都是以 ref/… 命名的,如果不加 ref , check-ref-format 命令将不能正确识别。
可以发现两个点 .. 、~、^、:、? 这些都是不允许的,斜杠允许但不允许在末尾。
最后,来看看你最关心的中文名:
kevin@T410:~$ git check-ref-format "refs/heads/中兴"
kevin@T410:~$ echo $?
0
是允许的。
那么 branch/tag 的命名到底规则如何呢?
看 git 帮助即可:
$ git help check-ref-format
里面是这样解释的:
Git imposes the following rules on how references are named:
1. They can include slash / for hierarchical (directory) grouping, but no slash-separated component can begin with a dot . or end with the sequence .lock.
2. They must contain at least one /. This enforces the presence of a category like heads/, tags/ etc. but the actual names are not restricted. If the --allow-oneleveloption is used, this rule is waived.
3. They cannot have two consecutive dots .. anywhere.
4. They cannot have ASCII control characters (i.e. bytes whose values are lower than \040, or \177 DEL), space, tilde ~, caret ^, or colon : anywhere.
5. They cannot have question-mark ?, asterisk *, or open bracket [ anywhere. See the --refspec-pattern option below for an exception to this rule.
6. They cannot begin or end with a slash / or contain multiple consecutive slashes (see the --normalize option below for an exception to this rule)
7. They cannot end with a dot ..
8. They cannot contain a sequence @{.
9. They cannot be the single character @.
10. They cannot contain a \.
$ vi ~/.bashrc
增加
function git-branch-prompt {
local branch=`git symbolic-ref --short -q HEAD 2>/dev/null`
if [ $branch ]; then printf " [%s]" $branch; fi
}
PS1="\u @ \[\033[0;36m\]\W\[\033[0m\]\[\033[0;32m\]\$(git-branch-prompt)\[\033[0m\] \$ "
学会了分支操作(创建、合并、冲突……)是与人合作的基础,你是否已经准备好了走出个人的宇宙,拥抱开源的大世界了!
第 3 局,Over!
是不是已经不满足于只管理个本机的日记了?太好了,git 天生就是为了程序猿合作用的,几条关键的命令要出场了:
git clone url [localname]
git fetch
git pull
git push
一幅图说明问题:
最左边的 remote 和左边 3 个(repository、index、workspace)是同等地位的,意思是 remote 是远方某人的电脑,里面其实也包含了(repository、index、workspace)中的 3 个或 2 个,大家地位是平等的。
svn 中每个人的本机是 client,数据库在 server 上,离开 server,你看不到 log,无法做 commit。但 git 没有 server 的概念,或者说 server 就在每个人的本机上,无非是保存了 repo 的哪些东东。
每两人之间互相分享、交换代码是通过: pull、push、fetch、clone 这几个命令来完成的,你是我的 remote,我也是你的 remote,大家彼此之间就像橡树与木棉,谁也不是攀援谁的凌霄花,谁也不是谁的痴情鸟儿,两个 remote 只是两颗近旁的橡树和木棉,根紧握在地下,叶相触在云里,仿佛永远分离,却又终身相依。
我可以独立的生长,我 commit 代码不必次次都跟你商量,你也是,我们之间的每次 push 和 pull 都建立的互相尊重、地位平等的前提下,你不必绑架我,我也不限制你。
这个问题就像经典的 C++与 VC 的区别,问了很多年了还是有刚出校门的同学在问,我后来一度觉得这不能怪同学们,谁叫他俩非要起这么相近的名字,让人傻傻分不清。
git 和 github 更加淋漓尽致的体现这个现象,也是 git 入门的必问问题之一。谁让 github 这帮小伙起了个这样的名字,人家 sourceforge、googlecode、codeplex 都不以自己所用的版本管理软件的名字来标榜自己,唯独 github,为了显摆自己用了 git,起了个 git 转发器(hub)这个名字 —— 呵呵,开玩笑了,仔细琢磨,你会发现 hub(转发器)这个词选的真是绝了,太贴切了。
git 和 github 我用一句话就让你理解:就像 BT 和迅雷。——类似“迅雷使用 BT 协议,增加了权限、种子列表、热门榜单等”,github 使用 git 协议,增加了权限、项目列表、热门榜单等。其他就不多解释了。
开源项目托管网站(及其开始支持 git 的时间)有:
这些网站之间有很多有趣的历史,也是互为竞争对手。2004 年我第一次接触开源的时候,项目经理给我的任务是到 sourceforge 下载一个叫做 rainbow 的源码,当时还没有 git,开源项目托管第一平台 sourceforge 已经独霸江湖 10 余年,它是在用 cvs,作为一个还在学校的学生,真的是摸索了很久。
很多年过去了,看江湖风云,开源项目版本管理系统从 cvs 到 svn,又从 svn 到 git。开源项目托管网站从 sourceforge 逐步衰败,到群众寄予厚望的 google code 风光无限,但最终都还是和其他网站一起,败在了一个 2008 年创立、2011 年才火起来的后起之秀手上,google code 也于 2015 年底宣布关闭。此后起之秀就是当下无人匹敌的:github。
有个有趣的小插曲:CodePlex 是微软家的,也开张好多年了,sourceforge 时代就不愠不火,反正在大家眼里微软和开源本就是水火不容、盖茨/鲍尔默和托瓦茨也是井水不犯河水。但在 2012 年微软突然做出了一个有趣的决定:
近几年,微软更是几乎放弃了自家的 CodePlex,转投 github,开源自己的.NET 都在 github 上了。
2017 年 4 月 1 日,愚人节这天微软宣布将关闭了 codeplex,4 月 1 日开始关闭新项目的创建,10 月进入只读模式,12 月 15 彻底 over,结束 codeplex 11 年的生涯。回想当然用 C# 和 ASP.NET 的时候,经常上 codeplex 上溜达,都将随风而逝,成为回忆。
不解释,它就是那么酷! – 谁用谁知道
git 会使用“git confing 配置”和“shell 变量配置”两类:
$ git config --global http.proxy http://<proxyserver>:<port>
$ git config --global https.proxy http://<proxyserver>:<port>
实战:
$ git clone https://github.com/wkevin/GitChat.git GC
Cloning into 'GC'...
fatal: unable to access 'https://github.com/wkevin/GitChat.git/': Failed to connect to github.com port 443: Timed out
$ git config --global http.proxy http://proxysz.zte.com.cn:80
$ git config --global https.proxy http://proxysz.zte.com.cn:80
$ git clone https://github.com/wkevin/GitChat.git GC
Cloning into 'GC'...
remote: Counting objects: 64, done.
remote: Compressing objects: 100% (5/5), done.
remote: Total 64 (delta 0), reused 0 (delta 0), pack-reused 59
Unpacking objects: 100% (64/64), done.
Checking connectivity... done.
$ export |grep proxy
$
$ export http_proxy="http://proxysz.zte.com.cn:80/"
$ export https_proxy="https://proxysz.zte.com.cn:80/"
$
$ export |grep proxy
declare -x http_proxy="http://proxysz.zte.com.cn:80/"
declare -x https_proxy="https://proxysz.zte.com.cn:80/"
$
$ export -n http_proxy //export -n 只是标记此变量不再是环境变量,但仍然是shell变量
$ set |grep http_proxy
http_proxy=http://proxysz.zte.com.cn:80/ //set命令仍然能够查看到
$ export http_proxy //再次export即可
若你处在某个 proxy 之内,你的某个项目又有两个 remote,分别在外网和公司内网,则会遇到这样的困扰:
解决这个问题有几种方法:
git config --local --add remote.<name>.proxy ""
git clone
要麻烦一点export http_proxy ...
来访问外网 remote,另开一个 shell 则无此环境变量,可用于内网 remote。OK,书接上文。
$ ssh-keygen -t rsa -C "wkevin27@gmail.com"
C:\ShellHome
,而git push
等命令使用的是用户根目录,这两个目录未必一致,可能会被用户无意间修改。clip < ~/.ssh/id_rsa.pub
拷贝到粘贴板$ eval "$(ssh-agent -s)"
$ ssh-add ~/.ssh/id_rsa
$ ssh -T git@github.com
git remote add xyz git@github.com:wkevin/GitChat.git
git push xyz master
各个开源项目托管网站对 ssh 的要求可能还会有些许差别,但都会在显著位置进行说明,如果如上操作后仍不能访问,请仔细查阅。
当面沟通必不可少,svn 和 git 并不能解决所有问题,每个团队都可以有自己的分支策略、日志模版、合并规则、标签原则……
github 的工作流: Understanding the GitHub Flow
和团队成员保持紧密合作在敏捷中非常重要,SVN 的时候有一个非常优秀的软件 CommitMonitor,能够监控 SVNServer 的更新,图标是一双大眼睛,悄悄的躲在任务栏,发现更新大眼睛变成红色转啊转的。
git 和 svn 有所不同,svn 有 server,监控器只需要监控 server 即可,git 没有 server,只有 hub,或者说每个人电脑里的 git 都是 server,大家通过 hub 进行同步。监控 server 和监控 hub 的思路是有些差别的,各位看官可细细品味。
监控 git hub 的软件有:
但经过我的试用,功能还都不完善,希望它们早日成熟、好用。
目前可以使用 github/gitlab 的 RSS Feed 功能:
针对个人的 RSS Feed:
针对团队的 RSS Feed:
使用 RSS Reader(图中使用的是 Snafer)订阅的效果:
git pull 的问题是它把过程的细节都隐藏了起来,以至于你不用去了解 git 中各种类型分支的区别和使用方法。当然,多数时候这是没问题的,但一旦代码有问题,你很难找到出错的地方。看起来 git pull 的用法会使你吃惊,简单看一下 git 的使用文档应该就能说服你。
将下载(fetch)和合并(merge)放到一个命令里的另外一个弊端是,你的本地工作目录在未经确认的情况下就会被远程分支更新。当然,除非你关闭所有的安全选项,否则 git pull 在你本地工作目录还不至于造成不可挽回的损失,但很多时候我们宁愿做的慢一些,也不愿意返工重来。
SVN 和 git 面对同样的一个问题:大部分程序猿是含蓄、内敛的,他既希望频繁的 commit,让 svn/git 帮他记住每一步,但并不想把这些过程信息全都公诸于众,而只是把阶段性成果推送给大家或服务器。
具体情况还要分为两种:假定两名程序员 A、B
git daemon --reuseaddr --base-path=/opt/git/ /opt/git/
$ git config credential.helper store
以后 git push
再要求输入用户名、密码后,会保存在 ~/.git-credentials 文件中。
或者,也可以手工创建.git-credentials
文件,替换其中的 name 和 password:
$ echo "https://<name>:<password>@github.com" > ~/.git-credentials
注意几点:
git branch -D <branchName>
git push origin :<branchName>
git push origin --delete <branchName>
git tag -d <tagname>
git tag -d <tagname>
git push origin :refs/tags/<tagname>
git push origin --delete tag <tagname>
张三删除了某个 branch 并 push 到 github,李四git fetch/pull
之后该 branch 在李四本地库里是仍然存在的,如何删除之?
git fetch -p
git remote prune origin
A successful Git branching model
方法就是:在 git push 之前,先 git fetch,再 git rebase
git rebase(衍合)
git rebase 一般自己一个人开发时使用,用来保持提交记录的整洁。一旦上传到 github 后,不应该使用 git rebase,不然被骂死。
一旦分支中的提交对象发布到公共仓库,就千万不要对该分支进行衍合操作。
如果你遵循这条金科玉律,就不会出差错。否则,人民群众会仇恨你,你的朋友和家人也会嘲笑你,唾弃你。
这里有一篇 git 的创始人 Torvalds(同时也是 Linux 的创始人)的接受中国媒体的一篇访谈录:
2005 年 7 月 26 日开始,Torvalds 把 git 托付给了一位日本人:Junio Hamano。Torvalds 也说过自己一生最大的成功之一就包括把 git 托付给 Hamano。
Hamano 现在 google,他的 github 帐号为:gitster,头像中抱着个大熊猫,说不定比较喜欢 China 哦。
git 源码的提交统计中可以发现 Hamano 是提交最踊跃的:
github 上有这样几个卓越的组织(Orgnization):
读到这里是需要系统的了解、学习一下 git 的时候了,
当然,我明白你说的是中文资料。
官方 Specification
man gittutorial
; Part2 – 即 man gittutorial-2
Book
Website
google 或 bing 上搜索图片:git cheat sheet —— 不要在 baidu 上搜索,结果你会很失望。
可以看到很多热心网友们整理的常用命令集,快选一幅打印出来或作为桌面吧!
比如(版权归作者所有,本文仅是转发):
也有一些复杂到令人发指的:
是时候看一下 git 的 维基百科 了:
Git is a widely-used source code management system for software development. It is a distributed revision control system with an emphasis on speed,[6] data integrity,[7] and support for distributed, non-linear workflows.[8] Git was initially designed and developed in 2005 by Linux kernel developers (including Linus Torvalds) for Linux kernel development.[9]
As with most other distributed version control systems, and unlike most client–server systems, every Git working directory is a full-fledged repository with complete history and full version-tracking capabilities, independent of network access or a central server.[10] Like the Linux kernel, Git is free software distributed under the terms of the GNU General Public License version 2.
UI 前端也有,比如 github 出品的 github desktop
Git for Windows
_ 第一代的名字叫 sysGit](https://github.com/msysgit/git),基于 msys(属于 MinGW)—— 2015 年底已废弃
_ 第二代重新建立了 github 项目Git for Windows,基于 msys2(不再属于 MinGW),英语有自信的可以读读它的背景
git 库的托管网站(git host)有很多,github 并不是唯一的选择,github 最困扰开发者的一点就是:它丫竟然不交钱不给创建私有库。
下面介绍几个其他的:
这里有一个表格,对比各个 git host: https://git.wiki.kernel.org/index.php/GitHosting ,关注 Free private repositories,提供免费服务的也不老少,但大部分只是提供 git 的基本托管,并没有 github 的 fork、PR、gist、gitbook……等服务。
如果你要开发一个开源的模块来扬名,请使用 github;如果要开发一个秘密的、需要保密的大项目,请使用 Bitbucket;如果只是玩一玩,请使用码云(毕竟速度要快一点)。
那是必须可以的。
在 linux 上使用源码编译基本可以解决,步骤:
tar xvf git-2.9.5.tar.xz
cd git-2.9.5
./configure --prefix=/home/kevin/.local/git295
: 这里根据需求指定分开的目录即可make -j8 && make install
比如:
建议方案:
方案 1: git archive
导出一份不受 git 管理的纯内容出去
$ cd projectA.git
$ mkdir ../projectA-v1.2
$ git archive v1.2 | tar -x -C ../projectA-v1.2
方案 2:git worktree
在另外一处创建一个分支 —— 这是 git2.5 新增的一个功能,相当有趣
$ git br
* master cf4edda [origin/master] 修订proxy的使用方法
remotes/origin/HEAD -> origin/master
remotes/origin/master cf4edda 修订proxy的使用方法
remotes/zte/master cf4edda 修订proxy的使用方法
$ git worktree list
E:/demo/GitChat.git cf4edda aster]
$ git worktree add -b 4compare ../forCompare
Preparing ../forCompare (identifier forCompare)
HEAD is now at cf4edda 修订proxy的使用方法
$ ls ../
forCompare/ GitChat.git/
git branch
可以查看到新创建的分支$ git branch -vv
4compare cf4edda 修订proxy的使用方法
* master cf4edda [origin/master] 修订proxy的使用方法
$ git worktree list
E:/demo/GitChat.git cf4edda aster]
E:/demo/forCompare cf4edda [4compare]
$ cd ../forCompare/
$ git branch -vv
* 4compare cf4edda 修订proxy的使用方法
master cf4edda [origin/master] 修订proxy的使用方法
OK,可以在 forCompare 目录下工作了
我们继续探讨一下git worktree
的其他用法
git worktree
不但可以根据某个 branch 创建,也可以从某个 tag 创建$ git worktree add -b new2 ../new2 v0.1
Preparing ../new2 (identifier new2)
HEAD is now at 5d21d8b new file: img/git-state-and-area.svg
$ git worktree add -b new ../new 5d21d8b
Preparing ../new (identifier new)
HEAD is now at 5d21d8b new file: img/git-state-and-area.svg
git prune
能够删除目标文件已经被删除的 worktree$ git worktree list
E:/demo/GitChat.git cf4edda aster]
E:/demo/forCompare cf4edda [4compare]
E:/demo/new 5d21d8b [new]
E:/demo/new1 12205fd [new1]
E:/demo/new2 5d21d8b [new2]
$ rm -rf ../new2
$ git worktree list
E:/demo/GitChat.git cf4edda aster]
E:/demo/forCompare cf4edda [4compare]
E:/demo/new 5d21d8b [new]
E:/demo/new1 12205fd [new1]
E:/demo/new2 5d21d8b [new2]
$ git worktree prune
$ git worktree list
E:/demo/GitChat.git cf4edda aster]
E:/demo/forCompare cf4edda [4compare]
E:/demo/new 5d21d8b [new]
E:/demo/new1 12205fd [new1]
$ rm -rf ../new ../new1 ../forCompare/
$ git worktree prune
$ git worktree list
E:/demo/GitChat.git cf4edda aster]
cd oldrepo
git subtree split -P subdir -b newbranch
gitk newbranch
mkdir ../newrepo.git
cd ../newrepo.git
git init
git config --bool core.bare true
cd ../oldrepo
git push ../newrepo.git newbranch:master #newrepo.git is a pure repo without my files
cd ..
git clone newrepo.git
使用 git 的时候,我们往往使用 branch 解决任务切换问题,例如,我们往往会建一个自己的分支去修改和调试代码, 如果别人或者自己发现原有的分支上有个不得不修改的 bug,我们往往会把完成一半的代码 commit 提交到本地仓库,然后切换分支去修改 bug,改好之后再切换回来。这样的话往往 log 上会有大量不必要的记录。其实如果我们不想提交完成一半或者不完善的代码,但是却不得不去修改一个紧急 Bug,那么使用’git stash’就可以将你当前未提交到本地(和服务器)的代码推入到 Git 的栈中,这时候你的工作区间和上一次提交的内容是完全一样的,所以你可以放心的修 Bug,等到修完 Bug,提交到服务器上后,再使用’git stash apply’将以前一半的工作应用回来。也许有的人会说,那我可不可以多次将未提交的代码压入到栈中?答案是可以的。当你多次使用’git stash’命令后,你的栈里将充满了未提交的代码,这时候你会对将哪个版本应用回来有些困惑,’git stash list’命令可以将当前的 Git 栈信息打印出来,你只需要将找到对应的版本号,例如使用’git stash apply stash@{1}’就可以将你指定版本号为 stash@{1}的工作取出来,当你将所有的栈都应用回来的时候,可以使用’git stash clear’来将栈清空。
git clean -df
:丢弃 untracked 的文件,不丢弃 modified 的文件
git checkout HEAD .
: 见下条git reset --hard HEAD
: 见下条仅丢弃暂存区的修改,不丢弃本地目录的修改
git reset [--soft] HEAD
: 用 HEAD 分支覆盖一下暂存区,不影响本地文件丢弃暂存区&本地目录的修改
git checkout HEAD .
或指定文件 git checkout HEAD file
:用当前分支的库中文件覆盖暂存区和本地的git reset --hard HEAD
用 HEAD 覆盖一下暂存区和本地目录
git reset commitHash~1
: 即可让 HEAD 指向 commitHash 前一个 commit,即:丢弃 commitHash上面这 3 个丢弃都用到了 git reset
命令,这是个危险的命令,没有搞懂之前一定要慎重,否则丢了就可能找不回来了。
理解 git reset
请参考下文git 原理章节中的 git reset 原理图
管理员: 胆子越来越大了啊,都 push 到 server 里了,还要删除,羞不羞啊 :)
程序员: 给个机会吧,这几个 commit 确实不想 push 到 server 上。
管理员: 有没有想过 server 上的这个分支已经被 N 多同学 fetch 过了,已经基于它做开发了,你 reset 了几个节点,别人会出错的!
程序员: 不会的,这个分支只有我一个人用
管理员: 哪个分支啊?
程序员: master
管理员: 啥? master 分支 O_O ,你有没有看 gitlab 的帮助文档,master 分支被 gitlab 保护了,保护分支除了只允许 Master 角色有写权限外,还不允许任何人对其git push --force
操作,也不允许任何人删除保护分支的。
程序员: 有这一说?我说 git push --force origin master:master
咋提示我失败呢
十几分钟后……
程序员: 我研究了 gitlab 的权限说明: https://about.gitlab.com/2014/11/26/keeping-your-code-protected/ ,保护分支是可以取消保护的
管理员: shit,这都被你找到了,好吧,我承认是可以,就在 project - seetings 里面,只给你这一次机会啊,快去吧。
终于可以霸王硬上弓了。上弓方法有三:
git reset xxx
先回退本地,然后 git push --force origin master:master
git reset --soft hashcode remoteRepo
比如说,你对一个文件进行了多次修改并且想把他们分别提交。这种情况下,你可以在添加命令(add)中加上-p 选项
git add -p [file_name]
会逐段(hunk)提示你是否 add。
git cherry-pick [commit_hash]
这个命令会带来冲突,请谨慎使用
HEAD^
和HEAD~
是啥^
和~
是 2 个很有意思的字符,配合使用可表示祖宗十八代。任你给一个节点(HEAD 或 哈希值),都能顺藤摸瓜,找到其祖先是谁。
网友通常这么解释:
^
:表示第几个父/母亲 —— git 存在多个分支合并的情况,所以不只有 1 对父母亲~
:表示向上找第几代,相当于连续几个 ^
比如有这样一个库:
b1(HEAD)
|
C4 ------ C6
/
--- C5
自己: C6 = HEAD^0 = HEAD~0 = C6^0 = C6~0
父亲: C4 = HEAD^1 = HEAD^ = HEAD~1 = HEAD~ = C6^1 = C6^ = C6~1 = C6~
母亲: C5 = HEAD^2 = C6^2
我经过反复琢磨,给出另外一种更形象的解释:
^x
:抬头走1 步,入 x 号岔路口。~y
:低头走y 步,无视岔路口。所以:^^
: 抬头 2 步,都是 1 号口;^2^
:抬头走 2 步,第 1 步入 2 号口;^^3^
:抬头走 3 步,第 2 步入 3 号口;~^~
:低头 1 步,抬头 1 步,再低头 1 步;~2^~^2
:低头走 2 步,抬头 1 步如 1 号口,再低头 1 步,再抬头 1 步如 2 号口 …… 怎么样?用抬头走、低头走理论是不是一下就理解了?心情舒畅吧。
这样,我们就可以方便的得到:
爷爷:HEAD^^ = HEAD^~ = HEAD~2
奶奶:HEAD^^2
姥爷:HEAD^2^ = HEAD^2~
姥姥:HEAD^2^2
……
^
和 ~
可用于 git 的多种操作:log、diff、show、checkout……
实际操作一下:
kevin@:linux.git$ git log --oneline -n20 --graph
* c11fb13a117e (HEAD -> master, tuna/master, kernel/master, github/master) Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid
|\
| * 3ed224e273ac HID: logitech-dj: Fix 064d:c52f receiver support
| * f9482dabfd16 Revert "HID: core: Call request_module before doing device_add"
| * e0b7f9bc0246 Revert "HID: core: Do not call request_module() in async context"
| * 15fc1b5c8612 Revert "HID: Increase maximum report size allowed by hid_field_extract()"
| * eb6964fa6509 HID: i2c-hid: add iBall Aer3 to descriptor override
* | b076173a309e Merge tag 'selinux-pr-20190612' of git://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/selinux
|\ \
| * | fec6375320c6 selinux: fix a missing-check bug in selinux_sb_eat_lsm_opts()
| * | e2e0e09758a6 | 2019-06-12 21:28:21 +0800 | Gen Zhang selinux: fix a missing-check bug in selinux_add_mnt_opt( )
| * | aff7ed485168 | 2019-06-11 10:07:19 +0200 | Ondrej Mosnacek selinux: log raw contexts as untrusted strings
* | | 35110e38e6c5 | 2019-06-12 05:57:05 -1000 | Linus Torvalds Merge tag 'media/v5.2-2' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media
自己:
kevin@:linux.git$ git log --oneline -n1 HEAD^0
c11fb13a117e (HEAD -> master, tuna/master, kernel/master, github/master) Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid
kevin@:linux.git$ git log --oneline -n1 HEAD~0
c11fb13a117e (HEAD -> master, tuna/master, kernel/master, github/master) Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid
父亲:
kevin@:linux.git$ git log --oneline -n1 HEAD^
b076173a309e Merge tag 'selinux-pr-20190612' of git://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/selinux
kevin@:linux.git$ git log --oneline -n1 HEAD^1
b076173a309e Merge tag 'selinux-pr-20190612' of git://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/selinux
kevin@:linux.git$ git log --oneline -n1 HEAD~
b076173a309e Merge tag 'selinux-pr-20190612' of git://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/selinux
kevin@:linux.git$ git log --oneline -n1 HEAD~1
b076173a309e Merge tag 'selinux-pr-20190612' of git://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/selinux
爷爷:
kevin@:linux.git$ git log --oneline -n1 HEAD^^
35110e38e6c5 Merge tag 'media/v5.2-2' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media
kevin@:linux.git$ git log --oneline -n1 HEAD^~
35110e38e6c5 Merge tag 'media/v5.2-2' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media
kevin@:linux.git$ git log --oneline -n1 HEAD~2
35110e38e6c5 Merge tag 'media/v5.2-2' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media
奶奶
kevin@:linux.git$ git log --oneline -n1 HEAD^^2
fec6375320c6 selinux: fix a missing-check bug in selinux_sb_eat_lsm_opts()
母亲:
kevin@:linux.git$ git log --oneline -n1 HEAD^2
3ed224e273ac HID: logitech-dj: Fix 064d:c52f receiver support
姥爷:
kevin@:linux.git$ git log --oneline -n1 HEAD^2~
f9482dabfd16 Revert "HID: core: Call request_module before doing device_add"
总体来说:找父亲一族要方便些,找母亲一族要麻烦些。
为了快速了解代码,有时候需要快速的查看代码的统计信息,做一些宏观的把握,上面这个需求可能会有些用处。
先来复习一下 git 自身的 统计功能:
--stat
:每个文件的修改的行数,并用+-符号展示kevin@:linux.git$ git diff HEAD^ --stat
drivers/hid/hid-a4tech.c | 11 ++++++++---
drivers/hid/hid-core.c | 16 +++-------------
drivers/hid/i2c-hid/i2c-hid-dmi-quirks.c | 8 ++++++++
10 files changed, 136 insertions(+), 54 deletions(-)
--numstat
:每个文件的修改的行数,并用数字展示kevin@:linux.git$ git diff HEAD^ --numstat
8 3 drivers/hid/hid-a4tech.c
3 13 drivers/hid/hid-core.c
8 0 drivers/hid/i2c-hid/i2c-hid-dmi-quirks.c
--shortstat
:所有文件修改的行数汇总kevin@:linux.git$ git diff HEAD^ --shortstat
10 files changed, 136 insertions(+), 54 deletions(-)
--dirstat=[changes|lines|files]
:按文件夹统计和汇总下述数据
kevin@:linux.git$ git diff HEAD^ --dirstat=changes
100.0% drivers/hid/
kevin@:linux.git$ git diff HEAD^ --dirstat=lines
4.2% drivers/hid/i2c-hid/
95.7% drivers/hid/
kevin@:linux.git$ git diff HEAD^ --dirstat=files
10.0% drivers/hid/i2c-hid/
90.0% drivers/hid/
另外,--stat
也能用于 git log
,等价于每条 commit 都 diff 一下,即: git log --stat
== for (ci in commints) { git diff ci --stat }
kevin@:linux.git$ git log --oneline --shortstat
c11fb13a117e (HEAD -> master, tuna/master, kernel/master, github/master) Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid
b076173a309e Merge tag 'selinux-pr-20190612' of git://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/selinux
fec6375320c6 selinux: fix a missing-check bug in selinux_sb_eat_lsm_opts()
1 file changed, 14 insertions(+), 6 deletions(-)
35110e38e6c5 Merge tag 'media/v5.2-2' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media
e2e0e09758a6 selinux: fix a missing-check bug in selinux_add_mnt_opt( )
1 file changed, 14 insertions(+), 5 deletions(-)
aa7235483a83 Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/ebiederm/user-namespace
4d8f5f91b8a6 Merge branch 'stable/for-linus-5.2' of git://git.kernel.org/pub/scm/linux/kernel/git/konrad/swiotlb
c23b07125f8a Merge tag 'vfio-v5.2-rc5' of git://github.com/awilliam/linux-vfio
6fa425a26515 Merge tag 'for-5.2-rc4-tag' of git://git.kernel.org/pub/scm/linux/kernel/git/kdave/linux
aff7ed485168 selinux: log raw contexts as untrusted strings
1 file changed, 8 insertions(+), 2 deletions(-)
大量没有 file 修改的 merge 挺碍眼的,使用 --no-merges
清理
kevin@:linux.git$ git log --oneline --shortstat --no-merges
fec6375320c6 selinux: fix a missing-check bug in selinux_sb_eat_lsm_opts()
1 file changed, 14 insertions(+), 6 deletions(-)
e2e0e09758a6 selinux: fix a missing-check bug in selinux_add_mnt_opt( )
1 file changed, 14 insertions(+), 5 deletions(-)
aff7ed485168 selinux: log raw contexts as untrusted strings
1 file changed, 8 insertions(+), 2 deletions(-)
OK,基础功能展示完毕,基于这些,我们来发挥一下:
--no-pager
:git 默认使用 linux 的 less 模式显示,即满屏后提示用户按一下按键才继续显示,加上此选项则可以一口气打印完毕%h
:此处我只需要 hash 值,所以其他都省略了--no-merges
: 虽然 merge 也是工作量,但和我们的统计任务无关kevin@:linux.git$ git --no-pager log --format=format:'%h' --no-merges --since 2019-06-10
fec6375320c6
e2e0e09758a6
aff7ed485168
f6581f5b5514
--stat-name-width=300
:git diff 的输出默认会压缩到 80 列,使用...
这种,这样会丢失我想要的信息,所以我加大到 300,应该不会有丢弃了--name-only
:我只是想统计文件的个数,并没有计划汇总每次、每个文件内部变更的行数,所以只要名字即可"$1" "$1"~"
:最终会形成 git diff xxx xxx~
,为什么没用 ^
,效果一样么?留作思考题 :)kevin@:linux.git$ git --no-pager log --format=format:'%h' --no-merges --since 2019-06-10 | \
awk '{system(" git --no-pager diff --stat-name-width=300 --name-only "$1" "$1"~") }'
security/selinux/hooks.c
security/selinux/hooks.c
security/selinux/avc.c
kernel/cred.c
kernel/ptrace.c
fs[$0]+=1
: 用文件名做 key,value 每次+1kevin@:linux.git$ git --no-pager log --format=format:'%h' --no-merges --since 2019-06-10 | \
awk '{system(" git --no-pager diff --stat-name-width=300 --name-only "$1" "$1"~") }'| \
awk '{fs[$0]+=1} END{for(f in fs) printf("%d\t%s\r\n",fs[f],f) }' | sort -k 2
1 kernel/cred.c
1 kernel/ptrace.c
1 security/selinux/avc.c
2 security/selinux/hooks.c
-e 's/[^/]*$//'
:去掉文件名,只留路径-e 's#/#|#1'
:精确匹配第 1 个/
更换成|
,这里的 1 可以自己修改,统计不同 level 的文件夹深度-e 's/|.*//'
:把|
以后的字符删除 —— 这样只留下我们想要的某个 level 深度的路径kevin@:linux.git$ git --no-pager log --format=format:'%h' --no-merges --since 2019-06-1 | \
awk '{system(" git --no-pager diff --stat-name-width=300 --name-only "$1" "$1"~") }'| \
sed -e 's/[^/]*$//' -e 's#/#|#1' -e 's/|.*//' | \
awk '{fs[$0]+=1} END{for(f in fs) printf("%10d\t%s\r\n",fs[f],f) }'|sort -k 2
676 arch
9 block
10 crypto
2 Documentation
2027 drivers
128 fs
286 include
2 ipc
43 kernel
21 lib
16 mm
103 net
9 samples
17 scripts
93 security
304 sound
114 tools
5 virt
上面是 6.1 至今(6.14),半个月来 Linux 的修改,仍然是 drivers 中的文件最多,达到 2027 件次(类比“人次”这个单位,哈哈),kernel 前几天已经发布 5.1 了,其实 kernel 半个月才更新了 43 个件次,还是超级稳定的。
BTW:上面思考题的答案:用^
和 ~
是一样的。
svn 是基于增量存储的,两次提交对于 repo 来说只保存变化量,git 不使用 svn 的增量方式保存数据,而是使用快照。因为 git 的分布式特性,并没有一颗树一样成长的 repo,repo 更像是一张网式的成长,节点与节点之间可能会绕很远才能找到亲戚关系,所以增量无从谈起。
来看下面这个过程:
如果用增量存储,将很难把整个过程记录下来,根本原因是:开发者提交前是不需要同步别人代码的。
git show <对象名>
来查看一个 object 的内容4 类 object 的图示:
blob | tree | commit | tag |
---|---|---|---|
我的粗浅认识是:
git commit
时的相关信息。来实际操作一把:
git log
找一条 commit$ git lg
e3426a5 | 2016-02-16 11:20:22 +0800 | 2016-02-16 11:20:22 +0800 | Kevin Wang 调整章节,内容基本没变
a15f695 | 2016-02-16 10:28:16 +0800 | 2016-02-16 10:28:16 +0800 | Kevin Wang 增加 git cheat sheet 小节
c242093 | 2016-02-04 11:30:21 +0800 | 2016-02-04 11:30:21 +0800 | wkevin 笔误
48eda25 | 2016-02-04 11:21:57 +0800 | 2016-02-04 11:21:57 +0800 | wkevin 笔误: 缺少一个反括号
git show <object-name>
可以查看各类 object 的细节git show <commit-object-name>
可以查看 commit 类型的细节,其中包括了 diff(即:和 parrent 父级 commit 对象相比的差异:tree 及其 blob 的差异)$ git show e3426a5
commit e3426a51534d97f5c73369a98fd38d6fb2f83f0a
Author: Kevin Wang <wkevin27@gmail.com>
Date: Tue Feb 16 11:20:22 2016 +0800
调整章节,内容基本没变
diff --git a/README.md b/README.md
index 17944e2..a0be055 100644
--- a/README.md
+++ b/README.md
@@ -31,21 +31,24 @@ git 有自己的 [user manunal](https://www.kernel.org/pub/software/scm/git/docs
- erge是怎么玩儿的](#merge)
- [Round 4](#round4)
git show --pretty=raw <commit-object-name>
还能更多的查看 commit 对象所指向的 tree 对象$ git show --pretty=raw e3426a5
commit e3426a51534d97f5c73369a98fd38d6fb2f83f0a
tree 65e1673d28da6cf7554cc0bed020673f68276112
parent a15f6954d609da2bebc243a52a8dd1094e6e2fd6
author Kevin Wang <wkevin27@gmail.com> 1455592822 +0800
committer Kevin Wang <wkevin27@gmail.com> 1455592822 +0800
调整章节,内容基本没变
diff --git a/README.md b/README.md
index 17944e2..a0be055 100644
--- a/README.md
+++ b/README.md
git show <tree-object-name>
或 git ls-tree <tree-object-name>
或 git ls-tree <commit-object-name>
都能够看到 tree 对象更多的细节$ git show 65e1673
tree 65e1673
.gitignore
README.md
img/
$ git ls-tree 65e1673
100644 blob 40f51b88ea8b90ff1c9db36ffc45cfd71f71c078 .gitignore
100644 blob a0be0555eeeb50e4702e137a7837ad9970be7755 README.md
040000 tree 5aec0814a9b43d040a1a3388aaf2c4ae60e296f4 img
$ git ls-tree e3426a5
100644 blob 40f51b88ea8b90ff1c9db36ffc45cfd71f71c078 .gitignore
100644 blob a0be0555eeeb50e4702e137a7837ad9970be7755 README.md
040000 tree 5aec0814a9b43d040a1a3388aaf2c4ae60e296f4 img
git show <blob-object-name>
查看一个 blob 对象的细节,如果是文本文件就等同于$cat file
了$ git show 40f51b
*.html
.vim.*
$ git show --pretty=raw 48eda25
commit 48eda255c3f727e57f1462592a8cd8fd8d16839a
tree f1683d3e377fcbb99cca10c481d0070375e1bf23
parent 12205fd2616a0af5ebe8243f6e5c16a64e9e9127
author wkevin <wkevin@users.noreply.github.com> 1454556117 +0800
committer wkevin <wkevin@users.noreply.github.com> 1454556117 +0800
笔误: 缺少一个反括号
$ git ls-tree f1683d3e
100644 blob 40f51b88ea8b90ff1c9db36ffc45cfd71f71c078 .gitignore
100644 blob 5211c0dae2ab042cf0cf2edff08809af510e358a README.md
040000 tree 4867a64660a9d90a8a5a966c9fac1187861762f3 img
可以发现两次 commit 所指向的 tree 对象中:
因此说明:每次提交仅会把有改动的 file 重新计算 HASH 并封装为对象进行存储
svn commit
的时候是提交到网络服务器的,存在网络时延的问题,git commit
只有本地操作svn commit
的时候要实时计算 diff,git add/commit
不存在 diff 计算,git add
时会做对象的生成,但 git 对象的生成是执行压缩算法 —— 执行 diff 计算和执行压缩算法在当前水平的 CPU 能力下已不分伯仲git checkout file
:用暂存区的 file 覆盖工作区的 filegit checkout branch
:HEAD 指向 branch,然后去覆盖暂存区和工作区git checkout --detach branch
:游离指向 branch,然后去覆盖暂存区和工作区git checkout commithash
:游离指针指向某次 commit,,然后去覆盖暂存区和工作区git checkout branch/commithash file
:拿指针指向的 file 去覆盖暂存区和工作区的 file,所以暂存区会有待提交内容详细:
git checkout <./file>
git checkout <branch>
git checkout --detach [<branch>]
git checkout --detach
:会从当前 HEAD 创建游离指针git checkout --detach anotherBranch
:会从 anotherBranch 指针创建游离指针git checkout [--detach] <commit>
git checkout [[-b|-B|--orphan] <new_branch>] [<start_point>]
git reset [-q] [<tree-ish>] [--] <paths>…
git reset (--patch | -p) [<tree-ish>] [--] [<paths>…]
git reset [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<commit>]
git reset
==git reset HEAD
:用 HEAD 重置暂存区,工作区不受影响,相当于回滚/撤销 git add
git reset -- filename
== git reset HEAD filename
:仅将文件的改动撤出暂存区,暂存区中其他文件不改变。git reset --soft HEAD^
:工作区和暂存区不改变,但是 HEAD 和当前分支引用向前回退一次
git reset --hard
== git reset --hard HEAD
: 用 HEAD 覆盖暂存区和工作区,即:丢弃所有本地修改$ git br
* master ecfc106 2
new ab3fa01 3
$ git reset --soft new
$ git br
* master ab3fa01 3
new ab3fa01 3
Phabricator/--arc/--arcanist
| |-libphutil
|-xampplite-win32-1.7.3
+ arcantist是arc的主程序:https://github.com/phacility/arcanist.git
+ libphutil是php的工具集:https://github.com/phacility/libphutil.git
+ xampplite是apache+php+mysql+perl的一个打包,160+M,要知道phabricator整个才180+M。
arc help
:列出来子命令看看arc diff
:调用svn diff
或git diff
生成差异并发送给 phabricator 生成评审单arc list
:列出当前 peding 的 revision —— revision 要说明一下:pha 生产的每个评审单都对应一个 revision,可以理解为 svn/git 的一次提交,但又不在用户的 svn/git 上体现,可以理解为 pha 上也驻留这一个 svn/git,来存储评审单信息,每单对应一个 revision。arc patch
:将 pha 上的 revision 变更 patch 到本地工作拷贝上arc amend
:更新 git commit 的 message,即:可以把 pha 上某个 revision 的 message 应用到本地 git 的某个 commit 上arc commit
:svn 专用,pha 上评审完毕后,将本地的变更做svn commit
arc land
:git 专用,pha 上评审完毕后,将本地分支做git push
,所以需要有 originarc lint
:静态代码分析,不要以为 arc 只是生成评审单的,它还内嵌了一堆的 lint 工具,python 的、java 的、js 的……五花八门,在 Phabricator\arc\arcanist\src\lint\linter\__tests__
这个目录下列出了这些 lint 工具arc unit
:执行单元测试,这个就需要用户自己来指定单元测试工具了arc close-revision
:使用 arc 关闭某个 revision,而不必上 pha 上鼠标点点点啦git config --[global/system/local] xxx ...
,arc 也有arc set-config --[user/local] xxx ...
git config -l
,arc 也有arc get-config
Windows 下的安装
略
Ubuntu 下的安装
sudo apt-get install php5 php5-curl
cd somewhere
//arc 的安装目录git clone https://github.com/phacility/libphutil.git
git clone https://github.com/phacility/arcanist.git
sudo ln -s arcanist/bin/arc /usr/local/bin/arc
vi ~/.bashrc
source $somewhere/arcanist/resources/shell/bash-completion
arc set-config $pha-server
//eg: arc set-config http://pha.etz.com.cn
arc install-certificate
arc diff
会把未提交的本地工作拷贝中的变更生成评审单,执行arc diff
之前不需要也不能执行svn commit
,最终评审完,用arc commit
来代替svn commit
arc diff <startCommit>
之前需要首先git add
&git commit
,如果本地工作拷贝中有变更,arc diff 会自动替你 add 和 commit,因为arc diff
是把 git 中两个 commit 之间(即:一个 range)的变更提交到 pha 上生成评审单,所以问题来了:两个 commit 节点是如何指定的?
git merge-base origin/master HEAD
git help merge-base
,意思是找到 origin/master 和 HEAD 之间的最近祖先节点。git help merge-base
中有几个例子,其中一个是: o---o---o---B
/
---o---1---o---o---o---A
* `git merge-base A B `将返回节点1,好好体会一下,呵呵。
arc diff
需要填写一些信息,所以执行过程中会跳入到一个编辑器中,windows 版的 arc 会打开一个简陋的窗口,ubuntu 版的 arc 就直接打开默认的编辑器(如 vi)了。需要填写的信息有:
实战一下:
10036143@A20939270 MINGW32 /f/temp (master)
$ git log
* 75c616b | 2016-06-08 15:55:19 +0800 | wkevin hah
* 7584e84 | 2016-06-08 15:55:01 +0800 | wkevin create
arc diff
git merge-base origin/master HEAD
嘛arc diff 7584
git show HEAD
可以查看 HEAD 指向哪个节点arc diff 7584 --preview
arc diff
会根据工作拷贝的相关信息(比如 path, branch name, local commit hashes, and local tree hashes)来自动创建和关联一个 pha 上的 revision,这让一些掌控欲比较强的人可能有些恼火,可以手工指定
arc diff --create <startCommit>
:在 pha 上创建一个新的 revisionarc diff --update Dxxxx <startCommit>
:在 pha 上一个已有的 revision(编号 Dxxxx)上做增量在上面的步骤中有一个奇怪的地方:执行完arc diff xxxx
后,原有的 HEAD 节点被 arc 重新创建的一个节点所替代
arc diff 7584
后,75c6 被替代为了 26c0$ git l
* 26c0efc | 2016-06-08 15:55:19 +0800 | wkevin hah
* 7584e84 | 2016-06-08 15:55:01 +0800 | wkevin create
arc diff 7584
后,26c0 被替代为了 e6db$ git l
* e6db93c | 2016-06-08 15:55:19 +0800 | wkevin hah
* 7584e84 | 2016-06-08 15:55:01 +0800 | wkevin create
arc diff 7584
后,e6db 被替代为了 7c29$ git l
* 7c29204 | 2016-06-08 15:55:19 +0800 | wkevin hah
* 7584e84 | 2016-06-08 15:55:01 +0800 | wkevin create
git show
可以看到$ git show 75c6
commit 75c616b3a6de15e7004c231486a91e338ae023a6
Author: wkevin <wkevin27@gmail.com>
Date: Wed Jun 8 15:55:19 2016 +0800
hah
事情变得很蹊跷,arc 为什么要新建一个 commit 呢?
下面再来验证一下:如果本地有 modified(待 add)或 stagging(待 commit)文件的话,arc diff
是不是也会新建一个 commit 呢?
$ git l
* 1cce5be | 2016-06-08 16:05:27 +0800 | wkevin neww
* 7c29204 | 2016-06-08 15:55:19 +0800 | wkevin hah
* 7584e84 | 2016-06-08 15:55:01 +0800 | wkevin create
git commit -a
arc diff HEAD^
,会首先把未提交的变更进行提交,并且更新(amend)当前 commit 的 message,然后向已有的 revision 进行 update$ arc diff HEAD^
You have uncommitted changes in this working copy.
Working copy: F:\temp\
Unstaged changes in working copy:
README.md
Do you want to amend this change to the current commit? [y/N] y
Linting...
No lint engine configured for this project.
Running unit tests...
No unit test engine is configured for this project.
SKIP STAGING Unable to determine repository for this change.
Updated an existing Differential revision:
Revision URI: http://pha.zte.com.cn/D30449
Included changes:
M README.md
$ git l
* 20ae4c5 | 2016-06-08 16:05:27 +0800 | wkevin neww
* 7c29204 | 2016-06-08 15:55:19 +0800 | wkevin hah
* 7584e84 | 2016-06-08 15:55:01 +0800 | wkevin create
为了解开这个谜团,我们来跟踪一下arc diff
的操作
arc diff --trace <startCommit>
摘录一部分打印:
>>> [1] <http> http://pha.zte.com.cn/api/user.whoami
>>> [2] <exec> $ git diff --no-ext-diff --no-textconv --raw 'HEAD' --
>>> [3] <exec> $ git ls-files --others --exclude-standard
>>> [4] <exec> $ git diff-files --name-only
>>> [6] <exec> $ git rev-parse 'HEAD'
>>> [7] <exec> $ git merge-base 'f8c1' 'd6efce6e8804ecb027762e0151ed071bc7d63b6d'
>>> [8] <exec> $ git log --first-parent --format=medium 'f8c101daaf75121dd4f1f1380b4dc5c1ed85cea0'..'d6efce6e8804ecb027762e0151ed071bc7d63b6d'
首先到 phabricator 服务器上验证 tocken,并根据 startCommit 做出一些判断
>>> [16] <event> diff.willBuildMessage <listeners = 0>
>>> [17] <conduit> differential.getcommitmessage() <bytes = 295>
>>> [18] <http> http://pha.zte.com.cn/api/differential.getcommitmessage
>>> [19] <exec> $ git symbolic-ref --quiet HEAD
>>> [20] <exec> $ which 'editor'
>>> [21] <exec> $ editor '/tmp/edit.cjol8q3bi1sg0kwk/new-commit'
然后到 phabricator 服务器上创建一个单,并根据 pha 的请求,打开 editor,编辑评审单的信息
>>> [22] <exec> $ git commit --amend --allow-empty -F '/tmp/8qihi3x4l2ww4o8w/10039-Vbjrxm'
关键是这里了,无条件的更新了当前 HEAD 节点的 message。
其实 git commit --amend
的官方 help 中是这样解释的: Replace the tip of the current branch by creating a new commit.
这样arc diff <startCommit>
步骤就明朗了:
git commit --amend
到当前分支的 HEAD 节点git diff
,输出的内容提交到 phaarc 为什么要这么做?为什么要“玷污”我的现有节点?如果这个节点是其他分支的基础节点怎么办?…… —— 这个事情可以这么看:arc diff
只是新建了一个 commit,用来存储评审单的相关信息,并且把当前分支的 HEAD 指向了新建的 commit,想好了这一点,事情其实很好办,下一节我们来规避它。
创建专用于评审的分支
git branch review
git checkout review
arc diff <xxx>
或 arc diff --preview <xxx>
//创建评审单或预审单(到 pha 网站上进行下一步的操作,可用于 ubuntu 下不能自动补全人名的环境)git checkout master
git branch -D review
//评审单一旦创建,review 分支就没有存在的必要性了可能只希望评审方案文件(假设: design.md),但 commit 中包含相关的图片、svg、等文件,不需要提交到 pha,如下处理:
git branch review <oneOldCommit>
//从 design.md 创建或修改前的节点创建一个分支git checkout review
git checkout master design.md
//将 master 分支上的 design.md check 到 review 分支git commit -am "review for design.md"
arc diff HEAD^
或 arc diff --preview HEAD^
git checkout master
git branch -D review
跋
本文本来只是我个人使用 git 多年来的学习笔记和备忘录,2015 年底陆续有一些项目上的要求同事朋友们开始使用 git,我当然乐于答疑释惑。和大家互动了之后,发现直接拿笔记给别人看是不妥当的,因为笔记从头到尾没有难度梯度,大家更希望我分享的是一篇由简入难的文章。—— 听取用户需求,重新编排文章,并且查漏补缺……终于在 2016 年春节假期中成文。