多人场景Git演练
本文基于中国科学技术大学软件学院孟宁老师博客《五⼤场景玩转 Git,只要这一篇就够了!》
1.前言
入学后选择了孟宁老师的高级软件工程课,感觉孟宁老师讲的非常细致,并且提供了很多有用的课外资料,因此感谢孟宁老师细心指导。
那么接下来进入正题,讲讲我对git的理解~~
2.git是什么?
git 是一种分布式版本控制系统,能记录代码版本改动,具备团队协作功能,非常方便
3.几个概念
3.1. 工作区(Working Directory):在电脑里能看到的目录,你编辑代码的地方
3.2. 暂存区(Stage or Index):当前哪些改动是被git所追踪的(对的,不是所有修改都会被追踪,所以你需要把修改添加到暂存区,否则可能就会丢失)
3.3. 版本库(Repository):存储代码版本的仓库,在.git目录中(跟暂存区不同的是,把修改提交到这里会形成一个版本号,这样就能在git的分支树上体现出来了~~)
3.4. 远程库(Remote Repository):在服务器上存储代码的仓库。作为本地仓库的备份,也可以通过远程仓库进行多人协作,如果你不想因为意外丢掉你的代码,请一定记得把本地仓库的最新修改同步到远程仓库
4.多人场景演练
4.1 建立远程库
这个没啥好说的,在自己的github域名下建一个仓库就行
4.2 建立本地库
由于远程库中没有任何内容,需要新建本地库然后与远程库关联起来。如图,先新建本地仓库,再使用git init
命令初始化本地仓库,如此一来,就可以在这个仓库里放代码了
接下来来点java代码试试?
public class GitTest {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
新增代码文件后,使用git add fileName
命令,可以把代码文件从工作区添加进暂存区,接下来继续修改下代码文件,使用git status
可以查看当前工作区,暂存区,和未追踪文件的状态
接下来使用git commit -m "第一次提交"
把暂存区的文件提交到本地仓库中,这样就在本地形成一个版本号
4.3 关联远程库
git remote add origin https://github.com/JohnsonGreen/GitTest.git
git push -u origin master
在本地仓库增加远程库,并推送到远程库的master分支
4.4 建立个人开发分支
git checkout -b mybranch
不加-b
参数则是直接切换到该分支。接下来我们新增一些代码,并推送到远程
public class GitTest {
public static void main(String[] args) {
System.out.println("Hello World!");
System.out.println("Hello ChenHeng");
}
}
此时使用git log --oneline --graph
显示本地和远程都已经有两个节点
4.5 与同事协同开发
同事可以把远程仓库的内容完整的下载到本地,形成本地仓库,并建立他的分支。由于同事是个急性子,我才刚刚在远程master上推送了第一个结点"Hello World!",他就把代码clone下来了,并在此基础上建立了新的分支(不幸的是,我随后又在远程的master分支上增加了新的结点,猜猜他之后合并到master上会发生什么?)
git clone https://github.com/JohnsonGreen/GitTest.git
git branch -b hisbranch
加点代码试试
public class GitTest {
public static void main(String[] args) {
System.out.println("Hello World!");
System.out.println("Hello ChenYuHong");
}
}
然后推送到远程建立远程的hisbranch分支,并且此时想合并到远程的master分支,那么做法是,先在本地切换到master分支,然后使用git pull
将最新的内容更新到master分支,再使用 git merge --no-ff hisbranch
(使用--no-ff参数可以在git分支树上看到分叉 )合并到本地的master分支,但是,能合并成功吗?
4.6 冲突解决
public class GitTest {
public static void main(String[] args) {
System.out.println("Hello World!");
<<<<<<< HEAD
System.out.println("Hello ChenHeng");
=======
System.out.println("Hello ChenYuHong");
>>>>>>> hisbranch
}
}
我们看到,命令提示有冲突,并且代码中标注出了冲突位置,那这就需要选择保留哪一项了。保留之后,就可以add,commit以及push了。注意:在改同事代码的时候记得一定联系下他,否则可能被打~~
合并之后的分支树如图所示
4.7 吃后悔药
上次merge之后,有点后悔合并了,想还是继续回到第一次提交时"hello world"的状态,那么在通过git log --graph
查找到该commit ID后,就可以使用下面这个命令完成回退啦!
git reset --hard 54a05c8
然后瞬间在master分支上只有一个结点了,惊不惊喜,意不意外?
4.8 吃后悔药的后悔药
经过上次回退之后,感觉又想把merge之后的代码找回来,但是此时只剩一个孤零零的"Hello World!",之前写的代码全丢了,这可咋办?
答案是依旧使用git reset --hard
命令,但是commit ID要怎么找?
然后时光机神器出现了
git reflog
还好还好,有了git reflog
这个能记录你所有git操作的大神器,妈妈再也不用担心我的代码丢失了
于是我们找到merge结点的commit ID,然后就可以乘坐时光机回到未来了
4.8 终结杂乱无章的提交
现在,我的同事要向孟宁老师打招呼,但是同事是个结巴,只能一个字母一个字母的打,于是我们在同事的提交记录上看到了一长串的commit,并且每个commit也就只打了一个字母
但是这一个一个字母形成的commit终究只是为了完成"Hello Meng Ning"这个打招呼的功能,真的有必要写这么多commit吗?
接下来我们使用git rebase -i [startCommitID] [endCommitId]
这个命令,来让同事的招呼打得更顺畅一些。
git rebase -i dc8e07b
在上图中我们删除除了最后一条的所有commit,只保留最后一条commit
然后出现了冲突,如下
public class GitTest {
public static void main(String[] args) {
System.out.println("Hello World!");
System.out.println("Hello ChenYuHong");
<<<<<<< HEAD
=======
System.out.println("Hello Meng Ning");
>>>>>>> 1704756... 添加g
}
}
我们当然要保留向孟宁老师打招呼的内容,修改之后把内容add进暂存区,并使用git rebase --continue
完成rebase操作,此时会跳出编辑框,让我们把保留的这个commit的说明改一下,从"添加g"
改为"向孟宁老师打招呼"
, 保存并提交,然后合并到master,并推送到远程库
从而我们在继续查看远程库的master分支时,就不会继续包含那些"结巴"的commit啦!
5. 一点小小的补充
5.1 为什么需要暂存区?
做某件事情的时候可能会改好几个文件,例如a、b、c三个文件,其中a和b文件是代码逻辑的修改,c文件是文案修改。这个时候为了让提交日志更可读,会先提交a和b,再提交c,于是就有如下的git操作
git add a b
git commit -m' change logic'
git add c
git commit -m 'change text'
如果没有暂存区这个东西,那只可能有一条提交,这个提交包含了逻辑调整和文案修改(可能是两件毫不相干的事情)
5.2 git push --force /-f
命令有什么安全问题?
--force 会使用本地分支的提交覆盖远端推送分支的提交。也就是说,如果其他人在相同的分支推送了新的提交,你的这一举动将“删除”他的那些提交!就算在强制推送之前先 fetch 并且 merge 或 rebase 了也是不安全的,因为这些操作到推送之间依然存在时间差,别人的提交可能发生在这个时间差之内.
2018年9月19日,一名程序员在美国某办公楼向4名同事开枪,导致一人情况危机,两人伤情严重,一人被子弹擦伤。目前,凶手已死,身份被警方查明。 目前,码农持枪杀人的动机仍然是个谜。有人猜测道:“同事不写注释,不遵循驼峰命名,括号换行,最主要还天天 git push -f 等因素” 激怒了这名行凶者。
5.3 git reset 和 git revert 有什么区别?
二者最大的区别是git revert是用新的一个commit来回滚之前的commit,HEAD是要继续前进,但是git reset
是直接删除指定的HEAD,其是相当于在不断后退
5.4 版本回退之后,想吃后悔药怎么办?
1) revert
: 两次revert ,负负得正
2) 如果知道回退之前的HEAD所指向的结点的版本号,那么依然可以使用 git reset --hard e475afc
来实现
5.5 正在dev分支进行开发,但是突然在master分支上有紧急bug要修复,怎么办?
1)git stash save "增加readme.md2"
会将工作区和暂存区的内容储藏起来,前提是这些文件已经被git追踪
储藏未跟踪的文件:git stash save -u
或 git stash save --include-untracked
2)git stash list
查看储藏列表
stash@{0}: On dev: 修改为Yes
stash@{1}: On dev: 增加readme.md2
3)git stash apply1
应用最顶层的stash, 也可以指定哪个stash: git stash apply stash@{1}
git stash pop
与之类似,但会删除stashs
stash
之后又对同一个位置做了修改,造成冲突?
先对修改进行commit
或者git stash
4)git stash drop stash@{1}
删除某个stash
或者git stash clear
清除所有stash
5)git stash show -p stash@{1}
查看当前分支与某个stash的对比
6)git stash branch newBranch stash@{1}
有的时候可以将储藏起来的代码应用到新建的分支,而不是当前分支上
5.6 如果只是想丢弃掉工作区的内容,该怎么做?
1) git reset --hard HEAD
直接重置版本号(丢弃掉所有文件在工作区的修改 + 已经add到暂存区但没有commit的修改,需要谨慎使用
)
变种: git reset HEAD~1 或 git reset HEAD^ (均是回退到HEAD之前的一个版本)
2)git checkout -- filename
(针对选中的文件,可以添加多个)
这个操作总是让这个文件回到最近一次 git commit 或 git add 时的状态(commit之后修改没有add或add之后又做了修改),都是丢弃工作区的修改(修改包含删除操作)。
git checkout . 放弃当前目录下的修改
3) 推荐使用git stash, 万一后悔了呢?
5.7 merge错了分支,咋办?
git merge --abort
该命令仅仅在合并后导致冲突时才使用。git merge --abort将会抛弃合并过程并且尝试重建合并前的状态。但是,当合并开始时如果存在未commit的文件,git merge --abort在某些情况下将无法重现合并前的状态
5.8 在发布版本号的时候,怎么给结点打标签?
1)git tag -a v1.4 -m "my version 1.4"
会在当前分支上的HEAD结点打上打标签
2) git tag -a v1.1 -m "my tag" a0472e6
给某一个结点打标签
3) 推送标签到远程仓库
push单个tag,命令格式为git push origin [tagname]