Git是一个开源的分布式版本控制系统,可以有效、高速地处理从很小到非常大的项目版本管理。
集中式(SVN) VS 分布式(git)
集中式版本控制系统,版本库是集中存放在中央服务器的,工作时要先从中央服务器取得最新的版本,完成工作后,再把自己的文件推送给中央服务器。集中式版本控制系统在工作时需要联网。
分布式版本控制系统没有“中央服务器”,每个人的电脑上都是一个完整的版本库,所以就不需要联网。
分布式版本控制系统的安全性要高很多,因为每个人电脑里都有完整的版本库,某一个人的电脑坏掉了,随便从其他人那里复制一个就可以了。
分布式版本控制系统通常也有一台充当“中央服务器”的电脑,但这个服务器的作用仅仅是用来方便“交换”大家的修改,没有它大家也一样干活,只是交换修改不方便而已。
https://git-scm.com/downloads
https://download.tortoisegit.org/tgit/
按默认选项安装即可。安装完成后,在开始菜单里找到“Git”->“Git Bash”,出现命令行窗口的,就说明Git安装成功!
$ git config --global user.name "Your Name"
$ git config --global user.email "[email protected]"
# 示例:
# git config --global user.name "jiangxiaonan"
# git config --global user.email "[email protected]"
因为Git是分布式版本控制系统,所以,每个机器都必须自报家门:你的名字和Email地址。
版本库又名仓库,英文名repository
,可以简单理解成一个目录,这个目录里面的所有文件都可以被Git管理起来,每个文件的修改、删除,Git
都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以“还原”。
$ mkdir git_study
$ cd git_study
$ pwd
/d/Work/Git/git_study
注意:为了避免遇到各种莫名其妙的问题,请确保目录名(包括父目录)不包含中文。
$ git init
Initialized empty Git repository in D:/Work/study_git/.git/
此步骤为初始化仓库,同时生成一个.git
的隐藏目录。这个目录是Git来跟踪管理版本库的,不要乱改动。
创建一个.txt
的文件,内容可随意写。
$ git add file1.txt
$ git commit -m "write a file"
[master (root-commit) e6fddb0] write a file
1 file changed, 2 insertions(+)
create mode 100644 file01.txt
git add
是告诉git
有一个文件添加到了仓库。
git commit
是告诉git
这个文件正式提交了。-m
后面输入的是本次提交的说明,可以输入任意内容,当然最好是有意义的。
说明:
使用命令git add
,可反复多次使用,添加多个文件;最后可使用命令git commit -m
一次性提交。
通过以上命令,即可创建一个仓库并提交文件。
$ git status
可以看到哪些文件修改了,或者新增了等等,就是告诉你当前哪些文件处于什么状态。
将file01.txt重新修改多次,然后提交仓库。
我这里修改了两次。
git log
通过命令即可看到每次提交的信息。重点关注commit -m
时填写的说明,方便查看每次主要修改了那些内容。
$ git log --pretty=oneline
此命令可以简化日志的输出。从下往上看,依次显示每次的提交内容,最上面为当前的最新版本。
通过日志,可以看出每次提交都会形成一个commit_id
,使用以下命令可以回退到之前的版本。
git reset --hard commit_id
版本回退成功。
$ git reflog
如果你又不想回退了,想恢复到回退以前,可以使用git reflog
查看之前的commit_id
是什么,继续使用版本回退的命令返回过去。
注意:commit_id
没有必要写全,一般写前五六位即可,git
会自行判断。
workspace:工作区
Index:暂存区
Repository:本地仓库
Remote:远程仓库
可以在工作区中,添加、删除、修改文件等等操作。add
实际是将文件保存在暂存区,commit
是将文件一次性提交给本地库。所以暂存区和本地仓库(初始化时默认会创建一个master
分支)实际都在.git
隐藏目录中。
说明:Git跟踪并管理的是修改,而非文件。如果一个文件的修改要想被提交到Repository,必须经过add
->commit
的顺序。因为工作区的修改不会被提交,只有暂存区的修改才能被提交。
比如我们将file01.txt修改了,然后直接commit
。
# 修改完之后查看工作区和版本库里面最新版本的区别
$ git diff HEAD -- file01.txt
分为多种情况:
场景1:如果只是工作区写坏了,想丢弃修改,可以使用以下命令
$ git checkout -- file01.txt
git checkout -- file
命令中的--
很重要,没有--
,就变成了“切换到另一个分支”的命令。
场景2:如果工作区文件写坏了,并且add
到了暂存区,想丢弃修改,可以使用以下命令
$ git reset HEAD file01.txt
$ git checkout -- file01.txt
用命令git reset HEAD
,就回到了场景1,然后按场景1操作。
场景3:已经提交了不合适的修改到版本库时,想要撤销本次提交,可以进行版本回退的方法操作,不过前提是没有推送到远程库。
场景1:如果你要删除的文件之前已经提交过版本库了,那么可以很轻松得找回来。
$ git checkout -- <file>
说明:不管是误删,还是修改错了,都可以使用这个命令将文件复原回来。
注意:从来没有被添加到版本库就被删除的文件,是无法恢复的!
场景2:如果你真要删除此文件,可以使用以下命令
# 告诉git要删除文件了
$ git rm test.txt
# 再次提交
$ git commit -m "remove test.txt"
现在,文件就从版本库中被删除了。
一个仓库本身只有一条分支线。一开始只有一条分支,你的所有提交都会提交到master
上,这条线会随着提交的次数增多而越来越长。
如果创建了新的分支,那么会在master的位置新创建一条分支,比如dev
,但是分支线还是只有一条。
那么你新的提交就是针对新的分支了,随着提交的增多这条线会越来越长。而master
还在原来的位置没有动。
当你将dev
和master
合并,实际上是将master指针指向当前dev的位置。
$ git checkout -b dev
# `git checkout`命令加上`-b`参数表示创建并切换,相当于以下两条命令
$ git branch dev # 创建分支
$ git checkout dev # 切换分支
对于创建分支,还有如下命令可以使用
$ git switch -c dev
# `git switch`命令加上`-c`参数表示创建并切换,相当于以下两条命令
$ git branch dev # 创建分支
$ git switch dev # 切换分支
注意:使用git checkout
时区分前面说过的撤销修改git checkout --
.
查看当前分支
$ git branch
git branch
命令会列出所有分支,当前分支前面会标一个*
号。
现在我们就可以在dev
分支上提交了,而且不会影响到master
分支。
比如我们修改了README.md的文件。
切换到master
分支发现文件并没有被修改。
现在处在master
分支,现在我们将dev
分支合并上来。再次查看README.md文件,发现是被修改过的。
$ git merge dev
现在master
分支和dev
分支内容一致了,那么dev
分支的使命也就完成了,可以将其删除,当然你也可以继续在上面工作。
$ git branch -d dev
说明:在实际的工作中,通常会创建多条分支,防止master
被频繁修改导致出错的现象,确保过程更安全。
冲突发生在合并分支的时候。
我们新建一个分支feature1
,并且修改README.md的内容。
然后提交。
现在切换到master分支同样修改README.md文件内容。并提交。
最后合并feature1分支。
显示合并失败,并指出了是哪个文件。
这时需要手动修改冲突文件的内容。
<<<<<<< HEAD
表示当前所处分支的内容。=======
为分割线。>>>>>>> feature1
表示冲突分支的内容,需要将他们修改为最终需要的内容。
最后重新提交。
用git log --graph
命令可以看到分支合并图。
$ git log --graph --pretty=oneline --abbrev-commit
这个时候你可以删除feature1
分支,或者继续使用。
git分支合并时,默认采用Fast forward
模式,这种模式下,删除分支后,会丢掉分支信息。
如果要强制禁用Fast forward
模式,git会在merge时生成一个新的commit,这样,从分支历史就能看出分支信息。
$ git merge --no-ff -m "merge with no-ff" dev
合并dev分支,使用--no-ff
,表示禁用Fast forward
模式。
因为本次合并是新的commit
,所以需要加上-m
,并把提交的说明填写上去。
1.添加公钥
$ ssh-keygen -t ed25519 -C "Gitee SSH Key"
# -t key 类型
# -C 注释
2.创建远程仓库
通常情况下远程仓库名称和本地名称一致,方便区分。
3.关联仓库并提交文件
根据提示,既可以新建本地仓库与远程仓库,也可以使用已有的本地仓库。
$ git remote add origin [email protected]:xie_xuchun/study_git.git
origin
是给远程仓库起的名称。
$ git push -u origin "master"
把本地库的内容推送到远程,用git push
命令,实际上是把当前分支master
推送到远程。
由于远程库是空的,我们第一次推送master
分支时,加上了-u
参数,Git不但会把本地的master
分支内容推送到远程新的master
分支,还会把本地的master
分支和远程的master
分支关联起来,在以后的推送或者拉取时就可以简化命令。
$ git push origin master
说明:当你第一次使用Git的clone或者push命令连接Gitee时,会得到一个警告,这是因为Git使用SSH连接,而SSH连接在第一次验证Gitee服务器的Key时,需要你确认Gitee的Key的指纹信息是否真的来自Gitee的服务器,输入yes回车即可。Git会输出一个警告,告诉你已经把Gitee的Key添加到本机的一个信任列表里了。
在删除仓库之前,我们需要先查看当前仓库的信息。
$ git remote -v
然后,根据名字删除,比如删除origin:
$ git remote rm origin
说明:“删除”其实是解除了本地和远程的绑定关系,并不是物理上删除了远程库。
之前我们是先有本地库,然后才创建和关联的远程库。现在我们不创建本地库,直接从远程库进行克隆。
1.创建远程库
为了方便查看变化,给他初始化一个readme的文件
2.克隆到本地
$ git clone [email protected]:xie_xuchun/we-are-study.git
说明:Gitee给出的地址不止一个,还可以用https://gitee.com/xie_xuchun/we-are-study.git
这样的地址。实际上,Git支持多种协议,默认的git://
使用ssh,使用https除了速度慢以外,每次推送都必须输入口令。
每个Bug都可以通过创建临时分支来修复。修复后,合并分支,然后即可删除临时分支。
问题?我的dev分支代码还没完成,就创建临时分支,该如何提交?
方法:可以通过stash
把当前现场储藏起来,等以后恢复现场后再继续工作。
$ git stash
假定我们现在要在master分支修复代码,这时就可以在master分支上创建临时分支。
$ git checkout master
$ git switch -c issue-101
# 然后进行代码修复
$ git add readme.txt
$ git commit -m "update bug"
然后切换到master分支进行合并,再删除bug分支。
$ git switch master
$ git merge --no-ff -m "merge bug" issue-101
现在bug修复完成了,我们又可以回到dev分支继续干活了。
$ git switch dev
$ git status
此刻dev分支时干净的,因为我们之前将现场储藏了起来,可以通过
$ git stash list
查看。可以发现有一个隐藏。
现在我们就可以恢复现场了。
$ git stash apply
# 或者使用
$ git stash pop
# 两个区别就是恢复完成之后是否删除stash内容,使用后者会进行删除。
当然,你如果有多次stash,可以恢复指定的stash。
$ git stash apply stash@{0}
思考:刚才是在新建的临时分支上修改的bug,但是我当前工作的分支并没有这个修改,如果重新写一遍就是重复造轮子了,怎样让这些修改在dev分支上重放?
这时,我们可以查看之前新建分支上提交的commit_id
,将他重放到dev上。
说明:在重放之前,需要保持储藏的状态,重放完才可以取消储藏。
$ git cherry-pick 4c805e2
git给dev分支自动做了一次提交。使用了cherry-pick
,就不需要在dev分支重复修改bug了。
实际的工作中,各种新功能是非常多了,但是我们又不希望新的功能将主分支搞乱,这个时候对于新的功能需求,就可以新建一个feature分支,在上面开发,完成后合并,最后删除该feature分支。
$ git switch -c feature
$ git add readme.txt
$ git commit -m "new"
一切顺利的话,就可以切换到dev合并代码了
$ git switch dev
但是这时出了意外,新功能不需要了,那么直接删除feature分支就可以。
$ git branch -D feature
说明:如果我们合并过了,可以使用-d
,但是这种没有合并就删除的情况需要使用-D
进行强制删除。
在Git中打标签非常简单,首先,切换到需要打标签的分支上,比如dev分支:
$ git switch dev
然后在当前分支打标签
$ git tag v1.0
默认标签是搭载最新提交的commit上的。如果要对历史某一次提交打标签,需要找到历史的某次提交commit_id,然后打标签。
$ git tag v0.9 db6707e
还可以创建带有说明的标签,用-a
指定标签名,-m
指定说明文字。
git tag -a v0.1 -m "This is v0.1" f8d7e15
查看标签
$ git tag
$ git show v0.9
git tag
列出所有的标签。git show v0.9
,列出某一个tag的详细信息。
本地删除标签
$ git tag -d v0.1
创建的标签只存储在本地,不会自动推送到远程。可以安全删除。
远程推送标签
如果要推送某个标签到远程,使用命令git push origin
$ git push origin v0.9
或者,一次性推送全部未推送到远程的本地标签:
$ git push origin --tags
远程删除标签
如果标签已经推送到远程,要删除远程标签就麻烦一点,先从本地删除:
$ git tag -d v0.9
然后,从远程删除。格式如下:
$ git push origin :refs/tags/v0.9
当从远程仓库克隆时,Git自动把本地的master分支和远程的master分支对应起来了,并且,远程仓库的默认名称时origin。
要是看远程仓库的信息,用
$ git remote -v
-v
参数显示更详细的信息。
推送分支,就是把该分支上的所有本地提交推送到远程库。推送时,要指定本地分支,这样,Git就会把该分支推送到远程仓库对应的远程分支上:
$ git push origin master
$ git push origin dev
master
和dev
为指定的分支。一般情况下,和其他人有协作开发的分支需要推送远程仓库,某些自己定义的分支可以不用推送。
说明:即使远程仓库当前没有dev分支,按照这样的方式推送会自动创建dev分支。
默认情况下,其他人从远程库clone时,只能看到本地的master分支。
git clone [email protected]:xie_xuchun/we-are-study.git
如果你们要在dev分支上合作开发,就需要先建立dev分支的关系。
$ git checkout -b dev origin/dev
现在,其他人就可以在dev分支上修改,然后进行提交等等操作,进行协作开发了。
如果远程仓库的代码比本地代码更新,这时候git push
会产生冲突,从而导致推送失败,这时需要先git pull
,在本地解决完冲突之后再推送。
如果在git pull
的过程中有报错
$ git pull
There is no tracking information for the current branch.
Please specify which branch you want to merge with.
See git-pull(1) for details.
git pull <remote> <branch>
If you wish to set tracking information for this branch you can do so with:
git branch --set-upstream-to=origin/<branch> dev
原因是没有指定本地dev分支与远程origin/dev分支的链接,根据提示,设置dev和origin/dev的链接:
$ git branch --set-upstream-to=origin/dev dev
多人协作的工作模式通常是这样
首先,可以试图用git push origin
推送自己的修改;
如果推送失败,则因为远程分支比你的本地更新,需要先用git pull
试图合并;
如果合并有冲突,则解决冲突,并在本地提交;
没有冲突或者解决掉冲突后,再用git push origin
推送就能成功!
如果git pull
提示no tracking information
,则说明本地分支和远程分支的链接关系没有创建,用命令git branch --set-upstream-to
。
这就是多人协作的工作模式。
$ git config --global color.ui true
有些时候,你必须把某些文件放到Git目录中,但又不能提交他们,比如保存了数据库密码的配置文件等等。每次git status
都会显示Untracked files ...
。这个问题解决起来很简单,在Git工作区的根目录下创建一个特殊的.gitignore
文件。
注意:.gitignre
文件本身应该提交给Git管理,这样可以确保所有人在同一项目下使用相同的.gitigore
文件。
示例:
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
示例文件:https://github.com/github/gitignore
。
忽略文件的原则是:
java
编译产生的.class
文件。如果你想添加某些文件,但是发现添加不了,可以排查是不是这种类型的文件被忽略掉了。比如添加了App.class
文件。
$ git check-ignore -v App.class
.gitignore:2:*.class App.class
发现是因为.gitignore
的第二行.class
给忽略掉了。
如果确实要添加,可以这样改造.gitignore
文件。
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
# 不排除.gitignore和App.class:
!.gitignore
!App.class
使用!
+文件名的方式排除掉忽略。
这样就可以正常添加了。
对于经常使用,而写起来难度较大的命令,我们可以配置别名
$ 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 "bala bala bala..."
甚至你还可以这样写,将你需要起别名长命令都可以写进来。
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"
--global
参数是全局参数,也就是这些命令在这台电脑的所有Git仓库下都有用。当前用户的Git配置文件放在用户主目录下的一个隐藏文件.gitconfig
中:
如果不使用--global
,那么只是对当前仓库生效,每个仓库的Git配置文件都放在.git/config
文件中
对于大多数情况,使用别名的方式更好,推荐。
比如bootstraps项目。https://github.com/twbs/bootstrap
。
点“Fork”就在自己的账号下克隆了一个bootstrap
仓库,然后,从自己的账号下clone:
这个时候就可以拉取到本地进行修改提交。注意修改提交还是在自己的仓库。
之后可以通过pull request
推送给作者,由作者选择是否使用代码。
搭建Git服务器需要准备一台运行Linux的机器,强烈推荐用Ubuntu或Debian,这样,通过几条简单的apt命令就可以完成安装。
第一步,安装git
:
$ sudo apt-get install git
第二步,创建一个git
用户,用来运行git
服务:
$ sudo adduser git
第三步,创建证书登录:
手机所有需要登陆的用户的公钥,就是他们自己的id_rsa.pub文件,把所有公钥导入到/home/git/.ssh/authorized_keys文件里,一行一个。
第四步,初始化Git仓库:
先选定一个目录作为Git仓库,假定是/srv/sample.git,在/srv目录下输入命令:
$ sudo git init --bare sample.git
Git就会创建一个裸仓库,裸仓库没有工作区,因为服务器上的Git仓库纯粹是为了共享,所以不让用户直接登录到服务器上去改工作区,并且服务器上的Git仓库通常以.git
结尾。
然后,把owner改为git
$ sudo chown -R git:git sample.git
第五步,禁用shell登录:
出于安全考虑,第二步创建的git
用户不允许登录shell,这可以通过编辑/etc/passwd文件完成。找到类似下面的一行:
git:x:1001:1001:,,,:/home/git:/bin/bash
改为
git:x:1001:1001:,,,:/home/git:/usr/bin/git-shell
这样,git
用户可以正常通过ssh使用git,但无法登录shell,因为我们为git
用户指定的git-shell
每次一登录就自动退出。
第六步,克隆远程仓库:
现在,可以通过git clone
命令克隆远程仓库了,在各自的电脑上运行:
$ git clone git@server:/srv/sample.git
完成。
说明:
要方便管理公钥,用Gitosis;
要像SVN那样变态地控制权限,用Gitolite。