使用Git已有很长一段时间,遇到一些痛点问题,而且大都是网上难以直接查到的,故总结于此。
1.两幅重要的图
以上两幅图对理解Git的原理十分重要。
2.基础命令
首先是一些基础命令,这些命令经常使用,而少为人知。
git add -A
或git add --all
把包括修改、增加、删除都全部加入暂存区。等同于git add .
和git add -u
两个命令。git add -p
一段段地把修改提交到暂存区,每一个修处改都会提示你是否加入暂存区。可以按照想要的方式把所有修改分成多个commit。git commit -am
把已经加入暂存区(Stage)的文件的更改提交到本地版本库,无论这些更改是否已经加入暂存区。与git commit -m
的区别在于后者只能向本地版本库提交已经加入暂存区的更改。如果只是改动某些代码,而没有增加或者删除文件,使用git commit -am
一个命令就可以替代git add --all
和git commit -m
两个命令。git commit --amend
修改最后一次commit的提交信息。注意,是最后一次,也就是HEAD指向的commit。git branch -vv
显示本地分支与远程分支的对应关系。执行git push
(没有带分支参数)时代码被提交到对应远程分支,就是依据该对应关系。这里需要注意Git的simple和matching模式,后者会一次性提交所有的对应关系的分支的代码到远程分支,前者只会提交当前所在分支。git stash
和git statsh pop
主要用来暂存当前的工作。如果需要git pull
又不想把当前的修改提交commit,则可以先使用git stash
把修改暂存。该命令运行完后工作区是干净的,此时再使用git pull
拉新代码。完事了后使用git statsh pop
恢复之前的工作状态。git reflog
显示曾经的commit。如果我们使用git reset
将HEAD指向某个过去的commit了,此时要想回到最开始的commit,使用git log
是无法查到最开始的commit的。但是也不要慌,git reflog
能给你回到最开始的commit的机会,它会显示所有的提交记录。
3.使用场景
根据一些常见的场景给出我自己的解决方法。
3.1 基于远程分支建立本地分支
你可能刚入职。你的新同事突然丢过来一个地址A,让你git clone
一下代码。接着他又丢过来一个分支名dev0.1,让你基于该分支进行开发。如果之前只会在master上git pull
和git push
,此时就可能有点不知所措了。
你可能首先想到如何把远程的dev0.1分支clone下来,因为执行git clone A
之后,你再用git branch
查一下,发现本地只有master分支。
这里需要纠正一个问题。git clone A
实际上把所有的分支都从远程拉下来了,但是git branch
只显示那些与远程保持了追踪(tracked)的分支。并且git clone A
还会创建本地master分支并将其与远程(origin)的master建立追踪(tracked)关系。于是我们最开始使用git branch
就只显示master了。
那么,问题来了。既然所有分支都已经拉下来了,我们如何切入dev0.1分支并进行开发呢?其实很简单:
git checkout dev0.1
这个命令的含义是将dev0.1分支从本地版本库取到工作区来(参考上面的两幅图中的第二幅),并将工作区切换到该分支。此时一个本地名为dev0.1的分支与远程(origin)的dev0.1分支就建立了追踪关系。我们就可以基于dev0.1进行开发了。试试git branch
看看会不会显示出dev0.1
。
假如远程分支中并没有一个叫dev0.1的分支,我们运行以上命令,就会发现这样的错误提示:
error: pathspec 'dev0.1' did not match any file(s) known to git.
此时我们可以看看到底有哪些远程分支,请使用git branch -r
。当然git branch -a
显示的信息就多一些,它显示全部本地分支和远程分支。
但我通常并不这样做。而是用以下命令:
git checkout -b dev origin/dev0.1
它的含义是新建一个本地分支dev,且让这个分支与远程dev0.1分支保持追踪关系。这样做的好处在于可以自己取一个本地分支名。
3.2 分支开发模式同步Master分支代码
你可能基于本地的dev0.1分支写了一些代码,测试后并提交到本地仓库中了。你们团队代码主干分支是master,且基于这个分支进行发布。这就面临着一个问题,你的新写的代码如何提交到主干分支上。
1.如果的本地dev0.1分支与远程(origin)dev0.1分支保持了追踪关系,且如果你们团队使用类似github/gitlab这样的git仓库托管服务,则可以直接在本地dev0.1分支上操作:
- 先
git pull origin dev0.1
拉下远程代码,以防有更新。 如果git是simple模式,则可以直接使用git pull
[目前git2.0及以上版本都默认为simple模式]。 - 如果有更新则会自动合并,合并如果失败,则会要求手动处理冲突。处理完后将这次修改提交到本地仓库。
- 使用
git push origin dev0.1
向远程仓库dev0.1分支提交代码。git 2.0及以上版本直接git push
即可。 - 在github/gitlab的界面上操作,由dev0.1分支向master分支提交merge请求。负责master分支维护的同事合并你的分支代码到master即可。
2.如果本地dev0.1分支是基于master分支新建的,即在本地master上使用git checkout -b dev0.1
命令---它会创建一个dev0.1的本地分支,并切换到该分支,但它不会设置与远程分支的追踪关系。这时候如何将该分支的新代码合并到master分支呢?
- 首先dev0.1分支要合并到本地master上去。先使用
git checkout master
切换到主干分支,在主干分支上操作。 - 合并dev0.1分支。使用
git merge dev0.1 --no-ff
或git merge dev0.1
命令。这两者的区别可用以下图片说明:
前者带有分支记录,后者没有。
- 然后将master分支的代码提交到远程master:
git pull
拉新代码,解决冲突,git push
推到远程。 - 如果dev0.1分支没用处了,就可以直接删掉:
git branch -d dev0.1
。
3.3 将工作区恢复成干净的状态
如果开发了一阵子,修改了一些代码,但没控制好,把工作区搞成了一堆乱麻。这时候就想,要是能把工作区恢复成最开始的样子就好了。
首先确定要恢复成的最初状态。一般来讲就是将工作区恢复成当前本地仓库中HEAD所指向的commit。不过如果你之前提交到本地的一些commit你也不想要了,那么先用
git log
查一下你要恢复到的commitID,复制下来。使用
git reset --hard HEAD/commitID
命令。运行完毕之后,用git status
查一下状态。一般状态下会显示
nothing to commit, working directory clean
但是如果你最开始时新增了一些文件,且没有将其加入暂存区,那么就不是这种提示了。你需要把你新增的那些文件删掉,git status
才会恢复成以上状态。主要原因是git reset
无法重置那些没有加入暂存区的更改。
如果你只是需要把某个文件B恢复成仓库里上一次提交的状态,那么有以下两种可能:1.这个文件没有任何修改提交到暂存区;2.这个文件有一部分修改提交到暂存区了,但是想把暂存区的修改也恢复成HEAD指向的版本。
- 1情况下直接使用
git checkout B
恢复工作区的文件B。 - 2情况下先使用
git reset HEAD B
撤销暂存区里面对B的修改,再使用git checkout B
恢复工作区中的修改。
3.4 修改某次的提交信息
这个场景并不常见,而有的时候又一定用的到。例如你开发一段时间了,却发现公司的gitlab要求你push的时候必须使用公司的邮箱,而你之前的commit都用的是你自己的邮箱。我之前就遇到过这个问题,在这儿记录一下解决方案。
首先,git commit --amend
只能对最新提交的Comment内容或邮箱的修改,并不能对中间提交的Comment或者邮箱进行修改。如果只是修改最新提交的信息:
-
git commit --amend
直接进入修改提交信息的模式。 -
git commit --amend --author="chenyi <chenyi@xxx.com>"
可以修改作者信息。
如果需要修改中间某一次的提交信息,则需要按照一定的方法操作。以下是一个修改示列。
如上图,第二个提交的邮箱同其他提交不一样,我们需要将它修改成与其他一样的。
先进入第二个提交:
git rebase -i preCommitID
,这里preCommitID就是第二个提交的下面的一个ID,即ff4e24
。
我们可以看到,列表里面显示了我们想要修改的commit的ID,即
c1c6685
。将其前面的pick
更改为edit
,保存并退出。
当我们从第2步的编辑过程中退出后,git会提示我们可以用的两个命令
git commit --amend
和git rebae --continue
。这两个命令在后面都会用到。
我们目前已经处于c1c6685
commit上了。此时我们只需要使用git commit --amend --author="chyoo <chyoo1991@gmail.com>"
修改该提交的作者信息。上图就是运行该命令后进入修改的界面。
git rebase --continue
继续rebase就可以完成目标了。
3.5 将Commit记录变成直线模式
通常很多人合作写代码时,希望将远程master分支维护成直线的形式,这样commit干净明确,检查问题时能省不少事。而一般合并最新代码时用的git pull
会将生成一个merge commit,这将导致推送到远程分支的代码也是各种分支交叉,一点也不干净明确。
那么,该如何实现这个目的呢?
首先这里要上一份干货,就是git merge
与git rebase
的区别,需要注意到git pull
实际上是调用git merge
进行代码的合并。->干货在此。
从干货文章中我们可以发现,能够使用git rebase
命令用打补丁的方式来实现commit保持直线的目的。我们这样操作:
git fetch origin master
用这个命令将远程(origin)的master分支最新提交取到本地。git rebase origin/master
使用rebase 进行pack打补丁,将我们自己提交的本地代码以补丁的方式放在最新代码的后面。这个过程中可能会出现冲突,修改完了之后使用git rebase --continue
继续pack,直到完成rebase。如果出现无法解决的问题,想回到最开始,可以使用git rebase --abort
。git push
提交代码到远程master分支。
这里要求开发协作的每一个人都用这种方式操作和提交代码,这样才能保持远程master分支的干净整洁和直线性。
3.6 为一个本地仓库设定多个远程仓库
这个看起来是有点奇怪的需求。不过前不久gitlab的员工删除了它们的数据库,导致很大部分项目受影响,这让我们觉得如果能多一份代码保障或许更好。
使用git clone
时,会自动设定远程地址,一般都是以origin
指代。如果我们有特殊需求,需要将代码提交到另一个远程仓库,这时就要设定新的远程仓库地址了。
git remote add hegel http://xxxx.git
这个命令就可以给当前的本地库设置一个远程仓库hegel
。以后我们想推拉代码到这个远程库时,就需要用git push hegel master
或git pull hegel master
了。
4.快捷设定
有一些比较有用的设定,能在写命令时少了很多麻烦。
- 全局设置user和email
- git config --global user.name "chenyi"
- git config --global user.emali "chenyi@xxx.com"
- 给常用命令起一个简单的名字
- git config --global alias.co checkout
- git config --global alias.st status
- git config --global alias.br branch
- git config --global alias.ci commit
后续就可以使用git co
/git ci
等命令了,是不是简洁了很多?
3.更好看的git log
git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"
配置了这个之后,使用git lg
简直眼前一亮。