- Git是分布式版本控制系统,客户端是把代码仓库完整地镜像下来。
- Git中的文件的三种状态:已修改、已暂存(对一个已修改文件的当前版本做标记,让它包含在下次快照中)、已提交(完成了一次快照)。这也对应了三个阶段:工作目录、暂存区域、Git仓库。
本地Git仓库
-
两种获取Git仓库的方式:
1、将尚未进行版本控制的本地目录转化为Git仓库;首先cd
进项目目录,然后git init
初始化一个仓库,该命令创建了一个名为.git
的子目录,这个子目录含有初始化的Git仓库中所有的必须文件。但是,此时项目里的文件还没有被跟踪?:通过git add <filename>
来指定所需的文件进行追踪,然后执行git commit
进行快照。
2、从服务器上clone一个已存在的Git仓库:git clone <url>
,如果想自定义本地仓库的名字,可以在这行命令后加一个新名字作为参数:git clone <url> <filename>
。 -
文件的状态:在之前说到Git文件有三种状态,但这个前提是这个文件已被Git跟踪/已经纳入Git版本控制(Git知道这个文件)。在我们刚刚
clone
了一个仓库后,所有文件都是被跟踪的。我们可以使用git status
来查看文件的状态。
1、如果我们新添加了一个文件到仓库里,它的状态是untracked
,这时候我们需要用git add <filename>
去跟踪这个文件。
2、如果我们新修改了一个被跟踪的文件,它的状态是unstaged
,我们仍然是用git add <filename>
暂存这次更新。这次add的意义是将内容添加到下一次提交中。
3、提交更新: git commit(每次提交更新前最好用git status查看下文件的状态),然后会启动文本编辑器来输入提交说明。当然也可以在commit后加-m,将提交信息与命令放在同一行(如下图所示)。任何一次提交都是对项目做了一次快照,以后可以回到这个状态,或者进行比较。使用git commit -a
会自动把所有已经跟踪过的文件暂存起来一起提交,跳过git add
。
-
查看提交历史:
git log
,此操作会按时间先后列出所有提交。 -
撤销操作:
远程仓库
-
查看远程仓库有哪些:
git remote
,如果已经clone了自己的仓库,那么至少能看到origin
(这是Git给clone的仓库服务器的默认名字),加上参数-v
,可以显示独写远程仓库使用的Git保存的简写与其对应的URL。 -
添加远程仓库:
git remote add <shortname> <url>
,添加一个新的远程Git仓库,同时指定一个方便的简写,所以就可以用这个简写来代替URL。 -
获取远程仓库的信息:
git fetch <remote>
, 执行完后,会拥有那个远程仓库中所有分支的引用,可以随时合并和查看。fetch
只是将数据下载到本地仓库-----并不会自动合并或者修改当前的工作。 -
推送到远程仓库:
git push <remote> <branch>
,将本地某个分支推送。分享项目的时候必须将其推送到上游。但是命令生效需要两个条件:
1、你拥有所克隆服务器的写入权限。
2、之前没有人推送过。当你和其他人在同一时间克隆,他们先推送到上游然后你再推送到上游,你的推送就会毫无疑问地被拒绝。 你必须先抓取他们的工作并将其合并进你的工作后才能推送。 -
查看远程仓库的详细信息:
git remote show <remote>
,它会列出远程仓库的跟踪分支信息,并且说明git pull后会产生哪些效果,最后还会列出拉取到的所有远程引用。 -
远程仓库的重命名与移除:
git remote rename <original name> <newname>
;git remote remove <remote>
。
分支
- 分支简介:每次提交,Git会保存一个提交对象,该对象包括一个指向暂存内容快照的指针,文件快照由三个blob对象保存,并有一个树对象保存blob对象索引,所以最后提交对象里保存指向树的指针就好了。最后,提交对象里包含了树的指针,作者信息、提交信息、指向父对象的指针。Git分支本质仅仅是指向提交对象的可变指针。
- 分支创建 :
git branch <branchname>
,它只是创建了一个可以移动的指针。Git通过一个名为HEAD的特殊指针得知目前在哪一个分支上。可以通过git log查看各个分支当前所指的对象。
$ git log --oneline --decorate
f30ab (HEAD -> master, testing) add feature #32 - ability to add new formats to the central interface
34ac2 Fixed bug #1328 - stack overflow under certain conditions
98ca9 The initial commit of my project
- 分支切换:git checkout ,其实就是改变HEAD指针指向的那个分支。当切换分支之后:1、使HEAD指向指定的分支;2、工作目录恢复成那个分支所指向的快照内容(即最后一个提交的样子)。
- 查看分查历史:
$ git log --oneline --decorate --graph --all
- c2b9e (HEAD, master) made other changes
| * 87ab2 (testing) made a change
|/
- f30ab add feature #32 - ability to add new formats to the
- 34ac2 fixed bug #1328 - stack overflow under certain conditions
- 98ca9 initial commit of my project
-
合并分支: 先将HEAD指向目标分支
git checkout master
,再选择合并到目前分支的那个分支:git merge <targetbranch>
。
1、此时,如果想要合并的分支targetBranch
所指向的提交是你分支master
指向的提交的直接后继,那么Git会直接将master
这个指针向后移动。—这个过程就叫快进(fast-forward)
2、如果开发历史从一个更早的地方开始分叉开来,Git会使用两个分支的末端所指的快照以及两个分支的公共祖先,做一个三方合并,并做了一个新的快照,自动创建一个新的提交指向它,它有不止一个父提交。
3、此时,这个iss53分支已被合并,就可以被删除了:git branch -d <branchname>
-
分支管理: 直接用
git branch
可以得到所有分支的一个列表,前面带 * 的代表现在检出的一个分支;如果需要查看每一个分支的最后一次提交,可以运行git branch -v
;在git branch
后面加上--merged
与--no-merged
可以过滤这个列表中已经合并或尚未合并到当前分支的分支。当使用–merged时,出来的没带 * 的分支就可以剪掉了,因为它已经合并到了当前分支。 -
远程分支: 远程引用是对远程仓库的引用,包括分支、标签等。
-
远程跟踪分支: 远程跟踪分支是远程分支状态的引用。它们是你无法移动的本地引用。一旦你进行了网络通信, Git 就会为你移动它们以精确反映远程仓库的状态。请将它们看做书签, 这样可以提醒你该分支在远程仓库中的位置就是你最后一次连接到它们的位置。
它们以<remote>/<branch>
的形式命名。
假设你的网络里有一个在 git.ourcompany.com 的 Git 服务器。 如果你从这里克隆,Git 的clone
命令会为你自动将其命名为origin
,拉取它的所有数据, 创建一个指向它的master
分支的指针,并且在本地将其命名为origin/master
。 Git 也会给你一个与origin
的master
分支在指向同一个地方的本地 master 分支,这样你就有工作的基础。
只要保持不与origin
服务器连接并拉取数据,那么这个origin/master
指针就不会移动。
如果想要与远程仓库同步数据,运行git fetch <remote>
,这样origin/master
指针就会移动到更新之后的位置。
-
推送: 当你想要公开分享一个分支时,需要将其推送到有写入权限的远程仓库上。 本地的分支并不会自动与远程仓库同步——你必须显式地推送想要分享的分支。1、假如想要推送到的远程仓库的分支(即使未被创建)和本地的分支名字一样,运行
git push <remote> <branch>
。2、假如分支名字不一样,可以通过运行git push <remote> <localBranchName>:<remoteBranchName>
指定名字,将本地的<localBranchName>
分支推送到远程仓库(必须要有写入权限)上的<remoteBranchName>
分支。3、如果目前检出的分支已经设置了上游分支,直接git push
。 -
下一次其他协作者从服务器上
fetch
的时候,他们会在本地生成一个远程分支origin/abc
,指向服务器的abc
分支的引用。要注意:抓取到新的远程跟踪分支后,本地不会自动生成一份可编辑的副本。也就是说,本地不会有一个新的abc分支,只有一个不可以修改的origin/abc
指针。现在有两种方案可以获得刚刚fetch的新分支:
1、直接合并,将这个分支合并到本地仓库的某个分支:先git checkout <localBranch>
,再git merge origin/abc
。
2、重新创立一个本地分支(这个名字可以自己取),起点位于origin/abc
,git checkout -b <branch> <romote>/<branch>
$ git checkout -b abc origin/abc
Branch serverfix set up to track remote branch serverfix from origin.
Switched to a new branch 'serverfix'
- 跟踪分支:从一个远程跟踪分支检出一个本地分支会自动创建所谓的“跟踪分支”(它跟踪的分支叫做“上游分支”)。 跟踪分支是与远程分支有直接关系的本地分支。 如果在一个跟踪分支上输入
git pull
,Git 能自动地识别去哪个服务器上抓取、合并到哪个分支。(git pull
不要乱用)
当克隆一个仓库时,它通常会自动地创建一个跟踪origin/master
的master
分支。
设置一个已有的本地分支跟踪一个刚刚拉取下来的远程分支,或者想要修改正在跟踪的上游分支, 你可以在任意时间使用 -u 或 --set-upstream-to 选项运行 git branch 来显式地设置:
( 但是这个远程(上游)分支必须是已经存在的)
$ git branch -u origin/serverfix
Branch serverfix set up to track remote branch serverfix from origin.
- 如果远程仓库不存在对应的上游分支,可以直接先切到那个想push的本地分支,然后
git push -u origin <remotebranch>
,但是这个操作会直接push。如果只想设置一个上游跟踪分支:git push --set-upstream origin <remotebranch>
- 如果想要查看设置的所有跟踪分支,可以使用
git branch
的-vv
选项。这会将本地分支列出来,并且包含每一个分支正在跟踪哪一个远程分支、本地分支落后或领先:
$ git branch -vv
iss53 7e424c3 [origin/iss53: ahead 2] forgot the brackets
master 1ae2a45 [origin/master] deploying index fix
* serverfix f8674d9 [teamone/server-fix-good: ahead 3, behind 1] this should do it
testing 5ea463a trying something new
- 这些数字的值来自于从每个服务器最后一次抓取的数据。这个命令并没有连接服务器,它只会告诉你关于本地缓存的服务器数据。
如果想要统计最新的领先与落后数字,需要在运行此命令前抓取所有的远程仓库。 可以像这样做:
$ git fetch --all; git branch -vv
-
拉取:
git pull = git fetch + git merge
,git pull
会查找当前分支所跟踪的服务器与分支,从服务器上抓取数据然后尝试合并那个远程分支。(git pull
最好不好乱用,单独地显式使用fetch
和merge
会更好) -
删除远程分支: 假设你已经通过远程分支做完所有的工作了——也就是说你和你的协作者已经完成了一个特性, 并且将其合并到了远程仓库的 master 分支(或任何其他稳定代码分支)。 可以运行带有 --delete 选项的 git push 命令来删除一个远程分支(基本上这个命令做的只是从服务器上移除这个指针):
$ git push origin --delete serverfix
To https://github.com/schacon/simplegit
* [deleted] serverfix
- 变基(rebase): rebase是除了merge外另一种整合来自不同分支地修改方式。merge是这样的:
而rebase是将提交到某一个分支的所有修改都移至另一分支(原理:首先找到这两个分支(即当前分支 experiment、变基操作的目标基底分支 master) 的最近共同祖先 C2,然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件, 然后将当前分支指向目标基底 C3, 最后以此将之前另存为临时文件的修改依序应用。)但是,merge和rebase整合方式最终结果没有任何区别,但是rebase会使得提交历式更加整洁。
先检出experiment
分支,然后将其换基到master
分支上,相当于C4被放弃,然后我们回到master
分支,进行一次fast-forward
合并:
$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command
$ git checkout master
$ git merge experiment
- 变基的风险: 变基的操作的实质是丢弃一些现有的提交,然后相应地新建一些内容一样但实际上不同的提交(提交时间、哈希值等等都不同)。如果你已经将提交推送至某个仓库,而其他人也已经从该仓库拉取提交并进行了后续工作,此时,如果你用
git rebase
命令重新整理了提交并再次推送,你的同伴因此将不得不再次将他们手头的工作与你的提交进行整合,如果接下来你还要拉取并整合他们修改过的提交,事情就会变得一团糟。
一开始远程仓库和本地仓库是这样:
某人又提交了一些修改(包括一个合并),你抓取了这些在远程分支上的修改,并将其合并到你本地的开发分支,然后你的提交历史就会变成这样:
接下来,这个人又决定把合并操作回滚,改用变基;继而又用git push --force
命令覆盖了服务器上的提交历史。 之后你从服务器抓取更新,会发现多出来一些新的提交:
如果你执行 git pull 命令,你将合并来自两条提交历史的内容,生成一个新的合并提交,最终仓库会如图所示:
此时如果你执行 git log 命令,你会发现有两个提交的作者、日期、日志居然是一样的,这会令人感到混乱。 此外,如果你将这一堆又推送到服务器上,你实际上是将那些已经被变基抛弃的提交又找了回来,这会令人感到更加混乱。 很明显对方并不想在提交历史中看到 C4 和 C6,因为之前就是他把这两个提交通过变基丢弃的。 - rebase VS merge: 总的原则是,只对尚未推送或分享给别人的本地修改值行变基操作清理历史,从不对已推送至别处的提交值行rebase(否则会出现上面的问题)。