分布式版本控制系统

版本控制系统(VCS)的核心:版本控制,主动提交,中央仓库

中央式版本控制系统的工作模型

分布式版本控制系统(DVCS)的工作模型

中央式VCS的中央仓库有两个功能:保存版本历史,同步团队代码

分布式VCS把保存版本历史交给了本地仓库,中央仓库只负责同步团队代码

优缺点:

避免了对网络的依赖

大型项目体积太大无法保存在本地

团队的基本工作模型

核心内容

  1. 写完所有的commit后,不用考虑远程仓库是否有新的commit,直接push
  2. 如果push失败,就用pull把远程仓库上新的commit取回到本地和本地合并,然后再push

原理

pull操作发现不仅远程仓库包含本地没有的commit,而且本地仓库也包含远程仓库没有的commit时,它就会把远端和本地的独有 commit进行合并,自动生成一个新的 commit ,和手动的 commit 不同,这种 commit 会自动填入一个默认的提交信息

git status本质上是将本地分支与它正在跟踪的远程分支进行比较

  1. git status只能判断本地分支是否超前于远程分支,无法判断远程分支是否超前于本地分支
  2. 通过git pull拉取远程代码时,如果远程仓库包含本地没有的commit,本地也包含远程没有的commit,那么除了把远程的commit拉取到了本地外,还会在本地新建一个commit表示自动合并。
  3. 也就是说自动生成commit只是用来表示一次合并的过程,没有实际意义,commit会按照的时间线进行同步

最流行的工作流:Feature Branching

优势

  • 代码分享
  • 一人多任务

核心内容

  1. 任何新的功能或者bug修复全都新建一个branch来写
  2. branch写完后,合并到master,然后删掉这个branch

删除本地有但在远程库已经不存在的分支

  1. git remote prune origin

Git引用

  • HEAD是一个永远自动指向当前commit的引用
  • branch也是一种引用,HEAD除了可以指向commit,还可以指向一个branch,当它指向某个branch时,会通过这个branch来间接指向某个commit,当HEAD提交时自动向前移动的时候,会带着它所指向的branch一起移动

Git使用 - 图1

  • HEAD指向的branch不能删除,如果要删除HEAD指向的branch,需要先用checkoutHEAD指向其它地方
  • 删除branch的操作只是删除引用,并不会删除任何commit,如果一个commit不在任何一个branch的路径上(野生commit),那么在一定时间后,它会被Git的回收机制删除

clone本质

git clone除了从远程仓库中把.git这个仓库目录下载到工作目录中,还会checkout mastercheckout就是把某个commit作为当前commit,把HEAD移动过去,并把工作目录的文件内容替换成这个commit所对应的内容

push的本质

把当前branch 的位置(即它指向哪个 commit)上传到远端仓库,并把它的路径上的 commit一并上传

不加参数的 git push 只能上传那些之前从远端 clone 下来或者 pull 下来的分支,而如果需要 push 你本地的自己创建的分支,则需要手动指定目标仓库和目标分支(并且目标分支的名称必须和本地分支完全相同),

feature1push 时,远程仓库的 HEAD 并没有和本地仓库的 HEAD 一样指向 feature1。这是因为,push 的时候只会上传当前的 branch 的指向,并不会把本地的 HEAD 的指向也一起上传到远程仓库。事实上,远程仓库的 HEAD 是永远指向它的默认分支(即 master,如果不修改它的名称的话),并会随着默认分支的移动而移动的。

merge的本质

从目标commit和当前commit分叉的位置起,把目标commit的路径上的所有commit的内容一并应用到当前 commit,然后自动生成一个新的commit

  1. git merge branch1

Git使用 - 图2 merge原理.gif)

冲突:如果两个branch修改了同一部分内容,merge就无法自动合并,需要手动解决冲突

  1. 解决掉冲突
  2. 手动commit

放弃解决冲突

  1. git merge --abort

pull本质

git pull 这个指令的内部实现就是把远程仓库使用 git fetch 取下来以后再进行 merge 操作的

Git使用 - 图3 pull原理.gif)

origin/masterorigin/HEAD 是什么鬼:它们是对远端仓库的 masterHEAD 的本地镜像,在 git pull 的「两步走」中的第一步——git fetch 下载远端仓库内容时,这两个镜像引用得到了更新,也就是上面这个动图中的第一步:origin/masterorigin/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,你将会提交什么」:

  1. git diff --staged

比对工作目录和暂存区

这条指令可以让你看到「如果你现在把所有文件都 add,你会向暂存区中增加哪些内容」:

  1. git diff

比对工作目录和上一条提交

这条指令可以让你看到「如果你现在把所有文件都 add 然后 git commit,你将会提交什么」

  1. git diff HEAD

rebase

rebase 是站在需要被 rebasecommit 上进行操作,这点和 merge 是不同的。

为了避免和远端仓库发生冲突,一般不要从 master 向其他 branch 执行 rebase 操作。而如果是 master 以外的 branch 之间的 rebase(比如 branch1branch2 之间),就不必这么多费一步,直接 rebase 就好。

