Git 入门
学习来源:李二狗;敏捷的水
前言:可能你也不想看概念,或者看了也不明白,问题不大,你就一直看,带着所有疑问,看完之后就会大概就会有一个基础的了解。
1. 版本控制系统
1.1 什么是版本控制
- 定义:通俗来说,就是一种记录一个或若干文件内容变化,以便来查阅特定版本修订情况的系统;
- 目的:
- 协同合作:
- 试想一下,如果没有版本控制系统,当你需要处理那些共享文件夹中的文件时,你必须告知办公室里的所有人,你正在对哪些文件进行编辑;
- 与此同时,其他人必须要避免与操作相同的文件。
- 这是一个不现实和完全错误的流程。
- 当你花了很长时间完成你的编辑后,可能这些文件早已经被团队里的其他开发成员修改或者删除了。
- 使用了版本控制系统,每一个团队成员都可以在任何时间对任何文件毫无顾虑的进行修改,版本控制系统可以把之后所有的改动合并成一个共同的版本,不论是一个文件还是整个项目。这个共同的中心平台就是版本控制系统。
- 版本存储:
- 每一个版本控制系统仅仅对应一个项目。
- 因此,在你的本地只存在一个版本,那就是这个项目的当前工作版本。
- 除此之外,而其它所有之前的版本和改动都已经被有序地存储在版本控制系统中了。
- 当你需要时,你可以随时来查看之前的任何一个版本,而且还可以得到整个项目的快照。
- 恢复之前的版本:
- 要把一些文件恢复到上次改动之前的版本(甚至整个项目恢复到之前的版本)。
- 如果你确定那些改动是错误的或者是没有必要的,那轻松的点几下你就可以简单地撤销它。
- 了解发生了什么:
- 每当你提交一次对项目新的改动时,你的版本管理系统会要求你添加一个对这次改动的简短描述。
- 除此之外(如果是一个代码或者文本文件),你还可以看到一个改动前和改动后的内容的详细对照。
- 这样也可以帮助你很好地了解版本与版本之间的发展关系。
- 备份:
- 备份是一个分布式版本控制系统(例如 Git)提供的非常好的附带功能。
- 每一个团队成员都会在他的本地有一个完整的项目副本,包括整个项目的历史记录。
- 如果你所依赖的服务器宕机了,或者是你的存储硬盘坏,所有你需要的恢复文件都可以在另外的团队成员的 Git 本地仓库中得到。
- 协同合作:
1.2 版本控制器分类
1.2.1 本地版本控制
- 记录文件每次的更新,可以对每个版本做一个快照,或是记录补丁文件,适合个人用;
- 如
RCS(GNU Revision Control System)
;
1.2.2 集中版本控制
- 所有的文件都保存在服务器上;
- 协同开发者从服务器上同步更新或上传自己的修改所有的版本数据都存储在服务器上;
- 用户的本地只有自己以前所同步的版本;
- 如果不联网,就看不到历史版本;
- 也无法切换版本验证问题,或在不同分支工作;
- 所有数据都保存再单一的服务器上,有很大的风险。
- 代表产品:
- SVN(Subversion) - 集中式的版本控制系统;
- CVS(Concurrent Versions System);
- VSS(Microsoft Visual SourceSafe);
1.2.3 分布式版本控制
- 所有版本信息仓库全部同步到本地的每个用户;
- 这样就可以在本地查看所有版本历史;
- 可以离线在本地提交;
- 只需要在联网是 push 到相应的服务器或者其他用户那里;
- 由于每个用户那里保存的都是所有的版本数据,只要有一个用户的设备没有问题就可以恢复所有数据;
- 但这增加了本地存储空间的占用;
- 如:Git;
2. Git 介绍
2.1 Git 是什么
- Git 是一个开源的分布式版本控制系统;
2.2 为什么使用 Git
- Git 是分布式的,可以带来的好处:
- 工作时不需要联网;
- 分布式版本控制系统根本没有“中央服务器”,每个人的电脑上都是一个完整的版本库,这样,你工作的时候,就不需要联网了,因为版本库就在你自己的电脑上。
- 多个人协作:比方说你在自己电脑上改了文件 A,你的同事也在他的电脑上改了文件 A,这时,你们俩之间只需把各自的修改推送给对方,就可以互相看到对方的修改了。
- 更加安全;
- 集中式版本控制系统,一旦中央服务器出了问题,所有人都无法工作。
- 分布式版本控制系统,每个人电脑中都有完整的版本库,所以某人的机器挂了,并不影响其它人。
- 工作时不需要联网;
2.3 安装
-
Debian/Ubuntu , 安装命令为:
$ apt-get install libcurl4-gnutls-dev libexpat1-dev gettext > libz-dev libssl-dev $ apt-get install git-core $ git --version git version 1.8.1.2
-
Centos/RedHat ,安装命令为:
$ yum install curl-devel expat-devel gettext-devel > openssl-devel zlib-devel $ yum -y install git-core $ git --version git version 1.7.1
2.4 配置
- Git 自带一个
git config
的工具来帮助设置控制 Git 外观和行为的配置变量。这些变量存储在三个不同的位置:/etc/gitconfig
文件:- 包含系统上每一个用户及他们仓库的通用配置。
- 如果使用带有
--system
选项的git config
时,它会从此文件读写配置变量;
~/.gitconfig
或~/.config/git/config
文件:- 针对当前用户;
- 可以传递
--global
选项让 Git 读写此文件;
- 当前使用仓库的 Git 目录中
config
文件(.git/config
):- 针对该仓库;
- 每一个级别覆盖上一级别的配置,所以,
.git/config
的配置变量会覆盖/etc/gitconfig
中的配置变量。
2.5 用户信息
- 当安装完 Git 应该做的第一件事就是设置你的用户名称和邮件地址;
- 因为每一个 Git 的提交都会使用这些信息,并且它会写入你的每一次提交中,不可更改:
git config --global user.name "John Doe"
git config --global user.email johndoe@example.com
2.6 版本库
- 当你一个项目到本地或创建一个 git 项目时,项目目录下会有一个隐藏的
.git
子目录。 - 这个目录是 git 用来跟踪管理版本库的,千万不要手动修改。
2.7 哈希值
- Git 中所有数据在存储前都计算校验和,然后以校验和来引用;
- 这意味着不可能在 Git 不知情时更改任何文件内容或目录目录;
- 这个功能建构在 Git 底层,是 Git 不可或缺的部分;
- Git 用于计算校验和的机制叫做 SHA-1 散列;
- 这是一个由 40 个十六进制字符(0-9、a-f)组成的字符串;
- 基于 Git 中文件的内容或目录结构计算出来;
- Git 数据库中保存的信息都是以文件内容的哈希值来索引,而不是文件名;
2.8 文件状态
- 已修改(modified):表示修改了的文件,但还没保存到数据库中;
- 已暂存(staged):表示对一个已经修改的文件的当前版本做了标记,使之包含在下次提交的快照中;
- 已提交(committed):表示数据已经安全的保存在本地数据库中。
2.9 工作区域
与文件状态对应的,不同状态的文件在 Git 中处于不同的工作区域;
-
工作区(Working):
- 当你
git clone
一个项目到本地,相当于在本地克隆了项目的一个副本; - 工作区是对项目的某个版本独立提取出来的内容;
- 这些从 Git 仓库的压缩数据库中提取出来的文件,放在磁盘上供你使用或修改;
- 当你
-
暂存区(Staging):
- 暂存区是一个文件,保存了下次将要提交的文件列表信息,一般在 Git 仓库目录中;
- 有时候也被称为
索引
。
-
本地仓库(Local):提交更新,找到暂存区域的文件,将快照永久性存储到 Git 本地仓库;
-
远程仓库(Remote):
- 以上几个工作区都在本地;
- 为了让别人看到你的修改,需要将你的更新推送到远程仓库;
- 同理,如果你想同步别人的修改,你需要从远程仓库拉去更新;
2.10 Git 命令
1. 创建仓库
-
克隆一个已经创建的仓库:
# 通过 SSH $ git clone ssh://user@domain.com/repo.git # 通过 HTTP $ git clone http://domain.com/user/repo.git
-
创建一个新的本地仓库:
$ git init
2. 添加修改
-
添加修改到暂存区:
# 把指定文件添加到暂存区 $ git add xxx # 把当前所有修改添加到暂存区 $ git add . # 把所有修改添加到暂存区 $ git add -A
-
提交修改到本地仓库:
# 提交本地的所有修改 $ git commit -a # 提交之前已标记的变化 $ $ git commit # 附加消息提交 $ git commit -m 'commit message'
3. 储藏
-
有时,需要在同一个项目的不同分支上工作;
-
当需要切换分支时,若本地的工作还没有完成,此时,提交修改显得不严谨;
-
但是不提交代码又无法切换分支;
-
使用
git stash
将本地的修改内容作为草稿存储起来; -
官方称为储藏;
# 1. 将修改作为当前分支的草稿保存 $ git stash # 2. 查看草稿列表 $ git stash list stash@{0}: WIP on master: 6fae349 :memo: Writing docs. # 3.1 删除草稿 $ git stash drop stash@{0} # 3.2 读取草稿 $ git stash apply stash@{0}
4. 撤销修改
-
撤销本地修改:
# 移除缓存区的所有文件(i.e. 撤销上次git add) $ git reset HEAD # 将HEAD重置到上一次提交的版本,并将之后的修改标记为未添加到缓存区的修改 $ git reset <commit> # 将HEAD重置到上一次提交的版本,并保留未提交的本地修改 $ git reset --keep <commit> # 放弃工作目录下的所有修改 $ git reset --hard HEAD # 将HEAD重置到指定的版本,并抛弃该版本之后的所有修改 $ git reset --hard <commit-hash> # 用远端分支强制覆盖本地分支 $ git reset --hard <remote/branch> e.g., upstream/master, origin/my-feature # 放弃某个文件的所有本地修改 $ git checkout HEAD <file>
-
删除添加
.gitignore
文件前错误提交的文件:$ git rm -r --cached . $ git add . $ git commit -m "remove xyz file"
-
撤销远程修改(创建一个新的提交,并回滚到指定版本):
$ git revert <commit-hash>
-
彻底删除指定版本:
# 执行下面命令后,commit-hash 提交后的记录都会被彻底删除,使用需谨慎 $ git reset --hard <commit-hash> $ git push -f
5. 更新与推送
-
更新:
# 下载远程端版本,但不合并到HEAD中 $ git fetch <remote> # 将远程端版本合并到本地版本中 $ git pull origin master # 以rebase方式将远端分支与本地合并 $ git pull --rebase <remote> <branch>
-
推送:
# 将本地版本推送到远程端 $ git push remote <remote> <branch> # 删除远程端分支 $ git push <remote> :<branch> (since Git v1.5.0) $ git push <remote> --delete <branch> (since Git v1.7.0) # 发布标签 $ git push --tags
6. 查看信息
-
显示工作路径下已修改的文件:
$ git status
-
显示与上次提交版本文件的不同:
$ git diff
-
显示提交历史:
# 从最新提交开始,显示所有的提交记录(显示hash, 作者信息,提交的标题和时间) $ git log # 显示某个用户的所有提交 $ git log --author="username" # 显示某个文件的所有修改 $ git log -p <file>
-
显示搜索内容:
# 从当前目录的所有文件中查找文本内容 $ git grep "Hello" # 在某一版本中搜索文本 $ git grep "Hello" v2.5
7. 分支
-
增删查分支:
# 列出所有的分支 $ git branch # 列出所有的远端分支 $ git branch -r # 基于当前分支创建新分支 $ git branch <new-branch> # 基于远程分支创建新的可追溯的分支 $ git branch --track <new-branch> <remote-branch> # 删除本地分支 $ git branch -d <branch> # 强制删除本地分支,将会丢失未合并的修改 $ git branch -D <branch>
-
切换分支:
# 切换分支 $ git checkout <branch> # 创建并切换到新分支 $ git checkout -b <branch>
8. 标签
# 给当前版本打标签
$ git tag <tag-name>
# 给当前版本打标签并附加消息
$ git tag -a <tag-name>
9. 合并与重置
-
merge 与 rebase 虽然是 git 常用功能;
-
但是强烈建议不要使用 git 命令来完成这项工作;
-
因为如果出现代码冲突,在没有代码比对工具的情况下,实在太艰难了;
-
可以考虑使用各种 Git GUI 工具。
-
合并:
# 将分支合并到当前HEAD中 $ git merge <branch>
-
重置:
# 将当前HEAD版本重置到分支中,请勿重置已发布的提交 $ git rebase <branch>
2.11 clone 方式
- Git 支持三种协议:
- HTTPS:要求每次 push 时都需要输入用户名、密码;
- SSH:要求生成本地证书,然后在 GitHub 账户中注册。除了第一次配置麻烦,其他还好。
- Git:
- GitHub 支持:
- HTTPS:
- SSH:
1. 生成 SSH 公钥
-
许多 Git 服务器都使用 SSH 公钥进行认证;
-
为了向 Git 服务器提供 SSH 公钥,如果某系统用户尚未拥有密钥,必须事先为其生成一份;
-
首先,需要确认自己是否已经拥有密钥;
-
默认情况下,用户的 SSH 密钥存储在
~/.ssh
目录下; -
进入该目录后并列出其内容:
$ cd ~/.ssh $ ls authorized_keys2 id_dsa known_hosts config id_dsa.pub
-
需要找到一对以
id_dsa
或id_rsa
命名的文件,其中一个带有.pub
扩展名; -
.pub
文件是公钥,另一个则是私钥; -
如果找不到这样的文件(或者没有
.ssh
目录),可以通过运行ssh-keygen
程序创建它们; -
在 Linux/Mac 系统中,
ssh-keygen
随 SSH 软件包提供; -
在 Windows 上,该程序包含于 MSysGit 软件包中;
$ ssh-keygen Generating public/private rsa key pair. Enter file in which to save the key (/home/schacon/.ssh/id_rsa): Created directory '/home/schacon/.ssh'. Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /home/schacon/.ssh/id_rsa. Your public key has been saved in /home/schacon/.ssh/id_rsa.pub. The key fingerprint is: d0:82:24:8e:d7:f1:bb:9b:33:53:96:93:49:da:9b:e3 schacon@mylaptop.local
-
首先
ssh-keygen
会确认密钥的存储位置(默认是.ssh/id_rsa
); -
然后回要求你输入两次密钥口令;
-
若不想在使用密钥时输入口令,将其留空即可。
-
然后,需要将公钥发给任意一个 Git 服务管理员;
-
要做的就是复制
.pub
文件内容,并通过邮件发送; -
公钥看起来是这样的:
$ cat ~/.ssh/id_rsa.pub ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAklOUpkDHrfHY17SbrmTIpNLTGK9Tjom/BWDSU GPl+nafzlHDTYW7hdI4yZ5ew18JH4JW9jbhUFrviQzM7xlELEVf4h9lFX5QVkbPppSwg0cda3 Pbv7kOdJ/MTyBlWXFCR+HAo3FXRitBqxiX1nKhXpHAZsMciLq8V6RjsNAQwdsdMFvSlVK/7XA t3FaoJoAsncM1Q9x5+3V0Ww68/eIFmb1zuUFljQJKprrX88XypNDvjYNby6vw/Pb0rwert/En mZ+AW4OZPnTPI89ZPmVMLuayrD2cE86Z/il8b+gw3r3+1nKatmIkjn2so1d01QraTlMqVSsbx NrRFi9wrf+M7Q== schacon@mylaptop.local
-
在 Github 账户中,依次点击 Settings > SSH and GPG keys > New SSH key;
-
然后,将上面生成的公钥内容黏贴到
Key
编辑框并保存;
2. Git Flow
2.1 定义:
- 对于 Git Flow 的定义,完全没看懂,有人说是一个工作流,有人说是开发模型;
- 现在就单纯的把它当作一个模型来看,估摸着看完后边的内容也会有个大体的了解;
- 英文好的,可以看看创始人发的文章:一种成功的 Git 分支模型
- 大概就是利用 Git 创建和管理分支的能力;
- 为每个分支设定具体特定的含义的名称;
- 并将软件生命周期中的各类活动归并到不同分支上;
- 实现了软件开发过程不同操作的相互隔离;
2.2 用处:
- 解决开发活动混乱的问题:
- 由于源代码在开发过程中的各种冲突导致;
- 将图转了 90° 看着顺眼一点,原图,会在最后上传一个 PDF;
2.3 分支
-
Git Flow 模型中定义了主分支和辅助分支两类;
- 主分支:用于组织与软件开发、部署相关的活动;
- 辅助分支:组织为了解决特定的问题而进行的各种开发活动;
-
主分支是所有开发活动的核心分支;
-
所有开发活动产生的输出物最终都会反应到主分支的代码中;
-
主分支:
- master 分支
- 存放随时可供在生产环境中部署的代码(Production Reday State);
- 当开发活动告一段落,产生了一份新的可供部署的代码时,Master 分支上的代码会被更新;
- 同时,每一个更新,最好添加对应的版本号标签(TAG);
- 下图中 v0.1 和 v0.2 就是 TAG。
- development 分支
- 保存当前最新开发成果的分支;
- 这个分支上的代码也是可进行每日夜间发布的代码(Nightly build);
- 因此,这分支也被称为”Integration Branch“;
- 当 develop 分支上的代码已实现了软件需求说明书中的所有功能,通过了所有测试后,并且代码已经足够稳定时,就可以将所有的开发成果合并回 Master 分支;
- 对于 Master 分支上的新提交的代码都打上TAG,供后续代码跟踪使用;
- 因此,每次将 develop 分支上的代码合并回 master 分支时,我们就可以认为一个新的可供在生产环境中部署的版本就产生了;
- 通常而言:仅在发布新的可供部署的代码时才怪呢更新 master 分支上的代码;
- 基于此,每当代码提交到 master 分支时,我们可以使用 Git Hook 出发软件自动测试以及生产环境代码的自动更新工作;
- 这些自动化操作将有利于减少新代码发布之后的一些事务性工作;
- master 分支
-
辅助分支:
-
辅助分支主要用于组织软件新功能的并行开发、简化;
-
新功能开发代码的跟踪;
-
辅助完成版本发布工作;
-
对生产代码的缺陷紧急修复工作;
-
通常只会在有限的时间范围内存在;
-
通常分为:
-
Feature 分支:用于开发新功能;
- 可以从 develop 分支发起 feature 分支;
- 代码必须合并回 develop 分支;(或者抛弃掉)
- feature 分支永远不会和 master 分支打交道;
- feature 分支命名可以使用除
master
、develop
、release-*
、hotfix-*
之外的任何名称; - 一般而言,feature 分支代码可以保存在开发者自己的代码库中而不强制提交到主代码库中;
-
Release 分支:用于辅助版本发布;
- 可以当作一个“待发布”的分支;
- 它可以从 develop 分支派生;
- 必须合并并回归 develop 分支和 master 分支;
- 分支命名惯例:
release-*
; - release 分支是为了发布新的产品版本而设计的;
- 在这个分支上的代码允许做小的缺憾修正、准备发布版本所需的各项说明信息(版本号、发布时间、编译时间……);
- 这个分支不会添加任何新的特性;
- 通过在 release 分支上进行这些工作可以让 develop 分支空闲褚时健来接受新的 feature 分支上的代码提交,进入新的软件开发迭代周期;
- 当 develop 分支上的代码已经包含了所有即将发布的版本中所有计划包含的软件功能,并通过所有测试时,就可以考虑准备创建 release 分支;
- 而所有在当前即将发布的版本之外的业务需求一定要确保不能混到 release 分支(避免引入一些不可控的系统缺陷);
- 成功的派生了 release 分支,并被赋予版本号之后,develop 分支就可以为下一个版本服务了;
- 下一个版本:当前即将发布的版本之后的版本;
- 版本号可以依据项目定义的版本号命名规则进行;
-
Hotfix 分支:用于修正生产代码中的缺陷;
- 一个项目发布后或多或少会存在一些 bug,而 bug 修复工作并不适合在 develop 上做,是因为:
- develop 分支上包含还未验证过的 feature;
- develop 还不能马上发布,而客户急需这个 bug 的修复;
- Hotfix 分支可以从 master 分支派生;
- 必须合并回 master 分支和 develop 分支;
- 分支命名习惯:
hotfix-*
; - 除了是计划外创建的以外,hotfix 分支与 release 分支十分相似:
- 都可以产生一个新的可供在生产环境中部署的软件版本;
- 生产环境中的软件遇到了异常情况或发现了严重到必须立即修复的软件缺陷的时候;
- 就需要从 master 分支上指定的 TAG 版本中派生 hotfix 分支来组织代码的紧急修复工作;
- 这样做的显而易见的好处就是不会打断正在进行的 develop 分支的开发工作,能够让团队中负责新功能开发的人与负责代码紧急修复的人并行的开展工作;
- 一个项目发布后或多或少会存在一些 bug,而 bug 修复工作并不适合在 develop 上做,是因为:
-
-
2.4 总结
-
从流程上看,大体懂了 Git Flow,应该就是一种软件协同开发时的规范;
-
就像一个蚁群中,各自分工不同,各做各的事情,使得整个团队有序而又整洁的生存;
-
Git Flow 开发模型可以让代码仓库保持整洁;
-
让各个成员开发之间相互隔离;
-
能够有效的便面开发状态中的代码相互影响而导致的效率低下和混乱;
2.5 代码示例
-
Initialize:
git flow init
-
Feature:
-
从 develop 开启一个新的分支:
git flow feature start MYFEATURE
-
完成一个 Feature:
- 一个 feature 分支开发完毕之后,要做以下的事:
- 把 MYFEATURE 合并到 develop;
- 把这个分支干掉;
- 切换回 develop 分支
git flow feature finish FEATURE_NAME
- 一个 feature 分支开发完毕之后,要做以下的事:
-
发布一个 Feature:
- 如果你想要让别人跟你一起开发 MYFEATURE 分支,那就可以把这个分支 push 到服务器上;
git flow feature publish MYFEATURE
-
获得一个已经发布的 Feature:
- 获得别人发布到服务器上的 feature 分支:
git flow feature pull origin MYFEATURE
-
-
Release:
-
开始一个 Release:
- 创建一个 release 分支,派生自 develop 分支;
git flow release start RELEASE
-
发布一个 Release:
git flow release publish RELEASE
-
完成一个 Release:
- 一个 Release 分支结束后,需要做以下工作:
- 把 Release 分支合并回 master;
- 给本次发布打 TAG;
- 同时把 Release 分支合并回 develop;
- 干掉 Release 分支;
git flow release finish RELEASE
将 TAG push 到服务器
git push --tags
- 一个 Release 分支结束后,需要做以下工作:
-
-
Hotfix:
-
开启一个 hotfix 分支:
git flow hotfix start VERSION
-
结束一个 hotfix 分支:
git flow hotfix finish VERSION
-