git重要基本概念
1.git的工作目录下面的所有文件又分为两种情况:已跟踪和未跟踪。
- 已跟踪:就是你曾经提交过文件或直接从Git仓库克隆下来的那些文件,也就是说对一个新创建的文件进行第一次git的添加,这样文件就有了快照记录后,这文件就转变成已跟踪状态了。
- 未跟踪:就是新创建的文件没添加过一次,没有之前的快照记录。
这两种文件状态和暂存区的关系如图:
2.git内目录(这里指的是已跟踪的目录)有三种状态:
- 提交状态:如果是 Git 目录中保存着的特定版本文件,也就是说将暂存区的文件提交到仓库中,就属于已提交状态 (git commit)。
- 暂存状态:如果对Git目录作了修改并已放入暂存区域,就属于已暂存状态。(git add)
- 已修改状态:如果从远程仓库取出Git目录后,作了修改但还没有放到暂存区域,就是已修改状态。
三种状态之间的关系图:
当使用 git commit 新建一个提交对象前,Git 会先计算每一个子目录(本例中就是项目根目录)的校验和,然后在 Git 仓库中将这些目录保存为树(tree)对象。之后 Git 创建的提交对象,除了包含相关提交信息以外,还包含着指向这个树对象(项目根目录)的指针,如此它就可以在将来需要的时候,重现此次快照的内容了。比如我创建三个文件,分别是REMADE,LICENSE,test.rb,然后把这是三个文件提交到本地仓库中,那么Git仓库会产生五个对象,如图:
git是采用快照来记录,当再次对文件连续提交两次时,那么每次的提交对象会包含一个指向上次提交对象的指针,如图:
3.HEAD指针是用来跟踪本地仓库的分支,它与git checkout命令符紧密相连,HEAD指针随着git checkout切换到那个分支,就指向那个分支。
例子:
当在master分支用git branch test创建一个test分支时,HEAD指针并不会移动,如图:
在上面基础上用git checkout test时,HEAD指针就会指向test所指向的对象,并会跟随test分支移动。如图:
在上面基础上再用git commit -a -m”test”提交一次,HEAD指针也会跟随test分支向前移。如图:
4.本地仓库分支与远程仓库的分支,当你克隆远程仓库时,本地分支会创建一个远程分支和跟踪远程的分支,比如你用git clone https://git.oschina.net/xuguoli_beyondboy/StudentGradeProject.git,那么你本地分支会创建origin/master远程分支,和跟踪远程仓库当地的master分支,当你用git push origin时(如果你的远程仓库没有master分支,远程仓库会创建master分支),origin/master 会移到mater指向的对象,而远程仓库的master分支也会移到你本地仓库提交分支相对应的位置。
例子:
当你用git clone https://git.oschina.net/xuguoli_beyondboy/StudentGradeProject.git后,你的本地分支情况如图:
当你用了git commit -a -m”“两次后,这是本地仓库中的跟踪远程的分支会向前移,而origin/master是不会的,如图:
当你用了git push origin后,本地仓库的 origin/master和远程仓库master都会向前移,如图:
git常用基本操作
1.对一个文件初始化到纳入git目录管理当中,要经过三个步骤:
- git init(初始化文件)
- git add fileName or directoryName(保存文件到暂存区)
- git commit -m”说明提交信息”(把暂存区文件全部提交到本地仓库中)
注意:如果你想要用git commit -a -m”说明提交信息”跳过暂存区,文件必须是已跟踪状态。
2.对git目录执行文件执行查看信息或比较操作,有些命令可以用匹配模式,有以下几个常用命令: - git status 查看文件状态,是否处于暂存区或是否提交到本地仓库中,并提示相关操作。
- git diff是对比工作目录和暂存区的内容差异。git diff –cached是对比暂存区和上次提交的内容差异。如图:
- git log查看历史:显示最近几次提交的commit_id,提交者,时间,提交说明信息,如果你想看最近n次的提交,你可以用git log -p -n。
- git remote -v 可以详细查看你的从远程仓库克隆的网址,git remote show remote-name,它会详细显示了有哪些远端分支还没有同步到本地,哪些已同步到本地的远端分支在远端服务器上已被删除,以及运行 git pull 时将自动合并哪些分支。
- git tag会列出git目录所记录的所有版本号,git tag -l “edition_version(即版本号)”会列出符合可以匹配这版本号的所有版本号,git show edition_version会查看相应标签的版本信息,并连同显示打标签时的提交对象(包括提交者,时间,标签说明,commit_id等)
- git branch列出本地仓库所有的分支(分支前面有星号表示当前分支),git branch -v将要显示各个分支分别最后一次提交的对象信息,git branch –merged会显示已合并的分支,没合并的分支不会显示,git branch –no-merged与git branch –merged效果相反。
3.本地仓库与远程仓库(假设远程仓库的url的别名为origin)交互有以下几个常用的命令: - git clone [url(即远程仓库克隆网址)],那么就会在你的本地仓库中创建一个origin(远程仓库url的别名) /master和跟踪远程仓库的分支 master。
- git fetch origin(远程创库url的别名),此命令会抓取从你上次克隆以来别人上传到此远程仓库中的所有更新(或是上次 fetch 以来别人提交的更新),fetch 命令只是将远端的数据拉到本地仓库(在本地仓库中会创建origin/remote-branch,但并不会创建跟踪分支,这就是与git clone小小区别。),并不自动合并到当前工作分支,git fetch origin [remote-branch]拉取远程仓库的[remote-branch]的信息。
- git pull origin(本地仓库分支有了某个分支用于跟踪某个远端仓库的分支) 命令自动抓取数据下来,然后将远端的最新分支自动合并到本地仓库中对应的跟踪分支,而 git pull origin [remote-branch],会将远程仓库的[remote-branch]分支合并到本地仓库当前分支,而 git pull origin [remote-branch]:[local-branch]会将远程仓库的[remote-branch]分支合并到本地仓库[remote-branch]分支.
- git checkout –track origin/[remote-branch]会默认在本地仓库(如果已存在对应的跟踪分支的话,这条和下面命令都无效)中创建与本地仓库中的origin/[remote-branch]同名的分支指向同一个地方来跟踪其远程分支,git checkout -b [branch] origin/[remote-branch]会默认在本地仓库中创建[branch]分支与本地仓库中的origin/[remote-branch]分支指向同一个地方来跟踪其远程分.
上面后三点的区别如图所示:
拉取远程仓库之前的情况:
git fetch origin拉取远程仓库信息后情况:
git pull origin拉取远程仓库信息后情况:
git checkout -b master1 origin/master信息情况后
- git push origin [branch(分支名)]取出我在本地的branch分支,推送到远程仓库的相对应的远程分支中去(git将会显示refs/heads/branch:refs/heads/[remote-branch]), git push origin [branch]:[remote-branch]此命令上传我本地的branch分支到远程仓库[remote-branch], git push origin :[remote-branch]会删除对应的远程仓库[remote-branch]的分支且本地仓库相对应的origin/[branch]也会删除。
4.git 提供了以下常用几个添加,删除,修改操作(有些命令适用字符匹配模式)。
添加命令 - git remote add [shortname(远程仓库的别名)] [url]该命令是用[shortname]替代远程仓库的[url]地址。
- git tag -a [edition-version] -m “说明信息”该命令添加一个带有说明信息的[edition-version]标签。
git branch [branch-name]该命令在本地仓库创建一个[branch-name],但是HEAD指针没有切换到的该分支上(如果要切换到该分支上还要执行git checkout [branch-name]),git checkout -b [branch-name]改命令会在本地仓库创建[branch-name]分支并切换到给分支上。
删除命令git rm fileName(要删除的文件名),该命令同时删除工作目录和暂存区的fileName文件(删除后你要执行一次git commit -m”“后,git status才会干净),但如果你在你删除之前修改过了git目录的文件,并把它放入了暂存区,你要用git rm -f fileName来删除其文件,git rm –cached fileName该命令只删除暂存区的fileName文件,而不删除工作区的fileName文件。(如果你手动删除工作目录的fileName文件时,因为暂存区的fileName文件没要删除,故你需要执行git rm fileName命令后才能提交。)
- git remote rm remote-name该命令删除本地仓库中的远程仓库的URL的别名。
- git branch -d [branch-name]该命令删除本地仓库已经合并的分支(如果该分支没有合并,则不能删除,不过可以用git branch -D [branch-name]删除没合并的分支)。
git push origin :[remote-branch]会删除对应的远程仓库[remote-branch]的分支且本地仓库相对应的origin/[branch]也会删除(远程仓库URL为origin前提下)。
修改操作git commit -amend该命令修改最后一次提交,该说明信息会覆盖最好提交的信息,如果你有工作目录的文件没有被提交到git本地仓库中,你可以先执行git add fileName,把该文件放入暂存区,然后在执行git commit -amend命令。
- git reset HEAD fileName该命令取消已经放入暂存区fileName文件,使之又回到了之前未暂存的状态。
- git checkout – fileName该命令取消对文件的修改,也就是说,当你修改了一个文件,该文件会是修改状态,当你用这条命令的时候,该文件又会恢复为原来的纳入git目录的版本。
- git mv file_from file_to该命令把file_from名字的文件重命名为file_to名字,这相当于mv README.txt README,git rm README.txt,git add README这三条命令,故你要提交一次,git status(git目录状态)才干净。
- git remote rename remote-name target-name该命令意思是把原来的仓库别名改为新的仓库别名,例如:git remote rename test1 test2命令会把本地仓库的test1/[branch]改为test2/[branch].
5.一般我们总会有些文件无需纳入 Git 的管理,也不希望它们总出现在未跟踪文件列表。通常都是些自动生成的文件,比如日志文件,或者编译过程中创建的临时文件等。我们要一个名为 .gitignore 的文件,列出要忽略的文件模式,文件 .gitignore的格式规范: - 所有空行或者以注释符号 # 开头的行都会被 Git 忽略。
- 可以使用标准的 glob 模式匹配。
- 匹配模式最后跟反斜杠(/)说明要忽略的是目录。
要忽略指定模式以外的文件或目录,可以在模式前加上惊叹号(!)取反。
.gitignore例子:
6.有时候我们只想要把两个分支有差异的内容提取出来,然后合并到另一个分支中,git也提供了两个方便的操作符。git diff [branch] > patch该名命令是把当前的分支的git目录的内容与branch分支比较,如果与当前分支内容有差异,则把差异的部分的内容生成patch补丁,这是你可以用git checkout branch,git apply patch这两条命令合并patch补丁的内容.可以用git apply –check 查看补丁是否能够干净顺利地应用到当前分支中。
- git format-patch [branchA] [branchB]会先对比两个分支内容并根据这个分支提交的次数来生成多少个patch补丁文件(每提交一次会生成相对应的一个文件),git format-patch -n [branch],会比较当前分支和branch分支内容,并生成当前分支最后n次提交的内容patch补丁,切换到另一个分支用git am patchName命令符应用这些补丁。
7.git checkout的命令符基本用法。 - git checkout master取出master版本的head。
- git checkout edition-version在当前分支上 取出 edition-version的版本
- git checkout .(点号) 放弃所有的文件的修改,git checkout fileName放弃当前分支的fileName的修改, git checkout master fileName放弃当前的master分支对文件fileName的修改,这三个命令都使它恢复成原来上次提交的fileName的内容,而不用执行git commit -m”“命令符。
git checkout commit_id(为提交时产生的SHA-1校验码) filename取文件fileName在commit_id的文件版本, 这里取到的文件会存入暂存区,所以你要执行一次git commit -m”“。
8.git提供两种代码合并的方式,一种是merger(合并),另一种是rebase(衍合),下面就是这些两种操作的命令用法和例子:git merge branch,当前分支会合并branch分支,它会找当前分支和branch分支的祖先,如果发现branch不是当前分支的祖先,则会找两个分支最近的共同祖先进行三方合并, git merge origin/branch也会像上面的规则去合并,只不过这里是当前分支合并本地仓库中的远程仓库的分支。
例子:
合并前的情况:
用git merge master命令后git会选择 C2,C4,C5进行三方合并,合并后的情况后如图:
注意这种合并,如果你遇到合并冲突,请用手动解决后,再用git add命令可以表示冲突已解决。- git rebase branch命令是回到两个分支最近的共同祖先,根据当前分支后续的历次提交对象,生成一系列文件补丁,然后以基底 branch分支,最后一个提交对象为新的出发点,逐个应用之前准备好的补丁文件,最后会生成一个新的合并提交对象,从而改写当前分支的提交历史,使它成为branch分支的直接下游,
例子:
衍合前:
衍合后:
我们看看另一种衍合的方式,假设本地仓库有三个client server master分支,git rebase –onto master server client(client分支是当前分支)取出 client 分支,找出 client 分支和 server 分支的共同祖先之后的变化,然后把它们在 master 上重演一遍(这里重演的对象是client和server共同祖先下游的对象,不包括其祖先上游的对象),git rebase master server(server是当前分支)命令会先取出特性分支 server,然后在主分支 master 上重演,如图:
衍合前:
执行了git rebase –onto master server client(当前分支是client)的情况后:
执行了git rebase master server (当前分支是server)的情况后:
注意:一旦分支中的提交对象发布到公共仓库,就千万不要对该分支进行衍合操作,遵循这条金科玉律,就不会出差错。否则,你的合作者会痛恨你的。
git小型项目协作开发的流程
第一种情况
假设有三个人(beyondboy,sungirl,scauboy)协作开发一个小型项目,并设置远程仓库有两个分支master(稳定分支),develop(开发分支),其中beyondboy拥有远程仓库master的管理权限,而sungirl,scauboy没有这个管理权限,三个合作者都拥有这个人develop管理权限。如图:
远程仓库设置:
刚开始工作的三者本地仓库:
工作后的三者本地仓库的情况
sungirl用git push origin把本地仓库的develop推送到远程仓库中后,远程仓库的情况:
这是scauboy用git fetch origin要把自己的develop分支推送到远程仓库中,必须先执行git fetch origin命令后,拉取sungirl推送的信息,然后在合并其分支(建议合并时候先创建新的一个分支,合并其sungirl的推送的代码,如果没问题后,再用develop分支(git merger origin/develop)去合并代码,合并后的scauboy本地仓库情况:
scauboy把本地仓库的develop推送远程仓库后:
beyondboy用git fetch origin从远程仓库拉取信息:
beyondboy先新创建一个分支合并拉去的信息,如果没问题后,再用develop分支区合并,合并后,再切换到master分支,合并develop后,将其master分支推送到远程仓库中。
beyondboy推送后的情况:
其beyondboy合作者推送后,远程仓库的情况:
第二种情况:
假设有三个人(beyondboy,sungirl,scauboy)协作开发一个小型项目,并设置远程仓库有两个分支master(稳定分支),develop(开发分支),其中beyondboy拥有远程仓库master的管理权限,而sungirl,scauboy没有这个管理权限,三个合作者都拥有这个人develop管理权限,不过这里有点不同的是,beyondboy想要参与sungirl,scauboy开发的另一种功能(就是他们两个除了develop分支,还有另外一个分支在开发新的功能),而不是只开发一个功能。如图:
远程仓库设置:
本地仓库工作后的情况:
sungirl和scauboy都各自的另一个功能的任务完成了,这时用git push origin featureA:featureA和git push origin featureB:featureB应该把它们这两个功能推送到远程仓库中后,继续完成剩下的develop分支的任务,这时beyondboy可以拉取该featureA和featureB功能的信息,这样beyondboy就可以参与开发了。
sungirl和scauboy推送到远程仓库后,其远程仓库后的信息:
这时beyondboy执行git fetch origin命令后,beyodnboy的本地仓库的情况:
接下来就可以按照第一种情况去开发合并了,这里我就不详细描述了。
建议:拉取远程仓库最好用fetch不要用pull,合并前创建一个新分支来检查与拉取信息合并的是否问题,推送禁止用git push -f强推分支到远程仓库,因为这会覆盖原来的远程仓库信息,会导致远程仓库的信息和你的本地仓库一样。
对于上面的流程,查看远程仓库历史的其提交对象有点多,你可以用衍合的操作,git rebase branch去减少提交的对象,不过你一定要牢记一旦分支中的提交对象发布到公共仓库,就千万不要对该分支进行衍合操作。