Git学习笔记
1. Git安装及推荐配置
下载GIt,选择相应版本。
- 安装完 Git 之后,要做的第一件事就是设置你的用户名和邮件地址。
$ git config --global user.name “
李老师
”
$ git config --global user.emailli@csdn.net
注意:
如果使用了 --global 选项,那么该命令只需要运行一次,因为之后无论你在该系统上做任何事情, Git 都会使用那些信息。
- 检查你的配置
git config --list
- 让 Git 显示颜色,会让命令输出看起来更醒目:
$ git config --global color.ui true
- 忽略文件
有些时候,你必须把某些文件放到 Git 工作目录中,但又不能提交它们,比如保存了数据库密码的配置文件等等,每次git status都会显示Untracked files …,这种情况下,就可以用忽略特殊文件 .gitignore 来很方便的解决这个问题
在 Git 工作区的根目录下创建一个特殊的 .gitignore文件,然后把要忽略的文件名填进去,Git 在每次进行提交的时候就会自动忽略这些文件。
例子:
- 你需要忽略 Windows 自动生成的垃圾文件:
- 忽略Python编译产生的.pyc、.pyo、dist等文件或目录:
- 加上你自己定义的文件,最终得到一个完整的.gitignore文件
# Windows:
Thumbs.db
ehthumbs.db
Desktop.ini
# Python:
*.py[cod]
*.so
*.egg
*.egg-info
dist
build
# My configurations:
db.ini
deploy_key_rsa
-
最后一步就是把.gitignore也提交到 Git,就完成了!当然检验.gitignore的标准是git status命令是不是说working directory clean
-
有些时候,你想添加一个文件到 Git,但发现添加不了,
-
原因是这个文件被.gitignore忽略了, 强制添加被忽略文件
$ git add -f App.class
-
可能是.gitignore写得有问题,需要找出来到底哪个规则写错了,可以用git check-ignore命令检查:
$ git check-ignore -v App.class
-
-
添加两条例外规则:
# 排除所有.开头的隐藏文件:
.*
# 排除所有.class文件:
*.class
# 不排除.gitignore和App.class:
!.gitignore
!App.class
- 配置别名
只需要敲一行命令,告诉 Git,以后st就表示status:
$ git config --global alias.st status
$ git config --global alias.co checkout
$ git config --global alias.ci commit
$ git config --global alias.br branch
配置完成以上别名后,以后提交就可以简写成:$ git ci -m "sth."
-
配置 git reset HEAD file
git reset HEAD file命令,他可以把暂存区的修改撤销掉(unstage),重新放回工作区。既然是一个unstage操作,就可以配置一个unstage别名:
$ git config --global alias.unstage 'reset HEAD'
-
配置 git log -1
$ git config --global alias.last 'log -1'
用git last就能显示最近一次的提交
-
配置 git lg
git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"
以上这些就是 git 常用的一些配置了。
-
配置文件
这些自定义的配置文件通常都存放在仓库的.git/config文件中:git config -e --global
查看全局配置
2.git的使用
2.1 在已存在目录中初始化仓库 —— git init
在已存在目录中创建一个版本库的过程非常简单
首先,选择一个合适的地方,创建一个空目录
第二步,通过git init命令把这个目录变成 Git 可以管理的仓库
- 创建目录
$ mkdir learning-git
$ cd learning-git
$ pwd
/Users/xxm/learning-git
- 初始化仓库
$ git init
Initialized empty Git repository in /Users/xxm/learning-git/.git/
瞬间 Git 就把仓库建好了,而且告诉你是一个空的仓库(empty Git repository),同时在当前目录下多了一个.git的目录
2.2 克隆现有的仓库 —— git clone
当你执行 git clone 命令的时候,默认配置下远程 Git 仓库中的每一个文件的每一个版本都将被拉取下来 git clone <url>
。
例:$ git clone https://codechina.csdn.net/codechina/help-docs
这会在当前目录下创建一个名为 help-docs 的目录,并在这个目录下初始化一个 .git 文件夹, 从远程仓库拉取下所有数据放入 .git 文件夹,然后从中读取最新版本的文件的拷贝。 如果你进入到这个新建的 help-docs 文件夹,你会发现所有的项目文件已经在里面了,准备就绪等待后续的开发和使用。
自定义本地仓库名称
当然如果你想在克隆远程仓库的时候,自定义本地仓库的名字也是可以的,你可以通过额外的参数指定新的目录名
$ git clone https://codechina.csdn.net/codechina/help-docs mydocs
这会执行与上一条命令相同的操作,但目标目录名变为了 mydocs。
Git 支持多种数据传输协议。 上面的例子使用的是 https:// 协议,不过你也可以使用 git:// 协议或者使用 SSH传输协议,比如 user@server:path/to/repo.git 。
2.3 提交文件操作
第一步,用命令git add告诉 Git,把文件添加到仓库:
第二步,用命令git commit告诉 Git,把文件提交到仓库:
例如:
Git 仓库中编辑一个readme.txt文件,内容如下:
Git is a version control system.
Git is free software.
$ git add readme.txt
//执行上面的命令,没有任何显示,说明添加成功。
$ git commit -m "wrote a readme file"
- -m后面输入的是本次提交的说明,可以输入任意内容,当然最好是有意义的,这样你就能从历史记录里方便地找到改动记录。
- git commit命令执行成功后会告诉你:
1 file changed:1个文件被改动(我们新添加的readme.txt文件)
2 insertions:插入了两行内容(readme.txt有两行内容)
为什么 Git 添加文件需要add,commit一共两步呢?因为commit可以一次提交很多文件,所以你可以多次add不同的文件,比如:
$ git add file1.txt
$ git add file2.txt file3.txt
$ git commit -m "add 3 files."
已经成功地添加并提交了一个readme.txt文件,接下来让我们继续修改readme.txt文件,改成如下内容:
Git is a distributed version control system.
Git is free software.
运行git status
命令看看结果:
$ 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: readme.txt
no changes added to commit (use "git add" and/or "git commit -a")
git status命令可以让我们时刻掌握仓库当前的状态,上面的命令输出告诉我们,readme.txt被修改过了,但还没有准备提交的修改。
用git diff
这个命令查看相较于上一次暂存都修改了些什么内容了:
$ git diff readme.txt
diff --git a/readme.txt b/readme.txt
index 46d49bf..9247db6 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,2 +1,2 @@
-Git is a version control system.
+Git is a distributed version control system.
Git is free software.
(END)
git diff顾名思义就是查看 difference,显示的格式正是 Unix 通用的 diff 格式,可以从上面的输出看到,我们在第一行添加了一个distributed单词。
git log
命令显示从最近到最远的提交日志- 如果嫌输出信息太多,看得眼花缭乱的,可以试试加上–pretty=oneline参数:
git log --pretty=oneline
$ git lg
* e55063a - (HEAD -> master) add distributed (4 minutes ago) <Miykael_xxm>
* 50ed06b - wrote a readme file (6 minutes ago) <Miykael_xxm>
(END)
2.4 回退提交文件
- 假设我们需要将 readme.txt 回退到上一个版本,也就是 wrote a readme file 的这个版本,使用git reset
在 Git 中,用HEAD表示当前版本,也就是最新的提交e55063a,上一个版本就是HEAD^,上上一个版本就是HEAD^^,当然往上100个版本写100个^比较容易数不过来,所以写成HEAD~100。
$ git reset --hard HEAD^
HEAD is now at 50ed06b wrote a readme file
Git 的版本回退速度非常快,因为 Git 在内部有个指向当前版本的HEAD指针,当你回退版本的时候,Git 仅仅是把HEAD从指向add distributed
- 回退到了某个版本,关掉了电脑,第二天早上就后悔了,想恢复到新版本怎么办?找不到新版本的commit id怎么办?
git reflog用来记录你的每一次命令,当你用git reset --hard HEAD^回退到wrote a readme file版本时,再想恢复到add distributed,就可以通过git reflog命令找到add distributed的commit id
$ git reflog
50ed06b (HEAD -> master) HEAD@{0}: reset: moving to HEAD~
e55063a HEAD@{1}: reset: moving to HEAD
e55063a HEAD@{2}: commit: add distributed
50ed06b (HEAD -> master) HEAD@{3}: commit (initial): wrote a readme file
ESC
从上面的输出可以看到,add distributed的commit id是e55063a,现在,我们就可以通过 git reset --hard e55063a切换到最新的版本上了。
总结:
- git init
- git clone
- git add
- git commit
- git status
- git diff
- git log
- git reset
- git reflog
3.版本控制
1.有时候我们提交完了才发现漏掉了几个文件没有添加,或者提交信息写错了。 此时,可以运行带有 --amend 选项的提交命令来重新提交:
git commit --amend
这个命令会将暂存区中的文件提交。 如果自上次提交以来你还未做任何修改(例如,在上次提交后马上执行了此命令),
那么快照会保持不变,而你所修改的只是提交信息。
例如,你提交后发现忘记了暂存某些需要的修改,可以像下面这样操作:
$ git commit -m 'initial commit'
$ git add forgotten_file
$ git commit --amend
最终你只会有一个提交——第二次提交将代替第一次提交的结果。
当你在修补最后的提交时,并不是通过用改进后的提交 原位替换 掉旧有提交的方式来修复的,
理解这一点非常重要。从效果上来说,就像是旧有的提交从未存在过一样,它并不会出现在仓库的历史中。
修补提交最明显的价值是可以稍微改进你最后的提交,而不会让“啊,忘了添加一个文件”或者 “小修补,修正笔误”这种提交信息弄乱你的仓库历史。
2.如何操作暂存区和工作目录中已修改的文件。
这些命令在修改文件状态的同时,也会提示如何撤消操作
例如,你已经修改了两个文件并且想要将它们作为两次独立的修改提交, 但是却意外地输入 git add * 暂存了它们两个。如何只取消暂存两个中的一个呢? git status 命令提示了你:
$ git add *
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
renamed: LICENSE -> LICENSE.md
modified: readme.txt
在 “Changes to be committed” 文字正下方,提示使用 git reset HEAD … 来取消暂存。 所以,我们可以这样来取消暂存 readme.txt 文件:
$ git reset HEAD readme.txt
Unstaged changes after reset:
M readme.txt
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
renamed: LICENSE -> LICENSE.md
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: readme.txt
如果你不想保留对文件的修改怎么办?如何方便地撤消修改——将它还原成上次提交时的样子
git checkout – file
$ git checkout -- readme.txt
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
renamed: LICENSE -> LICENSE.md
git checkout – 是一个危险的命令。 你对那个文件在本地的任何修改都会消失——Git 会用最近提交的版本覆盖掉它。 除非你确实清楚不想要对那个文件的本地修改了,否则请不要使用这个命令。
删除文件之后的操作
添加一个新文件test.txt到 Git 并且提交
$ git add test.txt
$ git commit -m "add test.txt"
[master c67077f] add test.txt
1 file changed, 1 insertion(+)
create mode 100644 test.txt
若在文件管理器中把文件删了,Git知道你删除了文件,因此,工作区和版本库就不一致了,git status命令会立刻告诉你哪些文件被删除了:
$ git status
On branch master
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
deleted: test.txt
no changes added to commit (use "git add" and/or "git commit -a")
一是确实要从版本库中删除该文件,那就用命令git rm删掉,并且git commit:
$ git rm test.txt
rm 'test.txt'
$ git commit -m "remove test.txt"
[master 5c7e5ea] remove test.txt
1 file changed, 1 deletion(-)
delete mode 100644 test.txt
另一种情况是删错了,因为版本库里还有呢,所以可以很轻松地把误删的文件恢复到最新版本:
$ git checkout -- test.txt
回顾:
-
场景1:当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令git checkout – file
-
场景2:当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,想丢弃修改,分两步,第一步用命令git reset HEAD ,就回到了场景1,第二步按场景1操作
-
场景3:已经提交了不合适的修改到版本库时,想要撤销本次提交,可以用命令git reset --hard commit_id,不过前提是没有推送到远程库
4.分支管理
1. dev分支
$ git checkout -b dev
Switched to a new branch 'dev'
git checkout命令加上-b参数表示创建并切换,相当于以下两条命令:
$ git branch dev
$ git checkout dev
Switched to branch 'dev'
用git branch命令查看当前分支
$ git branch
* dev
master
git branch命令会列出所有分支,当前分支前面会标一个*号。
在dev分支上的操作,master分支此刻的提交点并没有变
用 git merge 把dev分支的工作成果合并到master分支上:
$ git merge dev
Updating 599dbdb..4aac6c7
Fast-forward
readme.txt | 1 +
1 file changed, 1 insertion(+)
合并完成后,就可以放心地删除dev分支了:
$ git branch -d dev
Deleted branch dev (was 4aac6c7).
删除后,查看branch,就只剩下master分支了。
*切换分支使用git checkout ,而 Git 中撤销修改则是git checkout – ,同一个命令,有两种作用,确实有点令人迷惑。 *
最新版本的 Git 提供了新的git switch命令来切换分支:
创建并切换到新的dev分支,可以使用:
$ git switch -c dev
直接切换到已有的master分支,可以使用:
$ git switch master
通常,合并分支时,如果可能,Git 会用Fast forward模式,但这种模式下,删除分支后,会丢掉分支信息。
如果要强制禁用Fast forward模式,Git 就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。
$ git merge --no-ff -m "merge with no-ff" dev
Merge made by the 'recursive' strategy.
readme.txt | 1 +
1 file changed, 1 insertion(+)
合并后,我们用git log看看分支历史:
$ git log --graph --pretty=oneline --abbrev-commit
* fc76cf7 (HEAD -> master) merge with no-ff
|\
| * f52c633 (dev) add merge
|/
* cf810e4 conflict fixed
...
不使用Fast forward模式,merge后就像这样:
几个基本原则进行分支管理:
- 首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;
- 其次,干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,并在master分支发布1.0版本;
2. bug分支
接到一个修复一个代号101的 bug 的任务时,很自然地,你想创建一个分支 issue-101 来修复它,但是,当前正在dev上进行的工作还没有提交:
当前工作只进行到一半,还没法提交,但是,必须在两个小时内修复该 bug怎么办?
幸好,Git还提供了一个stash
功能,可以把当前工作现场“储藏”起来,等以后恢复现场后继续工作:
$ git stash
Saved working directory and index state WIP on dev: f52c633 add merge
用git status查看工作区,就是干净的(除非有没有被Git管理的文件),因此可以放心地创建分支来修复 bug。
首先确定要在哪个分支上修复 bug,假定需要在master分支上修复,就从master创建临时分支:
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 6 commits.
(use "git push" to publish your local commits)
$ git checkout -b issue-101
Switched to a new branch 'issue-101'
修复bug,然后提交:
$ git add readme.txt
$ git commit -m "fix bug 101"
[issue-101 8842ff5] fix bug 101
1 file changed, 1 insertion(+), 1 deletion(-)
修复完成后,切换到master分支,并完成合并,最后删除issue-101分支:
$ git switch master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 6 commits.
(use "git push" to publish your local commits)
$ git merge --no-ff -m "merged bug fix 101" issue-101
Merge made by the 'recursive' strategy.
readme.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
接着回到dev分支干活了!
$ git switch dev
Switched to branch 'dev'
$ git status
On branch dev
nothing to commit, working tree clean
用git stash list命令看看刚才的工作现场:
$ git stash list
stash@{0}: WIP on dev: f52c633 add merge
工作现场还在,Git 把stash
内容存在某个地方了,但是需要恢复一下,有两个办法:
- 一是用git stash apply恢复,但是恢复后,stash内容并不删除,你需要用git stash drop来删除;
- 另一种方式是用git stash pop,恢复的同时把stash内容也删了:
$ git stash pop
On branch dev
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: hello.py
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: readme.txt
Dropped refs/stash@{0} (5d677e2ee266f39ea296182fb2354265b91b3b2a)
再用git stash list查看,就看不到任何stash内容了:
你可以多次stash,恢复的时候,先用git stash list查看,然后恢复指定的stash,用命令:git stash apply stash@{0}
在master分支上修复了bug后,我们要想一想,dev分支是早期从master分支分出来的,所以,这个bug其实在当前dev分支上也存在。
怎么在dev分支上修复同样的bug?
只需要把8842ff5 fix bug 101这个提交所做的修改“复制”到dev分支。
Git 专门提供了一个cherry-pick命令,让我们能复制一个特定的提交到当前分支:
$ git branch
* dev
master
$ git cherry-pick 8842ff5
[dev 0944c8c] fix bug 101
1 file changed, 1 insertion(+), 1 deletion(-)
3. feature分支
每添加一个新功能,最好新建一个feature分支,在上面开发,完成后,合并,最后,删除该feature分支。
feature分支和bug分支是类似的,合并,然后删除。
但是!就在此时,新功能必须取消!须就地销毁:
$ git branch -d feature-vulcan
error: The branch 'feature-vulcan' is not fully merged.
If you are sure you want to delete it, run 'git branch -D feature-vulcan'.
销毁失败。Git 友情提醒,feature-vulcan分支还没有被合并,如果删除,将丢失掉修改,如果要强行删除,需要使用大写的-D参数
$ git branch -D feature-vulcan
Deleted branch feature-vulcan (was d12cf23).
删除成功!
解决合并请求中的冲突
Git 无法执行“快速合并”,只能试图把各自的修改合并起来
Cherry Pick
git cherry-pick
命令的作用,就是将指定的提交commit
应用于其他分支。
$ git cherry-pick <commitHash>
上面命令就会将指定的提交commitHash,应用于当前分支。这会在当前分支产生一个新的提交,当然它们的哈希值会不一样。
Cherry pick 支持一次转移多个提交。
$ git cherry-pick <HashA> <HashB>
上面的命令将 A 和 B 两个提交应用到当前分支。这会在当前分支生成两个对应的新提交。
- -e,–edit
打开外部编辑器,编辑提交信息。 - -n,–no-commit
只更新工作区和暂存区,不产生新的提交。 - -x
在提交信息的末尾追加一行cherry picked from commit …,方便以后查到这个提交是如何产生的。 - -s,–signoff
在提交信息的末尾追加一行操作者的签名,表示是谁进行了这个操作。 - -m parent-number,–mainline parent-number
如果原始提交是一个合并节点,来自于两个分支的合并,那么 Cherry pick 默认将失败,因为它不知道应该采用哪个分支的代码变动。
-m配置项告诉 Git,应该采用哪个分支的变动。它的参数parent-number是一个从1开始的整数,代表原始提交的父分支编号。
$ git cherry-pick -m 1 <commitHash>
上面命令表示,Cherry pick 采用提交commitHash来自编号1的父分支的变动。
一般来说,1号父分支是接受变动的分支,2号父分支是作为变动来源的分支。
如果操作过程中发生代码冲突,Cherry pick 会停下来,让用户决定如何继续操作。
(1)--continue
用户解决代码冲突后,第一步将修改的文件重新加入暂存区(git add .),第二步使用下面的命令,让 Cherry pick 过程继续执行。
$ git cherry-pick --continue
(2)--abort
发生代码冲突后,放弃合并,回到操作前的样子。
(3)--quit
发生代码冲突后,退出 Cherry pick,但是不回到操作前的样子。
5.多人协作
git rebase
6.git标签
发布一个版本时,我们通常先在版本库中打一个标签(tag),这样,就唯一确定了打标签时刻的版本。将来无论什么时候,取某个标签的版本,就是把那个打标签的时刻的历史版本取出来。所以,标签也是版本库的一个快照。
Git 的标签虽然是版本库的快照,但其实它就是指向某个commit的指针(跟分支很像对不对?但是分支可以移动,标签不能移动),所以,创建和删除标签都是瞬间完成的。
- 切换到需要打标签的分支上
$ git branch
* dev
master
$ git checkout master
Switched to branch 'master'
- git tag 就可以打一个新标签:
$ git tag v1.0
- 用命令git tag查看所有标签:
$ git tag
v1.0
-
也可以
$ git tag v0.9 fb8b190
-
还可以创建带有说明的标签,用
-a
指定标签名,-m
指定说明文字:
$ git tag -a v0.1 -m "version 0.1 released" 187f963
用命令git show 可以看到说明文字
注意:标签总是和某个commit挂钩。如果这个commit既出现在master分支,又出现在dev分支,那么在这两个分支上都可以看到这个标签。
删除 tag
$ git tag -d v0.1
Deleted tag 'v0.1' (was 94e0f81)
因为创建的标签都只存储在本地,不会自动推送到远程。所以,打错的标签可以在本地安全删除
推送 tag
$ git push origin v1.0
Total 0 (delta 0), reused 0 (delta 0), pack-reused 0
To codechina.csdn.net:xiongjiamu/learning-git.git
* [new tag] v1.0 -> v1.0
一次性推送本地 tag
$ git push origin --tags
Enumerating objects: 1, done.
Counting objects: 100% (1/1), done.
Writing objects: 100% (1/1), 171 bytes | 171.00 KiB/s, done.
Total 1 (delta 0), reused 0 (delta 0), pack-reused 0
To codechina.csdn.net:xiongjiamu/learning-git.git
* [new tag] remove -> remove
* [new tag] v0.1 -> v0.1
* [new tag] v0.9 -> v0.9
删除已推送的 tag
- 先从本地删除
$ git tag -d remove
Deleted tag 'remove' (was c47983c)
- 然后,从远程删除
$ git push origin :refs/tags/remove
To codechina.csdn.net:xiongjiamu/learning-git.git
- [deleted] remove
总结:
- 命令
git push origin <tagname>
可以推送一个本地标签; - 命令
git push origin --tags
可以推送全部未推送过的本地标签; - 命令
git tag -d <tagname>
可以删除一个本地标签; - 命令
git push origin :refs/tags/<tagname>
可以删除一个远程标签 - 命令
git tag -a <tagname> -m 'messages'
可以创建一个带附注的标签 - 命令
git tag -s <tagname> -m 'messages'
可以创建一个带 gpg 签名的标签