Git 基础学习总结
0 | 什么是版本控制
版本控制系统(Version Control System, VCS) 类似于代码的历史记录面板,当然功能更加丰富,它可以给多个人同时使用,添加修改,撤销修改等等…
1 | VCS 简史
1972年,贝尔实验室在开发祖宗级操作系统Unix时,最早的版本控制系统SCCS诞生了
1982年,Revison Control System 诞生了,虽然支持跨平台,但其与前者相同,只适用于文本文件
1986年,集中式版本控制系统(Centralized Version Control Systems, CVCS)开始研发,其采用一个中央服务器作为仓库载体,存储代码,追踪文件修改,但还没有追踪整个文件目录
80年代后期,Perforce已经广泛应用于各大网站了(直到现在仍有一些大公司在使用,如育碧,英伟达等)
2000年,Subversion出现了,不仅支持非文本文件,还支持追踪整个文件夹内的变化
2004年,微软开发了 Team Foundation Server (TFS),功能上更加全面丰富,利于协同开发,且收到了其他企业的订单
--------------------------------------------------------------
林纳斯·托瓦兹(Linus Torvalds) 与他的团队开发Linux系统的内核时,当时他们使用的VCS是免费版的BitKeeper,然而有一天 BitKeeper 不再免费服务,林纳斯决定创造一个免费的VCS
2005年4月7号,分布式版本控制系统 Git(的第一个提交)诞生了
commit e83c5163316f89bfbde7d9ab23ca2e25604af29
Author: Linus Torvalds < torvalds@ ppc970. osdl. org>
Date: Thu Apr 7 15: 13: 13 2005 –0700
Initial revision of "git", the information manager from hell
2 | 工作原理
集中式 VCS
通过同一个集中管理的中央服务器保存所有文件的修订,开发人员通过客户端连到这台服务器,更新文件或提交更新,如前文所说的 CVS、Subversion、Perforce,这种工作原理有一个致命问题:如果中央服务器(Central VCS Server)不工作了,那所有人就都不用工作了…
分布式 VCS
开发者不只提取最新版本的文件快照,并完整地镜像代码仓库到本地,如前文所述的 Git、Mercurial 以及 Bazaar、Darcs 等,即使文件无修改,也会快照文件,这种工作机制不仅避免了中央服务器带来的尴尬问题,而且能保证仓库在每个人本地有完整的备份,与前者相比,分布式 VCS 具有很大的优越性
3 | 安装
安装过程比较简单,除了在官网下载安装程序,Windows 也可通过 Chocolatey 安装
https://git-scm.com/downloads
$ choco install git
Linux 系统的安装指令
$ sudo apt-get install git
安装完成后,可直接在命令行输入 git 或者 git --version 确认是否安装成功
λ git --version
git version 2.21.0.windows.1
4 | Git 指令
输入 git help 可以获取帮助
$ git help <command>
$ git <command> --help
输入 git help --all 可显示所有子命令
git 子命令的选项有长短两种形式,长选项是 Linux 风格
$ git commit -m "Fixed a typo."
$ git commit --message=" Fixed a typo."
有时需要 双破折号– 分离参数,
比如分离指令的控制部分与操作数部分, 如标识文件名或区别文件名与tag
$ git diff -w master origin -- tools/ Makefile
# Checkout the tag named "main. c"
$ git checkout main. c
# Checkout the file named "main. c"
$ git checkout -- main. c
5 | 基本概念
版本库 Repository
一个 Git 版本库(仓库)对应一个文件夹(工作目录),仓库的重要信息存储于工作目录下的 .git 文件夹里,类似一个简单的数据库,其包含所有用于维护与管理项目的修订版本和历史的信息,主要维护两件东西:对象库、索引
对象库 Object Store
----- 对象库是版本库的核心,其包括 4 种对象,每个对象都有一个唯一的SHA1散列值,散列值由内容决定,与操作系统,路径等均无关
- 块 Blob:Binary Large Object,大意为二进制大对象,保存一个文件的主要数据
- 目录树 Tree:即目录信息,记录Blob,路径名以及文件元数据,如文件名
- 提交 commit:记录着一次提交的详细信息,每个提交指向一个目录树
- 标签 tag:分配给一个对象自定义的名称,通常是提交对象.
索引 Index
----- 一个临时的、动态的二进制文件,捕获项目在某一时刻的整体目录结构的版本,记录当前有哪些修改
6 | 特点与状态
重要特点
- 直接记录快照,而非差异比较:每次提交或在 git 中保存项目状态时,git对当时的全部文件制作快照并保存该快照的索引。没有修改的文件,则不再重新存储该文件,只保留一个链接指向其之前存储的文件。 git 对待数据更像是一个快照流
- 保证数据完整性:git 存储数据前以 SHA-1 散列计算校验和,使用前再校验,因此不可能在 Git 不知情时更改任何文件内容或目录内容
- 一般只添加数据:很难执行不可逆操作,或者以任何方式清除数据
- 近乎所有操作都是访问本地文件不需要网络
- 原子操作:要么执行所有修改要么放弃所有修改
状态
仓库中的文件通常处于 3 种状态下:
- 已追踪 Tracked ----已在版本库中的文件,或是已暂存到索引中的文件
- 已忽略 Ignored ----版本库中存在但不需要追踪的文件
- 未追踪 Untracked ----不属于以上两者的文件
而文件的修改状态有以下 3 种:
- 已修改 modified ----- 在工作目录中修改文件
- 已暂存 staged ----- 暂存文件,将已修改的文件的快照放入暂存区域
- 已提交 committed ----- 找到暂存区域的文件,将快照永久性存储到 Git 仓库目录
7 | 初次运行 Git 前的配置
配置级别
Git 的配置级别分为 3 种:
Local 针对当前仓库的配置 project/.git/ config
global 针对当前用户的配置 ~/.gitconfig
System 针对所有用户及其仓库通用的配置 /etc/ gitconfig
如果出现相同配置项如 user.email 则优先使用 Local 的其次是 global 最后 system
⚡ 查看所有配置
$ git config --list
core.symlinks=false
core.autocrlf=true
core.fscache=true
color.diff=auto
color.status=auto
⚡ 常用配置项
- 配置提交人的名字与邮箱
$ git config --global user.name "John Doe"
$ git config --global user.email johndoe@example.com
- 配置默认编辑器
$ git config --global core.editor emacs
- 配置提交模板
$ git config --local commit.template <template-name.txt>
- 配置忽略文件
$ git config --global core.excludesfile .gitignore_global
- 设置颜色输出 true/false/always
$ git config --global color.ui false
# 或者指定某指令的输出的颜色样式
$ git config --global color.diff.meta "blue black bold"
8 | 创建仓库
方法一:克隆
通常情况下,仓库已经建好,我们需要从 gitlab 或者 github 上将仓库下载到本地
$ git clone <url> <custom_repository_name>
默认创建的文件夹名称为仓库名,如需自定可以替换 <custom_repository_name>
⚠️ 这个目录下不能有与仓库同名的文件夹,如果有需要保证文件夹内是空的,否则报错
方法二:初始化+添加
如果是首次创建仓库,则需要先在 github 或 gitlab 上创建一个仓库,创捷完成后,会生成一个地址,在本地找个空目录,
$ git init
# 创建文件,或把项目需要的文件挪进来
$ ...
# 添加文件
$ git add *
# 输入提交信息并提交
$ git commit -m "Initialize a project !!"
# 设置远端地址,让 Git 知道仓库在 github 上的地址
$ git remote add origin git@github.com:<username>/test.git
# 推送,由于是初次提交,所以需要用到 -u 参数
$ git push -u origin master
9 | 提交更新
如前文所述,git 的文件状态会在三种状态间不断转换:
⚡ 检查当前文件状态 git status
$ git status
# 显示简洁版
$ git status -s
$ git status --short
M apps/api/forms.py
M apps/api/views.py
# 额外显示分支
$ git status -sb
⚡ 查看未暂存的文件变更 git diff
$ git diff
# 查看合并引入的内容
$ git diff --ours
# 查看合并的结果与他们那边有什么不同
$ git diff --theirs
# 查看文件在两边是如何改动
$ git diff --base
⚡ 加入暂存区 git add
$ git add <file-1> <file-2>
----------------------------
-i, --interactive 交互式处理文件,避免输入文件名
-u, --update 将 modified 的文件加到暂存区
-p, --patch 加patch
⚡ 提交 git commit
$ git commit -m <message>
Note: 提交信息应少于 50 个字符(25个汉字)
提交的时候,若需要忽略某些文件,如.pyc,local_settings.py等,可以设定.gitnore文件
.gitignore 语法:
- 所有空行或者以 # 开头的行都会被 Git 忽略
- 可以使用标准的 glob 模式匹配
- 匹配模式可以以(/)开头防止递归
- 匹配模式可以以(/)结尾指定目录
- 要忽略指定模式以外的文件或目录,可以在模式前加上 !取反
⚡ 删除文件 git rm
删除某个 Tracked文件 不追踪 + 删除文件
$ git rm <filename>
删除某个既是 staged 也是 modified 的文件只能 --force
$ git rm -f <filename>
不追踪 + 保留文件 Tracked —> Untracked
$ git rm --cached <filename>
⚡ 重命名/移动 git mv
$ git mv old.txt new.text
实际上,git 执行了下列三项指令
$ mv old.txt new.text
$ git rm old.txt
$ git add new.text
⚡ 暂存文件 git stash
$ git stash
$ git stash save
-------------------------
--patch 加 patch
--keep-index 不 stash 已暂存的文件
--include-untracked 包含未追踪的文件
查看暂存区
$ git stash list
恢复暂存的修改
$ git stash apply <stash@{n}>
$ git stash pop <stash@{n}>
# --index 恢复当前的上一个
移除指定暂存
$ git stash drop <stash@{n}>
直接将暂存移入新分支
$ git stash branch <branch-name> [<stash>]
⚡ 清理文件 git clean
$ git clean
--------------------
-f 直接删除未追踪的文件
-f -d 直接删除未追踪的文件和目录
-n 列出执行git clean将会被删除的文件
-i 交互式删除
⚠️注意:clean 不会删除 .gitignore排除的文件,删除排除文件加上 -x
$ git clean -f -d -x
10 | 撤销
⚡ 修改最近一次提交的备注
$ git commit --amend
⚠️ 如果提交已经push到仓库,修改备注之后,需要执行git push -f,普通 push 会失败
$ git push -f
修改多个提交的备注 可用git rebase 解决
⚡ 撤销修改/回退版本
# 将某个文件回退到最新版本
$ git checkout HEAD <filename>
# 回到到指定版本
$ git checkout <commit> <filename>
# 整个仓库回退到指定 commit
$ git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]
-- hard
选项是“硬”回退,回退不保留当前的任何修改-- soft
选项时“软”回退,回退并保留修改(即索引和工作目录的内容不变)
三种选项对项目的影响,mixed会重置索引
git reset 将 HEAD 指针指向指定的提交后,原始指针另存为 ORIG_HEAD
11 | 查看历史
通常情况下 git log 即可,当然也有一些实用的选项
$ git log -g 查看message与reflog
$ git log -p 查看各个差异
$ git log -p -2 指定显示数量
$ git log --stat 查看简略增删行统计
$ git log --graph 展示分支合并示意图与对应提交详情
显示的格式可以通过 –pretty 参数自定义
$ git log --pretty=oneline|short|full|fuller
# 或者自定义样式
$ git log --pretty=format:"%h %s"
$ git log --oneline
# 相当于git log --pretty=oneline
⚡ 按日期或作者筛选 log
写工作日志或月总结,忘了上周自己做了哪些项目,此时用这项指令很有用
$ git log --since=2019-06-01 --until=2019-06-30 --author=<author>
⚡ 快速查看昨天 develop 分支上的提交
$ git show develop@{yesterday}
⚡ 列出所有提交者的提交统计
可以看到每一个提交者分别有多少次提交,以及每次提交的提交信息
$ git shortlog
⚡查看引用日志
其记录了最近几个月 HEAD 和分支引用所指向的历史
$ git reflog
12 | 远程仓库
⚡ 查看
$ git remote
# 查看并展示地址
$ git remote -v
origin https://github.com/Jax/A.git (fetch)
origin https://github.com/Jax/A.git (push)
# 查看更多信息
$ git remote show <remote-name>
⚡ 增删
# 增加
$ git remote add <remote-name> <url>
# 删除
$ git remote rm <remote-name>
# 删除某个本地分支对应的远端
$ git branch -r -d <remote-name>/<branch>
# 删除本地版本库中陈旧远程追踪分支
$ git remote prune
# 重命名
$ git remote rename <old_name> <new_name>
# 直接修改某个远端的地址
$ git remote set-url origin git://repos.example.com/stuff.git
⚡ 推拉
# 推送代码
$ git push <remote> <local-branch>:<remote-branch>
# 抓取后自动合并远程分支到当前分支
$ git pull
# 拉取某个远端代码
$ git fetch <remote-name>
git fetch 命令会将数据拉取到你的本地仓库, 它并不会自动合并或修改你当前的工作
它与 git pull 的关系可大概理解为 git pull = git fetch + git merge
执行 git fetch之前(左侧本地/右侧远端)
执行 git fetch 之后:
13 | 分支
分支的本质是指向提交对象的可变指针, 是包含所指对象校验和(长度为 40 的 SHA-1 值字符串)的文件,分支还可以分为两种,长期分支如 master,功能(特性)分支如 feature, hotfix
Note: 个人建议将分支理解为指针,这样有助于理解学习git
⚡ 查看
$ git branch
# --merged 查看已合并
# --no-merged 查看未合并
# 查看每一个分支的最后一次提交
$ git branch -v
HEAD 指向当前所在的本地分支,ORIG_HEAD 指向原始HEAD
⚡ 切换与增删
# 创建分支,加上提交码可指定提交
$ git branch <branch-name> <commit>
# 创建并切换
$ git checkout -b <name>
# 手动切换分支
$ git checkout <branch-name>
# 删除分支
$ git branch -d <branch-name>
# 删除某个远端分支(本地还保留)
$ git push -d <branch-name>
14 | 变基
-----------直接挪动指针的神器之一
常见用途是保持你正在开发的分支相对于主分支是最新的
如我从主分支 master 上新建一个功能分支 feature-upload,开发当前迭代的新功能,
某位同事修复了一个重大bug并提交到了 master 分支上,此时可用变基获取这些修改
一如既往,获取修改时可能遇到需手动处理的冲突 ~~~
# 将feature-upload分支开出来时所在的“基”挪到 master 指针所在的位置
$ git rebase master
# 也可通过第二参数指定被挪的分支
$ git rebase <to-branch> <base-branch>
# 没有特殊需要的情况下,下列指令也可以达到相同目的
$ git pull --rebase
⚡ 交互式变基
$ git rebase -i HEAD~4
用途:一次修改多个提交的message | 重新排序commits | 删除某个不要的commit
举例:领导要求从提交历史(下图)中去掉提交C2,并将C4,C5 的先后顺序对调
$ git rebase -i master~4
# 4 指的是包括 master 指针在内的前四个提交
# 接下来会触发 git 默认编辑器,我们将 C2 前面的关键字 pick 换为 omit
改好之后保存退出编辑器,Job done ~
提示:
- 如果需要压缩多个提交为一个,可以在编辑器里将需要被压缩的 commit 前加上s(squash) 标记 s 的会被压入上一个提交
- 如果是修改多个提交的 Message,只修改四个中某个提交的message,只要将你想修改的提交前面的 pick 改为 edit
⚡ 两个比较实用参数:–onto | --preserve-merges
# 将分支 feature 上有但 maint 上没有的提交挪到 master 上
$ git rebase --onto master maint feature
# 保留被变基的整个分支的分支与合并结构,使用-- preserve-merges
$ git rebase --preserve-merges master dev
⚡ 中断或恢复
# 直接放弃变基并恢复原始状态
$ git rebase --abort
# 直接放弃但保留HEAD指针的位置
$ git rebase --quit
# 继续变基流程
$ git rebase --continue
15 | 合并
当前的提交历史
⚡ 往当前分支合入需要的分支
$ git merge <branch>
在 master 分支上合并了分支 one
用变基+合并也可以,提交历史会更加“完美”:
$ git rebase master one
$ git checkout master
$ git merge one
合并分支one后,指针会 fast-forward 到 one 指针所在的11931提交
- fast-forward
合并时出现该词,说明主分支是被合并分支的直接祖先,所以指针只是向后快速移动了
合并时出现冲突,可以通过 git status 查看冲突文件并解决
⚡ 继续 & 终止
$ git merge --abort
$ git merge --continue
⚡ 忽略合并时由任意数量空白引起的冲突
$ git merge -Xignore-space-change <branch-name>
但保留HEAD指针的位置
$ git rebase --quit
# 继续变基流程
$ git rebase --continue
END