参考资料:
起步
Git,分布式版本控制系统。
工作原理
Git 保存的是文件的快照。
- 它会保存当前全部文件的快照的索引,如果有修改的话,就创建修改后文件的快照,保存该快照的索引。如果当前没有修改该文件,就保存上一个版本的索引。
Cvs、Subversion 等其他版本控制系统存储的是每个文件的差异信息。
全球网络存储工业协会SNIA(StorageNetworking Industry Association)对快照(Snapshot)的定义是:关于指定数据集合的一个完全可用拷贝,该拷贝包括相应数据在某个时间点(拷贝开始的时间点)的映像。快照可以是其所表示的数据的一个副本,也可以是数据的一个复制品。
Git 文件有三种状态:已修改(modified)、 已暂存(staged) 和 已提交(committed)
- 已修改表示修改了文件,但还没保存到数据库中。
- 已暂存表示对一个已修改文件的当前版本做了标记,使之包含在下次提交的快照中。
- 已提交表示数据已经安全地保存在本地数据库中
这会让我们的 Git 项目拥有三个阶段:工作区、暂存区以及 Git 目录
查看所有的配置以及它们所在的文件
1 | git config --list --show-origin |
1 | C:\Users\LEN>git config --list --show-origin |
Git 基础
获取 Git仓库
通常有两种获取 Git 项目仓库的方式:
将尚未进行版本控制的本地目录转换为 Git 仓库;
进入需要版本控制的目录中(
cd /c/user/my_project
),执行git init
初始化 Git 仓库。然后通过git add
命令把相关文件添加到版本控制中,最后git commit
。(todo:这里还差一个链接&上传到远程仓库的操作)从其它服务器 克隆 一个已存在的 Git 仓库。
git clone <url>
:例如git clone https://github.com/libgit2/libgit2
查看仓库中所有文件的状态
git status
git status 命令的输出十分详细,但其用语有些繁琐 。可以使用 git status -s 命令或 git status —short 命令来获得更简洁的输出:
1 | git status -s |
说明:
README: 已修改未暂存。
Rakefile: 已暂存,暂存后又有修改,并且最后的修改还没暂存
lib/git.rb: 新添加到暂存区的文件
lib/simplegit.rb: 已暂存
LICENSE.txt: 未跟踪的文件
关于 git add 命令
它可以把新文件添加到版本控制系统中,也可以把已跟踪的文件放到暂存区,还能用于用于合并时把有冲突的文件标记为已解决状态等 。应该将这个命令理解为“精确地将内容添加到下一次提交中”而不是“将一个文件添加到项目中” 。
忽略文件
文件 .gitignore 的格式规范如下:
- 所有空行或者以 # 开头的行都会被 Git 忽略。
- 可以使用标准的 glob 模式匹配,它会递归地应用在整个工作区中。
- 匹配模式可以以(/)开头防止递归。
- 匹配模式可以以(/)结尾指定目录。
- 要忽略指定模式以外的文件或目录,可以在模式前加上叹号(!)取反。
glob 模式 :
- 星号(*)匹配零个或多个任意字符;
- [abc] 匹配任何一个列在方括号中的字符(这个例子要么匹配一个 a,要么匹配一个 b,要么匹配一个 c);
- 问号(?)只匹配一个任意字符;
- 如果在方括号中使用短划线分隔两个字符, 表示所有在这两个字符范围内的都可以匹配(比如 [0-9] 表示匹配所有 0 到 9 的数字);
- 使用两个星号(
**
)表示匹配任意中间目录,比如 a/**/z 可以匹配 a/z 、 a/b/z 或 a/b/c/z 等。
.gitignore 文件栗子:
1 | # 忽略所有的 .a 文件 |
如果子目录中有额外的 .gitignore 文件,那ta只作用于ta所在目录。
GitHub 有一个十分详细的针对数十种项找目及 到它。语言的 .gitignore 文件列表,你可以在 https://github.com/github/gitignore 找到ta。
针对 java项目的 .gitignore Demo:
1 | Compiled class file |
查看已暂存和未暂存的修改
git diff
命令显示具体修改了那些地方。
git diff --cached
查看已经暂存起来的变化
提交更新
每次提交前,记得先用 git status 看下,你所需要的文件是不是都已暂存起来了, 然后再运行提交命令 git commit。
默认情况下,用cmd执行 git commit 命令,git会打开默认编辑器,这时候编辑器会显示包含最后一次运行 git status 的输出,放在注释行里。如下:
1 | Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # # On branch master # Your branch is up to date with 'origin/master'. # # Changes to be committed: # new file: test.txt # # Changes not staged for commit: # modified: "Git\351\232\217\347\254\224.md" # |
另外开头还有一个空行,供你输入提交说明 。
退出编辑器时,Git 会丢弃注释行,用你输入的提交说明生成一次提交。
当然,你也可以直接一点,在 commit 命令后添加 -m 选项,直接将提交信息与命令放在同一行:git commit -m "commit msg"
。这样就不用弹出编辑器了。
给 git commit 加上 -a 选项,Git 会自动的把跟踪过的文件暂存起来(未跟踪过的新文件不会暂存!),这样就不用 git add
了。
删除文件
删除 Git 中的一个文件 test.txt 。可以有两种操作方式:
- 从磁盘中删除该文件。然后执行
git rm test.txt
。 - 【推荐】直接执行
git rm test.txt
,Git 会删除磁盘中的 test.txt。
- 从磁盘中删除该文件。然后执行
忘记改 .gitignore 文件导致一大堆数据添加到了暂存区或者 Git仓库。现在想要让这些文件都保存到本地,但是不想让 Git 继续跟踪,可以使用
--cached
选项:git rm --cached README
git rm 命令后面可以使用 glob 模式来匹配文件:
1
git rm log/\*.log # 此命令删除 log/ 目录下扩展名为 .log 的所有文件
注意到星号 * 之前的反斜杠 \, 因为 Git 有它自己的文件模式扩展匹配方式,所以我们不用 shell 来帮忙展开
移动/重命名文件
重命名:git mv test1.txt test2.txt
移动:git mv test1.txt test/
把当前目录下的 test1.txt 文件移动到当前目录的 test 子目录下。
运行 git mv 就相当于运行了下面三条命令:
1 | mv README.md README |
查看提交历史
git log -p -2 --pretty=format:"%h - %an, %ar : %s"
git log —pretty=format 常用的选项:
选项 说明
选项 | 说明 |
---|---|
%H | 提交的完整哈希值 |
%h | 提交的简写哈希值 |
%T | 树的完整哈希值 |
%t | 树的简写哈希值 |
%P | 父提交的完整哈希值 |
%p | 父提交的简写哈希值 |
%an | 作者名字 |
%ae | 作者的电子邮件地址 |
%ad | 作者修订日期(可以用 —date=选项 来定制格式) |
%ar | 作者修订日期,按多久以前的方式显示 |
%cn | 提交者的名字 |
%ce | 提交者的电子邮件地址 |
%cd | 提交日期 |
%cr | 提交日期(距今多长时间) |
%s | 提交说明 |
作者指的是实际作出修改的人,提交者指的是最后将此工作成果提交到仓库的人。 所以,当你为某个项目发布补丁,然后某个核心员将你的补丁并入项目时,你就是作者,而那个核心成员就是提交者。 我们会在分布式 Git 再详细介绍两者之间的细微差别。
撤销操作
修正提交命令 amend
git commit --amend
或 git commit --amend “commit msg”
可以修正暂存区的提交,让旧的提交好像没有发生过一样。
应用场景:提交完之后发现提交信息错了,或者提交完之后漏了几个文件。这样我们可以去修正暂存区的提交
问题:如果我暂存区的提交有多个,那么,这个指令会把所有提交都覆盖吗?还是只会修正最后一个提交?
答案:amend 只会修正最后一个提交。
取消暂存的文件
git reset HEAD <fileName>
还得看一下【重置揭秘】这一章来补充这里的 reset 命令。
用最近提交的版本来覆盖你对文件的修改
git checkout -- <fileName>
使用远程仓库
查看远程仓库
git remote
:列出每一个远程仓库的简写。注意,git clone
会把克隆下来的远程仓库创建默认简写“origin”。
git remote -v
:显示 git 保存的简写和对应的URL
向本地 git 添加远程仓库
git clone <URL>
git remote add <shortname> <URL>
:添加一个新的远程仓库同时指定一个简写(shortname)
从远程仓库中抓取和拉取
git fetch <remote>
:从远程仓库中拉取所有你还没有的数据 。执行完成后,你将会拥有那个远程仓库中所有分支
的引用,可以随时合并或查看。 它并不会自动合并或修改你当前的工作。
git pull
:如果你的当前分支设置了跟踪远程分支 ,ta会自动抓取后合并该远程分支到当前分支 。
推送到远程仓库
git push <remote> <branch>
当你和其他人在同一时间克隆,他们先推送到上游然后你再推送到上游,你的推送就会毫无疑问地被拒绝。 你必须先抓取他们的工作
并将其合并进你的工作后才能推送。
查看某个远程仓库
git remote show <remote>
本地git中远程分支的删除和别名重命名
重命名:git remote rename 原名 新名称
删除:git remote remove 分支别名
打标签
Git 可以给某个提交打上标签,以表示这个提交的重要性和代表性。一般人们都会用来标记发布节点,如 v1.0、v2.0等。
列出标签 git tag
1 | git tag -l "v1.8.5*" |
创建标签
Git 支持两种标签:
轻量标签(lightweight):本质上是将提交【校验和】存储到一个文件中(没有保存任何其他信息)。
创建命令:
git tag <tagName>
(推荐)附注标签(annotated):包含打标签者的名字、电子邮件地址、日期时间、标签信息。
创建命令:
git tag -a <tagName> -m "description"
git show
命令可以看到标签信息和与之对应的提交信息
对过去的提交打标签
git tag -a <tagName> 检验和(或部分校验和)
eg. git tag -a v1.2 9fceb02
共享标签
默认情况下。git push
不会传送标签到远程仓库上。你需要使用命令:git push origin <tagname>
。
如果要推送多个标签到远程仓库中,可以使用 git push origin --tags
,它会把本地所有的标签都推送上去。
删除标签
git tag -d <tagname>
删除本地标签(此时远程仓库中标签还在)git push origin --delete <tagname>
更新远程仓库标签
检出标签
git checkout <tagName>
查看某个标签所指向的文件版本
注意:如果你检出之后,又做了某些更改然后提交它们,标签不会发生变化, 但你的新提交将不属于任
何分支,并且将无法访问,除非通过确切的提交哈希才能访问。 因此,如果你需要进行更改,比如你要修复旧
版本中的错误,那么通常需要创建一个新分支:
1 | git checkout -b version2 v2.0.0 |
Git 别名
git config
命令可以为每一个命令设置一个别名。
1 | git config --global alias.co checkout |
当要输入 git commit 时,只需要输入 git ci。
我们还可以利用这个机制创建自定义命令:
1 | git config --global alias.unstage 'reset HEAD --' |
1 | git config --global alias.last 'log -1 HEAD' |
分支
Git 是如何保存数据的?
git 保存的是文件不同时刻的快照。在进行提交的时候,git会保存一个提交对象。该对象包含一个指向快照的指针、作者的姓名和邮箱、提交时输入的信息、以及指向父对象的指针。
首次提交产生的提交对象没有父对象,普通提交操作产生的提交对象有一个父对象, 而由多个分支合并产生的提交对象有多个父对象。
Git 的分支,其实本质上仅仅是指向提交对象的可变指针。 Git 的默认分支名字是 master。git init
命令默认创建 master 分支。
创建分支
git branch <branchName>
切换分支
git checkout branchName
如果是切换到一个较旧的分支,你的工作目录会恢复到该分支最后一次提交时的样子。如果 Git 不能干净利落地完成这个任
务,它将禁止切换分支。
创建新分支的同时切换过去
git checkout -b <newbranchname>
分支合并
git merge <branchName>
设置好跟踪分支后,可以用简写@{upstream} 或 @{u} 来代替上游分支:如使用 git merge @{u}
来取代 git merge origin/master
。
删除分支
git branch -d <branchName>
遇到冲突时的分支合并
执行 git merge,有时候会发生冲突。这时候 Git 没有自动的创建一个合并提交,它会暂停下来,等我们自己解决冲突。
可以用 git status
来查看冲突的文件:
1 | git status |
冲突文件中的 <<<<<<< HEAD:
标注表示该部分是当前分支代码,=======
下面的表示要合并分支的代码。
修改完冲突文件之后,可以使用 git add
命令来暂存这些文件,这时候 Git 也会将这些文件标记为已解决。
合并完成后记得删除无用分支 git branch -d <branchName>
分支管理
git branch --merged(或 --no-merged)
查看已经合并或尚未合并到当前分支的分支。
git branch --merged dev
查看已经合并到 dev 分支的分支。
远程分支
当你执行 git clone <URL>
命令的时候,Git 会从远程仓库中拉取ta的所有数据,并为这个远程仓库创建默认别名 origin
。Git也会在你本地仓库创建一个或者多个(与远程仓库对应的)远程分支,这些远程分支通常会这样命名:origin/master
。Git 还会给你创建一个本地 master 分支,ta 和origin/master
远程分支是指向同一个提交对象的。
在本地仓库中的远程分支是不可修改的,可以通过 git checkout -b <branch> <remote>/<branch>
来检出远程分支代码到本地,这样就可以修改了。
git fetch <remote>
命令用来更新位于本地仓库中的远程分支。
推送
创建一个本地分支,如果你不显式地推送到 Git服务器,那别人就永远不会看到ta,ta就是你的私有分支。
推送分支命令:git push <remote> <branch>
如果并不想让远程仓库上的分支叫做 serverfix,可以运行 git push origin serverfix:awesomebranch
来将本地的 serverfix 分支推送到远程仓库上的 awesomebranch 分支。
跟踪分支
从一个远程分支检出一个本地分支的时候,Git 会自动创建所谓的跟踪分支。如果在一个跟踪分支上 git pull
,Git 能自动地识别去哪个服务器上抓取、合并到哪个分支。
拉取
git fetch
命令从服务器上拉取本地没有的数据,然后让你自己合并。
git pull
会从服务器中找到当前分支跟踪的远程分支数据,并尝试合并,相当于 git fetch + git merge
。
删除远程分支
git push origin --delete serverfix
基本上这个命令做的只是从服务器上移除这个指针。 Git 服务器通常会保留数据一段时间直到垃圾回收运行,所以如果不小心删除掉了,通常是很容易恢复的。
变基
从A分支变基到B分支。就是从A和B的共同祖先开始,将A分支的修改应用到B分支上。
操作指令:
1 | git checkout A |
变基是为了确保在向远程分支推送时能保持提交历史的整洁,ta和三方合并的结果是一样的,只不过提交历史不同罢了。变基是将一系列提交按照原有次序依次应用到另一分支上,而合并是把最终结果合在一起。
特殊的变基例子:
从master分出A分支,提交一个版本到A,然后再从A分出一个分支B,提交B1、B2版本到B分支。现在想从B分支变基到到master分支:
1 | git rebase --onto master A B |
变基有风险:
如果你只对不会离开你电脑的提交执行变基,那就不会有事。 如果你对已经推送过的提交执行变基,但别人没有基于它的提交,那么也不会有事。 如果你对已经推送至共用仓库的提交上执行变基命令,并因此丢失了一些别人的开发所基于的提交, 那你就有大麻烦了,你的同事也会因此鄙视你。
分布式 Git
1 | git push origin master |
上方输出信息中最后一行显示的是推送操作执行完毕后返回的一条很有用的消息。 消息的基本格式是 <oldref>..<newref> fromref -> toref
, oldref 的含义是推送前所指向的引用, newref 的含义是推送后所指向的引用, fromref 是将要被推送的本地引用的名字, toref 是将要被更新的远程引用的名字。在后面的讨论中你还会看到类似的输出消息,所以对这条消息的含义有一些基础的了解将会帮助你理解仓库的诸多状态。
一些问题
.gitignore规则不生效的解决办法
把某些目录或文件加入忽略规则,按照上述方法定义后发现并未生效,原因是.gitignore只能忽略那些原来没有被追踪的文件,如果某些文件已经被纳入了版本管理中,则修改.gitignore是无效的。那么解决方法就是先把本地缓存删除(改变成未被追踪状态),然后再提交:
git rm -r —cached .
git add .
git commit -m ‘commit detail msg’
Git 在什么时候创建快照?(add? commit? push?)
关于创建一个git仓库的思考:(未解决)
能不能直接在我工作的目录中执行 git init,然后push目录中的文件?而不是通过在gitee创建一个项目,pull 下来,再把文件塞到项目中,再push上去。有这种方法么?
怎么查看暂存区里面提交?
总结
Git 会保存当前全部文件的快照的索引,如果有修改的话,就创建修改后文件的快照,保存该快照的索引。如果当前没有修改该文件,就保存上一个版本的索引。
Git 中所有的数据在存储前都计算校验和,然后以校验和来引用。 这意味着不可能在 Git 不知情时更改任何文件内容或目录内容。
暂存操作:为每一个文件计算校验和(使用我们在 起步 中提到的 SHA-1 哈希算法),然后会把当前版本的文件快照保存到Git 仓库中 (Git 使用 blob 对象来保存它们),最终将校验和加入到暂存区域等待提交:
提交操作: Git 计算每个子目录的校验和,然后在 git 仓库中将这些校验和保存为树对象,然后创建一个提交对象。提交对象包含一个指向快照的指针、作者的姓名和邮箱、提交时输入的信息、指向父对象的指针,以及指向这个树对象的指针。
merge:三方合并(要明白这句话的意思)
从正式分支拉一个开发分支的操作
创建新分支的同时切换过去 git checkout -b <newbranchname>
,然后推送到远程服务器中:git push origin testBranch
git diff master…contrib
该命令仅会显示自当前主题分支与 master 分支的共同祖先起,该分支中的工作。 这个语法很有用,应该牢
记。
想法
总结:学过了,但是大部分没记住,用的时候还需要翻查。后面还是得安排时间复习几遍。
感觉一整天的学习学习效率比一个星期零散的学习效率要高很多。