Git实战系列教程
介绍
本文详细记录了Git一系列核心概念和工作中常用的操作命令,通篇以实际出发拒绝过度理论,值得典藏:)。
概念
版本管理系统
版本控制是一种记录一个或若干文件内容变化,以便将来查阅特定版本修订情况的系统。
- 本地版本控制
RCS 的工作原理是在硬盘上保存补丁集(补丁是指文件修订前后的变化);通过应用所有的补丁,可以重新计算出各个版本的文件内容。 - 集中版本控制
有一个单一的集中管理的服务器,保存所有文件的修订版本,而协同工作的人们都通过客户端 连到这台服务器,取出最新的文件或者提交更新。代表有SVN,CVS。
![图片名称](https://img-blog.csdnimg.cn/f6624071c74a431fb491fea9855a6274.png)
- 分布式版本控制
集中版本控制最怕服务器宕机,如此众多客户端将无法协同工作。
于是分布式版本控制系统(Distributed Version Control System,简称 DVCS)面世了。
客户端并不只提取最新版本的文件快照, 而是把代码仓库完整地镜像
下来,包括完整的历史记录。 这么一来,任何一处协同工作用的服务器发生故障,事后都可以用任何一个镜像
出来的本地仓库恢复。 因为每一次的克隆操作,实际上都是一次对代码仓库的完整备份。
![图片名称](https://img-blog.csdnimg.cn/eaa0166b6f9842c79fc4154cb4922a68.png)
Git与SVN等的比较
SVN想必都不陌生,就我个人使用过而言,其使用体验就如其软件图标一样,慢的卡得像一只乌龟。
现在,告诉你GIT最牛逼的地方:
- 几乎所有操作都在本地完成
- 快得几乎所有操作都在1s中之内完成(命令行操作)
- 几乎不用担心文件损坏或丢失
- 强大的分支管理足以让你应对千军万马的开发者共同协作
这背后最大的功臣就是:
Git 更像是把数据看作是对小型文件系统的一系列快照。每当你提交更新或保存项目状态时,它基本上就会对当时的全部文件创建一个快照并保存这个快照的索引。
为了效率,如果文件没有修改,Git 不再重新存储该文件,而是只保留一个链接指向之前存储的文件。 Git 对待 数据更像是一个 快照流
实战环境
centos7.4
git2.24
首次使用
配置用户信息
安装完 Git 之后,要做的第一件事就是设置你的用户名和邮件地址。 这一点很重要,因为每一个 Git 提交都会使用这些信息,它们会写入到你的每一次提交中,不可更改:
git config --global user.name "laoxu"
git config --global user.email "laoxu@123.com"
检查配置信息
git config --list
输出:
user.email=indexman@126.com
user.name=indexman
abbrev-commit
core.repositoryformatversion=0
core.filemode=true
core.bare=false
core.logallrefupdates=true
remote.origin.url=git@gitee.com:indexman/git_in_action.git
remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
branch.master.remote=origin
branch.master.merge=refs/heads/master
branch.dev.remote=origin
branch.dev.merge=refs/heads/dev
升级Git
默认Centos7使用yum安装的git坂本为1.8.x.y,太旧了。
git version
# 卸载旧版
yum remove git -y
yum install \
https://repo.ius.io/ius-release-el7.rpm \
https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
yum search git |grep -E '^git'
# 选择需要安装的版本
yum install git224 -y
Git基础操作
初始化库
git init
添加文件到暂存区
git add readme.txt
提交
git commit -m "add readme.txt"
如果遇到Changes not staged for commit
说明仓库下有未add的文件或者未将修改提交到暂存区,先执行git add 文件名
再执行提交动作或者git commit -ma xxx
,直接二合一操作。
- 修正提交
假设commit
后发现忘记提交某些文件,但又不想在提交日志中产生2条提交记录。可以使用--amend
参数,这样就会以该次提交覆盖前一次提交信息。
git commit -m 'initial commit'
git add forgotten_file
git commit --amend
查看提交日志
git log
- 简洁的写法:
git log --pretty=oneline
版本回退
- 回退到上一版本
- 场景1::add后还没有commit的,使用:
git reset HEAD readme.txt
# discard changes in working directory
git checkout readme.txt
注意git reset HEAD操作只对暂存区起效果,不对工作区的内容起效果。所以还需要执行git checkout命令。
- 场景2:已经commit
git reset HEAD^
# 或
git reset [commitId]
git checkout readme.txt
- 一步到位
git reset --hard HEAD^
注意:加了参数–hard,直接把工作区的内容也修改了,慎重操作。
后悔药
假如不消息执行错了reset等操作,如果再恢复?git提供了reflog
命令来记录你历史操作,我们只需要找到历史操作的commit id
就能使用git reset --hard [commit id]
再穿越回去。
工作区
工作区,顾名思义就是已经使用git init
初始化了的文件夹。
暂存区
用于暂时存放git add
提交的修改。
版本库
工作区有一个隐藏目录.git,这个不算工作区,而是Git的版本库。
删除
- 情景1:删除工作目录文件
比如:git rm test.txt
,然后使用git status
查看可以发现哪些文件被删除了:
此时,如果真是要删除,那么继续操作:
# 将版本库中的也删了
git rm test.txt
否则,执行git checkout -- test.txt
,即使用版本库中的版本覆盖工作目录。这样被误删的文件又恢复了:
远程仓库
工作或学习中通常会把本地项目放在远程GIT仓库中进行管理,好处就是假如我有多台电脑或多个人开发同一个项目,方便集中版本管理。每个人或设备上修改的内容只要修改后提交到远程代码仓库,其他设备就能pull到更新到本地库。那么常见的远程仓库有:Github,国内码云Gitee,私服GitLab等。此处以码云举例,如何将我们虚拟机中的
git_in_action
项目托管到服务器。
添加公钥
这一步是要告诉远程仓库本设备的身份,假如你有多台设备那么可以分别在多个设备上面生成不同的公钥上传到码云。例如我的:
- 生成公钥
只需要2条命令:
# 邮箱替换成你的邮箱,出现的提示一路回车即可
ssh-keygen -t rsa -C "indexman@126.com"
# 查看已生成的公钥
cat ~/.ssh/id_rsa.pub
- 部署
新建仓库
仓库分为public和private,private只有自己可以看到,public所有人可以看到并参与协作。
由于我们本地已有git仓库,现在需要将本地内容上传到这个远程git仓库,按如下命令操作:
# 配置远程仓库地址
git remote add origin https://gitee.com/indexman/git_in_action.git
# 将本地仓库内容推送到远程仓库
git push -u origin master
如果不想每次push
都提示输入密码,可以将远程仓库换成不带https
的:
git remote rm origin
git remote add origin git@gitee.com:indexman/git_in_action.git
克隆仓库
git clone git@gitee.com:indexman/git_in_action.git
添加仓库
git remote add pb https://github.com/paulboone/ticgit
git remote -v
- 常用命令
克隆仓库:$ git clone git://github.com/jquery/jquery.git
查看远程仓库:$ git remote -v
添加远程仓库:$ git remote add [name] [url]
删除远程仓库:$ git remote rm [name]
修改远程仓库:$ git remote set-url --push[name][newUrl]
拉取远程仓库:$ git pull [remoteName] [localBranchName]
推送远程仓库:$ git push [remoteName] [localBranchName]
分支管理
创建分支
# 创建dev分支,然后切换到dev分支
git checkout -b dev
# 查看当前分支
git branch
注意
:
切换分支会改变当前工作目录。创建一个新分支testing
,然后切换到该分支做一些修改后分支呈现的版本如下图所示:
合并分支
在dev
分支下修改文件,提交后再切换到master
分支将修改合并。
vim readme.txt
git commit -am "dev commit"
git checkout master
cat readme.txt
# 合并
git merge dev
cat readme.txt
# 删除分支
git branch -d dev
注意:切换分支也可以使用:git switch -c dev
- 关于
fast forward
当你试图合并两个分支
时, 如果顺着一个分支走下去能够到达另一个分支,那么 Git 在合并两者的时候, 只会简单的将指针向前推进 (指针右移),因为这种情况下的合并操作没有需要解决的分歧——这就叫做 “快进(fast-forward)”。
合并冲突
-
场景1:2个分支对同一文件内容做了不同修改
例如:dev和master分支对readme.txt最后一行最后标点符号都做了不同的修改。此时,在合并的时候就会提示冲突了:
-
解决冲突后再
commit
-
查看分支合并日志
git log --graph
分支管理
# 查看哪些分支已经合并到当前分支
git branch --merged
# 查看所有包含未合并工作的分支
git branch --no-merged
使用规则
master分支作为主分支,保证其稳定性,一般多人协作项目不直接在该分支开发。主要用于版本发布。
dev分支作为主力开发分支,多人可以不同时间进行提交,将修改合并到master分支。
其他分支主要视用途而创建,比如需要修复某Bug,此时可以创建一个分支专门用于修复该bug,修复完毕再合并到master分支,并在确认bug修复后删除该分支。
储藏室
为什么会有这个功能?
场景1:假设我在dev分支上新加了一个文件dev.txt
1.通过git add dev.txt将其添加到暂存区,然后switch到master分支,发现dev.txt也复制过来了!
假设dev.txt此时你只写了一半,甚至其中还有错误,这样岂不是别人也提前可以同步过去了。。。想想后果还是不好的。此时我们就可以在dev
切换到master
之前执行git stash
或git stash save message
命令将修改未完成的文件临时存储起来,等再次切换会dev
时执行git stash
- 常用git stash命令:
(1)git stash save "save message"
: 执行存储时,添加备注,方便查找,只有git stash 也要可以的,但查找时不方便识别。
(2)git stash list
:查看stash了哪些存储
(3)git stash show
:显示做了哪些改动,默认show第一个存储,如果要显示其他存贮,后面加stash@{$num}
,比如第二个git stash show stash@{1}
(4)git stash show -p
: 显示第一个存储的改动,如果想显示其他存存储,命令:git stash show stash@{$num} -p
,比如第二个:git stash show stash@{1} -p
(5)git stash apply
: 应用某个存储,但不会把存储从存储列表中删除,默认使用第一个存储,即stash@{0},如果要使用其他个,git stash apply stash@{$num}
, 比如第二个:git stash apply stash@{1}
(6)git stash pop
:命令恢复之前缓存的工作目录,将缓存堆栈中的对应stash删除,并将对应修改应用到当前的工作目录下,默认为第一个stash,即stash@{0},如果要应用并删除其他stash,命令:git stash pop stash@{$num}
,比如应用并删除第二个:git stash pop stash@{1}
(7)git stash drop stash@{$num}
:丢弃stash@{$num}
存储,从列表中删除这个存储
(8)git stash clear
:删除所有缓存的stash
部分合并
场景:假设此时我在dev
分支埋头敲代码,测试突然提了一个master
分支的紧急bug要修复。此时我需要switch
到一个新的分支假如是:issus-001
去修改,修改完毕后commit
,然后switch到master
做git merge issus-001
的操作。
注意此时,dev
分支上也存在相同的bug需要修复,那怎么办?不会去dev上面再重复一遍修复操作吧?当然不能这么愚蠢!
此时,我们可以利用git cherry-pick [commit ID]
命令只合并bug修复部分提交的代码即可!这操作真是太细致了,有没有!
git switch -c issus-001
vim dev.txt
git commit -am "fix bug-001"
git switch master
git merge issus-001
删除分支
git branch -D <name>
# 可以运行带有 --delete 选项的 git push 命令
# 来删除一个远程分支。
git push origin --delete serverfix
变基Rebase
变基是将一系列提交按照原有次序依次应用到另一分支上,而合并是把最终结果合在一起。
整合分支最容易的方法是 merge 命令。 它会把两个分支的最新快照(C3 和 C4)以及二者最近的共同祖先(C2)进行三方合并,合并的结果是生成一个新的快照(并提交)。
其实,还有一种方法:你可以提取在 C4 中引入的补丁和修改,然后在 C3 的基础上应用一次。 在 Git 中,这种 操作就叫做 变基(rebase)。 你可以使用 rebase 命令将提交到某一分支上的所有修改都移至另一分支上,就
好像“重新播放”一样。在这个例子中,你可以检出 experiment 分支,然后将它变基到 master 分支上:
$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command
它的原理是首先找到这两个分支(即当前分支 experiment、变基操作的目标基底分支 master) 的最近共同祖先 C2,然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件, 然后将当前分支指向目标基底 C3, 最后以此将之前另存为临时文件的修改依序应用。 (译注:写明了 commit id,以便理解,下同)
现在回到 master 分支,进行一次快进合并。
$ git checkout master
$ git merge experiment
此时,C4’ 指向的快照就和 the merge example 中 C5 指向的快照一模一样了。 这两种整合方法的最终结果没 有任何区别,但是变基使得提交历史更加整洁。 你在查看一个经过变基的分支的历史记录时会发现,尽管实际
的开发工作是并行的, 但它们看上去就像是串行的一样,提交历史是一条直线没有分叉。
多人协作
查看远程仓库
git remote -v
推送分支
git push origin master
A操作
- 创建本地分支
dev
git checkout -b dev
- 编写代码后
push
到远程dev
分支
vim upload.java
git add upload.java
git commit -m "add upload.java"
# 注意,此时远程没有dev分支
git push origin dev
- 结果如下:
B操作
- 在本地创建
dev
分支 - 编写代码后push
- 发现冲突
意思是有另一个push操作在本地没有同步,需要执行pull
拉取下来。
1)将本地dev
分支关联远程dev
分支
2)再次pull
,发现和远程有冲突
- 解决冲突
git add upload.java
git commit -m "add upload.java"
- 最后再push
标签管理
在项目开发的关键节点(版本发版,重大修改)等,我们需要使用标签来记录这些时间点,方便以后可以跟踪代码,在Git中创建标签非常简单。
轻量标签很像一个不会改变的分支——它只是某个特定提交的引用。
git tag v1.0
# 根据commit id创建标签
git tag v0.9 88d2002
# 查看标签详情
git show v0.9
# 标签推到远程仓库
git push origin v1.0
# 推送多个标签
git push origin --tags
# 删除本地标签
git tag -d v1.0
# 删除远程标签
git push origin :refs/tags/v0.1
*注意:
如果你要修复旧版本中的错误,那么通常需要创建一个新分支:
git checkout -b version2 v2.0.0
服务器端Git
教你如何快速搭建一个具有读写权限、面向多个开发者的 Git 服务器?
1)创建一个操作系统用户 git,并为其建立一个 .ssh 目录:
$ sudo adduser git
$ su git
$ cd
$ mkdir .ssh && chmod 700 .ssh
$ touch .ssh/authorized_keys && chmod 600 .ssh/authorized_keys
2)接着,我们需要为系统用户 git 的 authorized_keys 文件添加开发者 SSH 公钥。此处假设win开发者A
通过ssh-keygen生成秘钥上传
客户端操作
$ cat ~/.ssh/id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCgF4AxrRjo+mre2tAfTnuRDJ/+9BSgqdzqydPJ4fAT2foxuiBYlLF7TCNDvlKs1J4WdukHoaLYkomPCc2HkbqXe+IgKZwF5gBO+LbKVWGoJX33wJcpqiiYOfUCv7NQHSOHHwclfNZHmdVco+ySEhEQPlB+TY8lkaydSVut48MedTDefez0WPp2ovUmjz72rESGpqmq+NAQVceLwbttLQSCyl9iLREQRT0Nycso2OeiGUC4Z5ITfMv1wNqzdtGdPPSBvv6A3oFxFz/1yUN/CA+WD418T2gFzCPpKXXQiU0i5XwKA8VBzku1bHmJWCf7VDAKHLFWGpcp6Ngj indexman@126.com
服务器端操作
vim authorized_keys
# 将客户端秘钥添加进去,以后该客户端访问就无需密码了
3)创建裸仓库
服务器端git用户操作
cd ~
mkdir hellosys.git
cd hellosys.git
git init --bare
4)开发者A在本机克隆这个远程裸仓库
注意
:我这里是127.0.0.1是映射到了虚拟机ip:22。你们不必和我一样。
git clone git@127.0.0.1:/home/git/hellosys.git
vim Readme.md
git add Readme.md
git commit -m "add Readme.md"
# 查看远程仓库
$ git remote -v
origin git@127.0.0.1:/home/git/hellosys.git (fetch)
origin git@127.0.0.1:/home/git/hellosys.git (push)
$ git push
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Writing objects: 100% (3/3), 253 bytes | 126.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To 127.0.0.1:/home/git/hellosys.git
* [new branch] master -> master
搭建Gitlab私服
忽略文件
项目开发过程中,我们不希望提交一些无关的本地文件。比如idea工程中的:
- .idea文件夹
- target文件夹
- .iml文件
等等。
怎么办呢?
可以在工作区的根目录下创建一个特殊的.gitignore
文件。
例如,我本地的某个java项目中,使用idea
工具自动创建的内容如下:
# Created by .ignore support plugin (hsz.mobi)
target
.idea
*.iml
配置别名
通常有些命令加上参数会很长,不容易记住,此时为了便捷操作可以为此类命令配置别名。例如以下这个查看提交日志的丧心病狂的命令:
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 lg
效果是不是很牛逼!
常见问题
1.切换到别的分支,修改也带过去了
开发中建立了一个 dev 分支,修改了一些东西,然后想切换到 master,发现把 dev 上修改的内容也给带过去了!
- 原因是:
因为每一次的修改如果没有 commit,他都属于在工作区,而工作区是全局的
,那么在 dev 下修改的内容没有 commit,也就不属于 dev,这时候切换回 master,git status 一下, git 就会发现把当前工作区和 master 进行对比,发现有文件修改了。新建/修改的文件没有被纳入当前分支 dev 的版本管理,所以会被带到切换的分支。 - 解决方法:
可以commit后再切换分支;或者先执行git stash
暂存修改再切换。
2.忽略已经被跟踪的文件
git rm --cache upload.class
参考资料
https://www.liaoxuefeng.com/wiki/896043488029600/896954117292416
https://gitee.com/progit/