你的本地仓库有 Git 维护的三棵“树”组成,这是 Git 的核心框架。这三棵树分别是:工作区域、暂存区域和 Git 仓库
工作区域(Working Directory)就是你平时存放项目代码的地方。
暂存区域(Stage)用于临时存放你的改动,事实上它只是一个文件,保存即将提交的文件列表信息。
Git 仓库(Repository)就是安全存放数据的位置,这里边有你提交的所有版本的数据。其中,HEAD 指向最新放入仓库的版本(这第三棵树,确切的说,应该是 Git 仓库中 HEAD 指向的版本)。
Git 的工作流程一般是:
-
在工作目录中添加、修改文件;
-
将需要进行版本管理的文件放入暂存区域;
-
将暂存区域的文件提交到 Git 仓库。
因此,Git 管理的文件有三种状态:已修改(modified)、已暂存(staged)和已提交(committed),依次对应上边的每一个流程。
一初始化Git
在自己方便的盘中新建一个文件夹,这里以MyProject为例,注意路径中不要含有中文字符。打开cmd命令窗口,操作如下:
C:Userslenovo>F: F:> F:>cd MyProject F:MyProject> #初始化Git项目,成功后创建有一个.git隐藏文件 F:MyProject>git init Initialized empty Git repository in F:/MyProject/.git/ #在文件夹MyProject中添加一个文本文件README,md格式指Markdown格式(建议使用Notepad++编辑) #然后输入以下命令将文件加入暂存区 F:MyProject>git add README.md #将文件提交到git仓库(-m表示添加本次提交的说明,强制要求写的) F:MyProject>git commit -m "add a readme file" [master (root-commit) 9e08cf4] add a readme file 1 file changed, 1 insertion(+) create mode 100644 README.md
二.D:>Clone>git clone 目标 克隆github 上别人的代码
三.使用git status查看当前状态
F:MyProject>git status On branch master nothing to commit, working tree clean //表示没有提交过代码,很干净
F:MyProject>git status On branch master Untracked files: (use "git add <file>..." to include in what will be committed) LICENSE(红色) nothing added to commit but untracked files present (use "git add" to track)
Untracked files 说明存在未跟踪的文件(下边红色的那个)
所谓的“未跟踪”文件,是指那些新添加的并且未被加入到暂存区域或提交的文件。它们处于一个逍遥法外的状态,但你一旦将它们加入暂存区域或提交到 Git 仓库,它们就开始受到 Git 的“跟踪”。
这里圆括号中的英文是 git 给我们的建议:使用 git add <file> 命令将待提交的文件添加到暂存区域。
F:MyProject>git add LICENSE F:MyProject>git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) new file: LICENSE(绿色)
F:MyProject>git reset HEAD F:MyProject>git status On branch master Untracked files: (use "git add <file>..." to include in what will be committed) LICENSE(红色) nothing added to commit but untracked files present (use "git add" to track)
再次添加到暂存区域,然后执行 git commit -m "add a license file" 命令:
F:MyProject>git add LICENSE F:MyProject>git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) new file: LICENSE F:MyProject>git commit -m "add a license file" [master 9fdf9f4] add a license file 1 file changed, 7 insertions(+) create mode 100644 LICENSE F:MyProject>git status On branch master nothing to commit, working tree clean
关于修改
突然发现版权那块忘了写上自己的名字了……
打开 LICENSE 文件,将 Copyright (C) <year> <copyright holders> 改为 Copyright (C) 2016 FishC,保存……
执行 git status 命令:
F:MyProject>git status On branch master Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: LICENSE(红色) no changes added to commit (use "git add" and/or "git commit -a")
由于你对工作目录的文件进行了修改,导致这个文件和暂存区域的对应文件不匹配了,所以 Git 又给你提出两条建议:
- 使用 git add 命令将工作目录的新版本覆盖暂存区域的旧版本,然后准备提交
- 使用 git checkout 命令将暂存区域的旧版本覆盖工作目录的新版本(危险操作:相当于丢弃工作目录的修改)
还有一种情况我们没分析,大家先把新版本的文件覆盖掉暂存区域的旧版本:
F:MyProject>git add LICENSE F:MyProject>git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) modified: LICENSE(绿色)
然后我们打开 LICENSE 文件,将 FishC 改为 FishC.com,保存……
F:MyProject>git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) modified: LICENSE(绿色) Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: LICENSE(红色)
这次情况是:被绿的 LICENSE 说明文件存放在暂存区域(待提交),同时红色的 LICENSE 说明文件还在工作目录等待添加到暂存区域。
这种情况你应该意识到这里存在两个不同版本的 LICENSE 文件,这时如果你直接执行 commit 命令,那么提交的是暂存区域的版本(FishC),如果你希望提交工作目录的新版本(FishC.com),那么你需要先执行 add 命令覆盖暂存区域,然后再提交……
一步到位
从工作目录一步添加到仓库:git commit -am “说明”
F:MyProject>git commit -am "change the license file"
-a的意思是add。
git log 查看历史操作记录
Git使用教程5---回到过去
有关回退的命令有两个:reset 和 checkout
回滚快照
注:快照即提交的版本,每个版本我们称之为一个快照。
现在我们利用 reset 命令回滚快照,并看看 Git 仓库和三棵树分别发生了什么。
执行 git reset HEAD~ 命令:
注:HEAD 表示最新提交的快照,而 HEAD~ 表示 HEAD 的上一个快照,HEAD~~表示上上个快照,如果表示上10个快照,则可以用HEAD ~10
此时我们的快找回滚到了第二棵数(暂存区域)
git reset HEAD~ 命令其实是 git reset --mixed HEAD~ 的缩写, --mixed 选项是默认的。
默认 git reset HEAD~ 命令其实影响了两棵树:首先是移动 HEAD 的指向,将其指向上一个快照(HEAD~);然后再将该位置的快照回滚到暂存区域。 --soft选项 git reset --soft HEAD~ 命令就相当于只移动 HEAD 的指向,但并不会将快照回滚到暂存区域。相当于撤消了上一次的提交(commit)。一不小心提交了,后悔了,那么你就执行 git reset --soft HEAD~ 命令即可(此时执行 git log 命令,也不会再看到已经撤消了的那个提交)。 --hard选项 reset 不仅移动 HEAD 的指向,将快照回滚动到暂存区域,它还将暂存区域的文件还原到工作目录。
回滚指定快照
reset 不仅可以回滚指定快照,还可以回滚个别文件。
命令格式为: git reset 快照 文件名/路径
这样,它就会将忽略移动 HEAD 的指向这一步(因为你只是回滚快照的部分内容,并不是整个快照,所以 HEAD 的指向不应该发生改变),直接将指定快照的指定文件回滚到暂存区域。
不仅可以往回滚,还可以往前滚!
这里需要强调的是:reset 不仅是一个“复古”的命令,它不仅可以回到过去,还可以去到“未来”。
唯一的一个前提条件是:你需要知道指定快照的 ID 号。
那如果不小心把命令窗口关了不记得ID号怎么办?
命令:git reflog
Git记录的每一次操作的版本ID号
比较暂存区域与工作目录
直接执行 git diff 命令是比较暂存区域与工作目录的文件内容:
这里可能出现一个问题,直接执行的结果出现中文乱码。在cmd界面的冒号:后面输入q退出,使用Notepad++打开README文件,点击编码→转为UTF-8编码。(旧版本中选择UTF-8(无BOM),新版中的UTF-8默认为无BOM)重新执行git diff发现还是不行!怎么肥四??在本菜查遍各网站论坛之后,终于找到了解决方法
执行以下三条命令(别问我明明是四条为啥说三条...)
git config --global i18n.commitencoding utf-8 git config --global i18n.logoutputencoding utf-8 export LESSCHARSET=utf-8 ## linux bash配置环境变量 set LESSCHARSET=utf-8 #windows配置环境变量
比较两个历史快照
我们执行 git commit -am "添加功能:玩家只有三次机会" 命令,添加并提交工作目录中的所有文件。执行 git log 命令,可以看到现在 Git 仓库中已经有两个快照了:
执行 git diff 6e26975 ed3708c 命令,即可比较 Git 仓库中两个快照的差异:
比较之前版本的快照与当前工作目录内容
输入 git diff ed3708c 命令即可
比较当前版本快照与当前工作目录内容
输入 git diff HEAD 命令即可
Git使用教程7---修改最后一次提交、删除文件和重命名文件
删除文件
问题一:不小心删除文件怎么办?
现在从工作目录中手动删除 README.md 文件,然后执行 git status 命令:
提醒使用 checkout 命令可以将暂存区域的文件恢复到工作目录:
文件就会重新返回。
问题二:那么如何彻底删除一个文件呢?
假如你不小心把小黄图下载到了工作目录,然后又不小心提交到了 Git 仓库:
执行 git rm yellow.jpg 命令:
此时工作目录中的小黄图(yellow.jpg)已经被删除……
但执行 git status 命令,你仍然发现 Git 还不肯松手:
意思是说它在仓库的快照中发现有个叫 yellow 的东西,但似乎在暂存区域和当前目录不见了!
此时可以执行 git reset --soft HEAD~ 命令将快照回滚到上一个位置,然后重新提交,Git 就不会再提小黄图的事儿了:
注意:rm 命令删除的只是工作目录和暂存区域的文件(即取消跟踪,在下次提交时不纳入版本管理)
问题三:我在工作目录中增加一个 test.py 文件,然后执行 git add test.py 命令将其添加到暂存区域,此时我修改 test.py 文件的内容,那么暂存区域和工作目录就是两个不同的 test.py 文件了,此时如果我执行 git rm test.py 命令,Git 会下意识地阻止我,这是怎么办?
因为两个不同内容的同名文件,谁知道你是不是搞清楚了都要删掉?还是提醒一下好,别等一下出错了又要赖机器……
根据提示,执行 git rm -f test.py 命令就可以把两个都删除。
问题四:我只想删除暂存区域的文件,保留工作目录的,应该怎么操作?
执行 git rm --cached 文件名 命令。
重命名文件
直接在工作目录重命名文件,执行git status出现错误:
正确的姿势应该是:
git mv 旧文件名 新文件名
注:Windows 使用 ren 命令修改文件名,Linux 是使用 mv 命令
教程6彩蛋
如何让Git 识别某些格式的文件,然后自主不跟踪它们?
比如工作目录中有三个文件1.temp、2.temp 和 3.temp,我们不希望后缀名为 temp 的文件被追踪,可是每次执行git status都会出现:
解决办法:在工作目录创建一个名为 .gitignore 的文件。
然后你发现 Windows 压根儿不允许你在文件管理器中创建以点(.)开头的文件。windows需要在命令行窗口创建(.)开头的文件。执行 echo *.temp > .gitignore 命令,创建一个 .gitignore 文件,并让 Git 忽略所有 .temp 后缀的文件:
好了,Git 已经忽略了所有的 *.temp 文件(你还可以把 .gitignore 文件也一并忽略)。
Git使用教程7---创建和切换分支
分支是什么?
假设你的大项目已经上线了(有上百万人在使用),过了一段时间你突然觉得应该添加一些新的功能,但是为了保险起见,你肯定不能在当前项目上直接进行开发,这时候你就有创建分支的需要了。
对于其它版本控制系统而言,创建分支常常需要完全创建一个源代码目录的副本,项目越大,耗费的时间就越多;而 Git 由于每一个结点都已经是一个完整的项目,所以只需要创建多一个“指针”(像 master)指向分支开始的位置即可。
创建分支
来到之前创建的项目MyProject2,
目前仓库的状态如下:
执行git status查看状态:
可以看到 README.md 文件被修改并添加到暂存区域(还没有提交),所以当前三棵树应该是这样:
创建分支,使用 git branch 分支名 命令:
没有任何提示说明分支创建成功(一般也不会失败啦,除非创建了同名的分支会提醒你一下),此时可以执行 git log --decorate 命令查看:
如果希望以“精简版”的方式显示,可以加上一个 --oneline 选项(即 git log --decorate --oneline),这样就只用一行来显示一个快照记录。
可以看到最新的快照后边多了一个 (HEAD -> master, feature)
它的意思是:目前有两个分支,一个是主分支(master),一个是刚才我们创建的新分支(feature),然后 HEAD 指针仍然指向默认的 master 分支。
所以目前仓库中的快照应该是这样:
切换分支
现在我们需要将工作环境切换到新创建的分支(feature)上,使用的就是之前我们欲言又止的 checkout 命令。执行 git checkout feature 命令:
这样 HEAD 指针就指向 feature 分支了:
现在我们进行一次提交(暂存区域还有一个更改的文件没有提交呢):
现在仓库中的快照应该是酱紫(提交的快照由当前HEAD指针指向的分支来管理):
然后我们将 HEAD 指针切回 master 分支:
细心的朋友会发现上一次对 README.md 文件的修改已经荡然无存了,这是因为我们的工作目录已经回到 master 分支的状态中:
现在对 README.md 文件进行修改(随便改改),然后执行 git commit -m "再次修改说明文件":
目前仓库中的快照应该变成了酱紫:
执行 git log --oneline --decorate --graph --all 命令:
--graph 选项表示让 Git 绘制分支图,--all 表示显示所有分支
Git使用教程9---合并和删除分支
合并分支
当一个子分支的使命完结之后,它就应该回归到主分支中去。
合并分支我们使用 merge 命令,执行 git merge feature 命令,将 feature 分支合并到 HEAD 所在的分支(master)上:
从 Git 提示的内容来看,我们知道这次的合并并没有成功,Git 说:
合并 README.md 文件的时候出现冲突。
所以自动合并失败;请修改冲突的内容并重新提交快照。
意思是说现在你需要先解决冲突的问题,Git 才能进行合并操作。所谓冲突,无非就是像两个分支中存在同名但内容却不同的文件,Git 不知道你要舍弃哪一个或保留哪一个,所以需要你自己来决定。
此时执行 git status 命令也会显示需要你解决的冲突:
然后 Git 会在有冲突的文件中加入一些标记,不信你打开 README.md 文件看看:
以“=======”为界,上到“<<<<<<< HEAD”的内容表示当前分支,下到“>>>>>>> feature”表示待合并的 feature 分支,之间的内容就是冲突的地方。
现在我们将 README.md 统一修改(去掉 <<<<<<< HEAD 等内容)
保存文件,然后提交快照:
执行 git log --decorate --all --graph --oneline 命令,可以看到此时的分支已经自动合并了:
当然,如果不存在冲突,就不用搞这么多了……举个例子:
执行 git checkout -b feature2 命令(相当于 git branch feature2 和 git checkout feature2 两个命令的合体):
在工作目录随便创建一个文本文件(feature2.txt)并提交快照:
执行 git log --decorate --all --graph --oneline 命令:
可以看到,feature2 分支比 master 分支快了一步。现在我们切换回 master 分支,并将 feature2 分支合并进来:
这次 Git 只显示了 Fast-forward(快进)这词儿,这是因为 feature2 这个分支的父结点是 master 分支,所以 Git 只需要简单移动 master 的指向即可。
执行 git log --decorate --all --graph --oneline 命令:
删除分支
删除分支,使用 git branch -d 分支名 命令:
执行 git log --decorate --all --graph --oneline 命令:
由于 Git 的分支原理实际上只是通过一个指针记载,所以创建和删除分支都几乎是瞬间完成。
注意:如果试图删除未合并的分支,Git 会提示你“该分支未完全合并,如果你确定要删除,请使用 git branch -D 分支名 命令。
彩蛋:Git的两种合并方式 --- Fast-forward 和 Three-way merge
Fast-forward
所谓的 Fast-forward 就是当待合并的分支位于目标分支的直接上游时,Git 只需把目标分支的指针直接移动即可实现合并。
比如下面图片中 master 分支是位于 feature2 分支的直接上游:
将 feature2 分支合并到 master 分支,只需要移动 master 的指针即可:
Three-way merge
如果待合并的两个分支不在同一条线上,那么进行合并就需要解决一个根本的问题 —— 冲突!
为何两个分支在同一条线上就不会冲突呢?
因为 Git 的快照是按时间顺序提交的,所以在同一条线上的两个快照,它们是有先后顺序的,尽管两者可能出现同名文件不同内容,Git 会认为这是“改变”而不是“冲突”。
举个例子:
合并 C3 和 C4 得到 C5,但 C5 应该如何处理“冲突”呢?
SVN 会把问题抛给用户,让用户自行解决;Git 则显得更为高明,它会找到第三个快照,然后综合三者特点自动解决冲突。
那第三个快照应该如何决定呢?
没错,应该找两者的共同“祖先”作为参照物,一比较就知道两个分支都干了些什么。
图片中 C3 和 C4 的共同祖先是 C1,可以看到 C3 和 C4 分别增加了 test2.py 和 test1.py 两个文件。因为对比之后发现三者并没有冲突,所以 C5 应该是三者的合体,即同时拥有 common.py、test1.py 和 test2.py 三个文件。
另外,值得一提的是,Git 这种合并方式也适用于同名文件的不同更改。
举个例子:
这里 C3 和 C4 都只有一个文件(test.txt),但是内容却不一样。如果这样合并,你猜 Git 会不会报“冲突”?
答案是不会的!
因为 Git 找到它们的共同祖先 C1,可以看到 C3 和 C4 都是在 C1 的基础上进行添加(C4 在第 2 行添加了“I”,C3 在第 4 行增加了“FishC”,C1 第 3 行的“love”是它们共同拥有的),同时在每一行并没有产生冲突的地方,所以自动合并后的 C5 是这样的:
# test.txt I love FishC
注意:如果 Git 检测到同一行有不同的内容,还是会报冲突并让你自行决定谁去谁留的!
Git使用教程10---匿名分支和checkout命令
匿名分支
创建一个新的文件夹(MyProject3),然后初始化 git。依次创建三个文件并提交(每创建一个文件提交一次):
上一节课我们讲的是使用 branch 命令创建一个分支,然后使用 checkout 命令切换分支。
如果在没创建分支的情况下执行 checkout 命令,会有怎样的事情发生呢?
我们不妨试试看,执行 git checkout HEAD~ 命令:
当前的 HEAD 指针处于分离状态,你可以环顾四周,做一些实验性修改并提交它们,并且你可以在这个状态中丢弃任何你所做的提交,而不影响任何分支,做法是执行 checkout 命令切换回别的分支。
如果你希望创建一个新分支,并保持你所做创建的提交,你可以(现在或稍后)通过使用带有 -b 选项的 checkout 命令实现。例如:
git checkout -b <new-branch-name>
HEAD 指针当前指向 52861cf... 2.txt
你使用了 checkout 命令但没有指定分支名,所以 Git 帮你创建了一个匿名分支,OK,既然是匿名,那么当你切换到别的分支时,这个匿名分支中的所有提交都会被丢弃掉(因为你都没给人家命名,反正以后都找不回了,不丢了干啥?)。因此,你可以利用匿名分支来做做实验什么的,反正不会有负面影响。
来,我们试试,在工作目录中创建一个 4.txt(你会发现 3.txt 不见了,这是因为 checkout 将环境切换到上一次提交了),然后提交
执行 git log --decorate --all --graph --oneline 命令
可以看到有两个分支,但 HEAD 所在的分支并没有名字(匿名分支),那现在我们把 HEAD 切回 master 分支:
警告:你残留一个没有连接任何分支的提交:
178d909 4.txt
如果你想通过创建一个分支来连接它,这将是一个好时机,请执行:
git branch <new-branch-name> 178d909
已经切换到 'master' 分支
现在再来查看提交日志,可以看到匿名分支已经荡然无存:
再论checkout
事实上呢,checkout 命令有两种功能:
- 从历史快照(或者暂存区域)中拷贝文件到工作目录
- 切换分支
从历史快照(或者暂存区域)中拷贝文件到工作目录
当给定某个文件名时,Git 会从指定的提交中拷贝文件到暂存区域和工作目录。比如执行 git checkout HEAD~ README.md 命令会将上一个快照中的 README.md 文件复制到工作目录和暂存区域中:
如果命令中没有指定具体的快照 ID,则将从暂存区域恢复指定文件到工作目录(git checkout README.md):
有些朋友可能会问:“上次看你在文件名的前边有加两个横杆(--),这次怎么就没有了呢?”
问得好,Git 提醒你写成 git checkout -- README.md 的形式,那是为了预防你恰好有一个分支叫做 README.md,那么它就搞不懂你要恢复文件还是切换分支了,所以约定两个横杆(--)后边跟的是文件名。
反过来说,如果你确保你没有一个叫做 README.md 的分支,你直接写 git checkout README.md 也是妥妥的没问题啦
切换分支
首先我们知道 Git 的分支其实就是添加一个指向快照的指针,其次我们还知道切换分支除了修改 HEAD 指针的指向,还会改变暂存区域和工作目录的内容。
所以执行 git checkout 373c0 命令,Git 主要就是做了下边这两件事(当然事实上 Git 还做了更多):
那回过头来,如果我们只想恢复指定的文件/路径,那么我们只需要指定具体的文件,Git 就会忽略第一步修改 HEAD 指向的操作,这不正跟之前讲 reset 命令的时候一样吗?
checkout 命令和 reset 命令的区别
恢复文件
checkout 命令和 reset 命令都可以用于恢复指定快照的指定文件,并且它们都不会改变 HEAD 指针的指向。
下面开始划重点:
它们的区别是 reset 命令只将指定文件恢复到暂存区域(--mixed),而 checkout 命令是同时覆盖暂存区域和工作目录。
注意:也许你试图使用 git reset --hard HEAD~ README.md 命令让 reset 同时覆盖工作目录,但 Git 会告诉你这是徒劳(此时 reset 不允许使用 --soft 或 --hard 选项)。
这样看来,在恢复文件方面,reset 命令要比 checkout 命令更安全一些。
恢复快照
reset 命令是用来“回到过去”的,根据选项的不同,reset 命令将移动 HEAD 指针(--soft) -> 覆盖暂存区域(--mixed,默认)-> 覆盖工作目录(--hard)。
checkout 命令虽说是用于切换分支,但前面你也看到了,它事实上也是通过移动 HEAD 指针和覆盖暂存区域、工作目录来实现的。
那问题来了:它们有什么区别呢?
下面开始划重点:
第一个区别是,对于 reset --hard 命令来说,checkout 命令更安全。因为 checkout 命令在切换分支前会先检查一下当前的工作状态,如果不是“clean”的话,Git 不会允许你这样做;而 reset --hard 命令则是直接覆盖所有数据。
另一个区别是如何更新 HEAD 指向,reset 命令会移动 HEAD 所在分支的指向,而 checkout 命令只会移动 HEAD 自身来指向另一个分支。
看文字你肯定懵,我们举例说明。
来,大家先把刚才的例子改成下边这样:
执行 git checkout feature 命令:
可以看到只是 HEAD 指针跑到 feature 分支那儿去了。
好,我们执行 git checkout master 命令将其切回。
现在执行 git reset --hard feature 命令:
彩蛋
通常情况下,Git 会尽可能地尝试使用 Fast-forward 方式来合并分支,因为这样效率非常高,只有在万不得已的时候才使用三方合并(Three-way merge)
但是,有时候 Fast-forward 的方式却很容易让我们忽略了分支的存在!
举个例子,比如下面这样:
如果我们把“feature”分支删除,就会直接丢掉分支的信息:
根本就不知道有个分支存在过……
可有时候我们确实希望保留分支的信息,应该怎么办呢?
只需要在 merge 的时候使用 --no-ff 选项即可告诉 Git 不要使用 Fast-forward 方式合并分支。
这样 Git 就会乖乖地使用三方合并(Three-way merge):
OK,这样就算删掉了“feature”分支,我们仍然可以很容易看出有个分支曾经存在过:
链接:https://www.jianshu.com/p/e57a4a2cf077
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。