Note:本笔记是我学习廖雪峰老师的Git教程整理得到,在此向廖老师的无私付出表示衷心的感谢!
0、Git的历史
- Git是一个分布式的版本控制系统(C语言编写,一开始为Linux社区服务,替代BitKeeper),方便自己管理版本的同时方便了团队协作
- 分布式:每台终端上都有一个版本库,不依赖服务器(可选的服务器只是为了方便协作中的版本交换);集中式:依赖服务器工作,工作前下载最新版本,工作后上传自己的版本,没有服务器不能工作
- GitHub是一个为开源项目提供Git存储的软件仓库,可以免费使用(前提是将托管的代码公开)
- CVS和SVN是集中式版本控制系统,SVN是CVS的改进型
- 其他版本控制系统:Microsoft Visual Studio的VSS,BitKeeper,IBM的ClearCase等
1、Git的安装
- Mac要安装Command Line Tools,git工具在其中,Spotlight-Terminal-git-提示安装命令行工具,安装即可(也可以在Xcode-Preference-Downloads-Command Line Tools-Install安装)
- Linux一般自带,不带则使用包管理器安装:
- Debian系:
sudo apt-get install git
(or git-core,旧发行版中git指GNU Interactive Tools,后改名gnuit) - Red Hat系:
sudo yum install git
- Arch系:
sudo pacman -S git
- Debian系:
- Windows要依靠Cygwin环境或WSL子系统,建议使用打包好的mysysgit
2、Git的几个重要概念
- Git的几个区域:工作区 Working Directory – 暂存区 Stage/Index – 版本库Repository – 远程版本库(如GitHub)
- Git管理的是修改,而不是文件
- Git的HEAD是一个指针,指向当前分支的指针(比如说主分支master,而master指向主分支最新的提交),移动指针来改变分支和版本使得Git具有操作速度极快的特性
- Git的每一个分支都有自己的工作区(受到git监视的目录)、暂存区以及版本库,在切换版本库的同时会同步切换
- 标签是指向Commit号的指针,一旦建立不可移动;和Commit号一样标签具有唯一性
3、Git常用命令
-
git config 配置git基本信息(方便协作中识别身份,
--global
表示全局,对所有没有单独设定的版本库均有效)及配置git工具参数git config --global user.name "xxx"
设置全局用户名git config --global user.email "xxx@xxx"
设置全局用户邮箱git config --global color.ui true
打开全局下用不同颜色来区分不同的git提示的功能git config --global alias.<alias> <command>
为命令设置全局别名,是为命令设置的别名,可以是任何文本(如带参数的命令)
-
git init
在当前目录启用git来监视版本并初始化git,产生.git隐藏目录,其中有本地版本库的各分支(Branches,默认有master分支)、HEAD指针和暂存区,不要删除 -
git add <filename>
将指定文件放入暂存区(没有提示表示成功),第一次add某一文件的同时会新增对其的监视(Track) -
git commit -m "Commit messages"
将所有暂存区文件提交到版本库- Note:commit时使用
--allow-empty-message -m ''
参数可以不填写Commit messages而提交,但为了项目的可维护性极不推荐
- Note:commit时使用
-
git rm <filename>
用于在HEAD指向的版本库中删除某个文件,但注意需要git commit
才能将更改提交至版本库 -
git status
查看受监视目录的状态(工作区文件相对于最新版本库是否有修改,暂存区是否有未Commit的文件等) -
git diff <filename>
暂存区为空时比较当前工作区和最新版本库,暂存区不为空时比较当前工作区和暂存区,没有结果说明没有不同- Note:git diff加上
HEAD -- <filename>
参数则指定将当前工作区和最新版本库比较 - Note:git只能显示文本文件的具体变化(二进制文件只知道变了没有),git diff的结构用Unix标准diff格式显示
- Note:注意Windows记事本程序生成BOM头(0xEFBBBF)的问题
- Note:git diff加上
-
git log
查看版本库中存在的版本,每个版本包括一串SHA1计算得到的Commit号用于唯一标识改版本,此外还有版本的Commit messages,commit的日期、作者和作者的E-mail- Note:加入
--pretty=oneline
参数在一行内显示,更加紧凑 - Note:加入
--abbrev-commit
参数可以将Commit号缩写为前几位显示 - Note:加入
--graph
参数可以用图形画出分支情况,不需要自己看日志判断了(注意只有当其他分支被合并到当前分支,其他分支才会被显示) - Note:加入
-l
参数可以只显示最后一个Commit
- Note:加入
-
git reflog
(reference log) 显示自启用监视以来所有的版本操作记录(含Commit号),包括那些已经回退而不在 git log 中显示的版本 -
git reset
用于版本回退、版本回退后的强制还原和撤销暂存区修改(Unstage)- 版本回退:
git reset --hard HEAD^
(or HEAD^,etc.)(HEAD表示版本库最新版本,后面加几个就是向上回退几个版本) - 版本回退后的强制还原:
git reset --hard <commit_id>
,Commit号通过git reflog
查询,可以简写为前几位(有区分性的) - 撤销向暂存区里add的某文件的修改并将其放回工作区:
git reset HEAD <filename>
- 版本回退:
-
git checkout
用于撤销工作区的修改、(创建并)切换到新分支-
git checkout -- <filename>
暂存区有此文件的修改,就将撤销工作区使其与暂存区一致(注意并不会同时撤销或者说是清空暂存区,撤销暂存区要使用git reset HEAD <filename>
);暂存区没有此文件的修改,就将撤销工作区使其与最新的版本库一致(想要回退旧的版本库,要使用git reset --hard HEAD^
(etc.) orgit reset --hard <commit_id>
) -
git checkout <name_of_branch>
切换到某分支Note:加入参数
-b
可以创建<name_of_branch> 分支的同时切换到该新分支
-
-
git branch
用于查看当前分支情况、创建新分支和删除分支-
git branch
用于查看当前分支情况(当前分支用*
标注) -
git branch <name_of_branch>
用于创建名为<name_of_branch>的新分支 -
git branch -d <name_of_branch>
用于删除<name_of_branch> 分支Note:当一个分支从未被合并就需要被删除时,需要使用
-D
参数替代-d
参数以强制删除 -
git branch --set-upstream <name_of_branch> <name_of_remote_lib>/<name_of_remote_branch>
用于将本地库中未关联的<name_of_branch>分支与远程库<name_of_remote_lib>的<name_of_remote_branch> 分支建立关联,以方便使用简化命令git pull
-
-
git merge <name_of_branch>
用于自动合并<name_of_branch>分支到当前分支 -
git stash
用于将保存、恢复和删除当前工作现场(暂存区),在临时的Bug修复任务中十分有用(可以保护当前暂存区来不及Commit的修改)git stash
将当前暂存区保存“入栈”并清空git stash list
查看“堆栈”中保存的一个或多个工作现场git stash apply
恢复最近一次保存的工作现场(不删除stash的内容)git stash drop
删除最近一次stash的内容git stash pop
将最近一次保存的工作现场弹出“堆栈”(删除stash的内容)
-
git remote
用于远程库的添加、查看操作git remote add <name_of_remote_lib> git@server-name:path/repo-name.git
使用SSH协议为当前目录下的本地库关联一个远程库git remote add <name_of_remote_lib> https://git.server-name/path/repo-name.git
使用HTTPS协议为当前目录下的本地库关联一个远程库git remote
查看当前目录下本地库关联的远程库信息(默认只显示远程库名,添加-v
参数显示包括地址在内的详细信息)
-
git clone <address>
克隆一个远程库的master分支到当前目录,
https://git.server-name/path/repo-name.git ,只不过这样速度慢且每次都需要输入密码;git://地址默认使用SSH协议) -
Step3:在本地库中执行
git push <name_of_remote_lib> master
将当前主分支推送到远程库(Note:git push
命令添加-u
参数后,此后再次推送可以省略origin master
)
克隆一个现有的远程库到本地
- 在终端内执行
git clone git@server-name:path/repo-name.git
即可将该远程库克隆到当前目录下
从远程库拉取(git pull
)提示“no tracking information”的原因和解决办法
- 原因:本地当前分支未与远程分支建立关联(库是关联好的)(只有使用
git clone
命令得到的本地分支会自动与远程分支建立关联),且在git pull
中没有指定<name_of_remote_lib>和<name_of_remote_branch> - 解决办法:使用
git branch --set-upstream <name_of_branch> <name_of_remote_lib>/<name_of_remote_branch>
建立分支关联即可
向远程库推送(git push
)出现冲突的原因和解决办法
- 原因:推送的内容和远程库上的内容存在git无法处理的冲突(对于文本文件,即修改内容在同一行上),一般是由于其他开发成员在你尚未推送的时候向远程推送了和你的修改有冲突的修改
- 解决办法:先
git pull
拉回最新的远程版本库,此时会发生自动合并,出现合并冲突,手动修改冲突后Commit,最后再次尝试向远程库推送
利用GitHub代码协作编辑的管理逻辑
- 在他人的开源项目网页下点击“Fork”将该项目的各分支完整克隆到自己的账户下
- 在自己的账户下对“Fork”的项目进行编辑修改(即在本地库和自己账户下“Fork”得到的远程库之间工作)
- 在官方仓库的网页点击“Pull Request”,审核通过即可向官方仓库贡献代码
5、Git的分支管理
A、git merge <name_of_branch>
执行时可能发生的几种情况
合并时自动采用Fast-forward快进模式,master指针移动到dev处,完成合并(注意合并分支并不会删除被合并的分支,要删除请使用 git branch -d <name_of_branch>
)
合并后:
注意,以Fast-forward模式合并分支并删除被合并分支后,在git log
里将不再能看出删掉的分支存在过的信息,而加入--no-ff -m "Infomation of merged branch"
参数后,会在合并时新建一个Commit号(以"Infomation of merged branch"为Commit message)作为合并后的版本,而不是直接移动指针来合并,即:
- 2、产生分支后,两个分支都有修改,即:
-
合并时两分支最新版本库上的修改不冲突(对于文本文件,即修改内容不在同一行上):
合并时git会自动用vim打开一个说明文档,要求用户输入merge的理由(输入的内容不需要注释,git的提示会用
#
注释好以作区分),保存该说明文档后git自动合并两个分支上的修改(合并后的分支上的文件同时具有两分支各自的修改) -
合并时两分支最新版本库上的修改有冲突(对于文本文件,即修改内容在同一行上):
合并时git会提示冲突(Conflict),使用
git status
可以看到有冲突存在(both modified),git会自动将发生冲突的文档中有冲突的部分标注在该文档中,用vim/cat等工具查看该文档可以看到,比如:Git is a distributed version control system.
Git is free software distributed under the GPL.
Git has a mutable index called stage.
Git tracks changes of files.
<<<<<<< HEAD
Creating a new branch is quick & simple.
=======
Creating a new branch is quick AND simple.
>>>>>>> feature1其中,
=======
为分割线,<<<<<<<
和>>>>>>>
指明了分支的名字(HEAD为当前切换到的分支),之间夹的内容就是各个分支在同一行发生冲突的内容这种git无法自动处理的冲突需要手动解决,即对照git给出的冲突内容自行修改文档,得到一个自己认为总体满意的版本(定稿),对定稿进行
git add
和git commit
后冲突解决,分支自动合并,git status
里的冲突信息也会消失Note:冲突没有解决期间,不允许切换到待合并的分支!
B、开发时所应该采用的分支策略
-
master是极其稳定的分支,仅发布新版本使用,不轻易将其他分支合并到master分支;本地的master分支要时刻与远程同步以保持最新作为开发基础参考
-
dev是开发总分支,从master上产生,功能稳定后定期向master合并;本地的dev分支要时刻与远程同步以保证协作开发不出现严重冲突
-
每个人都应该有自己的开发分支,从dev上产生,个人阶段性工作完成后向dev上合并
-
需要修复某个分支上的Bug时,从该分支上产生新的Bug分支,在新分支上修复Bug,修复完成后向原分支合并并删除Bug分支即可;本地的bug分支一般都是临时性分支,不必向远程同步
-
需要添加某实验性的功能时,从dev或master分支上产生feature分支,在feature分支上开发稳定后再合并并删除feature分支;如果需要合作开发新功能,本地的feature分支就需要向远程同步
6、.gitignore文件的使用
- .gitignore是一个隐藏文件(以.开头),用来记录不被Git监控的文件名,这些文件不被Git监控(
git status
命令中不会列入"Untracked files…") - .gitignore文件中一行写一个文件名,允许使用通配符
*
- .gitignore文件放在被Git监视的目录下,也可以向远程同步,也允许对其进行版本管理
- 不受监视的文件常常为:缩略图文件、编译产生的中间文件、个人敏感信息等
7、搭建一个私有Git服务器(简易代码仓库)
- Step1:使用包管理器为Linux服务器安装git
- Step2:新建一个系统用户,
sudo adduser git
- Step3:将所有用户的公钥导入到/home/git/.ssh/authorized_keys文件中,方便SSH登陆
- Step3:初始化裸仓库(共享使用,没有工作区),选定一个目录,
sudo git init --bare <lib_name>.git
- Step4:更改仓库的属主和属组,
sudo chown -R git:git <lib_name>.git
- Step5:禁止git用户利用Shell登陆服务器,编辑/etc/passwd文件,将Shell从/usr/bin/bash改为/usr/bin/git-shell,这样从Shell登陆会引发自动退出
- 之后用户就可以正常从服务器克隆<lib_name>.git仓库并推送了
- 管理公钥的工具——Gitosis,管理权限的工具Gitolite(原生Git不支持权限管理)