分布式版本控制系统
版本控制系统(VCS)的核心:版本控制,主动提交,中央仓库
中央式版本控制系统的工作模型
分布式版本控制系统(DVCS)的工作模型
中央式VCS的中央仓库有两个功能:保存版本历史,同步团队代码
分布式VCS把保存版本历史交给了本地仓库,中央仓库只负责同步团队代码
优缺点:
避免了对网络的依赖
大型项目体积太大无法保存在本地
团队的基本工作模型
核心内容
- 写完所有的
commit
后,不用考虑远程仓库是否有新的commit
,直接push
- 如果
push
失败,就用pull
把远程仓库上新的commit
取回到本地和本地合并,然后再push
原理
当pull
操作发现不仅远程仓库包含本地没有的commit
,而且本地仓库也包含远程仓库没有的commit
时,它就会把远端和本地的独有 commit
进行合并,自动生成一个新的 commit
,和手动的 commit
不同,这种 commit
会自动填入一个默认的提交信息
git status
本质上是将本地分支与它正在跟踪的远程分支进行比较
git status只能判断本地分支是否超前于远程分支,无法判断远程分支是否超前于本地分支
通过git pull拉取远程代码时,如果远程仓库包含本地没有的commit,本地也包含远程没有的commit,那么除了把远程的commit拉取到了本地外,还会在本地新建一个commit表示自动合并。
也就是说自动生成commit只是用来表示一次合并的过程,没有实际意义,commit会按照的时间线进行同步
最流行的工作流:Feature Branching
优势
- 代码分享
- 一人多任务
核心内容
- 任何新的功能或者bug修复全都新建一个
branch
来写 branch
写完后,合并到master
,然后删掉这个branch
删除本地有但在远程库已经不存在的分支
git remote prune origin
Git引用
HEAD
是一个永远自动指向当前commit
的引用branch
也是一种引用,HEAD
除了可以指向commit
,还可以指向一个branch
,当它指向某个branch
时,会通过这个branch
来间接指向某个commit
,当HEAD
提交时自动向前移动的时候,会带着它所指向的branch
一起移动
HEAD
指向的branch
不能删除,如果要删除HEAD
指向的branch
,需要先用checkout
把HEAD
指向其它地方- 删除
branch
的操作只是删除引用,并不会删除任何commit
,如果一个commit
不在任何一个branch
的路径上(野生commit
),那么在一定时间后,它会被Git的回收机制删除
clone本质
git clone
除了从远程仓库中把.git
这个仓库目录下载到工作目录中,还会checkout master
,checkout
就是把某个commit
作为当前commit
,把HEAD
移动过去,并把工作目录的文件内容替换成这个commit
所对应的内容
push的本质
把当前branch
的位置(即它指向哪个 commit
)上传到远端仓库,并把它的路径上的 commit
一并上传
不加参数的 git push
只能上传那些之前从远端 clone
下来或者 pull
下来的分支,而如果需要 push
你本地的自己创建的分支,则需要手动指定目标仓库和目标分支(并且目标分支的名称必须和本地分支完全相同),
在 feature1
被 push
时,远程仓库的 HEAD
并没有和本地仓库的 HEAD
一样指向 feature1
。这是因为,push
的时候只会上传当前的 branch
的指向,并不会把本地的 HEAD
的指向也一起上传到远程仓库。事实上,远程仓库的 HEAD
是永远指向它的默认分支(即 master,如果不修改它的名称的话),并会随着默认分支的移动而移动的。
merge的本质
从目标commit
和当前commit
分叉的位置起,把目标commit
的路径上的所有commit
的内容一并应用到当前 commit
,然后自动生成一个新的commit
git merge branch1
merge原理.gif)
冲突:如果两个branch
修改了同一部分内容,merge
就无法自动合并,需要手动解决冲突
- 解决掉冲突
- 手动
commit
放弃解决冲突
git merge --abort
pull本质
git pull
这个指令的内部实现就是把远程仓库使用 git fetch
取下来以后再进行 merge
操作的
pull原理.gif)
origin/master
和 origin/HEAD
是什么鬼:它们是对远端仓库的 master
和 HEAD
的本地镜像,在 git pull
的「两步走」中的第一步——git fetch
下载远端仓库内容时,这两个镜像引用得到了更新,也就是上面这个动图中的第一步:origin/master
和 origin/HEAD
移动到了最新的 commit
。
而 git pull
的第二步操作 merge
的目标 commit
,是远端仓库的 HEAD
,也就是 origin/HEAD
,
checkout本质
git checkout branch
的本质,其实是把 HEAD
指向指定的 branch
,然后签出这个 branch
所对应的 commit
的工作目录。所以同样的,checkout
的目标也可以不是 branch
,而直接指定某个 commit
rebase本质
add理解
通过add
添加进暂存区的不是文件名,而是具体的文件改动内容,在add
时的改动都被添加进了暂存区,但在add
之后同一文件的新改动并不会自动被添加进暂存区
改动对比
比对暂存区和上一条提交
这条指令可以让你看到「如果你立即输入 git commit
,你将会提交什么」:
git diff --staged
比对工作目录和暂存区
这条指令可以让你看到「如果你现在把所有文件都 add
,你会向暂存区中增加哪些内容」:
git diff
比对工作目录和上一条提交
这条指令可以让你看到「如果你现在把所有文件都 add
然后 git commit
,你将会提交什么」
git diff HEAD
rebase
rebase
是站在需要被 rebase
的 commit
上进行操作,这点和 merge
是不同的。
为了避免和远端仓库发生冲突,一般不要从 master
向其他 branch
执行 rebase
操作。而如果是 master
以外的 branch
之间的 rebase
(比如 branch1
和 branch2
之间),就不必这么多费一步,直接 rebase
就好。
交互式 rebase
,它可以在 rebase
开始之前指定一些额外操作。交互式 rebase
最常用的场景是修改写错的 commit
,但也可以用作其他用途。它的大致用法:
- 使用方式是
git rebase -i 目标commit
; - 在编辑界面中指定需要操作的
commit
s 以及操作类型; - 操作完成之后用
git rebase --continue
来继续rebase
过程。
之前讲的修正 commit
的方法是把要修改的 commit
左边的 pick
改成 edit
,而如果你要撤销某个 commit
,做法就更加简单粗暴一点:直接删掉这一行就好。
Git安装
apt-get install git
git --version
Git基本命令
git init # 初始git仓库,出现.git目录
git status # 查看项目文件状态
git add xxx # 把工作区的文件提交到暂存区
git commit -m "xxx" # 把暂存区的文件提交到版本库
git log [--oneline] # 查看提交版本记录
git commit -am "xxx" # 直接将工作区的文件提交到版本库
Git结构和状态
git的3层结构
working directory // 工作区
staging index // 暂存区
git directory(Repository) // 版本库
git文件的4种状态
untracked // 未跟踪
changed/unstaged // 已修改/未暂存
staged // 已暂存
commited // 已提交
git配置
git config --global user.name xxx
git config --global user.email xxx
git config --list # 列出配置
Git撤销操作
git commit --amend # 撤销上一次提交,并将暂存区的文件重新提交
git checkout -- xxx # 拉取暂存区的文件并将其替换工作区的文件, 注意与git checkout branchname区别
git reset HEAD xxx # 拉取最近一次提交的版本库中的这个文件到暂存区,该操作不影响工作区
Git文件删除
git rm xxx # 删除工作区和暂存区中的文件,相当于删除文件后执行git add
git rm --cached xxx # 在不小心把不需要追踪的文件添加到暂存区,想删除暂存的文件但是不想删除工作区的文件时有用
git rm -f xxx # 当工作区或者暂存区的文件修改了
git mv oldname newname # 相当于mv oldname newname,git rm oldname,git add newname
Git分支
git log --oneline # 简洁查看分支
git log --graph --all # 以图表形式查看所有分支
git branch # 查看本地分支
git branch -a # 查看本地和远程仓库所有分支
git branch -r # 查看远程仓库
git checkout -b xxx # 等价于git branch xxx,git checkout xxx
git checkout -b xxx 2b1c225dc # 从某个版本创建分支
git checkout online # 切换到xxx分支
git checkout -
git branch -d online # 删除xxx分支
git checkout master # 先切换到master分支
git merge --no-ff xxx # 合并代码到master分支(禁止快进式合并)
Github远程仓库
git remote add xxx https://gitee.com/ouweibin/xxx.git # 添加别名
git remote -v # 查看添加的远程地址
git remote remove xxx # 删除指定的远程地址
补充
Git更新本地代码
git fetch xxx master:temp # 从远程的xxx仓库的master分支下载到本地并新建一个分支temp
git diff temp # 比较master分支和temp分支的不同
git merge temp # 合并temp分支到master分支
git branch -d temp # 删除temp
.gitignore文件
使用.gitignore忽略不想跟踪的文件或者文件夹
修改Git默认编辑器nano为vim
git config --global core.editor vim
Git修改最后一个版本
执行修改操作
git commit --amend
Git修改非最后一个版本(危险)
rebase -i
:交互式rebase
^
的用法:在 commit
的后面加一个或多个 ^
号,可以把 commit
往回偏移,偏移的数量是 ^
的数量。例如:master^
表示 master
指向的 commit
之前的那个 commit
; HEAD^^
表示 HEAD
所指向的 commit
往前数两个 commit
~
的用法:在 commit
的后面加上 ~
号和一个数,可以把 commit
往回偏移,偏移的数量是 ~
号后面的数。例如:HEAD~5
表示 HEAD
指向的 commit
往前数 5 个 commit
git log
git rebase -i HEAD^^
把要修改版本的pick改成edit,保存退出
执行修改操作
git commit --amend
git rebase --continue # 重复直到Successfully
Git删除最后一个版本
git reset --hard HEAD^ # 之后的版本全部清空
Git修改非最后一个版本(危险)
Git删除远程仓库的某次错误提交
在本地把远程的master分支删除,再把reset后的分支内容给push上去(远程仓库默认分支不能为master,否则会删除失败)
git push origin :master # 删除远程的master分支(注意master前有个:)
git push origin master # 重新创建远程master分支
或者:
git reset --hard <commit_id> # 回滚到你想回滚的commit
git push origin HEAD --force # 重新push到你的远程仓库
git push <远程主机名> <本地分支名>:<远程分支名>
如果远程分支名省略,则表示将本地分支推送到与之存在追踪关系的远程分支(通常两者同名),如果该远程分支不存在,则会被新建;如果本地分支名省略,则表示删除指定的远程分支,等同于推送一个空的本地分支到远程分支