学习来自于:
女朋友乱用Git,差点把我代码删了。。。
一些常用的Git 知识点整理
关于Git这一篇就够了
Git基本命令大全
30分钟精通Git,学不会来找我
Git 版本管理 | 莫烦PYTHON
Git 代码版本管理教程
Git 是目前为止最好用的分布式版本控制系统。
大名鼎鼎的 github 用的就是 git 系统来管理它们的网站,这里需要区分一下,github 和 git 是两个东西,github 是一个社区,git 是一个服务系统,github 只支持 git 分布式系统,所以故名成为 github。
除了 git 还有 svn、cvs 这样的版本控制系统,它们的区别在于一个是分布式一个是集中式
集中式就是 svn 和 csv 这样的版本控制系统,分布式是 git
区别在于集中式的版本控制系统每次在写代码时都需要从服务器中拉取一份下来,并且如果服务器丢失了,那么所有的就都丢失了,你本机客户端仅保存当前的版本信息,换句话说,集中式就是把代码放在一个服务器上集中管理,你的所有回滚等操作都需要服务器的支持。
分布式的区别在于,每个人的电脑都是服务器,当你从主仓库拉取一份代码下来后,你的电脑就是服务器,无需担心主仓库被删或者找不到的情况,你可以自由在本地回滚,提交,当你想把自己的代码提交到主仓库时,只需要合并推送到主仓库就可以了,同时你可以把自己的代码新建一份仓库分享给其它人。
像集中式它们都有一个主版本号,所有的版本迭代都以这个版本号为主,而分布式因为每个客户端都是服务器,git 没有固定的版本号,但是有一个由哈希算法算出的 id,用来回滚用的,同时也有一个 master 仓库,这个仓库是一切分支仓库的主仓库,我们可以推送提交到 master 并合并到主仓库上,主仓库的版本号会迭代一次,我们客户端上的 git 版本号无论迭代多少次,都跟 master无关,只有合并时,master 才会迭代一次。
工作区(Working Directory) 就是在电脑里能看到的目录,比如 xxx-master 文件夹就是一个工作区。
版本库(Repository)工作区有一个隐藏目录 .git
,是 Git 的版本库。
在版本库中标记为 index 的区域为暂存区,标记为 master 的是 Git 为我们自动创建的第一个分支,代表的是目录树。
此时HEAD实际是指向master分支的一个“游标”,所以图示的命令中出现HEAD的地方可以用 master来替换
图中的 objects 标识的区域为 git 的对象库,实际位于.git/objects
目录下。
Workspace:开发者工作区,也就是你当前写代码的目录,它一般保持的是最新仓库代码。
Index / Stage:缓存区,最早叫 Stage,现在新版本已经改成 index,位于 .git 目录中,它用来存放临时动作,比如我们做了 git add
或者 git rm
,都是把文件提交到缓存区,这是可以撤销的,然后在通过 git commit
将缓存区的内容提交到本地仓库
Repository:仓库区,是仓库代码,你所有的提交都在这里,git 会保存好每一个历史版本,存放在仓库区,它可以是服务端的也可以是本地的,因为在分布式中,任何人都可以是主仓库。
Remote:远程仓库,只能是别的电脑上的仓库,即服务器仓库。
远程仓库副本,可以理解为存在于本地的远程仓库缓存。如需更新,可通过 git fetch/pull 命令获取远程仓库内容。使用 fech获取时,并未合并到本地仓库,此时可使用 git merge 实现远程仓库副本与本地仓库的合并。git pull 根据配置的不同,可为 git fetch + git merge 或 git fetch + git rebase。
git add test.c
:将文件添加到缓存区
使用 git add 命令将文件添加到本地仓库的提交缓存,这个时候还不算添加到了本地仓库,我们还需要使用 git commit
命令为其添加修改的描述信息
注意在使用 git commit
时我们只需要简单描述一下我们做了什么,不要像写注释那样写一大堆,不然将来在回滚代码或者查看历史版本时,很难审阅。
我们需要使用 -m
命令来简写描述我们的信息,如果不使用 -m
,会调用终端的注释编辑器让你输入描述信息,但是不建议使用,因为注释编辑器比较难用,不舒服。
git commit -m "add new file \"test.c\""
:提交到本地仓库
git commit
会为我们生成 40 位的哈希值,用于作为 id,并把刚刚用 git add 添加到提交缓存区里的文件提交到本地仓库中,便于我们回滚,至此,这个文件就已经添加到本地仓库中了,同时本地仓库也迭代了一个版本。
git add 的灵活使用
git add [file1] [file2] ...
添加一个或多个文件到暂存区:
git add [dir]
添加指定目录到暂存区,包括子目录:
git add .
添加当前目录下的所有文件到暂存区:
git add 和 git commit 两个步骤是否可以一步到位
git commit -a -m [message]
等价于
git add .
git commit -m [message]
更进一步简写为 git commit -am [message]
git commit --amend
:重写上一次的提交信息
我们提交了仓库,但是发现写错了,我们可以使用 --amend 长命令选项来改写提交
git commit --amend -m
“提交描述” 修改 comment。
git commit --amend 会把暂存区的文件自动加入,可以使用 -a 把工作区的文件也一起加入。
如果有一个新的文件修改,也想提交到上一个commit中(不改动描述信息)
git add .
git commit --amend --no-edit
注意:这里的之前指最近的 commit,而且没有 push 到远程。
第一行的 commit 是哈希算法算出的 id,正如一开始所说,分布式是没有一个主版本号的,它们都是用 id 来做标志的,同时用master 作为主仓库,其它的分支怎么迭代都不会影响到 master
head->master
代表这次提交到 master 主仓库,如果是 head->分支仓库则代表提交到分支仓库
如果觉得 log 打印内容过多,git log --pretty=oneline
可以 简洁输出
查看某个人或者某几个人的提交
git log --author="John"
git log --author="John\|Mary"
git reset --soft id # 就仅仅将头指针恢复,已经add的暂存区以及工作空间的所有东西都不变。
git reset --mixed id # 就将头恢复掉,已经add的暂存区也会丢失掉,工作空间的代码什么的是不变的。
git reset --hard id # 那么一切就全都恢复了,头变,add 的暂存区消失,代码什么的也恢复到以前状态。
回滚到指定历史版本
git reset --hard id
回滚当前仓库指向的版本
git reset --hard HEAD^
^代表上一个版本的意思,HEAD代表当前仓库的指向,当前HEAD指向master,就代表回滚到master上一次提交的版本
git reset --hard HEAD~3
后面的 ~ 3,代表以当前版本为基数,回滚多少次。HEAD~3 代表回滚 master 前三个版本
使用 git remote
可以查看当前仓库名称
A:未修改
AM:修改
Untracked:未提交
modified:新文件,但未提交
我们使用 git add
添加到缓存区的内容后,我们在修改这个文件时,它跟缓冲区内容是没有任何关系的!我们使用 git commit
提交的时,它只会提交缓存区内容
如果想提交第二次修改,我们只需要再 git add
一次,然后在使用 git commit
提交就可以了,git会 自动帮我们合并提交
平时我们可能写代码的时候不可能保证只改动了一个文件,我们切来切去最后都不知道自己改了哪些文件,为了保证所有的文件都能被准确提交,我们可以使用 git add
我们确定修改的文件,当 git add
后在使用 git status
查看一下状态,看看是否有遗漏没有提交的文件
如果你实在不确信哪些文件是改动过的,你只需要使用 git add --all
这个命令会将当前目录下包括子目录下所有改动的文件提交到暂存区,注意只包括改动的文件,不改动的不会放到缓存区。
这个命令还会把删除的文件也提交进去
如你在本地删除了min.c 这个命令会把删除信息也记录进去,然后在提交的时候把仓库里对应的 min.c 也删除掉,也就是说你在本地做的删除操作会被记录,提交仓库时会删除同样的文件,如果不想删除文件,可以使用 git add .
,注意后面有一个“.” 点的符号,这个命令跟 git add --all
一样,但是不会记录删除操作。
最后别忘了 git commit
checkout:切换参数,通常用来切换分支仓库
注意这个恢复只能恢复到上一次提交的状态,如你刚提交了这个文件到仓库,随后你修改了它,那么使用这个命令只会回到刚刚提交后的那个状态里,不能回到你还没有提交,但修改的状态中。
注意这个功能不能一直迭代恢复,如你恢复到了修改前的版本,你想再次回滚回滚到修改前在之前的版本是不行的。
当我们想回滚指定文件到指定版本时,需要查看该文件有多少个版本可以回滚时,可以使用 git log filename
命令
即便你更新了一个文件,也会生成一个新的历史版本,注意历史版本里只包含了你更新的文件,你刚刚只 add了min.c 文件,所以新的历史版本里只有更新 min.c 文件,你当前的工作其它文件没有在这个历史版本里。
git reflog
可以查看当前版本库的提交历史,凡是对仓库版本进行迭代的都会出现在这个里面,包括你回滚版本都会出现在这个历史中
如果我们使用普通的命令,rm 删除文件,git 状态会提示你删除了文件,你只需要使用 add / commit 重新提交一次就可以了。
当然你也可以使用 git rm
删除文件,但是也需要使用 git commit 提交一次
此方法仅限 git rm,因为 git rm 会先将文件放入缓存区,且没有使用 commit 提交的情况下
git rm d.c # 使用git rm删除一个文件
git reset # 重置所有缓存区操作
git checkout d.c # 将文件取消操作
可以看到文件又恢复了
使用 git checkout -b
参数来创建一个分支,创建完成分支后会自动切换过去
git checkout -b dev
然后我们在使用 branch 来查看当前属于哪个分支,也就是查看HEAD的指向
git branch
git checkout -b
等价于
git branch dev
git checkout dev
git branch
如果后面跟着名字则会创建分支,但不会切换
git checkout
后面如果是分支名称则切换过去
如果要查看当前所有分支可以使用:git branch -a
git branch -m 分支名 新的分支名
当我们想切换分支可以使用 git checkout
来切换
git checkout
的作用是检出,如果是文件的话,会放弃对文件的缓存区操作,但是要使用 reset 重置一下变更才行。
如果是分支的话会切换过去。
当我们在本地进行开发时,有时会发现有些分支看不见
可以使用 git fetch 把远程全部分支拉取下来,同时也包括这些分支的仓库版本,log 日志等,这个操作不会进行合并。
也可以拉取指定分支的最新内容:
git fetch xxxx
git diff branch1 branch2 --stat //--stat参数,显示两分支简单diff信息
git diff branch1 branch2 //显示两分支详细的diff信息
git diff branch1 branch2 path //显示两分支指定路径下文件的详细diff信息
git diff branch1 branch2 file_name(带路径) //显示两分支指定文件的详细diff信息
注:这里的分支可以是本地的,也可以是远程的(git diff branch1 origin/branch2)
Git冲突:Please commit your changes or stash them before you merge
git stash
git pull
git stash pop
通过 git stash 将工作区恢复到上次提交的内容,同时备份本地所做的修改,之后就可以正常 git pull 了,git pull 完成后,执行 git stash pop 将之前本地做的修改应用到当前工作区。
git stash: 备份当前的工作区的内容,从最近的一次提交中读取相关内容,让工作区保证和上次提交的内容一致。同时,将当前的工作区内容保存到 Git 栈中。
git stash pop: 从 Git 栈中读取最近一次保存的内容,恢复工作区的相关内容。由于可能存在多个 Stash 的内容,所以用栈来管理,pop 会从最近的一个stash中读取内容并恢复。
git stash list: 显示 Git 栈内的所有备份,可以利用这个列表来决定从那个地方恢复。
git stash clear: 清空 Git 栈。此时使用 gitg 等图形化工具会发现,原来 stash 的哪些节点都消失了。
git stash show 查看栈中最新保存的 stash 和当前目录的差异。
注意 stash 是以栈的方式保存的,先进后出。
一般情况下,我们在修改代码时,突然来了一个新的需求,让我们先去做这个需求,但是此时我们正在写的代码还没有完成,是不可以提交的,所以我们先使用 git stash 保存当前工作状态,在拉取一个分支去这个分支里面干活,干完活之后回到之前的分支,在将工作内容恢复出来继续干活
准确来说,这个命令的作用就是为了解决 git 不提交代码不能切换分支的问题。
当我们新建分支并做完工作之后,想要把分支提交至 master,只需要切换到 master 仓库,并执行 git merge 分支名就可以了
如我们在分支中新建了一个 f.c 和 test.c 的文件
然后在使用 git checkout master 切换到 master
在使用 git merge dev 将其合并
这里需要说一点,如果你在任何分支下创建文件,没有提交到仓库,那么它在所有仓库都是可见的,比如你在分支 dev 中创建了一个文件,没有使用 git add 和 git commit 提交,此时你切换到 master,这个文件依旧存在的,因为你创建的文件在工作目录中,你切换仓库时 git 只会更新跟仓库有关的文件,无关的文件依然存放在工作区。
同时我们可以看到历史版本中有分支提交的历史
git branch --delete 分支名
git branch -d 分支名 # -d 是 --delete 的缩写,该分支必须完全和它的上游分支 merge 完成,如果没有上游分支,必须要和 HEAD 完全 merge
git branch -D 分支名 # -D 是 --delete --force 的缩写,跳过检查 merge 状态,直接 delete
git push origin --delete 远程分支名
本小节介绍常用分支,以及创建合并删除的基础操作
在开发中 git 分支的重要性
当我们在开发中,无论做什么操作都建议使用分支,因为在团队开发中,master 只有一个,合作开发里任何人都可以从 master 里拉取代码,拉取时 master 后创建分支,分支名改为你要做的操作,比如修改某某文件,修改什么什么 bug,单词以下划线做分割,然后在提交一个版本
分支名必须简洁,和标题一样,提交的 commit 在简单描述一下就可以了。
如我们的 master 中有个 bug,是内存泄漏
我们可以常见一个分支名为 Memory_Leak, 然后在 commit 里简单描述一下修复了哪个模块的内存泄漏,不要写修复了什么什么代码,什么什么问题导致的,只需要简单描述一下就可以了。
一般情况下,我们都是拉取master后,想要修改功能或者添加功能,都是创建分支,在分支里修改不影响master,如果修改错了代码或者误删之类的,在从master上拉取一份就可以了。
主分支只用来发布重大版本,日常开发应该在另一条分支上完成。我们把开发用的分支,叫做 Develop
Git 创建 Develop 分支的命令:
git checkout -b develop master
将 Develop 分支发布到Master分支的命令:
# 切换到Master分支
git checkout master
# 对 Develop 分支进行合并
git merge --no-ff develop
默认情况下,Git 执行"快进式合并"(fast-farward merge),会直接将 Master 分支指向 Develop 分支。
使用 --no-ff 参数后,会执行正常合并,在 Master 分支上生成一个新节点。为了保证版本演进的清晰,我们希望采用这种做法。
git checkout -b feature-x develop
合并到 develop 分支
git checkout develop
git merge --no-ff feature-x
它是指发布正式版本之前(即合并到 Master 分支之前),我们可能需要有一个预发布的版本进行测试。
创建
git checkout -b release-1.2 develop
合并到 master
git checkout master
git merge --no-ff release-1.2
# 对合并生成的新节点,做一个标签
git tag -a 1.2
再合并到 develop 分支
git checkout develop
git merge --no-ff release-1.2
最后,删除预发布分支
git branch -d release-1.2
软件正式发布以后,难免会出现 bug。这时就需要创建一个分支,进行 bug 修补。
创建
git checkout -b fixbug-0.1 master
修补结束后,合并到 master 分支
git checkout master
git merge --no-ff fixbug-0.1
git tag -a 0.1.1
再合并到 develop 分支:
git checkout develop
git merge --no-ff fixbug-0.1
最后,删除"修补 bug 分支"
git branch -d fixbug-0.1
git diff xxx.c
git status .
git checkout media/
git checkout framework/services/drive/
Please commit your changes or stash them before you merge
git stash
git pull
git stash pop
Git 是一个分布式版本控制系统. 它的灵活性, 优越性使得它从2005年发布以来. 获得了越来越多的使用和支持.
1)什么时候需要用 Git?
2)什么文件可以被 Git 管理?
3)什么文件不能被 Git 管理?
windows 系统中下载安装好 git
后,我们使用 git bash
,git bash
是 git 在 Windows 上为了方便使用所设置的一个 Unix 的环境. 如果你是 Windows 用户, 之后的教程你也能用这个来学习使用 git.
git中文件状态转换关系如下图:
图片来源:https://morvanzhou.github.io/tutorials/others/git/2-1-repository/
stage the file 是 add
针对本地的一个项目,如果不是从github上下载,则所有文件一开始都属于untracked
状态,否则都属于tracked
状态。
untracked
unmodified
modified
staged
新建一个文件夹,然后打开 git
把 git
的当前目录指向该文件夹,eg cd Desktop/git_test/
为了更好地使用 git, 我们同时也记录每一个施加修改的人. 这样人和修改能够对应上. 所以我们在 git
中添加用户名 user.name
和 用户 email user.email
:
git config --global user.name "Bryant"
git config --global user.email "[email protected]"
可以通过如下指令来查看用户信息
git config user.name
结果为 Bryant
git config user.email
然后我们就能在这个文件夹中建立 git 的管理文件了:
git init
通过 ls -a
可以查看管理库的信息(-a 可以查看隐藏的文件夹)
用 git status
来查看版本库的状态:
建立一个新的文件,eg 1.py
,用 touch
,哈哈,参考 Linux learning notes
touch 1.py
现在我们能用 git status
来查看版本库的状态:
git status
output 如下
On branch master # 在 master 分支
No commits yet
Untracked files:
(use "git add ..." to include in what will be committed)
1.py # 1.py 文件没有被加入版本库 (unstaged)
nothing added to commit but untracked files present (use "git add" to track)
现在 1.py
并没有被放入版本库中 (unstaged
), 所以我们要使用 add
把它添加进版本库 (staged
):
git add 1.py
然后再查看状态
git status
output
On branch master
No commits yet
Changes to be committed:
(use "git rm --cached ..." to unstage)
new file: 1.py
我们已经添加好了 1.py
文件, 最后一步就是提交这次的改变, 并在 -m 自定义这次改变的信息:
git commit -m "create 1.py"
output
[master (root-commit) 93d6b41] create 1.py
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 1.py
整个上述过程可以被这张 git 官网上的流程图直观地表现:(untracked、unstaged、staged)
unmodified(window 的 git bash 下显示红色) 和 modified(window 的 git bash 下显示绿色)都为 unstaged 状态,unmodified 到 modified 可通过 add 操作!!!
在 git 中, 每一次提交(commit)的修改, 都会被单独的保存起来. 也可以说 git 的中的所有文件都是一次次修改累积起来的. 文件好比楼房, 每个 commit 记录 了盖楼需添加或者拿走的材料. 整个施工过程也被记录了下来.
之前我们以 Bryant
的名义对版本库进行了一次修改, 添加了一个 1.py 的文件. 接下来我们就来查看版本库的些施工的过程. 可以看到在 Author 那已经有我的名字和 email 信息了.
git log
a = 1
然后我们就能在 status
中(git status
)看到修改还没被提交的信息了.
所以我们先把这次修改添加 (add) 到可被提交 (commit) 的状态, 然后再提交 (commit) 这次的修改:
git add 1.py
git commit -m "change 1"
再次查看 log
, 现在我们就能看到 create 1.py
和 change 1
这两条修改信息了. 而且做出这两条 commit
的 ID, 修改的 Author
, 修改 Date
也被显示在上面.
git log
如果删除一部分代码, 也会被记录上, 比如把 a = 1
改成 a = 2
, 再添加一个 b = 1
.
如果想要查看这次还没 add
(unstaged) 的修改部分 和上个已经 commit
的文件有何不同, 我们将使用
git diff
output
diff --git a/1.py b/1.py
index 1337a53..ff7c36c 100644
--- a/1.py
+++ b/1.py
@@ -1 +1,2 @@
-a = 1
+a = 2
+b = 1
git add . # add 全部修改文件
git status
用 git diff --cached
来查看添加后的代码与上次 commit 提交的区别
git diff --cached
output
diff --git a/1.py b/1.py
index 1337a53..ff7c36c 100644
--- a/1.py
+++ b/1.py
@@ -1 +1,2 @@
-a = 1
+a = 2
+b = 1
还有种方法让我们可以查看 add
过 (staged) 和 没 add
(unstaged) 的修改, 比如我们再修改一下 1.py
但不 add
:
a = 2
b = 1
c = b
目前 a = 2
和 b = 1
已被 add
, c = b
是新的修改, 还没被 add
.
git diff HEAD
staged & unstagedgit diff
unstagedgit diff --cached
staged# 对比三种不同 diff 形式
$ git diff HEAD # staged & unstaged
@@ -1 +1,3 @@
-a = 1 # 已 staged
+a = 2 # 已 staged
+b = 1 # 已 staged
+c = b # 还没 add 去 stage (unstaged)
-----------------------
$ git diff # unstaged
@@ -1,2 +1,3 @@
a = 2 # 注: 前面没有 +
b = 1 # 注: 前面没有 +
+c = b # 还没 add 去 stage (unstaged)
-----------------------
$ git diff --cached # staged
@@ -1 +1,2 @@
-a = 1 # 已 staged
+a = 2 # 已 staged
+b = 1 # 已 staged
哈哈,绕来绕去的还是很麻烦的,建议还是每次修改后(modified 红色,unstaged),直接 add(modified 绿了,staged),然后 commit,再用 git diff --cached
查看与已 commit 的区别!
我们把如上的修改 add 然后 commit ,方便后续实验!
git add .
git commit -m "change 2"
有时候我们总会忘了什么, 比如已经提交了 commit
却发现在这个 commit
中忘了附上另一个文件. 接下来我们模拟这种情况. 上节内容中, 我们最后一个 commit
是 change 2
, 我们将要添加另外一个文件, 将这个修改也 commit
进 change 2
. 所以我们复制 1.py
这个文件, 改名为 2.py
. 并把 2.py
变成 staged
, 然后使用 --amend
将这次改变合并到之前的 change 2
中
git add 2.py
git commit --amend --no-edit # "--no-edit": 不编辑, 直接合并到上一个 commit
git log --oneline # "--oneline": 每个 commit 内容显示在一行
原来的 change 2 只有 1.py,现在的 change 2 有 1.py 和 2.py
有时我们添加 add
了修改, 但是又后悔, 并想补充一些内容再 add
. 这时, 我们有一种方式可以回到 add
之前. 比如在 1.py
文件中添加这一行:
d = 3
然后 add
去 staged
再返回到 add
之前:
git add 1.py
git status -s # "-s": status 的缩写模式,绿了
git reset 1.py # 返回 add 之前
git status -s # 红了
在穿梭到过去的 commit
之前, 我们必须了解 git 是如何一步一步累加更改的. 我们截取网上的一些图片 http://bramus.github.io/ws2-sws-course-materials/xx.git.html
每个 commit
都有自己的 id
数字号, HEAD
是一个指针, 指引当前的状态是在哪个 commit
. 最近的一次 commit
在最右边, 我们如果要回到过去, 就是让 HEAD
回到过去并 reset
此时的 HEAD
到过去的位置.
# 不管我们之前有没有做了一些 add 工作, 这一步让我们回到 上一次的 commit
git reset --hard HEAD
git log --oneline
法一:HEAD^
,类推,回到上上个 commit 即为 HEAD^^
,那回到上……上(100个)怎么破?手动一百个^
?还有一种简便的形式 HEAD~1
回到前一个
git reset --hard HEAD^
等价于
git reset --hard HEAD:~
法二: "commit id"
,找到想要回去 commit 版本的 id(git log --oneline
),然后用如下指令,最后一串是 id 号
git reset --hard 9c1eeb3
输入 git log --oneline
看看
9c1eeb3 (HEAD -> master) change 1
怎么 change 2
消失了!!! 还有办法挽救消失的 change 2
吗? 我们可以查看 git reflog
里面最近做的所有 HEAD
的改动, 并选择想要挽救的 commit id
:
git reflog
重复 reset
步骤就能回到 commit (amend): change 2
(id=cb09a58) 这一步了:
git reset --hard cb09a58
git log --oneline
git log --help
git log -- ‘filename’
查看该文件相关的commit记录
git log -p ‘filename’
可以显示该文件每次提交的diff
git show ‘commit-id’ ‘filename’
查看某次提交中的某个文件变化
gitk --follow ‘filename’
以图形化的界面显示文件修改列表
常用选项
未完待续。。。
git clone总是失败解决办法
git clone https://github.com/xxx.git
提示下载失败,
可以尝试把 https:// 换成 git://
git clone git://github.com/xxx.git