1 git简介
- git为分布式版本控制系统,svn是集中式版本控制系统
- 集中式
- 版本库存放在中央服务器,干活前,用自己电脑先从中央服务器取得最新的版本,干完活后,再把最新文件推送给中央服务器。下载时,只下载最新版
- 更新后,svn存储的是两个版本之间的差异,需要从版本n回滚到版本1时,必须先从版本n回滚到版本n-1,依次类推,直到回滚到版本1,效率非常低
- 分布式
- 没有中央服务器,每个人的电脑上都是一个完整的版本库,也就是存放了当前版本代码的同时,还存放了历史版本,以及历史版本的索引,因此每个人的电脑都可以作为git的服务端
- 当你在自己电脑上改了文件A,你的同事也在他的电脑上改了文件A,这时,你们俩之间只需把各自的修改推送给对方,就可以互相看到对方的修改了。但在实际使用分布式版本控制系统时,其实很少在两人之间的电脑上推送版本库的修改,因为可能两人不在一个局域网内,两台电脑互相访问不了,也可能今天你的同事病了,他的电脑压根没有开机。因此,分布式版本控制系统通常也有一台充当“中央服务器”的电脑,但这个服务器的作用仅仅是用来方便“交换”大家的修改,没有它大家也一样干活,只是交换修改不方便而已
- 需要从版本n回滚到版本1时,只需要移动指针到版本1的索引处即可,效率非常高
2 git中的几个重要概念
- 工作区:你在电脑里能看到的目录,也就是写代码的地方
- 本地库:本地当前使用的版本库,暂存区的文件,执行git commit后进入本地库
- 版本库:工作区中一个隐藏的".git"文件就是Git的版本库,版本库中包含以下重要内容
- 暂存区(stage):工作区的文件,执行git add后,进入暂存区
- Git自动创建的master分支,以及指向master分支的HEAD指针
3 git常用命令
-
因为Git是分布式版本控制系统,所以,每个机器提交时都必须自报家门,可以使用如下命令设置你的名字和Email地址
#这个命令,会在"~/.gitconfig"文件中添加信息 #--global参数,用了这个参数,表示你这台机器上所有的Git仓库都会使用这个配置 git config --global user.name "Your Name" git config --global user.email "email@example.com"
-
版本库又名仓库,英文名repository,你可以简单理解成一个目录,这个目录里面的所有文件都可以被Git管理起来
#将当前目录变成Git可以管理的仓库,命令执行后,当前目录下多了一个.git隐藏文件 git init
-
将文件添加到暂存区
git add 文件名
-
将暂存区所有文件提交到当前分支
#如果不执行git add,而直接执行git commit,那么修改内容不会被提交。也就是说git commit只负责把暂存区的修改提交到本地库 git commit -m "本次提交的说明" #同时完成git add和git commit两个操作 git commit -am README.md
-
从近到远显示提交日志
#1. 由近到远记录了commit id、作者、提交时间、提交摘要等内容 #2. commit id相当于一个索引key,通过这个索引可以找到历史记录对应的具体内容 #3. 当历史记录过多,一页装不下,会分页,最后一行显示":",此时点击空格,翻到下一页,按b,翻到上一页,按q退出,如果到尾页,会显示"END" git log #4. 当条数过多后,查看比较费劲,可以将日志信息格式化后展示,此处表示将每次提交的信息,只用一行展示 git log --pretty=oneline #5. 一行展示,且只保留commit id的后七位 git log --oneline #6. git log命令无法查看当前版本之后的commit id信息,这就导致如果通过git reset命令恢复到历史某个版本后,git log无法再查看其之后版本的信息 #7. 而git reflog命令可以,且提供了commit id的后七位与HEAD@{数字}的对照关系,数字表示回到对应commit id的历史版本需要几步 git reflog
-
还原到指定版本
#1. HEAD指向当前分支master的最后一次提交 #2. HEAD^指向当前分支master的上一次提交 #3. HEAD^^表示上两个,HEAD~100表示上100个 #4. hard参数表示,当本地库指针移动后,强制重制暂存区和工作区,防止他们之间不一致,一般只用hard不用mixed和soft git reset --hard 67c8975 git reset --hard HEAD^ git reset --hard HEAD~100 #5. mixed:本地库指针移动后、重置暂存区,但工作区代码不变 git reset --mixed 67c8975 #6. soft:本地库指针移动后,暂存区、工作区都不发生变化 git reset --mixed 67c8975
-
比较工作区、暂存区、指定版本
#1. git是以行为单位管理变更的,当修改某行数据时,都是先删除该行,再重新添加一行,diff命令查出的内容中"-"就表示删除的行,"+"就表示新增的行 #1. 比较工作区与暂存区 git diff #2. 比较指定文件 git diff 文件名 #3. 比较工作区与指定版本(已commit中内容) git diff HEAD/${commit id} #4. 比较暂存区与指定版本(已commit中内容) git diff --cached HEAD/${commit id}
-
撤销工作区的修改
- 如果文件自修改后还没有被放到暂存区,会将该文件撤销到和最新版本库中内容相同,此时和
git reset --hard
作用相同 - 如果文件已被放到暂存区后又做了修改,会将该文件撤销到放到暂存区后的状态
#如果不加"--"",就变成了"切换到另一个分支"的命令 git checkout -- readme.txt
- 如果文件自修改后还没有被放到暂存区,会将该文件撤销到和最新版本库中内容相同,此时和
-
从版本库中删除文件
#1. 先在工作区删除,如果此步骤为误删,可以使用上面介绍的git checkout -- readme.txt恢复工作区内容 rm test.txt #2. 在暂存区中提交删除,此处使用git add test.txt也可以 git rm test.txt #3. 提交到版本库 git commit -m "remove test.txt"
-
查工作目录和暂存区的状态,和git log区别在于,后者用于查看commit的信息,前者用于查看当前目录与最新版本的区别,以及暂存区与最新版本的区别
git status
4 代码托管中心
- 我们通常使用代码托管中心来维护远程库
- 局域网环境:搭建gitlab作为代码托管中心,需要自己搭建
- 外网环境:使用github或gitee作为代码托管中心,不需要自己搭建
5 远程库与本地库交互方式
5.1 团队内部合作
5.2 跨团队合作
5.3 相关命令说明
- push:git操作,将本地仓库更新内容提交给远程库
- clone:git操作,将远程库中内容,拷贝一份到自己本地,并初始化本地库。如果克隆的是他人的仓库,由于他人并没有邀请你加入团队,因此你无法向他人仓库中push代码,只能pull
- pull:git操作,使用远程库中内容更新本地库
- fork:github操作,将别人仓库,拷一份到自己的github中
- pull request:github操作,将自己github中更新的内容,提交给他人的github,他人同意后,他人的github仓库中内容才会被更新。在github上项目的Pull requests标签中,能够查看到是谁请求的pull request,以及申请的备注信息等,其内Files changed中还能看到本次pull request所更新的具体内容,如果没有问题就可以通过merge pull request来同意pull request的申请
- fetch:git操作,pull=fetch+merge,一般都使用pull。会创建并更新origin/master分支,并将远程库中代码拉取到这个新分支中,此操作不会自动更新到工作区中,fetch后,可以通过
git checkout origin/master
命令查看新创建的这个分支,是否和远程库中内容一致,之后执行merge操作将origin/mater分支与master分支进行合并。
6 github使用
6.1 在github上创建账户
6.2 设置 SSH Key
-
使用https的url或ssh的url都可以将远程项目克隆到本地
-
使用https克隆时,每次push操作都需要重新输入github用户名和密码(windows10下可能会记录用户名和密码,不必每次输入)
-
使用ssh克隆时,push操作不需要输入github用户名与密码,但使用ssh克隆时,需要在克隆之前先配置和添加好ssh key,而只有项目拥有者本人才能添加ssh key
-
可以先使用自己电脑创建ssh key公钥和私钥,然后将公钥告诉给github,这样,就可以在自己的电脑上,通过ssh协议,使用私钥来访问github的服务器了
6.2.1 设置流程
-
本机创建创建一个 SSH key
#-t 指定密钥类型,默认是 rsa ,可以省略。 #-C 设置注释文字,比如邮箱。 #-f 指定密钥文件存储文件名,默认文件名为id_rsa(私有密钥)和id_rsa.pub(公开密钥) #邮箱为注册github使用的邮箱 ssh-keygen -t rsa -C "your_email@example.com" #Generating public/private rsa key pair. #Enter file in which to save the key #(/Users/your_user_directory/.ssh/id_rsa): #按回车键 #Enter passphrase (empty for no passphrase): #输入push文件的时候要输入的密码,可以不输密码直接回车 #Enter same passphrase again: #再次输入密码
-
github上添加公开密钥,今后就可以用私有密钥进行认证了,在SSH Keys中粘贴 id_rsa.pub 文件里的内容
cat ~/.ssh/id_rsa.pub
-
修改私钥密码
cd ~/.ssh #修改私钥,执行命令后提示输入密码,两次都直接回车就可以去掉密码,去掉密码后,git push就不再需要输入密码 ssh-keygen -p -f id_rsa
6.3 在github上创建仓库
Create repository
6.4 从远程仓库克隆到本地
#1. 此处也可以使用https协议克隆
#2. 克隆后,git会自动把本地的master分支和远程的master分支对应起来了,且为远程仓库创建别名,别名默认为origin
#3. A克隆了B的远程库后,是没有push权限的,需要由B邀请A才能拥有权限,具体流程为,B进入github--Settings--Manage access--输入要A的github账号--复制邀请链接,然后B将该邀请链接发送给A,A在浏览器中输入该邀请链接后接受邀请,之后A就获取了push权限
git clone git@github.com:michaelliao/gitskills.git
#查看远程库信息
git remote
#查看远程库详细信息,如果没有推送权限,就看不到push的地址
#origin git@github.com:michaelliao/learngit.git (fetch)
#origin git@github.com:michaelliao/learngit.git (push)
git remote -v
#删除远程库
git remote rm origin
6.5 如果已经存在本地仓库,也可以额为本地库添加远程仓库
#1. 创建本地库
#2. 在github上创建仓库
#3. 在本地创建远程库地址的别名,origin为别名,可以人为指定
#1. 注意michaelliao必须是自己的仓库,如果是别人的,虽然能加上,但push不上去,因为你的SSH Key公钥不在他人的账户列表中
git remote add origin git@github.com:michaelliao/learngit.git
#4. 将本地库中master分支推送到远程库origin上,这样,二者就建立起了联系
git push origin master
#5. 拉取也可以建立联系
git pull origin master
7 分支
7.1 分支简介
-
版本控制过程中,可以使用多条线同时推进多个任务,这里面说的多条线就是多个分支。每当需要新开发一个独立功能时,为了防止新功能对原功能有影响,通常开辟一个新分支
-
分支的好处:同时多个分支可以并行开发,互不耽误,互不影响,提高开发效率,如果有一个分支功能开发失败,直接删除该分支即可,不会对其他分支产生任何影响
-
每次提交,git都把它们串成一条时间线,这条时间线就是一个分支。之前的操作中,只有一条时间线,在git里,这个分支叫主分支,即master分支
-
一开始的时候,master分支是一条线,master指向最新的提交,HEAD指向master,这样就能确定当前分支,以及当前分支的提交点
- 每次提交,当前分支都会向前移动一步,随着在master上不断提交,master分支的线也越来越长
- 当我们创建新的分支,例如dev时,Git新建了一个指针叫dev,指向master相同的提交,再把HEAD指向dev,就表示当前分支在dev上
- Git创建一个分支非常快,因为只需增加一个dev指针,并且修改HEAD的指向,工作区的文件都没有任何变化
- 从现在开始,对工作区的修改和提交就是针对dev分支了,比如新提交一次后,dev指针往前移动一步,而mater指针不变
- 假如我们在dev上的工作完成了,就可以把dev合并到master上,Git通过把master指向dev的当前提交,就完成了合并
- 所以Git合并分支也很快,就改改指针,工作区内容也不变
- 合并完分支后,甚至可以删除dev分支。删除dev分支就是把dev指针给删掉,删掉后,我们就剩下了一条master分支
7.2 分支的最佳实践
- 创建、合并和删除分支非常快,所以git鼓励你使用分支完成某个任务,合并后再删掉分支
- 这样做和直接在master分支上工作效果一样,但过程更安全
- 添加一个新功能时,你肯定不希望因为一些实验性质的代码,把主分支搞乱了,所以,每添加一个新功能,最好新建一个分支,在上面开发,完成后,合并,最后,删除该feature分支
7.3 分支操作
-
查看分支
#查看分支名称、最后一次commit id、摘要,*表示当前所在分支 git branch -v
-
创建与切换分支
#1. 创建分支feature-A git branch feature-A #2. 切换到feature-A分支 #1. 切换到新分支后,通过git add、git commit就会在新分支上提交更新 #2. 在A分支修改文件并提交后,切换为原来的master分支后,是看不到修改内容 git checkout feature-A #3. 创建并切换到feature-A分支 git checkout -b feature-A #4. 快速切回到上一个分支 git checkout - #5. 由于git checkout和上面撤销工作区的修改命令重复,因此最新版本的Git提供了新的git switch命令来切换分支 #切换分支 git swtich dev #创建并切换分支 git switch -c dev
-
删除分支
#5. 删除分支 git branch -d feature-vulcan #6. 强行删除未被合并过的分支 git branch -D feature-vulcan
-
合并两个分支
#1. 现要将dev分支合并到master分支上,需要先切换回master分支 git checkout master #2. 将dev合并到master上 git merge dev
-
–no-ff合并分支
-
假设当前分支信息如下
A---B---C feature / D---E---F master
-
执行
git merge feature
命令时,只会简单地把指针右移,叫做"快进"(fast-forward),合并后结果如下A---B---C feature / master D---E---F
-
而如果使用
git merge --no-ff feature
命令禁止快进式合并,合并后结果如下,也就是会产生一个新的提交A---B---C feature / \ D---E---F-----------G master
-
快进式合并会把feature的提交历史混入到master中,搅乱master的提交历史。但如果你根本不在意提交历史,也不爱管 master 干不干净,那么
--no-ff
其实没什么用。不过,如果某一次 master 出现了问题,你需要回退到上个版本的时候,比如上例,你就会发现退一个版本到了 B,而不是想要的 F,因为 feature 的历史合并进了 master 里
-
-
图表形式查看分支的合并情况
git log --graph
-
与远程库中分支建立关联:可以将远程库中的某个分支设为本地仓库中的某个分支的上游,这样之后,
git push
、git pull
命令就不需要再加参数,默认会向当前分支的上游分支去进行push
和pull
#1. 方案一:push的同时添加上游分支,比较常用,且如果远程库中当前没有master分支,就必须使用这个命令,为远程库建立分支的同时,设定上有游分支 git push -u origin master #2. 克隆后,默认情况下本地只有master分支,如果需要其他分支,需要创建远程origin的dev分支到本地 git checkout -b dev origin/dev #3. 方案二:直接与远程服务器上的dev进行关联 git branch --set-upstream-to=origin/dev dev
8 git冲突解决
-
不同分支,对同一文件,同一位置,进行了修改与提交,当想进行merge操作时,就会产生冲突
#1. 由于当前master分支和feature1分支都修改了readme.txt文件的同一位置,尝试将feature1合并到master上时,提示Merge conflict in readme.txt,产生冲突 git merge feature1 #2. 查看冲突的文件 git status #3. 冲突文件中,两个分支的文件中间由==========隔开,需要人为修改冲突文件,决定保留哪部分内容,之后将冲突文件add、commit
-
当向远程库中push代码时,也可能会产生冲突,此时就需要先从远程库pull下来最新内容,此时就获取到了冲突文件,对冲突文件修改后(冲突解决方式和本地一样),再push即可
9 git stash
-
如果再当前的feature-A做了修改,且当前修改内容与master最新版本中内容冲突,此时是无法切换到master分支,会报错
-
但如果此时急需切换回master分支,解决一个致命的bug,此时可以使用git stash把当前分支的"工作现场""储藏"起来,等以后bug解决后,再恢复"工作现场"并继续工作
-
git stash储藏的内容可以为任何分支使用,保存的内容包括工作区与暂存区新增的内容
-
执行git stash后,git status查看工作区与暂存区,会发现没有任何内容,表示当前工作区、暂存区,和上一版本没有任何区别
#1. stash命令可以执行多次,下面命令可以查看stash储藏的所有历史内容 git stash list #2. 恢复 git stash apply #3. 删除 git stash drop #4. 恢复并删除 git stash pop #5. 恢复到指定的某次stash,stash@{0}为git stash list中查出的stash版本号 git stash apply stash@{0}
-
解决完master上的bug后,切回到feature-A,并使用git stash恢复了feature-A,但此时feature-A内容是从master来的,它其实也存在着master中修复的那个bug,该bug在feature-A中尚未被修复,可以使用
git cherry-pick 4c805e2
复制一个特定的提交到当前分支,要注意这次提交的hash值,和修复bug时提交的hash值不同,这两个commit只是改动的内容相同,但是两个不同的commit
10 标签
-
标签是打在提交上的
-
打标签
#1. 为HEAD指向的当前分支的当前版本打标签 git tag v1.0 #2. 为指定commit id内容打标签 #a. 先找到历史的commit的id git log --pretty=oneline --abbrev-commit #b. 对id值为f52c633的commit打标签 git tag v0.9 f52c633 #3. 创建带有说明的标签,-a指定标签名,-m指定说明文字 git tag -a v0.1 -m "version 0.1 released" 1094adb
-
查看标签
#1. 查看所有标签,标签内容按字母排序,而不是时间 git tag #2. 查看打了指定标签的提交 git show v0.9
-
删除标签
git tag -d v0.1
-
默认情况下,标签信息都只存储在本地,不会自动推送到远程,可以使用命令将标签推送到远程
git push origin v1.0 #一次性推送全部尚未推送到远程的本地标签 git push origin --tags
-
删除标签
#如果标签已经推送到远程,要删除远程标签就麻烦一点,先从本地删除 git tag -d v0.9 #然后,从远程删除 git push origin :refs/tags/v0.9
11 git深入学习
12 忽略特殊文件
-
有时某些文件会被放到git工作目录中,但又不想提交它们,比如保存了数据库密码的配置文件、UE修改后保留的备份文件等
-
每次
git status
都会显示Untracked files,影响使用 -
可以编辑.gitignore文件,从而让git不对这些文件进行管理
# Windows: Thumbs.db ehthumbs.db Desktop.ini # Python: *.py[cod] *.so *.egg *.egg-info dist build # My configurations: db.ini deploy_key_rsa
-
.gitignore文件也可以交给git管理
-
如果
git add
无法添加某个文件到暂存区,可能是这个文件被.gitignore忽略了,如果你确实想添加该文件,可以使用-f强制添加 -
如果你觉得不应该忽略该文件,是.gitignore文件写得有问题,需要找出来到底哪个规则写错了,可以用
git check-ignore -v App.class
检查是文件中哪行导致了App.class文件被git忽略
13 source tree
- git的图形化界面,和git关系类似tortorise svn和svn的关系,同样比较优秀的git图形化界面还有fork
14 idea集成git
-
配置git
-
初始化本地仓库
-
将当前项目初始化为本地仓库后,项目中新建文件时会提示是否进行
git add
操作,项目中,执行了git add
后的文件名是绿色,未执行git add
操作的文件名是红色,git commit
后文件名变为白色 -
可以手动
add
、commit
文件和目录 -
git边栏中,console标签就是之前git命令执行的日志、log就是git log的内容
-
当在文件中编写新代码时,和本地库代码不一致的位置,前面会出现绿条
-
可以在源码上或提交时查看与本地库代码内容对比
-
由于本地库和远程库之间,各自记录的起点终点完全不同,因此
git pull
或通过idea直接拉取代码会失败,所以先手工建立关联,使用--allow-unrelated-histories
参数,表示"允许忽略不相关的历史信息"地拉取#拉取远程库数据 git pull git@github.com:handidiao/git-tutorial.git master --allow-unrelated-histories #add commit拉取过来的数据 #将本地数据推送到远程库 git push -u git@github.com:handidiao/git-tutorial.git master -f
-
通过idea,add、commit后,就可以推送项目到远程仓库了
-
也可以commit的同时,push到远程仓库
-
当push到远程库时,如果有冲突,会提示如下信息,此时点击merge
-
此时提示如下信息,提示可以选择用自己的或者用远程库中内容,又或者进行合并,此处选择Merge
-
左侧为你的代码,中间为你修改之前代码,右侧为远程库中代码,此时可以将中间部分代码修改为你最后需要的代码,点击apply即可,此时本地库和工作空间代码已经被修改为最新,再次通过idea进行push即可最终解决冲突
-
通过克隆远程仓库方式创建项目