本博文大部分内容将来自于《Pro Git》,这是一本CC BY-NC-SA 3.0的书,所以可以随便下载来看(但是不能够用在商业用途上),有兴趣的话还是直接下载吧,阅读这本书的英文阅读能力要求也不高(大部分时候只需要读懂指令对应的用途就可以了)。实在是英语苦手还是有中文版的
其实Git就是个工具,所以这本书的阅读笔记也只是用来记录这个工具的使用方法/指令而已。这里先记录下最基本的操作(学了之后基本上就可以立马参与到项目开发中的操作)。
Git是什么
Git就是一个版本控制工具,你只需要知道借助这个工具可以很好地控制你或者团队的代码版本,查看你或者团队某次操作中增加或者删除了什么代码(想看更加多相关信息的话就自己下载《pro git》来看)。
Git安装及配置
git安装
安装的话直接上git官网,windows平台下载安装包(自己找),linux基本上用sudo apt-get install git
或者sudo yum install git
就可以了,这些指令官网都有(一定要养成看官方文档的好习惯),具体的安装步骤都已经是很傻瓜化了,所以就不细讲了。当然也可以从源码开始编译安装,但是贫僧没这个需求所以就懒得做了。git有gui工具(貌似是叫tortoise还是什么的),但是要另外下载。但是gui工具不是万能的,而且学会了命令行的话gui也就不用学了(因为其实gui工具只是把某些指令给封装好了而已。。。),所以下面的内容都是关于怎么在命令行下使用git的(即只记录操作相关的指令)。
git配置
安装好之后使用git之前还需要进行有一些配置(就好像创建账户一样,目的就是为了方便团队管理代码,不然找到bug都不知道该拿谁祭天)。下面提到的配置步骤只需要进行一次(前提是git的数据在之后的使用中都不会受到莫名其妙的损坏,例如遭遇了来自愤怒的程序员的sudo rm -rf /*
式毁灭性打击)。
git自己有一个工具(也会是到时候使用的命令)git config
,这个工具就是用来配置git的。这个工具修改的数据通常来自:
/etc/gitconfig
,明显是linux平台下才有的东西,这是个系统级的配置文件,保存的是系统级的配置,对所有用户有效。要在用git config
时搭配--system
来修改配置(要有root权限)~/.gitconfig
或者~/.config/git/config
文件,这个的话就是用户级的了,只对当前用户有效(其实和linux系统的设定差不多,毕竟都是林纳斯弄出来的东西。。。),要修改的话就要git config --global
- repo里面的
.git/config
,这个类似于只在这个repo中有效的设置变量,只有在路径处在这个repo路径下的时候设置才会起效。默认修改的都是这个文件的配置,不过也可以用--local
来指定修改这个文件下的配置
上面的排列顺序其实是有意义的,越后面出现的优先级越高(也就是git会最优先使用第3个列出来的文件里面的设置)。
如果是windows下的话git会用“HOME”环境变量指定的目录下的.gitconfig
文件(所以要小心这个环境变量别设置错了,或者和别的软件设置的变量冲突,本文后面就提到了这种情况,默认HOME是指向C:\Users\$USER
)。同时还有系统级的配置文件会出现在C:\Documents and Settings\All Users\Application Data\Git\config
(如果是windows xp的话),C:\ProgramData\Git\config
(Vista及后面的版本),这个文件里面的内容只能够在admin权限下用git config -f <file>
修改。
用户/身份配置
用户名字、邮箱的配置很简单,但是又很重要(因为要git的commit信息要用到这些信息,并且会一直记录在你创建的commit信息中,方便管理员在找到bug的时候拿你来祭天)。
$ git config --global user.name "你的名字"
$ git config --global user.email 你的邮箱地址@xx.com
这是系统级的配置,如果想要在某个项目里面用另外一套配置的话就将路径切换到那个repo中,并直接用上面的git config *****
,但是没有--global
这个flag。例如在某个repo下使用git config user.name "xxx"
。
然后可以给git指定一个编辑器,如果没有自己另外指定的话git会默认使用系统默认的编辑器:
$ git config --global core.editor emacs
上面的指令就把git的编辑器指定成了emacs
(其实贫僧不怎么用这款编辑器,这里只是个例子),如果是windows平台的话就要指定完整路径(并且指向的是一个可执行文件),例如:
$ git config --global core.editor "'C:/Program Files/Notepad++/notepad++.exe' -multiInst -nosession"
上面的例子就把notepad++(贫僧还是挺喜欢用这款编辑器的)设置成了git的默认编辑器了。
贫僧在windows平台下使用git,貌似没有设置,所以默认的编辑器是类似vim那样的编辑器(可能设置过了,具体的安装步骤忘了,只记得当时是一路按“下一步”的)。
检查设置
如果想要检查设置结果的话就要用到:
$ git config --list
上面这条指令会列出你的所有设置(会比较长),如果只想检查某一项的话:
$ git config 你想检查的那一项的名字
例如:
$ git config user.name
还可以加上--global
flag来检查系统级的设置:
$ git config --global user.name
git自带的帮助文档
$ git help <verb>
$ man git-<verb>
上面两条指令其实效果一样。一个例子“
$ git help config
另一种方式是用-h
或-help
flag来让git输出简要的帮助,例如:
$ git add -h
usage: git add [<options>] [--] <pathspec>...
-n, --dry-run dry run
-v, --verbose be verbose
-i, --interactive interactive picking
-p, --patch select hunks interactively
-e, --edit edit current diff and apply
-f, --force allow adding otherwise ignored files
-u, --update update tracked files
-N, --intent-to-add record only the fact that the path will be added later
-A, --all add changes from all tracked and untracked files
--ignore-removal ignore paths removed in the working tree (same as --no-all)
--refresh don't add, only refresh the index
--ignore-errors just skip files which cannot be added because of errors
--ignore-missing check if - even missing - files are ignored in dry run
--chmod <(+/-)x> override the executable bit of the listed files
Git的基本操作
下面正式进入正题:git的操作
获取repo
有两种方式得到repo:
- 自己创建一个
- 从别的地方(例如github)clone一个别人创建好了的repo下来
创建repo
自己创建的话只需要先进入到目录路径下:
$ cd /c/user/my_project
在git里面用的是/c/
代表c盘,不像cmd里面那样的c:\
,不过一般都不会搞混,因为在跑命令的时候都会显示出当前目录,可以当成一个提醒:
在进入到目标目录之后,就可以开始创建repo了:
$ git init
运行了上面这条指令之后就会创建一个新的文件夹(默认是隐藏的).git
,这里面会保存所有git在这个repo运行需要用到的文件。
创建好之后还没有文件被添加到git的监视列表里面,这时候就要用到这条指令来添加监视的文件:
$ git add 文件名
这条指令会让git监视指定的文件(如果想直接开始监视当前目录下所有文件就git add *
,贫僧的理解是git命令可以配合正则使用,类似linux命令行)。
在做了改变之后就要commit,这就类似让git对着现在的工作环境拍一次照,只有拍照了之后才能够将现在的状态记录下来(并且后面只能够将工作环境还原到已经拍过照的时刻,没有拍照的话是还原不了的):
$ git commit -m '开始挖坑'
-m
用来附上照片的注释,后面可以用git的相关指令查看,相当于是标签,方便使用者知道这次做了什么改动之类的。
clone一个repo
当你想要clone一个repo的时候就要用到clone
指令:
$ git clone <url>
例子:
$ git clone https://github.com/libgit2/libgit2
git会自动把链接里面的repo下载下来,并保存为repo名字命名的文件夹下,例如这里就是保存在当前目录的libgit2
下,没有这个目录的话git会自己创建一个。
如果想要保存在自己命名的文件夹下:
$ git clone https://github.com/libgit2/libgit2 mylibgit
这样就会把链接里面的repo保存在当前指令的路径下的mylibgit
文件夹里面。
repo中文件状态
repo中文件状态可以总结成这样(图片来自《Pro Git》,中文是贫僧添加的。。。):
所有repo下的文件可以大概分成两种状态:tracked和untracked的,tracked就是git知道的并且在记录改动的,untracked的就是git不记录改动的(可以想象成没有登记户口的)。tracked里面又可以分成unmodified、modified和staged,具体怎么分的就看上面那张图。
如果要查询当前repo下文件的状态可以用:
$ git status
git会列出所有不在unmodified状态的文件,可以针对这些文件做相应的动作。
例如你添加了一个README文件到了repo中:
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Untracked files:
(use "git add <file>..." to include in what will be committed)
README
nothing added to commit but untracked files present (use "git add" to track)
用git status
指令,git就会告诉你有一个新文件未添加到git的监视列表中。
添加文件到监视列表中
如果想要将这个文件添加到监视列表,那么要用这个指令:
git add 文件/文件夹名
如果是文件夹名的话那么就会自动添加文件夹下面的所有文件到gi的监视列表中。
接着上面的例子:
$ git add README
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: README
README文件在添加进来的时候就已经是staged了(不清楚的话看上面的状态图),这时候其实就已经可以commit了,commit之后git会记录下当前状态下README的文件的状态,恢复到commit的这个状态时README文件的内容也会被恢复到add时的内容。
stage一个modified状态的文件
用到的命令还是上面出现的:
$ git add 文件名
这个指令其实是多功能的指令,能够用来让git开始track一个文件,或者是stage一个文件(这句话看起来怪怪的,但是最好还是习惯这种称呼,因为git里面显示出来的状态或者指令就是这些动词)。
例子,假设已经开始track一个名为“CONTRIBUTING.md”的文件,然后改动了它,那么:
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: README
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: CONTRIBUTING.md
参考上面的状态表,就可以知道为什么这个文件现在处在这个状态。
为了stage这个文件,那么就要用到add
指令:
$ git add CONTRIBUTING.md
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: README
modified: CONTRIBUTING.md
贫僧对stage的理解就是将当前改动拍照,而commit才是真正的将照片保存到档案里面的动作(记录到档案里面的照片才可以重新调出来)。
然后这时候你贼心不死,又改动了文件并添加了一个bug,再用git status
看一下repo下文件状态:
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: README
modified: CONTRIBUTING.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: CONTRIBUTING.md
会看到改动的文件有两个modified状态,这是因为git之前已经stage了一个改动,这时候再改动,那么就会出现还没有stage的modified状态,所以看起来是重复出现了的两个状态,其实是不同的。要处理这种情况就只需要重新stage一次(重新拍一次照更新状态),就可以了。
$ git add CONTRIBUTING.md
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: README
modified: CONTRIBUTING.md
简略输出status
直接用git status
觉得输出太繁杂,想要更加简洁的输出?
$ git status -s
M README
MM Rakefile
A lib/git.rb
M lib/simplegit.rb
?? LICENSE.txt
用上面的指令就可以输出简洁的status结果,??
是untrackeed,add的新文件是A
,modified的文件是M
(MM
也是,只是一次stage了一次没有stage)。注意其实这些输出的状态是有两列的,第一列代表stage的状态,第二列代表工作空间的状态(就是还没stage的状态),所以README是改动了但是没有stage,lib/simplegit.rb是改动了而且stage了,而Rakefile是有一次stage了的改动和一次stage之后的改动。
让git忽略某些文件
有时候你会在repo下编译文件,而git不能够追踪二进制文件的变换(可以知道改变了,但是不知道哪里变了,所以git不能够用在图片、视频的版本控制上),所以会希望git忽略掉这些文件,而只追踪代码这些纯文本文件的版本变化。这时候就需要创建一个.gitignore
文件(可以用正则):
$ cat .gitignore
*.[oa]
*~
上面是书中的例子,这个内容能够让git忽略掉以.o
或者.a
结尾的文件,以及名字以~
结尾的文件。最好在创建repo的时候就写好.gitignore
文件,不然可能会不小心commit了不想commit的文件。
.gitignore
的语法
下面是.gitignore
的语法(可以先跳过,用到的时候再查):
- 空行及以
#
开头的行会被忽略掉 - 可以使用通配符,并且通配符定义的规则可以被运用到repo的所有文件
- 在通配符开头用
/
来避免运用到repo主目录下的子文件夹中 - 在通配符结尾用
/
来指明一个文件夹 - 在通配符前面用
!
来取反(让通配符的工作范围取反,就是只不忽略这一行指定的文件)
书中的例子:
# 忽略所有.a文件
*.a
# track名为lib.a的文件,但是还是忽略其他.a文件
!lib.a
# 只忽略主目录下的TODO文件,但是不忽略子文件夹下的TODO文件
/TODO
# 忽略所有build文件夹下的文件
build/
# 忽略doc文件夹下的.txt文件,但是不会忽略doc下子文件夹的.txt文件
doc/*.txt
# 忽略所有doc及doc的子文件夹下的.pdf文件
doc/**/*.pdf
还有其他的例子可以在GitHub上面看。
注意,可以不只有一个.gitiignore文件,如果是在子文件夹下面出现的.gitignore文件的话那么这个文件只对该文件夹下文件有效(具体细节自己用man gitignore
查。。。)。
diff
这应该是一个明星词汇了。。。基本上所有读过《黑客与画家》的人都见到过。可以用git diff
这个指令来比较stage的文件和stage之后又修改了的文件之间的区别(例如上面的CONTRIBUTING.md)。
用这个指令可以查看modified状态而且还没有stage的文件的变动:
$ git diff
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8ebb991..643e24f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -65,7 +65,8 @@ branch directly, things can get messy.
Please include a nice description of your changes when you submit your PR;
if we have to read the whole diff to figure out why you're contributing
in the first place, you're less likely to get feedback and have your change
-merged in.
+merged in. Also, split your changes into comprehensive chunks if your patch is
+longer than a dozen lines.
If you are starting to work on a particular area, feel free to submit a PR
that highlights your work in progress (and note in the PR title that it's
这个指令会拿没stage的modified状态的文件和已经stage了的同一个文件进行比较,并且像上面那样输出变动的地方。
如果想要看看stage了之后的文件里面有什么变了,那么就要加上--staged
这个flag:
$ git diff --staged
diff --git a/README b/README
new file mode 100644
index 0000000..03902a1
--- /dev/null
+++ b/README
@@ -0,0 +1 @@
+My Project
和上面这个指令一样的是git diff --cached
,作用一样。
注意:diff
不会显示出所有做过的改动,只显示没有stage的改动。
除了用diff之外还可以用git difftools
,这样可以调用另外的diff工具(例如emerge、vimdiff或者其他的软件),这里就不介绍了(可以通过git difftools --tool-help
来看有什么软件是在当前系统上可以用来diff的)。
commit
指令:
$ git commit
注意,没有stage的改动不会进入到commit提交的状态里面去(要stage的话用git add 文件名
)。
运行了上面的指令之后就会弹出你为git设置的默认处理器(就是上面设置的core.editor
),贫僧的是vim。弹出来之后的信息是这样的:
# 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: README
# modified: CONTRIBUTING.md
# ~ ~ ~
".git/COMMIT_EDITMSG" 9L, 283C
这里面的内容就是到时候会记录到commit信息里面的内容,可以自己修改或者添加什么东西。如果想要加入你更改了什么的信息(diff显示的那些),可以用git commit -v
。编辑之后退出编辑器就可以了,git会记录下这里面的内容作为commit信息。
或者不用打开编辑器,直接用指令添加commit信息:
$ git commit -m "Story 182: Fix benchmarks for speed"
[master 463dc4f] Story 182: Fix benchmarks for speed
2 files changed, 2 insertions(+)
create mode 100644 README
运行了上面的指令时候git就会输出上面的信息,信息里面包含了提交到了哪个branch里面,commit的SHA-1校验码(在将repo复原到某个commit状态时可能会用到),并显示有多少个文件被改动了,多少行代码添加了或者移除了。
注意,commit只保存stage了的状态,如果不像手动stage的话(直接commit所有改动),那么只需要加个-a
flag就可以了:
$ git commit -a -m '这条指令可以直接commit所有改动'
上面的例子只是示范,不要在commit信息里面写这种东西。。。
在repo里面删除文件
用git rm 文件名
,这样会把文件删除并把改动stage,不然直接删除的话会显示“有改动没有被stage”:
$ rm PROJECTS.md
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
deleted: PROJECTS.md
no changes added to commit (use "git add" and/or "git commit -a")
如果是直接用git rm 文件名
的话那么就不会多出个“有改动没有被stage”:
$ git rm PROJECTS.md
rm 'PROJECTS.md'
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
deleted: PROJECTS.md
运行了上面的指令之后要commit才能够把文件移除掉。如果在git rm
前更改并stage了文件,那么必须要加上-f
选项才可以移除,避免意外将更改了的文件在commit前删除了。
如果只是想要让git停止track这个文件,而在现实中保留这个文件的话那么只需要用这个指令:
$ git rm --cached 文件名
上面的指令都可以搭配通配符,例如:
$ git rm log/\*.log
注意不要忽略掉\
。上面这个指令会删除掉所有在log/
目录下的.log
文件。
移动文件
$ git mv 文件当前位置 文件要移动的位置
书上的例子:
$ git mv README.md README
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
renamed: README.md -> README
因为和linux系统的mv
指令类似,也有着重命名文件的功能,所以git会把上面这个操作理解成重命名。
git mv
指令其实和下面这三行指令的操作是有等同作用的:
$ mv README.md README
$ git rm README.md
$ git add README
最后git还是把这个操作看作重命名的操作。这个例子的意义在于你可以用别的软件(例如visual studio code或者atom)来对文件进行重命名。
查看commit历史
$ git log
最新的commit会最先输出,并且会列出commit的SHA-1校验码,commit的人的用户名和邮箱地址,commit信息等东东。
这个指令有一堆可以用的flag,下面只记录几个常用的。
-p
或者--patch
会让输出的信息里面加上改动的信息。
-2
,2可以换成别的数字,这可以让git只输出2条或者n条信息。
--stat
让git输出简化的历史:
$ git log --stat
commit ca82a6dff817ec66f44342007202690a93763949
Author: Scott Chacon <[email protected]>
Date: Mon Mar 17 21:52:11 2008 -0700
changed the version number
Rakefile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
Author: Scott Chacon <[email protected]>
Date: Sat Mar 15 16:40:33 2008 -0700
removed unnecessary test
lib/simplegit.rb | 5 -----
1 file changed, 5 deletions(-)
--pretty
用来改变输出格式,要配合选项使用。oneline
选项会让git输出一行的commit信息,short
、full
、fuller
用来输出更少或者更多的信息,format
用来规范输出格式,例子:
$ git log --pretty=oneline
ca82a6dff817ec66f44342007202690a93763949 changed the version number
085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 removed unnecessary test
a11bef06a3f659402fe7563abf99ad00de2209e6 first commit
$ git log --pretty=format:"%h - %an, %ar : %s"
ca82a6d - Scott Chacon, 6 years ago : changed the version number
085bb3b - Scott Chacon, 6 years ago : removed unnecessary test
a11bef0 - Scott Chacon, 6 years ago : first commit
format的其他占位符可以看书上43页的列表。
--graph
,会让输出commit信息变成ascii图,例子:
$ git log --pretty=format:"%h %s" --graph
* 2d3acf9 ignore errors from SIGCHLD on trap
* 5e3ee11 Merge branch 'master' of git://github.com/dustin/grit
|\
| * 420eac9 Added a method for getting the current branch.
* | 30e367c timeout code and tests
* | 5a09431 add timeout protection to grit
* | e1193f8 support for heads with slashes in them
|/
* d6016bc require time for xmlschema
* 11d191e Merge branch 'defunkt' into local
--since
和--until
,用来输出某日期之后或之前的commit信息,例子:
$ git log --since=2.weeks
--author
输出指定作者的commit信息。
--grep
用来筛选commit信息,和linux的grep
差不多。
-S
可以查找添加了某个函数的commit,例子:
$ git log -S 函数名
--no-merges
会让log不输出merge产生的commit信息。
记录一个意外遇到的错误
error: could not lock config file。。。 后面省略
贫僧是在windows 10下写这篇博文的,结果在创建repo的时候遇到了这个错误,后来查了查,发现原来是环境变量里面因为之前安装了别的软件,那款软件直接添加了一个叫做“HOME”的环境变量,并指向了这款软件的主目录。这个环境变量会导致git的默认HOME目录变成这个变量设置的目录。
解决方法:
- 直接删除这个变量
- 将这个变量换一个名字
其实就是个环境变量冲突引发的错误,用上面的方法改了下就解决了。
参考
《Pro Git》:基本上这篇文章的内容都是来自这本书(疯狂暗示去看),作者
Pro Git中文版
Pro Git中文版
两本中文版的没怎么看,不知道翻译水平怎么样。。。