进化论-Git的使用
版本控制(Revision control)是一种在开发的过程中用于管理我们对文件、目录或工程等内容的修改历史,方便查看更改历史记录,备份以便恢复以前的版本的软件工程技术。
一. git简介
git是一个分布式的,用于版本控制的系统/软件,由Linus使用C在两周内编写出最初版本,特点:
- git采用分布式,所以没有中心节点,每个clone都是一个完整的版本库,git的server只是方便交换修改;
- git更加强调个体,在单机上可以创建分支,修改代码,合并分支等;
1.1 构成
git本质上时一个基于键值对的文件系统,一个文件系统由文件和文件夹/目录组成;
git整体划分为三种结构:
仓库/版本库 Repository:一个被git所管理的目录,任何的变动都会被记录,保存所有的commit;
工作区 Working Directory:仓库中除了隐藏的.git文件夹的其他空间,区内文件用户可编辑/查看;
暂存区 Stage/Index:暂存工作区的变化并用于提交/回退/暂存等,Stage赋予Git更多灵活性,戳;
1.2 数据存储
git中主要有3种对象,stage及commit区的所有对象实际上都存储于objects中:
blob:文件系统中的文件,SHA-1文件内容为blob对象名/key,文件内容为blob对象的值/value;
- blob只存文件内容,不存文件名,文件名为在tree指向blob的mapping中的备注;
- 所以当一个文件的内容改变后,会额外生成一个blob对象,但是名字改变不会新增blob;
tree:文件系统中的文件夹,SHA-1计算生成tree对象名,tree里存放着与tree/blob的映射关系:
- tree在提交的时候生成,stage中仅映射至blob的对象;
- tree的SHA-1值,依赖内部的映射管理来生成,比如变更其内部文件名也会改变其SHA-1值;
commit:对文件系统当前状态的一次封装,里面有tree-对应文件系统,父提交-上一级commit对象,author的信息,committer的信息,msg等;
commit/tree/blob对象间的关系:
blob/tree/commit对象在objects中的存储:
- 三个对象均使用SHA算法来生成40位key,并使用前2位作为文件名来分类存储对象;
- pack中存放压缩后数据,可以使用git gc 来生成,包含共两个文件:
- .idx文件是.pack文件中的三对象列表及其偏移量,可以基于这两个文件提取所有的三对象;
- 在push与gc的时候生成,gc/push时只会打包被最新commit或stage所引用的对象,其他对象保留在本地,可以使用–prune=now参数或git prune --expire=0来清除;
1.3 整体的抽象
git 模型可以抽象为:
- 本地三级仓库之level1——working directory
- 本地三级仓库之level2——stage(index)
- 本地三级仓库之level3——repository(History)
- 远程移机仓库之level4——remote
git使用命令来实现各个仓的数据转移
等级 | 低level输入 | 高level输入 |
---|---|---|
Working directory-lv1 | 手工创建 | Git checkout/git stash |
Stage/index-lv2 | git add | git reset |
History/repository-lv3 | git commit | git pull |
Remote-lv4 | git push | - |
1.4 Git存储文件的方式
git使用快照流的方式来保存各个版本的文件:
- 如果文件没有修改,则不会产生新的blob对象,则新版本的这个文件仍指向之前的blob对象;
- 如果文件发生修改,则会产生新的blob对象,如A1,较A是一个全新的文件,没有关联;
二. git的使用
2.1 git仓库的初始化
配置当前系统/仓库的编辑者信息-签名
与github或gitee(代码托管中心)的账号没有关系.
# 安装好git后,需要配置当前系统的信息-自报家门
# 这个与登录github的账号没有半毛钱关系
git config --global user.name "Bruce"
git config --global user.email "bruce@gmail.com"
# 如果不带--global则是为每个本地库配置user信息,优于global
git config user.name "alvin"
git config user.email "alvin@gmail.com"
初始化一个仓库
mkdir pt_git
cd pt_git
git init # 仓库的初始化
最直接的效果是创建了一个.git的目录
.git目录中存放的是本丢相关的子目录和文件,不要删除或修改;
2.2 git命令图解
2.2.1 临近等级间的切换
git add file
:把文件file放入stage,.
代替所有,支持多个文件,支持*匹配字符等;
git commit
:把stage中文件生成快照并提交,建议添加 -m参数,为此次提交生成msg注释;
git reset
:撤销Stage中file文件(带 --file参数 ),或者撤销所有(不带参数);
git checkout --file
:复制stage的文件到working目录,撤销修改,还可用于分支的切换;
可以使用 git reset -p,git checkout -p,git add -p进入交互模式。
2.2.2 跨等级间的切换
git commit -a
:直接将工作目录被track的文件(当前commit中存在)&暂存区的文件提交;
git commit files
:将stage中的指定file提交;
git checkout HEAD -- files
:将working目录及stage中的files全部还原为上次提交时;
2.2.3 diff比较文件
2.2.4 rm删除文件
git rm file.txt
:working目录与stage中同时删除该文件,带 -r参数可以删除目录;
git rm --cached filexample.txt
:从stage中删除文件,working目录保存;
2.2.5 stash临时保存
git stash [save] [message]
:临时保存,只有git stash也可以,只是不方便查找
git stash list
:查看stash了哪些存储
git stash show[stash@{1}] [-p]
:显示stash与当前的区别,-p更加具体,指定某个stash
git stash apply [stash@{1}]
:取到某个stash,不删除stash;
git stash pop [stash@{1}]
:取到某个stash,同时删除stash;
git stash drop[stash@{1}]
:删除某个stash,不取出;
git stash clear
:清除所有暂存;
git stash –keep-index
:忽略modify已经提交到stage的忽略,复原其他的至本地库分支位置
三. git的操作
3.1 添加与提交
Git status 作用:显示目前git的状态,工作目录在哪个分支,本地库有没有commit,stage中是否有文件,是否有可操作的文件;
# 进入空目录
cd WeChat
git status
空目录中的显示在主分支/无history/无stage内容
# 创建一个文件并add
vim good.txt
111111
222222
# 查看状态
git status
其他不变,但显示有未被git所追踪的文件,可以使用git add <file>来添加到暂存区;
未track的文件只能使用git add来添加,而没法直接使用git commit -a命令来添加,区别于编辑已被track的文件;
# 按照提示添加
git add good.txt
good.txt已经被添加到stage且变为绿色,并告诉你可以使用git rm --cached <file>回退上一状态
# 提交到本地库
git commit
# 会进入一个vim类型的交互页面,填写一个commit的msg,或者可以直接用 -m参数来带
git commit -m "Wechat's first commit"
master分支的第一次提交/ID/MSG及变更的信息:1个文件修改,2行内容新增,创建一个good.txt;
提交后的状态:
# 修改文件后
vim good.txt
333333
git status
区别于新建,git add现在起update作用非track,同时可以使用git commit -a直接提交
git add good.txt
git commit -m "My second commit,modify good.txt"
第二次提交不会提示root-commit
3.2 前进与后退
git log 作用:显示所有的commit的信息;
git log
git log --pretty=oneline # 美化下显示
git log --oneline # SHA的值会更短
git reflog # 比Git log多显示了一个相对于HEAD的移动量
版本的操作本质上是操作HEAD指针,HEAD指针的前进与后退会影响目前的stage或working区域:
git reset --hard 614f781 # 退回777版本
HEAD的指针跟随移动了/working与stage目录都退回HEAD指向的commit版本
git reset --hard 5c6760b # 前进888版本
git reset --hard HEAD^ # 回退一个版本, 每个^会回退一个版本
git reset --hard HEAD~4 # 相对于当前HEAD移动4步
git log # 只能查看当前HEAD之前的commit,之后的看不到
git reflog # 可以查看所有
git的三个参数
git reset --soft # 仅仅移动本地库的指针
git reset --mixed # 移动本地库及stage的指针
git reset --hard # 移动本地库及stage及working的指针
git reset --soft HEAD^
git reset --mixed HEAD^
3.3 文件的删除与找回
# 新增文件
vim apple.txt
aaaa
git add apple.txt
git commit -m "new apple.txt"
# 删除文件
rm -rf apple.txt
git add apple.txt
git commit -m "del apple.txt"
# 恢复文件
git reset --hard 7b1b32c
# 只针对stage区域时
git reset --hard HEAD # 即可以使用HEAD指向的位置来恢复
3.4 文件的比较
git diff <file># working与暂存区的比较
git diff HEAD <file> # 与本地库HEAD的commit比较
git diff 5c6760b <file> # 与历史版本的commit比较
# 如果不带file表示比较所有文件
3.5 分支的操作
分支的好处:
- 同时并行推进多个功能开发,提高开发效率;
- 各个分支在开发过程中,如果某个分支开发失败,不会对其他分支有影响;
# 创建分支
git branch -v # 查看分支
git branch hot_fix # 创建分支
git branch -d hot_fix # 删除分支
git branch -v # 查看分支
git checkout hot_fix
# 修改host_ifx分支的修改
vim good.txt
....
git commit -a -m "edit good.txt add host_ifx"
# hot_ifx领先master分支版本
# 合并分支
# 1. 切换到接收修改的分支上-被修改的分支
git checkout master
# 2. 执行merge命令
git merge hot_ifx
# 在hot_ifx上编辑good的第8行
git checkout hot_ifx
vi good
git commit -a -m "edit good.txt line 8 by hot_ifx"
# 在master上编辑good的第8行
git checkout master
vi good
git commit -a -m "edit good.txt line 8 by master" # 此时commit不能提交
# 在master分支上合并
git merge hot_ifx
# 产生冲突
# 修复冲突
vi good.txt # 查看产生冲突的文件
<<<<<<< HEAD .... ========:当前分支这一块的内容
======= ..... hot_ifx >>>>>>>:被合并分支这一块的内容
# 删除特殊符号,选择保留的行,或者同时修改
vi good.txt
# 在解决冲突模式下add文件至stage
git add good.txt
# 在解决冲突模式下提交
git commit -m "merge master & hot_ifx"
四. git与github/gitee
4.1 同一term的协作
同一Term间的协作:
# 在github或gitee上创建创库
# 获取仓库的http地址/ssh地址-http://gitee.com/bruce/bbb.git
# 新建一个remote,别名为orgin,地址为...
git remote add origin http://gitee.com/bruce/bbb.git
# 查看当前可用远程库地址
git remote [-v]
# git推送参数为,origin-remote别名,master-想要推送的分支
git push origin master
# 另一个团队内成员将文件clone到本地
git clone https://gitee.com/Bruce_Amadeus_Lee/practice_git.git
# 效果:
# 1. 完整的将远程库下载到本地
# 2. 创建origin远程地址别名
# 3. 初始化本地库
# push = fetch + merge
# 1. fetch将远程库下载到本地称为远程地址/分组名
# 2. 使用merge将origin/master合并到本地库的master
git fetch origin master # 远程地址别名 + 分支名
4.2 同一term的冲突解决
当gitee上的master分支的同一文件同一位置被修改后,push操作将失败,如下图:
需要先pull(fetch + merge)后,修复conflict,然后add/commit/push即可;
4.3 跨团队协作
1. 其他团队的C,fork当前库到自己的远程库
2. 其将远程库下载到本地并完成修改后,push到自己的远程库;
3. 在gitee上发起pull-request;
4. master分支拥有人在线审核这个pull-request的代码;
5. 选择merge完成合并;
- 其他团队首先forked这个项目到自己的仓库里面
- 然后clone到本地
git clone https://gitee.com/alvin-apple/practice_git.git
- 本地修改后,其他团队推送到自己的远程库
- 其他团队向原项目拥有人发起request pull;
- bruce在pull-request可以看到;
- bruce审核与测试
- bruce选择merge
- 完成整体的合并
4.4 跨团队pull-request冲突解决
当有人给你的项目贡献代码,但他修改的地方你在之前也修改了,conflict产生。
当其他人给我们代码的pull-request与我的master/其他分支当前代码冲突时,gitee/github无法自动merge,需要人工干预;
# 在bruce/master(我们自己的仓)下,创建分支
git branch alvin-master [master] # 新建alvin-master分支并指向本地库的master
git branch -v | cat
git checkout alvin-master # 上面两条可以使用 git checkout -b alvin-master master
# 拉取alvin的practice_git的远程库
git pull https://gitee.com/alvin-apple/practice_git.git master
# 提示冲突,进入手工merging状态
# 编辑文件解决冲突
vi pp.txt
git add pp.txt # mark resolution
git commit -m "resolve conflict with alvin" # 提交至分支,冲突解决,alvin-master合并远程库成功;
# 切换至master并合并alvin-master分支
git checkout master
git merge alvin-master
git status
# 将本地master分支推送至别名为origin的远程库
git push origin master
参考: