1:init
Git 仓库的初始化命令是:
$ git init
该命令将创建一个名为 .git 的子目录,这个子目录含有 Git 管理仓库时要用的文件,.git 是一个隐藏目录,可以用 ls -a 查看。
2:config
仓库初始化之后要做的第一件事就是设置你的用户名称和电子邮件地址,以后每一次 Git 提交都会使用这些信息,用于记录代码是谁提交的。
配置信息分为全局配置和本地配置(即当前仓库的配置),使用 git config 命令维护,读写全局配置用 --global 参数,读写本地配置用 --local 参数。(注:还有一层系统配置,但用得少,此处不议。)
查看指定的配置信息用 --get 参数,如:
$ git config --get --local user.name
$ git config --get --global user.name
$ git config --get user.name
第1条命令是查看本地配置的用户名,第2条命令是查看全局配置的用户名,第3条命令是查看优先级最高的用户名,如果有本地配置项,就读取本地配置项,如果没有本地配置项,就读取全局配置项。
增加配置信息用 --add 参数,如:
$ git config --add --local user.name your-name
$ git config --add --global user.name your-name
$ git conifg --add user.name your-name
第1条命令是在本地配置中增加用户名,第2条命令是在全局配置中增加用户名,第3条命令和第1条命令相同,也是在本地配置中增加用户名。
3:声明周期git add
仓库初始化了,用户信息也设置好了,就该管理文件了。我们先看一下 Git 文件的生命周期(本地):
工作目录(untracked,modified)---->git add---->暂存区(staged)--->git commie--->提交区(commit)
工作目录里的文件不会自动被 Git 管理,要用 git add 命令把它添加到暂存区,暂存区可以存多个文件,举例来说,假设开发一个页面,要先写一个 html 文件,再写一个 css 文件,再写一个 js 文件,那就把它们逐个添加到缓存区,这时缓存区里有3个文件了,再用 git commit 命令把它们一起提交到本地仓库(图中的提交区)
把文件添加到暂存区的命令是这样的:
$ git add README
$ git add *.md
$ git add -A
第1条命令表示把 README 这个文件添加到暂存区,第2条命令表示把 md 扩展名的文件都添加到暂存区,第3条命令表示把所有没加到暂存区的文件都添加到暂存区。
撤销添加到暂存区的命令仍是 git rm,但要加一个 --cached 参数,如下面所示:
$ git rm --cached your-fil
4:提交commit
把文件添加到暂存区以后,就该提交文件了。提交命令是 git commit,像下面这样写:
$ git commit -m "your message"
加一个 -m 参数,表示要写备注,备注写在最后。
5:从远程clone仓库并重命名clone
当时我们是把本地目录初始化成 Git 仓库的,除此之外,还有一种情况可以创建 Git 仓库,就是把服务器上的仓库克隆到本地,它的命令格式是:
$ git clone http://remote.repo.url
此命令执行之后,就在当前目录下创建一个与远程仓库同名的子目录,同时把远程仓库的所有文件复制到这个新建的子目录中。
要是你想把克隆到本地的仓库命名为一个和远程仓库不一样的目录名,就在 git clone 的后面追加一个自定义的目录名,像这样:
$ git clone http://remote.repo.url your-folder
6:查看当前文件的操作状态status -s
Git 管理下的文件有多种状态,在工作中你经常需要先查询文件状态才能决定下一步做什么或怎么做,所以它的重要性和 Linux 的 ls 命令是一样重要的,是几乎所有操作的起点。
查看仓库状态的命令是:
$ git status
$ git status -s
第1条命令表示以详细格式查看,第2条命令表示以紧凑格式查看。默认的详细格式包含以下内容:
* untracked:仓库里新建的文件,或者从别的地方复制到仓库里的文件,它们的状态都是 "untracked",它们被用红字显示在查询结果的 "Untrakced files" 段落中。
* modified:被编辑过的文件的状态就变为 "modified",它们被用红字显示在查询结果的 "Changes not staged for commit" 段落中。
* staged:通过 git add 命令加入到暂存区的文件的状态就变为 "staged",它们被用绿字显示在查询结果的 "Changes to be committed" 段落中.
7:删除文件,移除暂存区(新增的文件)rm-cached
如果一个文件无用了,准备删除它,切记不要直接用 rm 命令,你应该用 Git 包装过的 git rm 命令,否则 Git 仓库不知道你对这个文件进行了删除操作,就追踪不到这个文件的变化了。
撤销添加到暂存区的命令仍是 git rm,但要加一个 --cached 参数,如下面所示:
$ git rm --cached your-fil
8:git stash临时区域
设想这样的场景:你正为一个类文件写一个新方法,写到一半了但还没写完,这时来了一个紧急任务,需要修改这个类的另一个方法,然后提交。现在你面临的问题是,手头的活儿还没干完,就又来新活儿了,而且是处理同一个文件!好吧,让我们思考一下操作系统是怎么处理这种情况的:外部中断到来时,系统会挂起当前进程,然后处理中断事件,处理完中断事件以后再恢复之前挂起的进程。git stash 命令就是类似这样的一种处理方式,它会把当前环境“藏”到一个临时区域,然后把工作环境恢复为最后一次提交的状态,这时你可以从刚才的工作状态跳出来在一个干净的工作环境处理紧急任务,之后再用 git stash pop 命令恢复此前“藏”的工作环境。
相关命令如下:
$ git stash
$ git stash list
$ git stash pop
第1条命令把当前环境“藏”起来;第2条命令列出被“藏”的环境;第3条命令恢复被“藏”的环境。
9:移动目录,文件rm
在第11关时我们曾用 git rm 来删除仓库里的文件,同样地,如果要对仓库里的文件改名,也不要直接用 mv 命令,而要用 git mv 命令,该命令会自动把改动记录到暂存区。
实际工作中,随着文件的增多,就要重新组织目录结构,把文件分类存放。操作方法是先建一个新目录,然后把需要移动的文件用 git mv 命令移过来就行了。
10:查询提交日志log
本关考察的是对日志查询结果的阅读能力。
每执行一次 git commit 命令都会在仓库里留下一个更新日志,可以通过 git log 命令查看历史日志,查询结果按提交时间倒序列出所有更新,每条日志包含一个40位的 hash 值、作者名字、电子邮件地址和提交日期及说明,示例如下:
$ git log
commit 30fef820b410142d3ded8c0908cd1dcb4c0cade0
Author: comehope <comehope@163.com>
Date: Tue Nov 22 17:30:24 2016 +0800
THIS IS THE COMMIT YOU ARE LOOKING FOR!
$ git log --pretty=oneline
411bf644d492f6106acda662612dbc627f951769 THIS IS THE COMMIT YOU ARE LOOKING
第2条语句增加了 ---pretty=oneline 参数,表示把日志以紧凑的格式显示,每行显示一次提交,只列出提交的 hash 值和说明,便于快速查看多条日志。
11:打标签(tag)
把开发一个项目当作一次旅行,如果每个 commit(提交) 都是从窗外晃过的一根电线杆,那么 tag(标签) 就是可以停靠的车站,你可以选择一刻不停地开到终点,也可以歇下脚看看风景再继续前行。镜头拉回到第17关,上面的比喻就是 commit 和 tag 的区别,commit 是细粒度的、面向程序员的,每写一个函数、每修正一个 bug,都可以提交一个 commit,而 tag 是粗粒度的、面向用户的,一般只有在增加或优化了一个用户可感知的功能时,才打一个 tag,软件的版本号就是最常见的 tag 形式,一个新的版本号意味着要对外发布一个新的 release 包。
打标签的命令如下:
$ git tag your-tag
$ git tag your-tag a38862a5a860
$ git tag
$ git tag -d your-tag
第1条命令是给最近一次提交打标签;第2条命令是给指定的某次提交打标签,后面要写上提交的 hash 值或者 hash 值的前几位;第3条命令是列出所有的标签;第4条是删除标签.
在默认情况下标签是不会被 git push 命令推送到远程服务器的,也就是说,你在本地打的标签,如果没有刻意指定把它推送到服务器,别人是看不到的。
把标签推送到服务器的命令如下:
$ git push --tags
此命令执行之后,当别人用 git clone 或者 git pull 从远程仓库读取时,就会看到你打过的标签了。
12:文件没提交全commit --amend
文件没提交全是很常见的小失误,解决办法就是先用 git add 把忘记提交的文件添加到暂存区,再用 git commit 命令加 --amend 参数把文件追加到最近一次提交中去,语法如下:
$ git commit --amend
$ git commit --amend -m "new message"
$ git commit --amend -C HEAD
第1条命令会弹出一个编辑器,供你编辑提交说明;第2条命令会用 "new message" 代替原有的提交说明;第3条命令会直接使用原有的提交说明,其中 -C 表示使用已提交过的说明,HEAD 表示最近一次提交,加在一起就是使用最近一次的提交说明。
13:设定时间date
这关的任务很奇怪,要求把提交时间设定在未来,可是 Git 这台时光机只能开往过去不能开往未来,它只负责把发生过的事管理好,所以把提交时间设定在未来某个时间点实在没有道理啊!如果本关是想考核对 git commit 命令的 --date 参数的运用,应该要求把提交日期设定在过去,比如昨天或者上周,才是合理的应用场景。
设定提交时间的命令如下:
$ git commit
$ git commit --date=“2016-10-10T12:01:01”
$ git commit --date="10 minutes ago"
$ git commit --date="noon yesterday"
$ git commit --date="last friday"
第1条命令没有 --date 参数,所以默认使用当前时间作为提交时间;第2条命令的提交时间设置为“2016年10月10日的12点01分01秒”;第3条命令的提交时间设置为“10分钟之前”;第4条命令的提交时间设置为“昨天中午12点”;第5点命令的提交时间设置为“上周五的现在时刻”。第2条设置的是绝对时间,后3条设置的都是相对时间。
14:文件移除暂存(已存在的文件)rm --cached
呵呵,本关和第12关很像啊,都是要从暂存区移出文件,甚至你用第12关的 git rm --cached 命令就可以完成任务,但是既然用已有的知识就可以解决,为什么还要专门设这么一关?
请再回忆一下第3关的“Git 文件生命周期”那张图(这张图真是很重要啊,我们在第9关也曾经回忆过一次)。处于工作目录的文件,可能会有2种状态,一种是 untracked,表示这是以前仓库没有的文件,它是新来的;一种是 modified,表示这是仓库里已有的文件,它被修改过了。虽然不管处于哪种状态,把它们加入到暂存区的命令都是 commit add,但这条命令其实包含了2种可能的含义:给仓库增加一个文件、或者修改仓库已有的文件,而把文件从暂存区移出的操作也就相对应地有了2种场景,一种是把新增的文件从暂存区移出,一种是把修改过的文件从暂存区移出。第12关和本关就是为了考察这2种场景而设计的,第12关的针对第1种场景,本关针对第2种场景。
把一个已修改过的文件从暂存区移出的命令是:
$ git reset your-file
15:撤销提交reset和checkout your-file
这又是一个撤销操作,撤销的是最后一次 git commit 命令,语法如下:
$ git reset --soft HEAD^
git reset 命令有很多复杂的参数,这里暂不细说,其中 --soft HEAD^ 表示取消最后一次提交操作,并且暂存区和工作目录的文件均不受影响。
git commit --amend(追加到最后一次提交中) 命令就相当于是先 git reset --soft 再 git commit。
这还是一个撤销操作。如果你想放弃工作目录中已经修改过的内容,就用这个命令:
$ git checkout your-file
Git 会用最后一次提交的文件覆盖掉工作目录中的同名文件。但做这个操作一定要谨慎,因为这个操作是不可以被撤销的,执行之后你修改过的内容就找不回来了。
16:查看你的项目连接了哪些远程仓库remote
我们大部分时间都是在操作本地仓库,与远程仓库相关的命令也不多,比如前面的23关中,也只有第5、第6、第18关这3关涉及到了远程仓库。接下来的5关将集中学习有关远程仓库的知识。
我们先来描述一下应用场景。你和其他人同时开发一个项目,大家都从中心仓库 clone 了文件到本地,然后分头工作,此时你只连接了一个远程仓库——中心仓库。如果你的同伴写了一个函数,正好你可以用到它,那么你可以把这个函数 pull 到你的仓库里,这时你就连接了第二个远程仓库——同伴的仓库,你们俩绕过了中心仓库,直接实现了私下交流,这也下是 Git 被称为分布式版本管理系统的原因。
每一个开发者都可以连接多个远程仓库,但是 URL 很长不好记,所以 Git 允许你为每个远程仓库命名,提高效率。
要查看你的项目连接了哪些远程仓库,用下面的命令:
$ git remote
在 git remote 命令后面加一个 -v 参数就可以查询远程仓库的 URL 了。
$ git remote -v
在查询结果中,每个远程仓库分别列出了 fetch(拉取) 和 push 的地址,这是因为在有些情况下 fetch 和 push 的地址是不一样的.
17:从远程仓库拉取更新pull
当有多人合作一起开发一个项目时,就不止是你一个人向远程仓库提交代码了,你的伙伴也会向远程仓库提交代码。为了得到远程仓库的最新内容,要用下面的命令把内容抓下来:
$ git pull remote-name branch-name
其中,remote-name 是远程仓库的名字,branch-name 是远程仓库的分支名字,如果是主干,那就是 master。该命令执行之后,远程仓库的代码会自动合并到本地项目中。
18:手工添加远程仓库remote
我们用 git remote -v 列出了多个远程仓库的地址,那这些地址是怎么添加的呢?
如果你的项目是 clone 来的,那么 Git 会并把 clone 命令的仓库地址保存下来。如果要手工添加远程仓库,请用下面的命令:
git remote add remote-name remote-url
19:推送push
当你和其他伙伴一起开发时,你们都从远程仓库把文件 clone 到本地,然后分头开发,再分头推送到远程仓库中,推送命令如下:
$ git push remote-name branch-name
$ git push -u remote-name branch-name
$ git push
第1条命令是把本地的文件推送到远程仓库,remote-name是远程仓库名,branch-name是分支名,如果你没有重命名过它们,那它们默认的名称分别是 origin 和 master;第2条命令加了一个 -u 参数,目的是让 Git 把 remote-name 和 branch-name 记住,下次就不用再写这2个参数了;第3条命令就是使用过 -u 参数以后的推送命令,不需要任何参数了。
多人开发时,推送是有先有后的,按照 Git 的规则,在你推送时如果已经有人比你早推送了,你若再推送就会收到一个 "non-fast forward" 的提示,直译就是“不能快进”。那么此时你至少有2种办法来解决:
方法一,先用 git pull 命令把远程仓库的最新代码合并到本地,然后再提交。这时本地的提交和远程的提交按时间顺序混合排列。
方法二,用 git rebase 命令把本地仓库的更新排到远程仓库更新之后,那这时候本地仓库的所有提交都排在远程仓库的最后一次提交之后。
本关考核的就是用 git rebase 方法来解决问题。
本关的场景是本地仓库有3次更新(分别名为 First commit, Second commit, Third commit),远程仓库有1次更新(名为 Fourth commit),在 rebase 并且在 push 之后,远程仓库就会有4次更新了。
20:查看被修改的细节diff
如果仓库中的文件被修改过,它的状态就变为 'modified',可以使用下面的命令查看被修改的细节:
$ git diff
$ git diff your-file
git diff 的结果
其中 @@ -2,7 +2,7 @@ 表示修改的内容是从第2行至第7行,接下来列出第2行至第7行的内容(其实只修改了第5行这1行,但会列出这1行的前3行和后3行)。其中红色的 -a5 和绿色的 +bbb5 表示把 'a5' 改为了 'bbb5'
21:查看修改记录blame
当系统曝出 bug 或者漏洞,要查清问题的来源时,首先定位问题代码,其次定位是谁引入了错误。Git 记录了详细的更新日志,所以通过 Git 提供的一个专门的命令就可以定位开发者:
$ git blame your-file
在结果中会列出指定文件的所有代码,每行代码的左侧会列出它最后一次被更新时的 HASH 值、开发者和时间,通过这些信息,你就可以分析每一行代码被谁编辑过了。
22:分之branch
接下来的10关都和分支有关。 如果你想在不影响主线的情况下进行安全的开发,就要以主线为基础创建一个分支,然后在分支上修改,最后再把分支合并到主线上。实际上,一般情况下都是在分支上工作的,因为在一个团队中,你和你的伙伴共享主线,直接在主线下工作会影响其他人,所以每个人都分别在各自的分支上工作。
分支的常用命令如下:
$ git branch branch-name
$ git branch
第1条语句用于创建分支,branch-name 就是你要创建的分支名称;第2条语句用于列出全部分支
默认情况下,你使用 git branch branch-name 语句创建分支时,创建出的分支与当前主线的内容是一样的,但是你也可以指定以主线的某一次提交为基础来创建分支,命令格式如下:
$ git branch branch-name hash-code
上面命令的最后一个参数表示 git commit 命令为某次提交生成的 HASH 值。
删除分支的命令如下:
$ git branch -d branch-name
和创建分支的区别在于增加了一个 -d 参数。
23:切换分之checkout branch-name
上一关我们创建了分支,但是还没有切换到新分支上。如果你仔细观察,会发现 git branch 语句的结果中,在 master 前面有一个 * 号,它表示当前你所在的分支。
切换分支的语句是:
$ git checkout branch-name
$ git checkout -b branch-name
$ git checkout -
第1条语句用于切换到指定的分支;第2条语句加了 -b 参数,表示创建一个分支并且切换到这个新建的分支,相当于连续执行 git branch branch-name 和 git checkout branch-name;第3条语句用于切换到上次所在的分支,当你经常在2个分支间来回切换时,用这个命令会比较方便。
不知你是否还记得,第23关我们用到了这样的命令:
$ git checkout your-file
它的作用是撤销对一个文件的修改。虽然从形式上看这个命令和本关的命令很相似,但因为参数的含义不一样,一个是文件名,一个是分支名,所以功能是完全不一样的。
24:切换到tag:checkout tag-name
在第17关我们学习了如何创建 tag,tag 是一个有语义的标签,便于记忆,我们可以把版本号或其他有特定含义的词语作为 tag。当我们要切换到指定的 tag 时,采用以下命令:
$ git checkout tag-name
你一定发现了,这个命令也和切换到分支的命令形式是一样的啊!第17关、第32关、第33关这三关的命令形式都一样,只因参数的含义不同,一个是文件名,一个是分支名,一个是标签名,结局就各不相同。
25:切换到tag(branch和tag重名)
如果存在一个和分支同名的 tag,比如都叫 'v1.2',那么当执行 git checkout v1.2 命令时,是该切换到分支,还是该切换到 tag 呢?答案是切换到分支。
如果要切换到 tag,就需要按下面这样给出明确的说明:
$ git checkout tags/tag-name
26:合并分之merge
当我们在分支完成修改和测试之后,就可以把分支合并到主线上了,它的命令是:
$ git merge branch-name
执行这条命令之前,要先切换到主线(一般是 master 分支),然后把待合并的分支名作为参数。
合并之后,在分支上修改过的文件的内容就会体现在主线上,而且日志中也加入了分支的修改日志。
如果遇到主线和分支修改了同一行代码,就会发生冲突,后面的关卡中我们还会学习如何解决冲突。
27: 更新拉取到本地不合并fetch
在第26关我们曾用 git pull 把远程仓库的更新拉到本地仓库,这个命令其实隐含了2个连续的动作,即 git fetch 和 git merge。如果只是抓取数据而不合并,那就不能用 git pull ,而只用前一个动作 git fetch 就可以了,语法如下:
$ git fetch
$ git branch -r
$ git log remote-name/branch-name
第1条语句是把远程仓库的数据抓取到本地,但不合并到本地分支;第2条语句是查看远程分支列表,如果远程仓库有了新分支,在 git fetch 之后用 git branch -r 查看时会发现新分支的名称,在本关中新分支名为 'new_branch';第3条语句用于查看远程分支的日志,比查看本地日志的 git log 语句多了远程仓库名和远程分支名这2个参数。
28:合并分之rebase
在第28关我们曾经使用过一次 git rebase 命令,现在我们再详细讲解一下。
git rebase 和 git merge 都是用来合并,各有优缺点,所以有些团队会约定合并时只能用 git merge 或只能用 git rebase,如果约定只能用 git rebase 来合并,这种工作方式就被称为 'git rebase 工作流'。在用 git rebase 合并分支时,合并后的日志并非按各分支的提交时间排列,而是把一个分支的日志全部排列在另一个分支的日志之后,即使它们是并行开发的,在开发过程中交错提交,但看起来也好像是按先后顺序开发的一样。
以本题为例,master 是主线,从 master 创建出 feature 分支,此后,master 提交了一次,提交说明是 “add content”,feature 也提交了一次,提交说明是 “add feature”,这时在 master 上执行以下命令:
$ git rebase feature
那么 master 的日志就会变成 "add content" 在 "add feature" 之后。
而反过来,如果是在 feature 上执行以下命令:
$ git rebase master
那么 feature 的日志就会变成 "add feature" 在 "add content" 之后。
29:打包repack
在第1关里我们提到,当 Git 项目初始化时,会创建一个隐藏的名为 .git 的子目录,用于存放 Git 管理仓库要用到的文件。在 Git 的世界里,一个文件是一个 Git 对象,一次提交也是一个 Git 对象,它们被存储在 .git/objects/ 目录下:
$ ls .git/objects/
4d a0 e6 info pack
其中前3个目录的目录名长为2个数字字母,分别各存放1个对象。在 Git 的操作越多,产生的对象就越多,为了优化仓库的效率,你可以手工把对象打包:
$ git repack
$ git repack -d
第1条命令是把对象打包到一起,第2条命令是在打包后删除已作废的对象。执行完打包命令之后,.git/objects/pack/ 目录下会生成2个文件:
$ ls .git/objects/pack/
pack-b7b37f445a40715c249bf8c0df9631e9fd6c8f4b.idx
pack-b7b37f445a40715c249bf8c0df9631e9fd6c8f4b.pack
.pack 是包文件,.idx 是包的索引文件。
30:分之中某次提交合并到主线cherry-pick
如果你创建了一个分支,在其中进行了多次提交,而在合并时不想把分支上所有的提交都合并到主线,只想选取其中的1个提交合并到主线,那么你可以用下面的命令:
$ git cherry-pick hash-code
其中 hash-code 是某次提交生成的 HASH 值。cherry-pick 直译就是摘樱桃,把一个分支想象成一棵树,多次提交就让树上结满了果实,那么 cherry-pick 命令就是摘下其中的一个果实。
31:搜索grep
和 Linux 的 grep 命令类似,Git 也提供了一个用于搜索文本的 grep 命令:
$ git grep keyword
$ git grep keyword file-name
第1条命令在当前项目下查找指定的关键词;第2条命令在指定的文件中查找关键词。
32:修改历史日志备注
在使用 Git 的过程中,难免会出现要改写提交内容的情况,Git 提供了非常强大的修改历史的工具,我们就以本关为例,详细说明如何修改历史,并在接下来的第45关和第47关再做另外2个练习。
先看一下提交日志:
$ git log --pretty=oneline
771b71dca888e80d2bf716672b1475e85a27d695 Second commit
06973a37415e520eff0bace38181f131698cd888 First coommit
37d84aed48418346c4567bb863a0eba4617ba5b1 Initial commit
一共有过3次提交,注意其中哈希值为 "06973a37415e520eff" 的这次提交,提交说明 "First coommit" 中的第2个单词拼错了。
修改提交历史的命令格式是:
$ git rebase -i hash-code
我们已经在第40关接触过 git rebase 命令,当时是用它来合并分支。但是加了 -i 参数之后,用途就变为修改提交历史了。其后再跟一个某一条提交日志的哈希值,表示要修改这条日志之前的提交历史。
现在,找到 "First coommit" 下面一条日志的哈希值 "37d84aed48418346c4",然后输入下面的命令:
$ git rebase -i 37d84aed48418346c4
这时,会启动文本编辑器,显示如下内容:
pick 06973a3 First coommit
pick 771b71d Second commit
这2行是历史日志,但和 git log 的区别在于 git log 是按更新时间从后到前显示日志,而这里是按从前到后显示。每一行的前面有一个命令词,表示对此次更新执行什么操作,有以下几种命令:
"pick",表示执行此次提交;
"reword",表示执行此次提交,但要修改备注内容;
"edit",表示可以修改此次提交,比如再追加文件或修改文件;
"squash",表示把此次提交的内容合并到上次提交中,备注内容也合并到上次提交中;
"fixup",和 "squash" 类似,但会丢弃掉此次备注内容;
"exec",执行命令行下的命令;
"drop",删除此次提交。
本关就使用 "reword" 命令来完成任务。把第1行前面的 "pick" 改为 "reword"(注意,不用改哈希值后面的备注内容),如下:
reword 06973a3 First coommit
pick 771b71d Second commit
接下来保存并退出,马上系统会再次打开编辑器,显示以下内容:
First coommit
# Please enter the commit message for your changes.
这时,你把 "coommit" 改为 "commit",保存并退出,再查看日志,就会发现历史日志的备注内容已经改变了。
33:把多次提交合并为一次提交
如果要把多次合并合并成一次提交,可以用 git rebase -i 的 squash 命令。
先查一下提交日志:
$ git log --pretty=oneline
55d9ec9d216767dd1e080c32f5bcff1b3c62ab5b Updating README (squash this commit into Adding README)
749b65067db05a02515c580ad8e791306ff02305 Updating README (squash this commit into Adding README)
1ac3ed61a0ae302cf76dc6f3a37e56e2b5f750f9 Updating README (squash this commit into Adding README)
606be40cc9e5c684cab87c22c37a9d0225308761 Adding README
994f2b3a2df48ef4a406a5c62b4b6f6c8c1fac03 Initial Commit
从查询结果看出,添加了 README 之后来又对它做了3次修改。
找到 "Adding README" 下面一条日志的哈希值 "994f2b3a2df48ef4a4",执行 reabse 命令:
$ git rebase -i 994f2b3a2df48ef4a4
系统自动打开文本编辑器,显示历史日志:
pick 606be40 Adding README
pick 1ac3ed6 Updating README (squash this commit into Adding README)
pick 749b650 Updating README (squash this commit into Adding README)
pick 55d9ec9 Updating README (squash this commit into Adding README)
把后3条日志前面的 "pick" 命令都改成 "squash":
pick 606be40 Adding README
squash 1ac3ed6 Updating README (squash this commit into Adding README)
squash 749b650 Updating README (squash this commit into Adding README)
squash 55d9ec9 Updating README (squash this commit into Adding README)
保存退出,系统再次自动打开编辑器,内容是合并过的更新说明:
# This is a combination of 4 commits.
# The first commit's message is:
Adding README
# This is the 2nd commit message:
Updating README (squash this commit into Adding README)
# This is the 3rd commit message:
Updating README (squash this commit into Adding README)
# This is the 4th commit message:
Updating README (squash this commit into Adding README)
你可以在此编辑合并之后的更新说明,然后保存退出。再查日志,就会发现3次 "Updating README" 的提交都合并到 "Adding README" 中了。
$ git log --pretty=oneline
3e8c0e3a729a9d5f959214a2267c481ff0197722 Adding README
994f2b3a2df48ef4a406a5c62b4b6f6c8c1fac03 Initial Commit
34:把分支中的多次提交合并为主干上的一次提交merge_squash
把名为 long-feature-branch 的分支合并到主干,把分支中的多次提交合并为主干上的一次提交。
在第38关我们曾学习过 merge 合并,它的语法是:
$ git merge branch-name
如果分支曾经提交过多次,那么用上面的语句合并之后,主干的日志也会出现多次提交记录。为了符合本关题意,把分支的多次提交合并为主干上的一次提交,要加一个 squash 参数,如下:
$ git merge branch-name --squash
如果不加 squash 参数,在合并之后系统会默默地做一个 commit 操作,而加了 squash 参数之后,不会自动 commit,这时你还需要手动执行 commit 命令,并且写上提交说明。
35:添加部分文件到暂存区 add --edit
用 git add 命令可以把文件添加到暂存区,但如果你不想把文件中的全部修改都提交到暂存区,或者说你只想把文件中的部分修改提交到缓存区,那么你需要加上 edit 参数:
$ git add your-file --edit
这时 Git 会自动打开文本编辑器,编辑的内容就是 git diff 命令的结果,这时你就可以编辑2个文件之间的差异,只保留要提交到暂存区的差异,而删除不需要提交到暂存区的差异,然后保存退出,Git 就会按你编辑过的差异把相应的内容提交到暂存区。
比如本关,文件的差异为:
$ git diff feature.rb
diff --git a/feature.rb b/feature.rb
index 1a271e9..4a80dda 100644
--- a/feature.rb
+++ b/feature.rb
@@ -1 +1,3 @@
this is the class of my feature
+This change belongs to the first feature
+This change belongs to the second feature
从最后2行可以看出,新增的代码分别对应2个不同的功能,如果我们只想提交第1个功能的代码,删除掉最后一行即可。
36:忘记刚在哪个分之工作reflog
你在一个分支上工作时,被分派处理一个重要的问题,可是处理完这个问题之后,你忘了刚才是在哪个分支上工作了。切换回那个分支。
这种情况确实经常发生,笨办法就是逐个进入各个分支查看日志,再回忆一下刚才的工作情景。而 Git 提供了一个工具,可以用来查看你在 Git 上的历史操作:
$ git reflog
894a16d HEAD@{0}: commit: commit another todo
6876e5b HEAD@{1}: checkout: moving from solve_world_hunger to kill_the_batman
324336a HEAD@{2}: commit: commit todo
6876e5b HEAD@{3}: checkout: moving from blowup_sun_for_ransom to solve_world_hunger
6876e5b HEAD@{4}: checkout: moving from kill_the_batman to blowup_sun_for_ransom
6876e5b HEAD@{5}: checkout: moving from cure_common_cold to kill_the_batman
6876e5b HEAD@{6}: commit (initial): initial commit
你看,不仅与文件相关的 git commit 操作被记录了,连你 git checkout 操作也都记下来了,现在,你就知道此前是怎么在各个分支之间跳转的了。
37:撤销一次提交到远程的提交revert
我们曾用过修改提交历史的 git rebase -i 命令,用此方法可以取消多次提交中的某次提交,不过,这只是针对还没有推送到服务端的情况,如果已经提交到服务端,你就不能改变已有的历史了,只能想别的办法解决了。
Git 提供了 revert 工具来解决这种问题,它相当于是对某次提交的逆操作,比如你提交时新建了一个文件,那么 Git 就会创建一个删除此文件的提交。语法如下:
$ git revert hash-code
$ git revert hash-code --no-edit
其中的 hash-code 就是你要取消的提交的哈希值。第2条命令中的 no-edit 参数表示由系统自动生成一句提交说明,如果没有这个参数,Git 会自动调用文本编辑器请你编写提交说明。
38:恢复被删除的提交restore
你决定用 git reset --hard HEAD^ 删除最后一次提交(一个不太明智的决定),然后你又反悔了,想回退到这条命令之前。请恢复被删除的提交。
我们先查一下提交日志,发现有过2次提交:
$ git log --pretty=oneline
1dc1ecdd071fd2a5baa664dce42a48b13d40cdae First commit
e586f55fde799d2b390d8a74d771db75279841f3 Initial commit
再看看操作日志:
$ git reflog
1dc1ecd HEAD@{0}: reset: moving to HEAD^
f766953 HEAD@{1}: commit: Restore this commit
1dc1ecd HEAD@{2}: commit: First commit
e586f55 HEAD@{3}: commit (initial): Initial commit
哦,原来还曾有过第3次提交,不过被 git reset --hard HEAD^ 删除掉了。git reset --hard HEAD^ 用于删除最后一次提交,使工作区恢复到上一次提交时的状态,仔细观察上面的操作日志也能发现,其中 "HEAD@{2}" 和 "HEAD@{0}" 的哈希值是一样的。
如果要撤销这条命令本身,也就是恢复到执行这条命令之前的状态,我们可以用下面的命令形式:
$ git reset --hard hash-code
上面命令中的 hash-code 就是你要恢复到的那次提交的哈希值。在执行此命令之后,提交日志中会增加一条提交日志,操作日志会自动增加一条 "reset: moving to hash-code" 的日志。
39:冲突
在第38关我们学习过 git merge 命令,但在工作中难免会发生合并冲突。发生冲突的原因是合并分支与被合并分支都修改了同一个文件的同一行代码,此时 Git 系统要求你介入,决定是保留你的代码还是别人的代码,或者都保留下来。
当发生冲突时,Git 会给出以下提示:
$ git merge mybranch
Auto-merging poem.txt
CONFLICT (content): Merge conflict in poem.txt
Automatic merge failed; fix conflicts and then commit the result.
以上信息告诉你自动合并失败,需要你手动解决冲突并提交修改后的结果。在本关中,是一个名为 poem.txt 的文件的第2行代码发生了冲突。
这时你可以编辑有冲突的文件,文件内容如下:
Humpty dumpty
<<<<<<< HEAD
Categorized shoes by color
=======
Sat on a wall
>>>>>>> mybranch
Humpty dumpty
Had a great fall
其中7个左尖括号 <<<<<<< 和7个右尖括号 >>>>>>> 之间的区域是冲突的部分,而中间的7个等号 ======= 则把有冲突的代码分开,上部分是你的代码(通常是主线代码),下部分是别人的代码(通常是开发分支的代码)。编辑这部分内容,保留你想要的,删除你不要的,保存退出,再单独提交这个文件即可。
40:模块化引入submodule
如果你想把别人的仓库代码作为自己项目一个库来使用,可以采用模块化的思路,把这个库作为模块进行管理。Git 专门提供了相应的工具,用如下命令把第三方仓库作为模块引入:
$ git submodule add module-url
其中的 module-url 就是第三方仓库的地址。
共同学习,写下你的评论
评论加载中...
作者其他优质文章