解说:
https://ihower.tw/git/
# 基础指令
1、安装 git + source tree 实现可视化
查看文件:
~~~
ls
~~~
但有些时候文件是隐藏的,此时用到
~~~
git ls -la
~~~
即可查看隐藏文件
~~~
mkdir README
~~~
↑新建名为 README 的文件
~~~
rm -rf README
~~~
↑删除名为 README的文件
~~~
git reset HEAD^ --hard
~~~
还原上一个动作
#### setup:可进行一些 git 非常基础的预设。
~~~
git config --global user.name "amy326"
git config --global user.email "amy87326@163.com"
git config --global color.ui true
~~~
之后也可以输入 以下命令,在隐藏文件.gitconfig中直接设置
~~~
vi ~/.gitconfig
~~~
比如设置快捷键,用 vi 命令打开.gitconfig 文件以后,按I进入编辑模式,增加以下行:
~~~
[alais]
ci = commit
ch = checkout
br = branch
st = status
~~~
![](https://ws1.sinaimg.cn/large/006tNc79gy1fmbdzam4w1j30e70560sx.jpg)
# 显示修改的内容
~~~
git diff
git diff --staged
~~~
↑git diff 可以显示在工作区中被修改电但还没放入暂存空间的内容
但如果已经 git add . 暂存了,则再输入git diff则不显示。此时要输入"git diff --staged"
~~~
git add README(文件名)
~~~
↑则只对该文件进行暂存
~~~
git add .
~~~
↑将所有文件统统放入暂存中等待 commit
~~~
git log
~~~
↑显示 commit 的时间和名字
~~~
.cat README
~~~
↑在屏幕上显示文件里面具体内容但不打开文件
~~~
vi README(文件名)
~~~
↑可以直接进入该文件中,再用 vi 命令进行编辑
按 esc 退出后,输入" :wq!" 即可存盘并退出。
~~~
git status
~~~
↑显示文档状态。详见《补充:Git 分区原理》
>可反悔:
![](https://ws3.sinaimg.cn/large/006tNc79gy1fmbeyg0hmgj30k3032751.jpg)
## 文件操作
#### 删除文件
~~~
git rm aaa
~~~
↑意思是在工作区中把名字叫aaa的文档删了并把这一情况记入stage暂存中
~~~
rm aaa
~~~
↑与上面的差别意思是只在工作区中删除 aaa,但还未将这一情况放入 stage 中。
#### 重命名
~~~
git mv b BBB
~~~
↑把名为b的文件改名成 BBB,并放入暂存中。
#### 复制文件
~~~
cp BBB DDD
~~~
↑ 把 BBB 复制一份名叫 DDD。
## **反悔 revert**
如何把之前 commit 进去 git 的文件删除:
比如上一步执行了复制命令并已经commit 了,但突然不想复制:
~~~
git revert HEAD
~~~
↑ 可以用 git revert 这个命令。后面的 HEAD 指的是最新的那次操作。如果是前一个,用HEAD^,前两个就用HEAD^^
> 但一般我们可以打开 source tree,用流水号码来执行。
比如上述,找到之前 commit 的文件,查看号码为e44f87e9c01……
直接输入
~~~
git revert e44f87e9c01 #不用全部打出来#
~~~
![](https://ws3.sinaimg.cn/large/006tNc79gy1fmbflwc490j30oj0f3tbf.jpg)
此时git会自动新增又一个commit 来记录此次反悔(如图的‘Revert 'add DDD' ’)。
#### 如何查看这个号码
建议直接用source tree查看,但也可以在item 中输入
~~~
git log --oneline
~~~
就可以看到了。按:wq退出查看。
![](https://ws1.sinaimg.cn/large/006tNc79gy1fmbfphxmvpj30e50ai3zn.jpg)
## 贴标签
一般可用于查找
~~~
git tag foo
~~~
↑给最新的那个 commit 贴一个叫 foo 的标签
~~~
git tag AAA e44f87e9c01(号码)
~~~
↑给号码为e44f87e9c01的 commit 贴一个叫 AAAd 标签
#### 删除标签
~~~
git tag -d AAA #或git tag AAA -d#
~~~
#### 贴备注
~~~
git tag CCC -m "hello" e44f87e9c01
~~~
↑给号码为e44f87e9c01的 commit 贴一个名字叫 CCC 的标签,并写一个备注叫“hello”。
![](https://box.kancloud.cn/8c71fb70922a00ca700e4a80ae0b48f3_1051x619.png)
## 查看谁写了什么代码在哪个文件里
~~~
git blame README
~~~
↑可在 README 这个文件中查看谁写了什么东西。按 “:q” 退出。
~~~
git blame README -L 100,10
~~~
↑从第100行开始往下查看10行。
![](https://ws3.sinaimg.cn/large/006tNc79gy1fmbfzwfom0j30m8098756.jpg)
## **砍掉untracked的档案**
比如刚要删除git rm a + git rm b比较麻烦,可以直接
~~~
git clean -n
~~~
列出要清理的档案,再来
~~~
git clean -f
~~~
清理掉。
或者直接git clean -x 连在.gitignore里面的都统统清除。
其实效果等于
~~~
git add .
git stash
#先把新文档全部暂存入 stage里面,再一次性 stash 清除#
~~~
↑但其实,git stash 不是删除,而是先放在一个叫 stash 的地方。
![](https://ws2.sinaimg.cn/large/006tNc79gy1fmbq3nhuayj30l30ast9u.jpg)
## 设置.gitignore执行部分 commit
比如 touch rabish以后,rabish这个文档不想被 commit 进去,可以执行
~~~
vi .gitignore
~~~
在里面输入rabish 以后,这个文件就可以不被 commit 了。但要记得,.gitignore这个文件是要被 commit 才可以
~~~
git add .
git commit -m "Add .gitignore"
~~~
#### 空目录(空文件夹)不会被 commit
如果新建mkdir SSS,但里面什么内容都没有,那么 commit 的时候,这个文件是不会被记录的。
![](https://ws1.sinaimg.cn/large/006tNc79gy1fmbgikgrrxj30jk01v3yq.jpg)
但有时候我们又需要 commit 一些空目录,此时用以下命令:
~~~
touch SSS/.gitkeep
~~~
再进行 commit 的时候,就会把SSS这个空文件夹放进去了。
## 分支
#### 开分支
~~~
git checkout -b feature1
~~~
#### 移动到分支
~~~
git checkout 分支名
~~~
如果输入git branch 分支名,这是要新建的意思,要注意命令不用用错。
#### 显示有哪几个分支 查看分支
~~~
git branch
~~~
如果是在 github 上,用这个命令看不到,于是可以用
~~~
git branch -r
~~~
![](https://ws3.sinaimg.cn/large/006tNc79gy1fmbv038ccjj30j301f3yo.jpg)
显示已被合并的分支
~~~
git branch -r --merged
~~~
#### 合并分支
一般是先到 master 主干以后,再把分支合进来
~~~
git br master
git merge feature1
~~~
如果commit分支中的内容后,又修改主干(**两次修改的是不同文件**),则 commit 时候会都保留,只不过有一个小节点。
![](https://ws2.sinaimg.cn/large/006tNc79gy1fmbgugrv1yj30d3025dfx.jpg)
但如果 feature 与 master 都对同一个文件进行修改,就会有大冲突,此时需要根据提示来解决才可以。
#### 编辑的都是同一个文件的同一行
比如新建分支feature2并修改 README文档使之第一行为hello
~~~
git ch -b feature2
vi README #假设此时文件时空的,我们在第一行输入 hello#
git add .
git commit -m "Update README 1"
~~~
此时又不小心回到master主干并对同一个文件同一行进行编辑。(因为此时还没把第二个分支feature2合并进来主干,所以如果此时回到主干并打开 README 文件时,仍是空的)
~~~
git ch master
vi README #此时看到的文件仍是空的,于是我们在第一行也输入文字 hi222#
git add .
git ci -m "Update README 222"
~~~
此时在没有合并以前,分歧已经产生。
![](https://ws2.sinaimg.cn/large/006tNc79gy1fmbhbfug2sj30n702mwf0.jpg)
若在master中执行合并,则会提示错误:both modified: README
![](https://ws4.sinaimg.cn/large/006tNc79gy1fmbhffks0vj30k407jjt1.jpg)
打开这个档案后会发现有提示:
![](https://ws3.sinaimg.cn/large/006tNc79gy1fmbhlwtca4j30ik08gq3q.jpg)
此时只要保留真实想要的结果就可以。并且记得把大于号小于号等于号都去掉。再执行
~~~
git add README #文件名,只要针对这个有冲突的进行保存就可#
git commit #此时会跳出预设信息出来,确认一下,没问题后只要:wq!存档以后,就可以自动执行完成git merge#
~~~
![](https://ws3.sinaimg.cn/large/006tNc79gy1fmbht4qvasj30mh01t3yy.jpg)
#### 删除分支
如果已经 merge 成功以后,再执行分支删除,此时会显示只剩下 master 这个分支。但原来在 feature1分支中做的内容其实是被保留没有删除的。
~~~
git branch fearure1 -d
#或者 git br -d feature 也一样#
~~~
**强制删除**
如果你的这个分支还没被合并进来,那么执行大写D,就可以强制删除该分支,不用管是不是被合并。
~~~
git br feature1 -D
~~~
![](https://ws1.sinaimg.cn/large/006tNc79gy1fmbh0659abj30l6042mxu.jpg)
![](https://ws1.sinaimg.cn/large/006tNc79gy1fmbib9evvej30ma051409.jpg)
#### 重命名分支
~~~
git branch -m feature2 feature2b
~~~
↑直接把名字叫 feature2 的分支重命名为feature2
**强制重命名**
如果是你命名到一个已经存在的名字,那么执行大写的M,则会强制覆盖掉已经存在的那个同名文件。
~~~
git br -M feature2 feature2b
~~~
#### 人为制造流程图小突起
~~~
git ch -b feature4
git ch master
git merge feature4 --no-ff
~~~
这样可以很方便看到差别。也可以清楚看到,几个 commit 是属于同一个 branch 的。
![](https://ws3.sinaimg.cn/large/006tNc79gy1fmblrgm38rj30m8024dgc.jpg)
#### 合并部分 commit 到 master
比如我们在开分支feature6的时候,写了很多 commit,大部分暂时先不想合并到主干里面,但里面有一个 commit 比如是修改错别字,想先合并到主干里面才不会影响开发。此时可以用 cherry-pick 功能
~~~
git ch master # 回到主干再操作#
git cherry-pick c224fce9840 #这条commit的号码#
~~~
![](https://ws1.sinaimg.cn/large/006tNc79gy1fmbma6azycj30mf0gj78j.jpg)
![](https://ws4.sinaimg.cn/large/006tNc79gy1fmbmboba72j30lj02owf5.jpg)
#### 开分支注意事项
如果有冲突,则无法 merge。但此时可以先 commit,只要不push都可以慢慢解决冲突。
可使用 git reset, 或者用git stash。
或者使用git stash 先暂存下来,放在一个叫wip的地方。
~~~
git stash
~~~
要再次显示的话,可以
~~~
git stash apply
~~~
清除:
~~~
git stash clear
~~~
![](https://ws2.sinaimg.cn/large/006tNc79gy1fmbq3nhuayj30l30ast9u.jpg)
## 推送到远端仓库
一般使用https://github.com 来储存 git。
#### ssh 码设定
ssh 其实是一种加密方式,通讯协定,类似https:// 用来在你的计算机创建一个私钥id_rsa以及公钥id_rsa.pub。
~~~
ssh-keygen -t rsa -C"amy87326@163.com"
~~~
↑邮箱是 github 注册的邮箱。直接按三个回车即可,也不需要输入密码,因为如果现在输入,后续就会一直需要输入,很讨厌。
~~~
cat ~/.ssh/id_rsa.pub
~~~
↑显示你的公钥号,然后复制到github中新建一个 ssh 码,并新建一个新的repository。
此时可以推送了
~~~
git remote add origin git@github.com:amy326/sandbox.git
git push -u origin master
~~~
>* 绑定 ssh 码的时候,只输入ssh-keygen 可能是不够的,要输入ssh-keygen -t rsa -C"邮箱号"
>* 新建完在调出 ssh时,如果输入more ~/.ssh/id_rsa.pub 也不太合适,使用 cat 这个命令更好,否则可能在git push -u origin master的时候会出现“无法找到ssh”。
>* 在推送git push -u origin master时候,如果提示“无法找到ssh密码”,则可以回到 cd 根目录中,输入cd ~/.ssd 移动到 .ssh这个隐藏文件中查看。
>![](https://ws2.sinaimg.cn/large/006tNc79gy1fmbt14u6uhj30ms02h3zc.jpg)
#### clone回来
~~~
git clone git@github.com:对方账号/要拷贝的专案名字 本地你想新建的专案名字
~~~
比如:git clone git@github.com:lololo/sandbox amy_sandbox就是,我从 lololo 这个人的 github 中克隆一个名叫sandbox的专案,并且在本地中我自己取名叫amy_sandbox。
这个命令操作的意思是,第一次把别人的专案完整clone出来。但后续如果在执行小修改的时候,就不需要这个命令了,直接git pull 即可。
~~~
git pull
~~~
#### 冲突
如果远端已经修改完了最新版,但是本地 git 仓库没有拉回来,又在同一个地方进行修改,那么如果你本地修改完以后想git push origin master,则会提示reject。
~~~
To github.com:amy326/sandbox.git
! [rejected] master -> master (fetch first)
error: failed to push some refs to 'git@github.com:amy326/sandbox.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
~~~
![](https://ws2.sinaimg.cn/large/006tNc79gy1fmbtr3gxv9j30mh05uwgn.jpg)
此时需要先pull 下来,进行修改后再推上去。但会发现其实 pull 下来以后仍然会有错,错在 README 这个文件中。于是打开 README 这个文件,解决冲突。
![](https://ws3.sinaimg.cn/large/006tNc79gy1fmbtumnkywj30mm04yjt1.jpg)
![](https://ws3.sinaimg.cn/large/006tNc79gy1fmbtwq4jnpj30j10avwew.jpg)
#### 推送分支 push 分支
~~~
git push origin feature6
~~~
这时另一台电脑如何拉 feature6?
~~~
git chekcout -t origin/feature6
~~~
出现以下错误提示:
~~~
fatal: Cannot update paths and switch to branch 'feature6' at the same time.
Did you intend to checkout 'feature6b' which can not be resolved as commit?
~~~
![](https://ws4.sinaimg.cn/large/006tNc79gy1fmbuso70a4j30mi01g74r.jpg)
此时可以更新远端仓库git fetch,再继续git checkout -t origin/feature6。如果再有冲突,可以把本地的feature6先删除,再直接克隆远端的。
~~~
git fetch
git checkout -t origin/feature6
~~~
## 合并分支的方式
reset
rebase
reverse
merge
## git rebase重新整理 commit
类似 merge,但有点不一样。
**整理线型**
一般在分支要合并进主干前,可以用 git rebase 这个 功能来再整理一下 commit,让别人更好理解每个 commit 的意思。
~~~
git rebase xxx节点 -i #进入互动模式#
~~~
常用的有:
#### 在某个节点之后插入新的一个 commit
插入 commit
可以用到edit 这个功能,只要把默认的pick改成edit 就可以。一般加入 git reset HEAD^,回到你要修改的 commit 之前的一个节点,先重置git reset hEAD^ ,然后 再继续编辑。
~~~
git reset HEAD^
~~~
比如你可以 edit 把原来的一个 commit 变成2个(这个操作就是你只要跟平时一样 commit 两次就可以),然后再继续git rebase --continue继续进行,就可以。
~~~
git add . #有冲突要先修好,并 add 进去stage才可以#
git rebase --continue #可继续把rebase进行完#
~~~
![](https://ws1.sinaimg.cn/large/006tNc79gy1fmbzfa3ioaj30i60bb0um.jpg)
最好在某个分支里面。因为 git rebase xxx -i 出现互动框后,只会出现你所在的分支的相关 commit 节点,不会出现其他分支的节点,所以如果有分叉出去的 commit,则会被跳过。
![](https://ws4.sinaimg.cn/large/006tNc79gy1fmc1fsuhj9j30jb060dhk.jpg)
#### 删除某个 commit
进入 -i 互动模式,只要找到对应的那行,直接删除(编辑状态下按两下 dd),再继续git rebase --continue 即可。
#### 保留某个 commit 内容,但不单独占用一个 commit 而是合并到前一个 commit 进去
用字母 f
#### 改 commit 信息
用字母 r
#### 调换顺序
也是一样,进入 -i 互动模式,把那两行直接剪切黏贴到合适的位置就可以。但要记得这是最危险的,因为会非常容易造成冲突。
#### 改善线型
git rebase 以后,commit 的线型可能不是非常好看,比如这个feature/forum 分支,如下图有点乱。
![](https://ws4.sinaimg.cn/large/006tNc79gy1fmc2h7a49mj307b08pt92.jpg)
我们可以通过 git rebase master 命令让它重新回到 master 里面,变好看点:
~~~
git checkout feature/forum #切换到想修改线型的分支#
git rebase master #从分支往主干合并,当然也可以合并到其他分支 git rebase feature1等#
git add . #如果有冲突,应先解决,并输入git add .#
git rebase --contimue #继续把没完成的 rebase 进行完#
~~~
![](https://ws3.sinaimg.cn/large/006tNc79gy1fmc2mghljzj30an09sjs0.jpg)
## git reset砍掉
与 revert 不同,reset 是直接砍掉,不留痕迹。
#### 回到要修改的节点
~~~
git reset xxx
git commit -a --amend
~~~
![](https://ws4.sinaimg.cn/large/006tKfTcgy1fmcfzglu0ej30eo09o74w.jpg)
~~~
git reset xxx号码 #重置 xxx 这个 commit#
git reset HEAD^ #保存完马上后悔,于是执行这个文件以后,需要 commit 的内容就会还原到在 working spsce 里面待操作#
git reset HEAD^ --soft #修改后放到 stage 里面#
git reset HEAD^ --hard #直接清除#
~~~
## 项目管理分支模式
* develop on mainline
这样的好处是比较清楚,避免后续 merge 的问题;
缺点是速度非常慢,需要前续完成后才能进行,后续必须有增量开发的能力(因为只有一条主线)。
![](https://ws1.sinaimg.cn/large/006tKfTcgy1fmcheh2ohaj30dw07wq3l.jpg)
* branch for release
比如主线是3.0版本,要更新3.1版本的时候,就开一个 release3.1的 branch ,后续也同理。
主要用来修复master 版本的bug。
* branch by feature
根据功能来切分,比较常见。优点多,但要避免开太多。另外需要有一个 leader 来负责主要 merge。
但需要大家都时不时更新主干 code。
* branch by team
也常见。
![](https://ws3.sinaimg.cn/large/006tKfTcgy1fmchkafjy3j30ec0avq3r.jpg)
#### 两个主要分支
master——主干,永远处于最新状态
develop——下一次要发布的版本,目前开发用。
![](https://ws2.sinaimg.cn/large/006tKfTcgy1fmchma6u6qj306f08c74d.jpg)
#### 三种支援分支
![](https://ws1.sinaimg.cn/large/006tKfTcgy1fmchwi5malj30er0b13ze.jpg)
* Feature
开发新功能,或者修 bugs;
从 develop 分出来,完成后 merge 到 develop 中;
如果开发时间长,建议定期同步develop 主干的 code,不然后期可能 merge 不进去
新手用 merge,进阶的建议用 rebase。
可以另设--no-ff的merge节点。
![](https://ws4.sinaimg.cn/large/006tKfTcgy1fmchr2e6fej309o08sdg0.jpg)
* Release branches
准备要 release 的版本,只修复bug;
从 develop 分出来,完成后 **merge** 回 master和 develop。
* Hotfix branches
等不及 release,必须马上修复的 bug;
一般从 master 分出来,完成后 merge 回 master 和 develop。
![](https://ws4.sinaimg.cn/large/006tKfTcgy1fmchv5q2qqj306y08wq33.jpg)
## 更多 git 功能
* git archive
* git bisect
* git blame
* git grep
* git show
* git gc
* git format-path, send-email, am
* git pull --rebase
可以避免不必要的 merge 节点。
## 弄丢了commit 怎么办
找回:
例如搞砸了 git reset, rebase 或误删branch
在git gc前都有机会,因为预设会保留孤立的 commit90天。
* git reflog 或 git fsck --lost-found
* git cherry-pick xxx号码
* git merge xxx号码
## git 储存
~~~
git cat-file -p xxxsha1号码
~~~
↑可显示文件位置