目录
Git 是一个开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目。
Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。
Git 与常用的版本控制工具 CVS, Subversion 等不同,它采用了分布式版本库的方式,不必服务器端软件支持。区别如下:
1、Git基本概念
要很容易的理解Git的工作模式,就必须要搞清楚几个概念:
- 工作区:电脑里能看到的目录。如下图绿色部分
- 暂存区:英文叫stage或index,一般存放在".git目录下"下的index文件(.git/index)中,所以我们把暂存区有时也叫作索引。如下图蓝色部分中的index区域
- 版本库:工作区有一个隐藏目录.git(这个不算工作区,而是Git的版本库),该目录存放了所有分支的信息,通常我们会在某一个本地分支上进行操作,如下HEAD游标所指向的分支master
当对工作区修改(或新增)的文件执行 "git add" 命令时,暂存区的目录树被更新,同时工作区修改(或新增)的文件内容被写入到对象库(上图蓝色区域的objects,实际位于 ".git/objects" 目录下)中的一个新的对象中,而该对象的ID被记录在暂存区的文件索引中。当执行提交操作(git commit)时,暂存区的目录树写到版本库(对象库)中,master 分支会做相应的更新。
2、Git基本操作
2.1、分支管理
几乎每一种版本控制系统都以某种形式支持分支。使用分支意味着你可以从开发主线上分离开来,然后在不影响主线的同时继续工作。有人把 Git 的分支模型称为必杀技特性,而正是因为它,将 Git 从版本控制系统家族里区分出来。
- 创建分支
$git branch <分支名>
$git branch shen #创建本地分支名shen
- 切换分支
$git checkout <分支名>
$git checkout shen #切换本地分支shen,将当前游标HEAD执行版本库中的本地分支shen
- 查看分支
$git branch -a #查看所有分支(包括远程分支)
$git branch -vv #查看本地分支
* shen #*表示当前所处的本地分支
remotes/szxgit10/HiAndroidX/master #remotes表示该分支为远程分支 其中
#远程主机名szxgit10
#远程分支名HiAndroidX/master
- 删除分支
$git branch -d <分支名>
$git branch -d shen #删除本地分支shen
- 合并分支
$git merge <被合并的本地分支>
$git branch -vv #当前分支为shen
* shen
dpc
$git merge dpc #当前分支与dpc进行合并
- 建立映射
$git branch --set-upstream <本地分支名> <远程分支全名>
$git branch --set-upstream shen remotes/szxgit10/HiAndroidX/master
#关联本地分支shen与远程分支remotes/szxgit10/HiAndroidX/master建立映射,后续就可以直接使用git pull来进行下拉
2.2、基础快照
当创建一个本地分支之后,我们就可以切换到这个本地分支内,来进行代码的修改和提交到本地版本库中(git add/commit),除此之外还能将本地版本库中的代码与远程分支进行同步(git push/pull)。
1)、git log/show/diff
版本库中其实有很多个提交节点,每个节点对应一次代码的修改和提交,且都有一个40位哈希值来标志它的唯一性。
我们可以使用命令git log .来列出当前分支所有的提交节点(这些节点都是按照提交时间顺序进行排列,越在前的提交表示时间越近)。HEAD除了指向当前版本库,其实最准确的说法是HEAD指向当前版本库最近的提交,即指向当前分支的第一个提交节点。
我们还可以使用git show <commit-id>来查看commit-id的提交节点详情,里面依次列出该节点与它上一个节点的区别。
2)、git add
$git status
no branch shen
Your branch is up-to-date with 'szxgit10/HiAndroidX/master'
nothing to commit, working directory clean
#没有什么东西可以提交,工作目录暂存区为空
$vi inc/HiBootvideoUI.h
#修改inc/HiBootvideoUI.h文件
$git status
changes not staged for commit:
modified: inc/HiBootvideoUI.h
#工作区HiBootvideoUI.h文件有修改,但是暂存区没有记录
$git add inc/HiBootvideoUI.h
#将工作区修改更新到暂存区中
$git status
changes to be commit:
modified: inc/HiBootvideoUI.h
#有修改可以进行提交
如上,git add <文件列表>可以将工作区的修改更新到暂存区,因为只有暂存区中记录的修改才可以提交到当前版本库(即工作区的修改不能直接更新到版本库中,只有去暂存区里面过渡一下)。
3)、git rm
上面小节属于我们在工作区新增了某个文件,现在我们想在工作区删除某个文件,并且也从暂存区也删除对应文件的记录,那么就需要使用git rm <需要删除的文件>命令了。如下:
$rm count_bg.png #删除工作区的文件count_bg.png
$git status
changes not staged for commit:
deleted: count_bg.png
#表示工作区文件count_bg.png是删除状态,且暂存区没有该记录所以不能将它提交到版本库
$git rm count_bg.png
#工作区域删除文件count_bg.png的更新到暂存区,即暂存区也将同步该操作删除文件count_bg.png
$git status
changes to be commited:
deleted: count_bg.png
#暂存区中也记录了删除文件count_bg.png的信息,在commit操作后将同步到版本库
4)、git reset
上节知道可以使用git add命令将工作区域的修改文件更新到暂存区域,如果我们不想提交这个文件了,那么就需要将它从暂存区中清掉,那么可以使用git reset <需要恢复的文件>来将其状态进行恢复,即git add/rm的反向操作。
除此之外,git reset有三种模式,上面只是使用了其中的默认mix模式,具体详情请点击。
5)、git commit
将暂存区中的所有记录更新到当前HEAD指向的版本库,即将暂存区中的所有修改提交到当前本地分支,并生成一个commit-id来标志当前的提交节点和所有的修改信息。执行该命令后,暂存区里面的所有记录将被情况,工作区域也将恢复成最初状态,当前版本库(HEAD本地分支)将多出一个commit节点,可以使用git show进行查看。
$git commit #打开vi命令并编辑提交信息,且生成一个新节点
$git commit --amend #打开vi命令可以重写编辑提交信息,但是不会生成新的节点
$git commit --amend --no-edit #不生成新的节点,也不会重写编辑提交信息
需要注意的commit-id与changed-id是不一样的概念。changed-id是用来标准gerrit网页上面的提交唯一性,然而commit-id只是来标志本地分支的提交节点唯一性。他们的用处完全不在同一个频道。
6)、git push
git commit只是将一次修改提交到当前本地版本库,注意是当前的本地版本库,也就是说前面那么多操作其实都是在玩单机游戏没有一点意思。通常开发中还需要将本地版本库的修改上传到远程分支,因此还需要使用git push命令将当前HEAD指向的提交节点同步到远程分支里面。如下:
$git push <远程主机名> <本地分支名/本地分支提交节点>:<远程分支>
$git push szxgit10 shen:refs/for/HiAndroidX/master
$git push szxgit10 HEAD:refs/for/HiAndroidX/master
#其中远程主机名szxgit10
# 远程分支名HiAndroidX/master
#因为当前分支为shen,所以HEAD其实就是指向的shen,即本地分支名可以使用HEAD也可以使用shen
#前面已经明白游标HEAD其实是指向的本地分支最新的提交节点,所以上面命令其实是将当前本地分支的最新commit节点同步到远程分支
$git push szxgit10 ^HEAD:refs/for/HiAndroidX/master #上传本地分支倒数第二个节点到远程分支上
7)、git pull
其实在上传代码到远程分支之前,为了防止冲突,往往需要在push之前做一次pull操作,git pull命令将远程分支的代码同步到本地分支。如下
$git pull <远程主机名> <远程分支名>:<本地分支名>
$git pull szxgit10 HiAndroidX/master:shen
#其中远程主机名szxgit10
# 远程分支名HiAndroidX/master
# 本地分支名shen
前面已经讲过,如果已经建立了映射,那么久无需指定远程分支和本地分支,直接git pull就行。
8)、流程总结
- workspace:工作区
- index:暂存区
- repository:本地分支
- remote:远程分支
2.3、高级快照
1)、git cherry-pick
git cherry-pick用来复制一个commit节点,一般用在什么地方呢?从一些托管网站上下载某个节点,或者因为各种操作(例如git checkout [新分支])把前面某个节点搞丢失了,这个时候只要记得上次的commitid就能够通过该命令找回。
$git cherry-pick [commt-id]
2)、git format-patch
git format-patch用来将当前commit节点向前移动n个位置,生成一个patch补丁文件,注意生成patch文件名是根据当前节点的描述生成,但是后续可以任意修改该patch文件。
$git format-patch HEAD^ #生成最近的1次commit的patch
$git format-patch HEAD^^ #生成最近的2次commit的patch
$git format-patch HEAD^^^ #生成最近的3次commit的patch
$git format-patch HEAD^^^^ #生成最近的4次commit的patch
$git format-patch <r1>..<r2> #生成两个commit间的修改的patch(包含两个commit. <r1>和<r2>都是具体的commit号)
$git format-patch -1 <r1> #生成单个commit的patch
$git format-patch <r1> #生成某commit以来的修改patch(不包含该commit)
$git format-patch --root <r1> #生成从根到r1提交的所有patch
3)、git apply/am
git apply和git am用来将指定patch补丁文件应用到当前目录下,即git format-patch的反操作。
$git apply xxxxx.patch
4)、patch命令
5)、clone命令
git clone拷贝一个Git仓库到本地,让自己能够查看该项目,或者进行修改。命令格式如下:
git clone [url] //url表示链接,可以是各种协议,例如https,或者git等
git clone 命令 | 菜鸟教程
有时候git clone拷贝完成之后发现该除了.git目录之外是空的,这是因为该仓库有很多分支,当前默认指向的分支是空的,所以这个时候需要使用git branch来查看有哪些远程分支,然后git checkout + branch_name进行远程分支的切换和下载,如下图:
3、Git冲突解决
在上传代码的时候经常遇到error或者failed的情况。大多数情况都属于修改冲突引起的,通常都有三种情况,详情请点击我,这里只说明已经有commit的情况。
我通常遇到两种情况:A 本地辛辛苦苦作好了提交,准备兴高采烈的进行git push的时候却犹如泼了一壶冷水,提示你需要解决冲突才能进行上传;B 辛辛苦苦的找SE合入代码,却被其他人抢先合入,只能无奈重新更新服务器代码,然后重新生成提交。解决步骤如下:
3.1、下拉最新代码
针对情况A,可以直接使用git pull --rebase命令下拉服务器代码,并将本地最新commit-id移动到最前面。
针对情况B,可以回退当前本地修改然后下拉服务器最新代码后,在下拉gerrit网页上面的提交节点,该网页提供了四种下载方式:
- cherry pick:复制整个节点,包括里面修改的代码和changed-id和commit信息,注意因为是复制,所以使用该方式下载代码之后,changed-id将于网页上保持一样,但是commit-id却是在本地随机生成(不过这并不能影响什么,因为gerrt网页上面是通过changed-id来标志是否为同一笔提交)。如果在复制的过程中发生冲突,那么为了保证代码的完整性将终止该操作。
- git pull:拷贝整个节点,包括里面修改的代码和changed-id和commit信息,甚至commit-id都是一样的。如果在下拉过程中发生冲突,那么并不会终止该操作,反而还会冲突文件打上both modified标记。
因此不论何种理由,在有冲突的情况下,我们使用git pull才是最合适的方式。
3.2、查看冲突文件
既然我们是要解决冲突,那么就需要直面冲突,因此上节而知,使用git pull强制下拉冲突代码,那么冲突的文件状态将会被标记成both modified状态,如下:
$git pull xxxx
#强制下拉冲突文件
$git status
changes to be commit:
modified: src/HiBootvideoUI.cpp
unmerged paths:(use "git add <file>..." to work resolution)
both modified: inc/HiBootvideoUI.h
#由此可知文件inc/HiBootvideoUI.h被标记为冲突状态
3.3、还原冲突文件
在知道了那些文件冲突后,通常的做法是先清除这些文件的状态,即将他们进行还原,可以使用git reset将他们从暂存区中还原到工作区,这样他们的both modified状态也顺便被清除了。如下:
$git reset inc/HiBootvideoUI.h
#还原文件inc/HiBootvideoUI.h状态
$git status
changes to be commited:
modified: src/HiBootvideoUI.cpp
changes not staged for commit:
modified: inc/HiBootvideoUI.h
#文件HiBootvideoUI.h的状态从both modified变成了modified:工作区有修改但是暂存区没有它的记录
3.4、回退冲突文件
当通过git reset命令将冲突文件的状态从both modified更改成modified之后,留给我们的往往是<<<<<<这样的乱码,如果想把这些全部舍弃重新修改的话,可以试试git checkout命令。如下:
$git checkout inc/HiBootvideoUI.h
#回退文件inc/HiBootvideoUI.h所有修改
$git status
changes to be commited:
modified: src/HiBootvideoUI.cpp
#文件HiBootvideoUI.h的状态modified已经没有了,即暂存区的修改都被清除了
3.5、解决冲突提交
上节已经还原了状态(即可以理解为最初状态,可以在工作区修改文件然后添加到暂存区的阶段),这个时候需要去掉那些冲突信息然后依次git add和git commit。
在git commit的时候我们可以决定是否需要修改changed-id,然后git push进行上传。
4、Git问题汇总
4.1、missing Change-Id in commit message footer
报错如上图,在git push的时候发现无法成功推上去,根据报错信息发现应该是缺少了change-id的信息,使用git log查看需要推送的提交,确实没有change-id:
解决方案详情点击我,直接根据提示执行如下命令,在重新commit生产change-id解决该问题:
5、Repo
android的源码是由无数个git仓库组成,因此仅仅通过git来管控android源码讲异常复杂。所以google提供了一个python脚本程序专门来统一管理这无数个git仓库,这个脚本程序就叫做repo。
5.1、repo获取
在google或者android官方网站已经提供了该脚本的下载路径,我们只需要将其下载到当前ubunut系统的bin目录下或者配置其环境变量,赋予其执行权限就可以在linux系统钟使用repo了。
5.2、repo init
repo init -u URL 用以在当前目录安装 repository ,会在当前目录创建一个目录 ".repo",该目录下存储了所有的git仓库信息。示例:
repo init -u gerrit207:mt6762r/platform/manifest -b sw -m MT6762R_DEV_MP_V1.0.xml
- -u 参数指定一个URL,从这个URL中取得repository的manifest 文件
- -m 参数来选择获取 repository 中的某一个特定的 manifest 文件,如果不具体指定,那么表示为默认的 namifest 文件 (default.xml)
- -b 参数来指定某个manifest 分支,如果不具体指定,那么会默认使用master分支
5.3、repo sync
执行repo sync会根据.repo/manifest.xml文件去遍历拷贝服务器的所有代码。如果是第一次运行 repo sync,则这个命令相当于 git clone,会把 repository 中的所有内容都拷贝到本地。 如果不是第一次运行 repo sync , 则相当于 git remote update ; git rebase origin/branch . repo sync 会更新 .repo 下面的文件。 如果在merge 的过程中出现冲突, 这需要手动运行 git rebase --continue。
- -c 参数只同步指定的远程分支。默认情况下,sync会同步所有的远程分支,当远程分支比较多的时候,下载的代码量就大。使用该参数,可以缩减下载时间,节省本地磁盘空间
- -f 参数当有git库sync失败了,不中断整个同步操作,继续同步其他的git库
- -j 参数开启多线程同步操作,会加快sync命令的执行速度。默认情况下,使用4个线程并发进行sync
- -r 参数如果.repo/manifest.xml有配置多个xx_remote属性的话,通过该参数指定从对应的远程服务器拉取。例如在深圳开发的同学可指定参数从深圳服务器中拉取代码
- --no-tags 参数不拉去tag信息,也可以缩减下载时间,节省本地磁盘空间
5.4、repo forall -c
此命令遍历所有的git仓库,并在每个仓库执行-c所指定的命令,被执行的命令不限于git命令,而是任何被系统支持的命令。例如ls pwd等这样的命令,如果后续跟的命令需要带参数,可以加上双引号。如下:
#遍历子目录,依次执行命令git merge TAG
repo forall -c "git merge TAG"
#遍历子目录,依次执行命令ls
repo forall -c ls