部分来源:狂神聊Git
git官方文档
Git本地有三个工作区域:工作目录(Working Directory)、暂存区(Stage/Index)、资源库(Repository或Git Directory)。如果在加上远程的git仓库(Remote Directory)就可以分为四个工作区域。文件在这四个区域之间的转换关系如下:
Workspace:工作区,就是平时存放项目代码的地方,即git文件夹
Index / Stage:暂存区,用于临时存放你的改动,事实上它只是一个文件,保存即将提交到文件列表信息
Repository:仓库区(或本地仓库),就是安全存放数据的位置,这里面有你提交到所有版本的数据。其中HEAD指向最新放入仓库的版本
Remote:远程仓库,托管代码的服务器,可以简单的认为是你项目组中的一台电脑用于远程数据交换
本地的三个区域确切的说应该是git仓库中HEAD指向的版本:
Directory:使用Git管理的一个目录,也就是一个仓库,包含我们的工作空间和Git的管理空间。
WorkSpace:需要通过Git进行版本控制的目录和文件,这些目录和文件组成了工作空间。
.git:存放Git管理信息的目录,初始化仓库的时候自动创建。
Index/Stage:暂存区,或者叫待提交更新区,在提交进入repo之前,我们可以把所有的更新放在暂存区。
Local Repo:本地仓库,一个存放在本地的版本库;HEAD会只是当前的开发分支(branch)。
Stash:隐藏,是一个工作状态保存栈,用于保存/恢复WorkSpace中的临时状态。
git的工作流程一般是这样的: 1、在工作目录中添加、修改文件; 2、将需要进行版本管理的文件放入暂存区域; 3、将暂存区域的文件提交到git仓库。
因此,git管理的文件有三种状态:已修改(modified),已暂存(staged),已提交(committed)
版本库是工作区中的隐藏目录文件.git
,版本库中最重要的是stage暂存区;此外git还自动创建了第一个分支master
和指针HEAD
文件修改先通过add
添加到stage暂存区,然后再统一使用commit
提交到主分支,并清空暂存区
Git管理的是修改而不是文件,未add
添加到暂存区的修改不会被commit
提交到主分支
每次提交,Git都把它们串成一条时间线,这条时间线就是一个分支。
对不同分支的操作实际上是对不同指针的操作,所以速度很快
目前只有一条时间线,即主分支(master
分支),HEAD
指针指向master
,master
指向提交。每次提交master
分支都会向前移动一步。
创建一个新分支dev
(即增加一个指针),再改变HEAD
的指向,对工作区的修改和提交就是针对dev
分支了
合并分支时,只需要将master
指向当前dev
所指的位置,就完成了合并;删除分支时也只需要删除指针即可。
当分别对feature1
和master
进行不同的修改后merge
合并,会发生冲突,git status
中也会提示。此时查看文件,Git用<<<<<<<
,=======
,>>>>>>>
标记出不同分支的内容,进行修改后再提交
最后再删除feature1
分支即可
合并有冲突时,会将冲突的内容写入到你的文件中,你解决冲突就是改这个文件,然后
add
commit
,这就完成了一次合并时冲突的解决过程
master
分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活
dev
分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev
分支合并到master
上,在master
分支发布1.0版本;
每个人都在dev
分支上干活,每个人都有自己的分支,时不时地往dev
分支上合并就可以了。
多人协作的工作模式:
首先,可以试图用git push origin <branch-name>
推送自己的修改;
如果推送失败,则因为远程分支比你的本地更新,需要先用git pull
试图合并;
如果合并有冲突,则解决冲突,并在本地提交;
没有冲突或者解决掉冲突后,再用git push origin <branch-name>
推送就能成功!
如果git pull
提示no tracking information
,则说明本地分支和远程分支的链接关系没有创建,用命令git branch --set-upstream-to <branch-name> origin/<branch-name>
。
#查看系统config $ git config --system --list #查看当前用户(global)配置 $ git config --global --list #设置用户名和邮箱(必要) $ git config --global user.name "Colin13" #名称 $ git config --global user.email 1002667204@qq.com #邮箱
#新建一个空目录并在目录下输入 $ git init
#会克隆项目和整个代码历史(版本信息) $ git clone https://gitee.com/kuangstudy/openclass.git
#首先需要在Git仓库文件夹中创建文件readme.txt $ git add readme.txt #第一步:git add 将文件修改添加到暂存区 $ git commit -m "worte a readme file for testing" #第二步:git commit 将文件修改提交到当前分支,-m 后是对本次提交的说明,可以记录每次改动的信息 # 修正错误的commit $ git commit -m 'new commit' $ git commit --amend #重新提交:提交暂存区的文件,并用新的一次提交覆盖上一次的提交信息。
为什么添加到Git仓库要分成两步?
commit
可以一次提交多个文件,所以可以多次使用add
添加最后再用commit
统一提交
#对readme.txt文件进行一些修改后 $ git status #查看工作区的状态,会提示文件被修改且未提交 $ git diff #查看修改内容
$ git log #查看提交日志(顺序为从近到远) #版本回退: $ git reset --hard HEAD^ #将当前版本退回上一个版本(HEAD指针指向上一个版本) #回退后再恢复: $ git reflog #查看每一次历史修改的commit id(历史文件的版本号) $ git reset --hard 1094a #恢复到1094a对应的版本
如果文件修改只在工作区内进行,未add
到暂存区:
$ git checkout -- readme.txt #将readme.txt在工作区的修改全部撤销,--表示本分支
git checkout
:用版本库的版本替换工作区的版本
使用git checkout -- readme.txt
撤销修改会有两种情况:
readme.txt
在修改后还没有add
到暂存区,这时撤销修改会回到和版本库相同的状态
readme.txt
在add
到暂存区后又进行了修改,现在撤销修改会回到刚add
到暂存区的状态
谨慎使用这个命令,因为所有本地的修改都会丢失(git中只有已提交的才能恢复)
如果已经将文件修改并add
到暂存区:
$ git reset HEAD readme.txt #将readme.txt退回工作区,此时暂存区被清空,工作区有修改 $ git checkout -- readme.txt #再用上文的命令撤销工作区的修改
$ rm readme.txt #在工作区中删除文件(这一步也可以省略) $ git rm readme.txt #将删除操作提交到暂存区 $ git restore --staged readme.txt #撤销提交到暂存区的删除操作,或 $ git commit -m "remove readme.txt" #将删除操作commit到版本库,彻底删除文件(无法找回)
只要删除操作没有被commit
到版本库,都可以通过git checkout
恢复到最新的版本
在GitHub账户设置添加本地.ssh
文件夹中的id_rsa.pub
公钥
GitHub新建仓库
上传本地仓库或创建新仓库,默认将远程库命名为origin
本地仓库上传时出现
src refspec main does not match any
错误:本地仓库中没有文件,本地添加README
并commit
后再上传
$ git remote add <shortname> <url>
自动将其添加为远程仓库并默认以 “origin” 为简写
默认情况下,git clone
命令会自动设置本地 master 分支跟踪克隆的远程仓库的 master
分支(或其它名字的默认分支)。 运行 git pull
通常会从最初克隆的服务器上抓取数据并自动尝试合并到当前所在的分支。
$ git clone git@github.com:michaelliao/gitskills.git $ git pull
这个命令会列出远程仓库的 URL 与跟踪分支的信息,当你在特定的分支上执行 git push
会自动地推送到哪一个远程分支。 它也同样地列出了哪些远程分支不在你的本地,哪些远程分支已经从服务器上移除了, 还有当你执行 git pull
时哪些本地分支可以与它跟踪的远程分支自动合并。
# git remote show <remote> $ git remote show origin
访问远程仓库,从中拉取所有你还没有的数据。 执行完成后,你将会拥有那个远程仓库中所有分支的引用,可以随时合并或查看。
git fetch
命令只会将数据下载到你的本地仓库——它并不会自动合并或修改你当前的工作。 当准备好时你必须手动将其合并入你的工作。
# git fetch <shortname> $ git fetch origin
# git push <remote> <branch> $ git push origin master #将本地master分支的最新修改推送至远程库 $ git push origin master:awesomebranch #将本地的master分支推送至远程库的awesomebranch分支
修改远程仓库的简写名
# git remote rename <oldname> <newname> $ git remote rename pb paul
(这里只是解绑,彻底删除库需要在GitHub网站上进行):
$ git remote -v #查看远程库信息 $ git remote rm origin #解除本地和远程的绑定关系(但不会删除远程库)
# 创建分支 $ git branch dev #创建dev分支 $ git checkout -b dev #创建分支并切换到该分支,此后提交都在该分支上 # 查看分支 $ git branch #查看当前分支(列出所有分支,当前分支前会标*) # 切换分支 $ git checkout master #切换回主分支 # 合并分支 $ git checkout master #先切换到想合并入的分支 $ git merge dev #将dev分支合并到主分支,默认为Fast forward策略 # 删除分支 $ git branch -d dev #删除dev分支
fast-forward:当你试图合并两个分支时,如果顺着一个分支走下去能够到达另一个分支,那么 Git 在合并两者的时候,只会简单的将指针向前推进(指针右移),因为这种情况下的合并操作没有需要解决的分歧,这就叫做 “快进(fast-forward)”。
如果开发历史从一个更早的地方开始分叉开来(diverged)时,Git 会使用两个分支的末端所指的快照(C4
和 C5
)以及这两个分支的公共祖先(C2
),做一个简单的三方合并。
如果你在两个不同的分支中,对同一个文件的同一个部分进行了不同的修改,Git 就没法干净的合并它们。此时 Git 做了合并,但是没有自动地创建一个新的合并提交。 Git 会暂停下来,等待你去解决合并产生的冲突。
$ git status #查看包含合并冲突而处于未合并(unmerged)状态的文件
任何因包含合并冲突而有待解决的文件,都会以未合并状态标识出来。 Git 会在有冲突的文件中加入标准的冲突解决标记,这样你可以打开这些包含冲突的文件然后手动解决冲突。
## 冲突文件示例 ## ## ======= 分隔两个版本的文件 <<<<<<< HEAD:index.html <div id="footer">contact : email.support@github.com</div> ======= <div id="footer"> please contact us at support@github.com </div> >>>>>>> iss53:index.html ## 可以将文件修改为下面这样以解决冲突 <div id="footer"> please contact us at support@github.com </div>
或者也可以使用图形化工具来解决冲突
$ git mergetool
解决冲突后,重新将该文件添加到暂存区并commit即可
$ git add <filename>
$ git branch #查看当前所有分支列表 $ git branch -v #查看每一个分支的最后一次提交 $ git branch --merged #查看哪些分支已经合并到当前分支 $ git branch --merged master #查看已合并到master的分支 $ git branch --no-merged #查看所有包含未合并工作的分支 $ git branch --no-merged master #查看尚未合并到master的分支 $ git branch -D test #强制删除test分支(包含未合并工作时-d删除会出错,要使用-D强制删除) $ git log --oneline --decorate #查看各个分支当前所指的对象、提交记录 $ git log --oneline --decorate --graph --all #以图的形式查看提交历史、各个分支的指向以及项目的分支分叉情况。 $ git merge --no-ff -m "merge with no-ff" dev #禁用Fastforward策略,以普通模式合并,因为本次合并要创建一个新的commit,所以加上-m参数,把commit描述写进去。
不使用Fast forward
模式,merge
后会形成和解决冲突后相同的分支情况:
长期分支:在整个项目开发周期的不同阶段,你可以同时拥有多个开放的分支;你可以定期地把某些主题分支合并入其他分支中。
许多使用 Git 的开发者都喜欢使用这种方式来工作,比如只在 master
分支上保留完全稳定的代码——有可能仅仅是已经发布或即将发布的代码。 他们还有一些名为 develop
或者 next
的平行分支,被用来做后续开发或者 测试稳定性——这些分支不必保持绝对稳定,但是一旦达到稳定状态,它们就可以被合并入 master
分支了。 这样,在确保这些已完成的主题分支(短期分支,比如之前的 iss53
分支)能够通过所有测试,并且不会引入更多 bug 之后,就可以合并入主干分支中,等待下一次的发布。
稳定分支的指针总是在提交历史中落后一大截, 而前沿分支的指针往往比较靠前。
你可以用这种方法维护不同层次的稳定性。 一些大型项目还有一个 proposed
(建议) 或 pu: proposed updates
(建议更新)分支,它可能因包含一些不成熟的内容而不能进入 next
或者 master
分支。 这么做的 目的是使你的分支具有不同级别的稳定性;当它们具有一定程度的稳定性后,再把它们合并入具有更高级别稳定 性的分支中。 再次强调一下,使用多个长期分支的方法并非必要,但是这么做通常很有帮助,尤其是当你在一 个非常庞大或者复杂的项目中工作时。
远程分支以<remote>/<branch>
的形式命名,如origin/master
,表示远程库origin上的master分支(origin是clone时git对远程服务器的默认命名,可以通过git clone -o <remote>
自定义)
克隆完成后本地会有master
和origin/master
两个指针
$ git ls-remote <remote> #显式获得远程引用的完整列表 $ git remote show <remote> #获得远程分支的更多信息 $ git remote add <shortname> <url> #添加新的远程仓库
当你想要公开分享一个分支时,需要将其推送到有写入权限的远程仓库上。本地的分支并不会自动与远程仓库同步——你必须显式地推送想要分享的分支。 这样,你就可以把不愿意分享的内容放到私人分支上,而将需要和别人协作的内容推送到公开分支。
从一个远程跟踪分支检出一个本地分支会自动创建所谓的“跟踪分支”(它跟踪的分支叫做“上游分支”)。 跟踪分支是与远程分支有直接关系的本地分支。 如果在一个跟踪分支上输入 git pull
,Git 能自动地识别去哪个服务器上抓取、合并到哪个分支。
如果你尝试检出的分支不存在且刚好只有一个名字与之匹配的远程分支,那么 Git 就会为你创建一个跟踪分支
# 将本地分支设置为与上游分支不同的名字 $ git checkout -b sf origin/serverfix #本地创建一个和远程分支名字不同的跟踪分支
# 更改当前分支跟踪的上游分支 $ git branch -u origin/serverfix #设置跟踪的上游分支为origin/serverfix $ git branch --set-upstream-to origin/serverfix #-u == --set-upstream-to
# 查看本地分支跟踪的远程分支,以及本地分支的版本情况