git基本使用 详解
目录
1.版本控制
版本控制(Revision control)是一种记录一个或若干文件内容变化,以便将来查阅特定版本修订情况的系统。
简单就是记录一个文件来自不同人不同时间的所有修订版本。
反过来说,假如没有版本控制的情况是什么样?你需要复制整个项目目录的方式来保存不同的版本,还需要改名加上备份时间以示区别,整个过程占用空间还存在很多丢失风险。
一个真正的版本控制系统,不止记录,还会有包括协同、追踪、恢复、统计更多的功能。
版本控制系统分类
版本控制系统主要分成本地版本控制、集中版本控制、分布版本控制三种。
本地版本主要思想就是将更新保存为特殊的补丁(Patch:文件补丁是一种特定格式的文本文件,记录着对应文件修订前后的内容变化).
本地考虑的就一个人的工作,那么多人同时协作的问题怎么解决?
- 集*暖的CVCS,比如CVS、Subversion、Perforce。主要思想是使用单一的集中管理的服务器,保存所有文件的修订版本,而协同工作的人们都通过客户端连到这台服务器,取出最新的文件或者提交更新。
- 分而治之的DVCS,比如Git,Mercurial,Bazaar,Darcs。主要思想是客户端并不只提取最新版本的文件快照,而是把代码仓库完整地镜像下来。
两者的其实各有好处,CVCS集中利于管理,DVCS分治隔离*。
2.什么是git
Git是目前世界上最先进的分布式版本控制系统。其实git各种命令只是表象,真正需要理解的是git的工作原理和特点,这才是我觉得最重要的。
git的工作原理和特点
- 直接记录快照,而非差异比较。git并不是像cvs这类系统在想去记录哪些文件更新了什么内容,而是直接做了一个文件内容管理系统,文件变化了,其指纹也会变化,对指纹发生变化的文件做快照。
- 近乎所有操作都是本地执行。在 Git 中的绝大多数操作都只需要访问本地文件和资源,不用连网。但如果用 CVCS 的话,差不多所有操作都需要连接网络。因为 Git 在本地磁盘上就保存着所有当前项目的历史更新,所以处理起来速度飞快。
- 时刻保持数据完整性。在保存到 Git 之前,所有数据都要进行内容的校验和(checksum)计算,并将此结果作为数据的唯一标识和索引。换句话说,不可能在你修改了文件或目录之后,Git 一无所知。这项特性作为 Git 的设计哲学,建在整体架构的最底层。所以如果文件在传输时变得不完整,或者磁盘损坏导致文件数据缺失,Git 都能立即察觉。
- 多数操作仅添加数据。Git 操作大多仅仅是把数据添加到数据库,也就是git的文件系统。所以你删除一个文件并且更新了以后其实并没有删除git文件系统的对应的文件,非常安心。
- 文件的三种状态。对于任何一个文件,在 Git 内都只有三种状态:已提交(committed),已修改(modified)和已暂存(staged)。已提交表示该文件已经被安全地保存在本地数据库中了;已修改表示修改了某个文件,但还没有提交保存;已暂存表示把已修改的文件放在下次提交时要保存的清单中。由此我们看到 Git 管理项目时,文件流转的三个工作区域:Git 的工作目录,暂存区域,以及本地仓库。
git配置
git安装好之后,需要进行配置。配置主要实在3个地方进行:
- /etc/gitconfig 文件:系统中对所有用户都普遍适用的配置。若使用 git config 时用 --system 选项,读写的就是这个文件。
- ~/.gitconfig 文件:用户目录下的配置文件只适用于该用户。若使用 git config 时用 --global 选项,读写的就是这个文件。
- 当前项目的 Git 目录中的配置文件(也就是工作目录中的 .git/config 文件):这里的配置仅仅针对当前项目有效。每一个级别的配置都会覆盖上层的相同配置,所以 .git/config 里的配置会覆盖 /etc/gitconfig 中的同名变量。
此外ssh配置github这种也要注意。
git配置相关命令
git config -l 查看所有配置
git config --local --list 查看当前仓库配置信息
git config --global user.name xxx 设置user.name为xxx
git config --global user.email aaa@qq.com 设置user.email为aaa@qq.com
git config --unset user.name 删除配置
git 获取帮助
git help <verb>
git <verb> --help
man git-<verb>
3.git基础命令
git文件生命周期
工作目录下面的所有文件都不外乎这两种状态:已跟踪(unmodified、modified、staged)或未跟踪(untracked)。
git基础命令
git init 在工作目录中初始化新仓库
git add [file] 告诉 Git 开始对这些文件进行跟踪,然后提交
git add -i Git就进入了一个交互式的shell模式
git clone [url] url可以是https协议,git协议,以及ssh传输协议
git status 确定哪些文件当前处于什么状态
git diff 查看尚未暂存的文件更新了哪些部分
git diff --staged 已经暂存起来的文件和上次提交时的快照之间的差异
git commit 会启动shell的默认文本编辑器以便输入本次提交的说明
git commit -m "message" 在一行命令中提交更新
git commit -a -m 'added new benchmarks' -a选项自动把所有已经跟踪过的文件暂存起来一并提交
git rm 从已跟踪文件清单中移除(确切地说,是从暂存区域移除),然后提交。可以用 git rm 命令完成此项工作,并连带从工作目录中删除指定的文件,这样以后就不会出现在未跟踪文件清单中了。
git mv git改名,相当于 1. mv README.txt README 2. git rm README.txt 3. git add README
.gitignore 文件列出要忽略的文件模式,不会被跟踪
git历史相关命令
git log 会按提交时间列出所有的更新,最近的更新排在最上面
git log --pretty=oneline 将每个提交放在一行显示,这在提交数很大时非常有用
git撤销命令
git commit --amend 提交完了才发现漏掉了几个文件没有加,或者提交信息写错了
git checkout -- [file] 抛弃文件修改的命令,文件已经恢复到修改前的版本,危险命令
git reset HEAD [file] 取消暂存区域中的文件,不会把修改抹除
git远程仓库命令
git remote -v 查看当前的远程库
git remote add [shortname] [url] 添加一个新的远程仓库,可以指定一个简单的名字,以便将来引用
git fetch [remote-name] 从远程仓库抓取数据到FETCH_HEAD
git remote show [remote-name] 查看远程仓库信息
git push [remote-name] [branch-name] 推送数据到远程仓库
git remote rename pb paul 修改某个远程仓库在本地的简称
git remote rm paul 移除对应的远端仓库
git标签命令
同大多数 VCS 一样,Git 也可以对某一时间点上的版本打上标签。人们在发布某个软件版本(比如 v1.0 等等)的时候,经常这么做。
git tag 列显已有的标签
git tag -l 'v1.4.2.*' 用特定的搜索模式列出符合条件的标签
git tag -a v1.4 -m 'my version 1.4' 创建一个含附注类型的标签非常简单,用 -a (译注:取 annotated 的首字母)指定标签名字即可
git show v1.4 查看相应标签的版本信息,并连同显示打标签时的提交对象
git tag -s v1.5 -m 'my signed 1.5 tag' 签署标签,用 GPG 来签署标签,只需要把之前的 -a 改为 -s (signed
git tag v1.4-lw 轻量级标签实际上就是一个保存着对应提交对象的校验和信息的文件
git push origin v1.5 默认情况下,git push 并不会把标签传送到远端服务器上,只有通过显式命令才能分享标签到远端仓库。
git push origin --tags 一次推送所有本地新增的标签上去,可以使用 --tags 选项
git小窍门
git补全 两次tab
git设置别名 git config --global alias.co checkout checkout==co
git config --global alias.last 'log -1 HEAD' 要看最后一次的提交信息
4.Git 分支操作
Git 的分支模型称为“必杀技特性”,Git 的分支可谓是难以置信的轻量级,它的新建操作几乎可以在瞬间完成,并且在不同分支间切换起来也差不多一样快。
git分支真正使得Git强大而独特,我们使用Git主要是利用git分支。
Git 中的分支,其实本质上仅仅是个指向 commit 对象的可变指针。Git 会使用 master 作为分支的默认名字。
分支其实就是从某个提交对象往回看的历史,对于1个master分支来说,每次提交都是包含上次提交的分支的指针。
Head指针可以理解为当前分支的别名。
在commit对象创建分支指针的命令:git branch
例如本来在master分支,那Head指针现在是在master指针的commit对象上,执行git branch testing,创建一个testing分支指向原来master指向的分支,注意head此时仍然在master。
要切换到其他分支,可以执行 git checkout 命令.
git checkout testing,这样HEAD 就指向了 testing 分支,在没有新的commit时,testing分支和master分支指的是同一个commit对象。当testing提交新的commit时,而master分支也有了新的commit时,可能会出现下面这种情况,等到时机成熟的时候,再将两者进行合并。由于每个指针会记录父指针的信息,所以合并时非常容易实现的。

由于一些原因新建了一个hotfix的紧急修复的分支,hotfix 完成了历史使命以后可以删掉。使用 git branch 的 -d 选项执行删除操作。 git branch -d hotfix
分支进行合并,使用git merge 命令,git checkout master 然后 git merge iss53,如果有冲突,Git 会在有冲突的文件里加入标准的冲突解决标记,可以通过它们来手工定位并解决这些冲突。一般用“ ======= ”隔开两个分支。
git远程分支
远程分支(remote branch)是对远程仓库中的分支的索引。它们是一些无法移动的本地分支;只有在 Git 进行网络交互时才会更新。远程分支就像是书签,提醒着你上次连接远程仓库时上面各分支的位置。
我们用 (远程仓库名)/(分支名) 这样的形式表示远程分支。如果你没有设置remote名称,Git 会自动为你将此远程仓库命名为 origin,并下载其中所有的数据,建立一个指向它的 master 分支的指针,在本地命名为 origin/master,但你无法在本地更改其数据。接着,Git 建立一个属于你自己的本地 master 分支,始于 origin 上 master 分支相同的位置,你可以就此开始工作。
如果你在本地 master 分支做了些改动,与此同时,其他人向 git.ourcompany.com 推送了他们的更新,那么服务器上的 master 分支就会向前推进,而与此同时,你在本地的提交历史正朝向不同方向发展。不过只要你不和服务器通讯,你的 origin/master 指针仍然保持原位不会移动。
git fetch origin 来同步远程服务器上的数据到本地。该命令首先找到 origin 是哪个服务器(例如github.com),从上面获取你尚未拥有的数据,更新你本地的数据库,然后把 origin/master 的指针移到它最新的位置上,
git push (远程仓库名) (分支名)
git merge origin/serverfix 把该远程分支的内容合并到当前分支
git checkout -b serverfix origin/serverfix 从远程分支的基础上分化出一个新的分支
git checkout --track origin/serverfix 和上面-b效果一些
git push [远程名] :[分支名] 删除远程分支
从远程分支 checkout 出来的本地分支,称为 跟踪分支 (tracking branch)。跟踪分支是一种和某个远程分支有直接联系的本地分支。在跟踪分支里输入 git push,Git 会自行推断应该向哪个服务器的哪个分支推送数据。同样,在这些分支里运行 git pull 会获取所有远程索引,并把它们的数据都合并到本地分支中来。
在克隆仓库时,Git 通常会自动创建一个名为 master 的分支来跟踪 origin/master。这正是 git push 和 git pull 一开始就能正常工作的原因。当然,你可以随心所欲地设定为其它跟踪分支,比如 origin 上除了 master 之外的其它分支。刚才我们已经看到了这样的一个例子:git checkout -b [分支名] [远程名]/[分支名]。
分支变基——rebase
把一个分支中的修改整合到另一个分支的办法有两种:merge 和 rebase。
merge命令是把两个分支最新的快照(C3 和 C4)以及二者最新的共同祖先(C2)进行三方合并,合并的结果是产生一个新的提交对象(C5)。

还有另外一种选择,可以把在 C3 里产生的变化补丁在 C4 的基础上重新打一遍,在 Git 里,这种操作叫做变基(rebase)。有了 rebase 命令,就可以把在一个分支里提交的改变移到另一个分支里重放一遍。

rebase的好处
现在的 C3' 对应的快照,其实和普通的三方合并,即上个例子中的 C5 对应的快照内容一模一样了。虽然最后整合得到的结果没有任何区别,但变基能产生一个更为整洁的提交历史。如果视察一个变基过的分支的历史记录,看起来会更清楚:仿佛所有修改都是在一根线上先后进行的,尽管实际上它们原本是同时并行发生的。
合并结果中最后一次提交所指向的快照,无论是通过变基,还是三方合并,都会得到相同的快照内容,只不过提交历史不同罢了。变基是按照每行的修改次序重演一遍修改,而合并是把最终结果合在一起。
git rebase [主分支] [特性分支] 先取出特性分支,然后在主分支上重演。
rebase的隐患
一旦分支中的提交对象发布到公共仓库,就千万不要对该分支进行变基操作,如下图。

当开发C6的人首先把C4变基到master得到C4'时,由于你基于C6开发,更新最新代码时,此时变基产生的提交对象 C4' 的 SHA-1 校验值和之前 C4 完全不同,于是merge得到的C8将会同时包含 C4 和 C4',两者有着不同的 SHA-1 校验值,如果用 git log 查看历史,会看到两个提交拥有相同的作者日期与说明,令人费解。而更糟的是,当你把这样的历史推送到服务器后,会再次把这些变基后的提交引入到*服务器,进一步困扰其他人。
5.其他git命令
衍合与挑拣——cherry-pick
一些维护者更喜欢衍合或者挑拣贡献者的代码,而不是简单的合并,因为这样能够保持线性的提交历史。如果你完成了一个特性的开发,并决定将它引入到主干代码中,你可以转到那个特性分支然后执行衍合命令,好在你的主干分支上(也可能是develop分支之类的)重新提交这些修改。如果这些代码工作得很好,你就可以快进master分支,得到一个线性的提交历史。
git cherry-pick e43a6fd3e94888d76779ad79fb568ed180e5fcdf 拉取e43a6到你的主干分支

代码压缩——archive
git archive master --prefix='project/' | gzip > git describe master
.tar.gz 将代码的压缩包归档,方便那些可怜的还没有使用Git的人们。
制作简报——shortlog
git shortlog --no-merges master --not v1.0.1 自从v1.0.1版本以来的所有提交的简介,内容按照作者分组
储藏——stash
经常有这样的事情发生,当你正在进行项目中某一部分的工作,里面的东西处于一个比较杂乱的状态,而你想转到其他分支上进行一些工作。问题是,你不想提交进行了一半的工作,否则以后你无法回到这个工作点。解决这个问题的办法就是git stash命令。
“储藏”可以获取你工作目录的中间状态——也就是你修改过的被追踪的文件和暂存的变更——并将它保存到一个未完结变更的堆栈中,随时可以重新应用。
比如,现在你的分支里还有变更,你想切换分支,但是你还不想提交你正在进行中的工作;所以你储藏这些变更。为了往堆栈推送一个新的储藏,只要运行 git stash,在使用git status会出现nothing to commit, working directory clean。
git stash list 查看现有的储藏
git stash apply 重新应用你刚刚实施的储藏,默认最近
git stash apply aaa@qq.com{2} 通过名字指定应用某个储藏
git stash apply --index 告诉命令重新应用被暂存的变更
git stash drop aaa@qq.com{0} 移除储藏的内容
git stash show -p aaa@qq.com{0} | git apply -R 应用了储藏之后想要取消
git stash branch testchanges 从储藏中创建分支,这是一个很棒的捷径来恢复储藏的工作然后在新的分支上继续当时的工作