版本控制
作用
用于管理多人协同开发项目的技术,可以有效记录程序的各个版本。
如果缺少版本控制很可能会出现软件代码一致性,内容冗余,并发,安全性等的问题。
相关术语
工作空间(Workspace): 本地硬盘或Unix 用户帐户上编辑的文件副本,平时存放代码的地方。
工作树/工作区(Working tree): 工作区中包含了仓库的工作文件。就是平时看到的项目目录。
暂存区(Staging area): 暂存区是工作区用来提交更改前可以暂存工作区的变化,用于临时存放改动。只是一个文件,存放即将提交的文件列表信息
版本仓库(Repository): .git目录,是受版本控制的所有文件修订历史的共享数据库,安全存放数据的地方。比如包含暂存区,分支信息等。
远程仓库(Remote): 托管代码的服务器
索引(Index): 索引是暂存区的另一种术语。
签入(Checkin): 将新版本复制回仓库
签出(Checkout: 从仓库中将文件的最新修订版本复制到工作空间
提交(Commit): 对各自文件的工作副本做了更改,并将这些更改提交到仓库
冲突(Conflict): 多人对同一文件的工作副本进行更改,并将这些更改提交到仓库
合并(Merge): 将某分支上的更改联接到此主干或同为主干的另一个分支
分支(Branch): 从主线上分离开的副本,默认分支叫master
锁(Lock): 获得修改文件的专有权限
头(HEAD): 指针,最常用以指向当前选择的分支
修订(Revision): 表示代码的一个版本状态。Git通过用SHA1 hash算法表示的ID来标识不同的版本
标记(Tags): 标记指的是某个分支某个特定时间点的状态。通过标记,可以很方便的切换到标记时的状态
分布式版本控制系统-Git
版本控制主要有分布式和集中式.
集中式:版本库存放在中央服务器,每个人从中央服务器中获得当前版本,然后再提交上去.缺点很明显,需要联网,也依赖网速.
分布式:没有中央服务器,每个人电脑中都有一个完整的版本库.本地修改后,彼此之间互传修改信息,一般会将其中一个当成’中央服务器’,便于信息传递。(这里提到’中央服务器’也可以用你自己的电脑充当)
Git安装和使用
安装git
windows: 官网下载安装就好了,一直默认下一步。推荐使用git bash。
linux: sudo apt-get install git
配置git
# 查看配置项
git config -l
# 查看全局,系统,当前用户配置
git config --global/system/local --list
# 配置邮箱和用户名(你的身份,也有不同级别之分,一般初始设置为全局)
git config --global user.name "your_name"
git config --global user.email your_email
配置文件:
~/.gitconfig
# 添加配置项
git config [--local|--global|--system] section.key value
# 删除配置项
git config [--local|--global|--system] --unset section.key
添加远程仓库
下面有请github登场。
1.使用SSH(安全,不用在每次push时输入用户名和密码):
# 获取SSH key.命令输入后,一直回车就行
ssh-keygen -t rsa -C "email address"
获得公钥和私钥,至于作用,建议了解网络安全中的非对称加密。这里只要知道你保存好私钥就行,公钥交给github。
钥匙保存在/home/user/.ssh下,名字为id_rsa.pub。
# 获得公钥内容
cat id_rsa.pub
在github的个人界面中settings -> SSH and GPG Keys -> New SSH Key。
名字随意,内容为之前得到的公钥内容。(从ssh-rsa开始,一直到最后)
2.本地仓库与远程仓库建立连接
github上新建一个项目,名字随意.
# 如果未创建仓库则可以选择先运行下面的语句,含义后续会解释
# git init
# 下面这行命令会在创建github项目时提示的,具体的远程仓库地址视实际情况而定
git remote add origin git@github.com:xiaowk5516/GitTest.git
git push -u origin master
此时本地仓库和远程仓库建立好连接
工作流程
git管理的是文件,还有文件的’修改’,注意编码问题。
git管理的文件有三种状态:已修改(modified),已暂存(staged),已提交(committed)
已修改: 在工作目录中添加、修改文件(都是在工作空间中完成);
已暂存: 将需要进行版本管理的文件放入暂存区域;
已提交: 将暂存区域的文件提交到git仓库(本地仓库)。
Git常见命令
地址,目录,文件等尽量不要使用中文
获取仓库
创建仓库
在项目的根目录执行创建仓库操作,会生成.git目录作为Git版本库,存储一些Git有关的信息,暂存区stage就在这里.
git init
# 可以一步创建目录并初始化为仓库
git init project_name
克隆远程仓库
将远程服务器上仓库的镜像拷贝到本地(不是取特定版本)
git clone url
# 例子,克隆hexo主题Butterfly
git clone https://github.com/jerryc127/hexo-theme-butterfly.git
文件操作
文件的状态
Untracked: 未跟踪, 此文件在文件夹中, 但并没有加入到git库, 不参与版本控制. 通过git add
状态变为Staged
.
Unmodify: 文件已经入库, 未修改, 即版本库中的文件快照内容与文件夹中完全一致. 这种类型的文件有两种去处, 如果它被修改, 而变为Modified
. 如果使用git rm
移出版本库, 则成为Untracked
文件
Modified: 文件已修改, 仅仅是修改, 并没有进行其他的操作. 这个文件也有两个去处, 通过git add
可进入暂存staged
状态, 使用git checkout
则丢弃修改过, 返回到unmodify
状态, 这个git checkout
即从库中取出文件, 覆盖当前修改。通过SHA-1算法计算文件校验和,判断文件是否修改
Staged: 暂存状态. 执行git commit
则将修改同步到库中, 这时库中的文件和本地文件又变为一致, 文件为Unmodify
状态. 执行git reset HEAD filename
取消暂存, 文件状态为Modified
查看文件状态
git status filename
# 查看所有文件的状态
git status
添加文件与目录
# 添加指定文件到暂存区
$ git add [file1] [file2] ...
# 添加指定目录到暂存区,包括子目录
$ git add [dir]
# 添加当前目录的所有文件到暂存区
$ git add .
移除文件与目录
# 直接从暂存区删除,工作区中的文件不会收到影响,类似工作区中的文件变成untracked
git rm --cached file
# 同时删除工作区中的文件,-f为强制删除
git rm -f file
# 通过修改目录树移除暂存区中的文件(用master分之指向的目录树替代),同样不会影响到工作区中的文件
git reset HEAD file
#移除所有未跟踪文件
git clean -df
# 更名,只能更改处于版本控制下的文件,即untracked不能更名
git mv file1 file2
# 用暂存区的文件替换工作区的文件
# 全部文件
git checkout .
# 指定文件
git checkout file
# 用master分支中的文件替换暂存区和工作区的文件
git checkout HEAD .
git checkout HEAD file
查看修改的文件内容
# 查看暂存区和工作区内文件的差异
git diff file
— a表示修改前的文件; +++ b表示修改后的文件; - 表示删除的内容; + 表示增加的内容; 还显示了增加删除内容的字节数
# 比较暂存区的文件与已经提交过的文件
git diff --cached file
# 比较repo和工作区的文件差异
git diff HEAD file
签出checkout
# 汇总显示工作区、暂存区与HEAD的差异。
git checkout
# 切换分支。切换index和工作目录,HEAD指针指向这个分支。
git checkout branch
git checkout HEAD
# 用暂存区中的文件来覆盖工作区中的文件。
git checkout -- file
# 维持HEAD的指向不变。用branch所指向的提交中的file替换暂存区和工作区中对应的文件。
git checkout branch -- file
# 注意git checkout 命令后的参数为一个点(“.”)。相当于用暂存区的所有文件直接覆盖本地文件。
git checkout -- .
git checkout .
# 如果不加commit_id,那么git checkout -- file表示恢复文件到本地版本库中最新的状态。
git checkout commit_id -- file
忽略文件
将不想纳入版本管理的文件忽略掉
在项目主目录下建立’.gitignore’文件,会根据.gitignore文件的内容忽略对应的文件。
语法:
# 1.'#'为注释
# 2.可以使用linux通配符
# 例子:忽略所有.txt结尾的文件
*.txt
# 3.名称前加'!',表示例外规则,不被忽略
# 例子:不忽略lib.txt
!lib.txt
# 4.最前面加'/',表示忽略项目根目录下的文件,类似于通过地址定位
# 例子:仅忽略项目根目录下的TODO文件,不包括其它目录temp
/temp
# 5.最后面加'/',表示该目录下的所有文件被忽略
# 例子:忽略build/目录下的所有文件
build/
提交
将暂存区的内容通过commit提交到本地仓库
# message代表本次提交的备注
git commit -m [message]
# 提交指定文件
git commit [file] [file]... -m message
# 提交上次commit之后工作区的修改,跳过add那一步,但是对新建文件不起作用
git commit -a
# 提交时显示diff信息
git commit -v
# 修改上一次的commit
git commit --amend -m [message]
# 重做上次的commit,包括制定文件的新变化
git commit --amend [file][file]... -m [message]
# 撤销暂存区中指定文件
git reset HEAD file
# 撤销repo中上一次的提交, --soft是保留到暂存区 --hard是直接删除
git reset --hard HEAD^
# 撤销repo中前n次提交
git reset --hard HEAD~n
# 根据版本编号撤销本地仓库中的提交
git revert commit-id
日志和历史
# 查看提交日志
git log
# 图形化查看日志
git log --graph
# 查看输入过的所有命令
history
# 查看所有分支的更新记录
git reflog
# 查看制定状态的文件
git ls-files (--[cached|deleted|others|ignored|stage|unmerged|killed|modified])* (-[c|d|o|i|s|u|k|m])
补充
#统计某人的代码提交量,包括增加,删除:
git log --author="$(git config --get user.name)" --pretty=tformat: --numstat | gawk '{ add += $1 ; subs += $2 ; loc += $1 - $2 } END { printf
"added lines: %s removed lines : %s total lines: %s\n",add,subs,loc }' -
#仓库提交者排名前 5(如果看全部,去掉 head 管道即可):
git log --pretty='%aN' | sort | uniq -c | sort -k1 -n -r | head -n 5
#仓库提交者(邮箱)排名前 5:这个统计可能不会太准,因为很多人有不同的邮箱,但会使用相同的名字
git log --pretty=format:%ae | gawk -- '{ ++c[$0]; } END { for(cc in c) printf "%5d %s\n",c[cc],cc; }' | sort -u -n -r | head -n 5
#贡献者统计:
git log --pretty='%aN' | sort -u | wc -l
#提交数统计:
git log --oneline | wc -l
# 显示有变更的文件
git status
# 显示当前分支的版本历史
git log
# 显示commit历史,以及每次commit发生变更的文件
git log --stat
# 搜索提交历史,根据关键词
git log -S [keyword]
# 显示某个commit之后的所有变动,每个commit占据一行
git log [tag] HEAD --pretty=format:%s
# 显示某个commit之后的所有变动,其"提交说明"必须符合搜索条件
git log [tag] HEAD --grep feature
# 显示某个文件的版本历史,包括文件改名
git log --follow [file]
git whatchanged [file]
# 显示指定文件相关的每一次diff
git log -p [file]
# 显示过去5次提交
git log -5 --pretty --oneline
# 显示所有提交过的用户,按提交次数排序
git shortlog -sn
# 显示指定文件是什么人在什么时间修改过
git blame [file]
# 显示暂存区和工作区的差异
git diff
# 显示暂存区和上一个commit的差异
git diff --cached [file]
# 显示工作区与当前分支最新commit之间的差异
git diff HEAD
# 显示两次提交之间的差异
git diff [first-branch]...[second-branch]
# 显示今天你写了多少行代码
git diff --shortstat "@{0 day ago}"
# 显示某次提交的元数据和内容变化
git show [commit]
# 显示某次提交发生变化的文件
git show --name-only [commit]
# 显示某次提交时,某个文件的内容
git show [commit]:[filename]
# 显示当前分支的最近几次提交
git reflog
Git分支
分支的概念: 类似与平行宇宙的概念,但是也有点区别。从某一个时间点开始,分出了一个平行世界branch(有主次之分,原来的世界为主世界master),平行世界和主世界互不干扰,并且每个分支世界都有一个命令可以回归到主世界。问题来了,不同世界中事物发展经历不一样,比如两个世界的绿巨人回到主世界后会产生冲突,到底谁才是客观存在的绿巨人?这时候master就可以判断分支世界回归后哪些事物的发展经历是可以留下来替换主世界中同一个事物,比如分支世界的绿巨人可以替换主世界中的绿巨人。
新建分支
我们之前在相关术语中提到指针HEAD指向当前分支,而分支指向一个有commit组成的链表,下面以HEAD指向master为例说明
# 新建分支,HEAD仍然指向master,新的branch和master指向同一个commit节点
git branch [branch-name]
# 新建分支并且切换到这个分支,HEAD指向新的branch
git checkout -b [branch-name]
# 新建分支,指向特定commit节点
git branch [branch-name] [commit-id]
# 新建分支,追踪指定远程分支
git branch --track [branch-name] [remote branch]
查看/切换分支
# 列出本地分支
git branch
# 列出远程分支
git branch -r
# 列出所有分支
git branch -a
# 切换分支,更新工作区,
git checkout [branch-name]
# 切换到上一个分支
git checkout -
分支合并和冲突
# 分支合并
git merge [branch-name]
执行上述命令后可以发现命令行中提示部分文件未合并,这些文件发生了冲突(同时在两个分支中修改过),这时可以手动修改,然后提交。
删除分支
# 删除本地分支,-D强制删除
git branch -d [branch-name]
# 删除远程分支
git push origin --delete [branch-name]
git branch -dr [remote/branch]
与远程仓库交互
git clone 克隆远程仓库
# 默认远程主机名为origin
git clone address
# 例子:github
git clone git@github.com:xiaowk5516/GitTest.git
# 为远程主机命名
git clone -o name address
git remote
# 列出远程主机
git remote
# 列出所有远程主机
git remote -v
# 显示远程主机名
git remote show name
# 添加远程主机
git remote add name address
# 删除远程主机
git remote rm name
# 远程主机改名
git remote rename name new-name
git fetch
# 获取远程版本库的更新
git fetch 远程主机名
# 取回特定分支的更新
git fetch 远程主机名 分支名
git pull
获取远程仓库某个分支的更新,并且与本地的指定分支合并(fetch+merge)
git pull 远程主机 分支:本地分支
# 例如
git pull origin nn:master
# 合并到当前分支
git pull 远程主机 nn
# 如果本地当前分支追踪远程分支
git pull 远程主机
# 只追踪一个分支,不会主动删除被远程仓库删掉的分支
git pull
# -p删除被远程仓库删掉的分支
git pull -p
git push
将本地分支的更新推送给远程主机,与git pull相对应
git push 远程主机名 本地分支:远程分支
# 删除远程分支
git push origin :master
git push origin --delete master
# 当前分支与远程分支存在追踪关系
git push origin
# 只有一个追踪分支
git push
# 如果当前分支与多个主机存在追踪关系,利用-u设置默认主机,以后就可以直接用git push
git push -u origin master
总结
我所用到的git知识就上面这些了,还是比较容易理解的,多实战!只要从事计算机方面的工作,这项技能还是必不可少的。