前言
发展历史
- 本地版本控制系统(Version Control Systems,简称VCS)
-
大多都是采用某种简单的数据库来记录文件的历次更新差异
-
最流行的:
RCS
- 工作原理是在硬盘上保存补丁集(补丁是指文件修订前后的变化);通过应用所有的补丁,可以重新计算出各个版本的文件内容。”
-
- 集中化版本控制系统(Centralized Version Control Systems,简称 CVCS )
- 诸如CVS、Subversion以及perforce等
- 容易单点故障、丢失所有历史更新记录的风险
- 分布式版本控制系统(Distributed Version Control System,简称 DVCS)
- 像Git、Mercurial、Bazaar以及Darcs
- 客户端并不只提取最新版本的文件快照, 而是把代码仓库完整地镜像下来,包括完整的历史记录
- 大部分系统以文件变更列表的方式存储信息,这类系统(CVS、Subversion、Perforce、Bazaar等)将他们存储的信息看作是一组基本文件和每个文件随时间逐步累积的差异(**基于差异[delta-based]**的版本控制)
- Git 更像是把数据看作是对小型文件系统的一系列快照。 在 Git 中,每当你提交更新或保存项目状态时,它基本上就会对当时的全部文件创建一个快照并保存这个快照的索引。 为了效率,如果文件没有修改,Git 不再重新存储该文件,而是只保留一个链接指向之前存储的文件。 Git 对待数据更像是一个 快照流
git:分布式版本控制系统,c语言开发
免费集中式版本控制:CVS、SVN — 速度慢,且需要联网
Git
电子书:progit_v2.1.54
简介
git校验和机制:SHA-1散列,这是一个由 40 个十六进制字符(0-9 和 a-f)组成的字符串,基于 Git 中文件的内容或目录结构计算出来
三种状态
- 已提交(committed):表示数据已经安全地保存在本地数据库中
- 已修改(modified):表示修改了文件,但还没保存到数据库中
- 已暂存(staged):表示对一个已修改文件的当前版本做了标记,使之包含在下次提交的快照中
- 工作区:对项目的某个版本独立提取出来的内容。 这些从 Git 仓库的压缩数据库中提取出来的文件,放在磁盘上供你使用或修改
- 暂存区:是一个文件,保存了下次将要提交的文件列表信息,一般在 Git 仓库目录中。 按照 Git 的术语叫做“索引”,不过一般说法还是叫“暂存区
- git目录:Git 用来保存项目的元数据和对象数据库的地方。 这是 Git 中最重要的部分,从其它计算机克隆仓库时,复制的就是这里的数据
工作流程
- 在工作区中修改文件。
- 将你想要下次提交的更改选择性地暂存,这样只会将更改的部分添加到暂存区。
- 提交更新,找到暂存区的文件,将快照永久性存储到 Git 目录。
安装
$ sudo apt-get install git-all
# 查看版本
$ git --version
工具目录结构
- git自带一个
git config
的工具来帮助设置,这些变量存储在三个不同的位置
- /etc/gitconfig 文件:包含系统上每一个用户及他们仓库的通用配置。 如果在执行 git config 时带上 --system 选项,那么它就会读写该文件中的配置变量。 (由于它是系统配置文件,因此你需要管理员或超级用户权限来修改它。)
- ~/.gitconfig 或 ~/.config/git/config 文件:只针对当前用户。 你可以传递 --global 选项让 Git 读写此文件,这会对你系统上所有的仓库生效
- 当前使用仓库的 Git 目录中的 config 文件(即 .git/config):针对该仓库。 你可以传递 --local 选项让 Git 强制读写此文件,虽然默认情况下用的就是它。。 (当然,你需要进入某个 Git 仓库中才能让该选项生效。)
- 每一个级别会覆盖上一级别的配置,所以 .git/config 的配置变量会覆盖 /etc/gitconfig 中的配置变量
$ git config --list --show-origin # 查看所有配置以及他们所在文件
# 配置用户信息
$ git config --global user.name "Weijieyang"
$ git config --global user.email "name@mail.com"
# git config 选项列表
* --list 列出所有 Git 当时能找到的配置
* --global 配置所有的项目
- git工作目录下每一个文件只有两种状态
- 已跟踪:指那些被纳入了版本控制的文件,在上一次快照中有它们的记录。即git已经知道的文件
- 未跟踪:其他文件
操作
版本库(Repository)
工作区有一个隐藏目录.git
,这个不算工作区,而是Git的版本库。
Git的版本库里存了很多东西,其中最重要的就是称为stage(或者叫index)的暂存区,还有Git为我们自动创建的第一个分支master
,以及指向master
的一个指针叫HEAD
。
- 创建版本库
$ git init
- 到需要版本管理的目录下
- 初始化后会在目录下形成一个
.git
文件,此文件为隐藏文件,需要使用命令ls -a
查看
- 使用步骤
$ git add file
- 添加本地修改后的文件到暂存区
$ git status
* -s/--short 更为紧凑的输出展示效果
- 时刻掌握仓库当前情况,查看未被提交的修改,包括工作区和暂存区
$ git commit -m '注释'
- 将暂存区的修改提交到版本库中
$ git commit -m '注释'
$ git add filemore
$ git commit --amend
- 第一次提交后发现漏了几个文件,可以使用
--amend
添加,此次提交将会替代上一次的提交,上一次的提交将不会出现在版本历史中ß
- 版本管理
$ git log
* —-pretty=onelines pretty可以指定不同于默认格式的方式展示历史
* onelines 输出消息更简洁,最前面是每个版本的commit id(用SHA1计算)
* format:"%h - %an, %ar : %s" 定制记录的显示格式
* 还有short、full和fuller
* -p/--patch 显示每次提交所引入的差异(按 补丁 的格式输出)
* --stat 每次提交的简略统计信息
* --shortstat 只显示 --stat 中最后的行数修改添加移除统计
* --name-only 仅在提交信息后显示已修改的文件清单
* --name-status 显示新增、修改、删除的文件清单
* --graph 查看分支合并图,在日志旁以 ASCII 图形显示分支与合并历
* --abbrev-commit 查看详细的合并信息,仅显示 SHA-1 校验和所有 40 个字符中的前几个字符
* --relative-data 使用较短的相对时间而不是完整格式显示日期(比如“2 weeks ago”)
- 显示从最近到最远的提交日志
- format格式设置:
$ git last
- 查看最后一次提交
取消暂存 reset
$ git reset --hard HEAD^
- 回退到前一个版本
HEAD
:相当于一个指针,指向master
,而master
指向提交^
:是上一个版本,^^
:前两个的版本,HEAD~100
:往上100个版本
$ git reset --hard commitId
- 回退到版本号为
commitId
的版本 - 注意:
--hard
是一个危险的选项
$ git rerset HEAD <file>
- 撤销暂存区(unstage)的修改回退到工作区
$ git reflog
e475afc HEAD@{1}: reset: moving to HEAD^
1094adb (HEAD -> master) HEAD@{2}: commit: append GPL
e475afc HEAD@{3}: commit: add distributed
eaadf4e HEAD@{4}: commit (initial): wrote a readme file
- 记录了你的每一次命令,可以用来找已经撤销掉的版本id
- 其他操作
$ git diff file
- 查看修改内容,
diff
均是未提交的内容
$ git diff --staged
# 查看已暂存文件和最后一次提交的文件差异
$ git diff --cached
# 查看已经暂存起来的变化(--staged和--cached是同义词)
$ git diff HEAD -- file
# 查看工作区和版本库里面最新版本的区别
撤销操作 checkout
$ git checkout -- <file>
- 撤销上一次修改,回到最近一次git commit或者git add状态,不管是在工作区还是在暂存区
- 场景1:当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令
git checkout -- file
。 - 场景2:当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,想丢弃修改,分两步,第一步用命令
git reset HEAD <file>
,就回到了场景1,第二步按场景1操作。 - 场景3:已经提交了不合适的修改到版本库时,想要撤销本次提交,参考版本回退一节,不过前提是没有推送到远程库。
- 场景1:当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令
删除文件 rm
$ rm file
- 通过rm删除了文件之后,status会告诉删除信息,因为删除也算是修改操作(deleted: file)
- 在本地目录中删除文件之后可以选择彻底删除或者使用git恢复
- 彻底删除:
$ git rm file
— 在暂存区中删除,$ git commit
— 确定要在版本库中删除- (如果一个文件已经被提交到版本库,那么你永远不用担心误删,但是要小心,你只能恢复文件到最新版本,你会丢失最近一次提交后你修改的内容。)
- 恢复:
$ git checkout --file
- 其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。
- 彻底删除:
改名操作 mv
$ git mv old new
---- 相当于 ----
$ mv old new
$ git rm old
$ git add new
忽略文件
一般我们总会有些文件无需纳入 Git 的管理,也不希望它们总出现在未跟踪文件列表。通常都是些自动生成的文件,比如日志文件,或者编译过程中创建的临时文件等。在这种情况下,我们可以创建一个名为 .gitignore 的文件,列出要忽略的文件的模式
$ cat .gitignore
*~ # 忽略所有名字以~结尾的文件(通常是副本文件)
*.[oa] # 忽略以.o .a 结尾的文件
!lib.a. # 忽略.a文件,但跟踪所有 lib.a 文件
/TODO # 只忽略当前目录下的 TODO 文件
build/ # 忽略任何目录下名为 build 的文件夹
doc/*.txt #忽略doc下的txt,但不忽略doc/server/a.txt
doc/**/*.pdf #忽略doc及其子目录下的所有pdf文件
格式规范:
- 所有空行或者以 # 开头的行都会被 Git 忽略。
- 可以使用标准的 glob 模式匹配,它会递归地应用在整个工作区中。
- 匹配模式可以以(/)开头防止递归。
- 匹配模式可以以(/)结尾指定目录。
- 要忽略指定模式以外的文件或目录,可以在模式前加上叹号(!)取反。
远程仓库(github)
- 创建电脑用户自己的密钥
$ ssh-keygen -t rsa -C "youremail@example.com"
* -t 指定密钥类型,默认是 rsa ,可以省略。
* -C 设置注释文字,比如邮箱。
* -f 指定密钥文件存储文件名.
- 在用户主目录
/home/username
下创建密钥 - 生成
id_rsa
和id_rsa.pub
- 绑定电脑用户和github用户
- 将公钥
id_rsa.pub
加载到github网站上Add SSH Key
- 添加远程仓库
$ git remote add origin git@github-link
$ git remote -v
# 显示需要读写远程仓库使用的 Git 保存的简写与其对应的 URL
$ git remote show <remote> # 查看摸一个远程仓库的更多信息
$ git remote rename old new # 给远程仓库改名
$ git remote remove name # 删除远程仓库,及所有跟踪分支和配置信息
- 链接本地仓库和github上创建的远程仓库
origin
为远程仓库的默认名字,可自行更改
$ git push
$ git push -u origin mymaster:master
- 将本地仓库的内容推送到远程仓库上,实际上是把当前分支master推送到远程
-u
:如果当前分支与多个主机存在追踪关系,则可以使用 -u 参数指定一个默认主机,这样后面就可以不加任何参数使用git push
- 不指定
mymaster
:将会删除远程的master
分支,这相当于是push了一个空分支到master
- 不指定
:master
:默认推送到与mymaster
有追踪关系的分支,一般为同名分支,没有的话会在远程建一个(git remote show <origin>
查看各个分支的追踪关系)
- 不指定
$ git pull origin master:mymaster
- 提交远程之前,最好先将远程仓库的文件拉取下来,再
push
,以免造成冲突
$ git fetch <remote> <remote branchname>:<your branchname>
# 不指定远程分支将会默认为‘default’分支(一般为master)
# 不给本地分支命名的话,会自动默认指针 FETCH_HEAD
# 或者使用远程分支名 origin/master 指定fetch的分支
- 他与pull一样,就拉取远程仓库中有但你没有的文件(不要拉取到当前所在分支)
- 不同的是,fetch并不会自动合并或修改你当前的工作,更安全。 当准备好时你必须手动将其合并入你的工作。
- pull = fetch + merge (FETCH_HEAD)
$ git fetch origin
$ git merge origin/'default'
# 拉取远程默认分支到本地当前分支上,再与远程的默认分支合并
$ git fetch origin master:mymaster-tmp
$ git merge mymaster-tmp
# 建议建一个专用存放fetch的分支tmp,拉去下来文件后,再与myaster来merge
FETCH_HEAD
- 某个branch在服务器上的最新状态.
- 每一个执行过fetch操作的项目都会存在一个FETCH_HEAD列表,这个列表保存在
.git/FETCH_HEAD
文件中, 其中每一行对应于远程服务器的一个分支.当前分支指向的FETCH_HEAD, 就是这个文件第一行对应的那个分支 - 一般来说, 存在两种情况:
- 如果没有显式的指定
远程分支
, 则远程分支的master
将作为默认的FETCH_HEAD
. - 如果指定了
远程分支
, 就将这个远程分支作为FETCH_HEAD
.
- 如果没有显式的指定
–rebase
- 作用是取消掉本地库中刚刚的commit,并把他们接到更新后的版本库之中。
- 克隆远程仓库
$ git clone git@github-link
- 如果远程仓库已有文件,可以将其克隆到本地的空仓库中
分支管理
master
:主分支
HEAD
:指向当前分支,即指向master
原理
-
新建
dev
分支:现在修改东西就是dev
指针移动了,而HEAD
指向当前的dev
分支- 此时修改
dev
分支,master
分支将不会改变
- 此时修改
-
合并分支:改改指针的问题
- 将
dev
分支合并到master
上,右图为不使用Fast-Forward
快进模式时(–no-ff)
- 将
-
删除分支:将dev指针给删掉就行了
操作
- 创建分支:
$ git checkout (-b) dev
* -b 创建并切换分支
-------------------------
$ git switch (-c) dev
* -c 创建并切换分支
相当于:
$ git branch dev //创建分支
$ git checkout/switch dev //切换分支
$ git branch
* dev
master
- 查看当前分支,当前分支上会加上
*
$ git branch -d dev
- 删除分支dev
$ git branch
* -vv 将所有的本地分支列出来并且包含更多的信息
* -r 查看远程所有分支
* -a 查看本地和远程的所有分支
* <branchname> 新建分支
* -d -r <branchname> 删除远程分支,删除后还需要推送到服务器
* -m old new 重命名分支
$ git branch --set-upstream-to origin/branchname
$ git branch --set-upstream-to=origin/branchname branchname
- 修改
pull
时的远程分支关联- upstream:将当前分支推送到其上游分支( tracking >是不推荐的同义词对于上游)
- current:将当前分支推送到同名分支
$ git merge dev
$ git merge --no-ff -m "merge with no-ff” dev
-
合并分支到当前分支(如:将
dev
上的工作结果合并到master
上) -
注:两个分支分别都修改了文件的冲突情况下,需要进行手动更改冲突
- 一般合并分支会在
Fast Forward
模式,但此模式下删除分支,会丢失掉分支信息; - 如果要强制禁用
Fast Forward
模式,Git就会在merge
时生成一个新的commit
,这样,从分支历史上就可以看出分支信息。 --no-ff
方式的git merge:看的出原来发生过合并,而merge看不出
- 一般合并分支会在
-
分支策略:
- master:最稳定的,用来发布版本;
- dev:不稳定,所有人将自己的修改合并到这个分支上
修改bug:创建新的分支进行修改,修改后合并到dev上,但是因为commit是一次提交所有的暂存区,所以需要将现在在写的,不需要提交的先用stash指令存储起来
git stash:将当前工作现场存储起来,等以后恢复现场工作后继续使用,此时git status非常的干净
查看:git stash list
恢复:git stash apply - 恢复后不删除stash中的内容,需要用 git stash drop 来删除
git stash pop - 恢复的同时删除
git stash apply stash@{0}:恢复指令的stash
feature分支
每添加一个新功能,最好新建一个feature分支,在上面开发,完成后,合并,最后,删除该feature分
强行删除未合并的分支:git branch -D
标签设置
$ git tag
# 列出标签
$ git tag -l "v1.8.5"
# -l/-list 指定需要某些标签,如上只输出 v1.8.5 系列的
标签类型
- 轻量标签(lightweight):像一个不会改变的分支,它只是某个提交的引用
- 附注标签(annotated):是存储在git数据库的一个完整对象,它可以被校验,其中包含打标签者的名字、电子邮件、地址、日期时间,此外还有一个标签信息,并且可以使用GNU Privacy Guard(GPG)签名并验证
创建标签
$ git tag -a v1.4 -m "指定一条标签信息"
# 轻量只用指定标签名字
$ git tag v1.4
# 给历史版本打标签
$ git tag -a v1.4 commitId
$ git show v1.4
- 查看标签信息和与之对应的提交信息,如果是轻量标签则只会有提交信息
删除标签
$ git tag -d v1.4
$ git push origin :refs/tags/v1.4
- 本地删除标签后并不会在远程仓库移除,需要执行第二条指令
- 将冒号前面的空值推送到远程标签名,从而高效地删除它
- 或者使用下面的方式更直观的删除
$ git push origin --delete <tagname>
共享标签
$ git push origin v1.4
$ git push origin --tags
-
在创建完标签后你必须显式地推送标签到共享服务器上
-
–tags:这将会把所有不在远程仓库服务器上的标签全部传送到那里
检出标签
$ git checkout 2.0.0
- 查看某个标签所指向的文件版本,但这会是你的仓库处于“分离头指针(detached HEAD)”的状态
- 此状态如果你做了某些更改然后提交它们,标签不会发生变化, 但你的新提交将不属于任何分支,并且将无法访问,除非通过确切的提交哈希才能访问。 因此,如果你需要进行更改,比如你要修复旧版本中的错误,那么通常需要创建一个新分支
$ git checkout -b version2 v2.0.0
- 此状态如果你做了某些更改然后提交它们,标签不会发生变化, 但你的新提交将不属于任何分支,并且将无法访问,除非通过确切的提交哈希才能访问。 因此,如果你需要进行更改,比如你要修复旧版本中的错误,那么通常需要创建一个新分支