交互式 rebase,它可以在 rebase 开始之前指定一些额外操作。交互式 rebase 最常用的场景是修改写错的 commit,但也可以用作其他用途。它的大致用法:

  1. 使用方式是 git rebase -i 目标commit
  2. 在编辑界面中指定需要操作的 commits 以及操作类型;
  3. 操作完成之后用 git rebase --continue 来继续 rebase 过程。

之前讲的修正 commit 的方法是把要修改的 commit 左边的 pick 改成 edit,而如果你要撤销某个 commit ,做法就更加简单粗暴一点:直接删掉这一行就好。

Git安装

  1. apt-get install git
  2. git --version

Git基本命令

  1. git init # 初始git仓库,出现.git目录
  2. git status # 查看项目文件状态
  3. git add xxx # 把工作区的文件提交到暂存区
  4. git commit -m "xxx" # 把暂存区的文件提交到版本库
  5. git log [--oneline] # 查看提交版本记录
  6. git commit -am "xxx" # 直接将工作区的文件提交到版本库

Git结构和状态

git的3层结构

  1. working directory // 工作区
  2. staging index // 暂存区
  3. git directory(Repository) // 版本库

git文件的4种状态

  1. untracked // 未跟踪
  2. changed/unstaged // 已修改/未暂存
  3. staged // 已暂存
  4. commited // 已提交

git配置

  1. git config --global user.name xxx
  2. git config --global user.email xxx
  3. git config --list # 列出配置

Git撤销操作

  1. git commit --amend # 撤销上一次提交,并将暂存区的文件重新提交
  2. git checkout -- xxx # 拉取暂存区的文件并将其替换工作区的文件, 注意与git checkout branchname区别
  3. git reset HEAD xxx # 拉取最近一次提交的版本库中的这个文件到暂存区,该操作不影响工作区

Git文件删除

  1. git rm xxx # 删除工作区和暂存区中的文件,相当于删除文件后执行git add
  2. git rm --cached xxx # 在不小心把不需要追踪的文件添加到暂存区,想删除暂存的文件但是不想删除工作区的文件时有用
  3. git rm -f xxx # 当工作区或者暂存区的文件修改了
  4. git mv oldname newname # 相当于mv oldname newname,git rm oldname,git add newname

Git分支

  1. git log --oneline # 简洁查看分支
  2. git log --graph --all # 以图表形式查看所有分支
  3. git branch # 查看本地分支
  4. git branch -a # 查看本地和远程仓库所有分支
  5. git branch -r # 查看远程仓库
  6. git checkout -b xxx # 等价于git branch xxx,git checkout xxx
  7. git checkout -b xxx 2b1c225dc # 从某个版本创建分支
  8. git checkout online # 切换到xxx分支
  9. git checkout -
  10. git branch -d online # 删除xxx分支
  11. git checkout master # 先切换到master分支
  12. git merge --no-ff xxx # 合并代码到master分支(禁止快进式合并)

Github远程仓库

  1. git remote add xxx https://gitee.com/ouweibin/xxx.git # 添加别名
  2. git remote -v # 查看添加的远程地址
  3. git remote remove xxx # 删除指定的远程地址

补充

Git更新本地代码

  1. git fetch xxx master:temp # 从远程的xxx仓库的master分支下载到本地并新建一个分支temp
  2. git diff temp # 比较master分支和temp分支的不同
  3. git merge temp # 合并temp分支到master分支
  4. git branch -d temp # 删除temp

.gitignore文件

使用.gitignore忽略不想跟踪的文件或者文件夹

修改Git默认编辑器nano为vim

  1. git config --global core.editor vim

Git修改最后一个版本

  1. 执行修改操作
  2. git commit --amend

Git修改非最后一个版本(危险)

rebase -i:交互式rebase

^ 的用法:在 commit 的后面加一个或多个 ^ 号,可以把 commit 往回偏移,偏移的数量是 ^ 的数量。例如:master^ 表示 master 指向的 commit 之前的那个 commitHEAD^^ 表示 HEAD 所指向的 commit 往前数两个 commit

~ 的用法:在 commit 的后面加上 ~ 号和一个数,可以把 commit 往回偏移,偏移的数量是 ~ 号后面的数。例如:HEAD~5 表示 HEAD 指向的 commit往前数 5 个 commit

  1. git log
  2. git rebase -i HEAD^^
  3. 把要修改版本的pick改成edit,保存退出
  4. 执行修改操作
  5. git commit --amend
  6. git rebase --continue # 重复直到Successfully

Git删除最后一个版本

  1. git reset --hard HEAD^ # 之后的版本全部清空

Git修改非最后一个版本(危险)

Git删除远程仓库的某次错误提交

在本地把远程的master分支删除,再把reset后的分支内容给push上去(远程仓库默认分支不能为master,否则会删除失败)

  1. git push origin :master # 删除远程的master分支(注意master前有个:)
  2. git push origin master # 重新创建远程master分支
  3. 或者:
  4. git reset --hard <commit_id> # 回滚到你想回滚的commit
  5. git push origin HEAD --force # 重新push到你的远程仓库

git push <远程主机名> <本地分支名>:<远程分支名>

如果远程分支名省略,则表示将本地分支推送到与之存在追踪关系的远程分支(通常两者同名),如果该远程分支不存在,则会被新建;如果本地分支名省略,则表示删除指定的远程分支,等同于推送一个空的本地分支到远程分支