
Git是什么
Git是目前世界上最先进的分布式版本控制系统(没有之一)。
那什么是版本控制系统?
想象一下你正在写一篇重要的论文或一份复杂的代码:
你想保存一个“稳定版本”,然后去尝试一些新的、可能失败的想法。
你不小心删了一大段内容,想恢复到几小时前的状态。
你和同学/同事一起协作,需要合并你们各自修改的部分,又怕互相覆盖。
如果你用Microsoft Word写过长篇大论,那你一定有这样的经历:
想删除一个段落,又怕将来想恢复找不回来怎么办?有办法,先把当前文件“另存为……”一个新的Word文件,再接着改,改到一定程度,再“另存为……”一个新文件,这样一直改下去,最后你的Word文档变成了这样:
TIPdoc文件是二进制文件,并不能够使用git这类工具协同,这里借用了 廖雪峰 - Git教程 生动形象的说明 补充说明:docx格式的文件本质上是一个XML文件,将
.docx
格式的文件后缀改为ZIP后解压, 可以看到解压出来有word文件夹,它包含了Word文档的大部分内容。而其中的document.xml文件则包含了文档的主要文本内容,这是可供协同的部分
过了一周,你想找回被删除的文字,但是已经记不清删除前保存在哪个文件里了,只好一个一个文件去找,真麻烦。
看着一堆乱七八糟的文件,想保留最新的一个,然后把其他的删掉,又怕哪天会用上,还不敢删。
更要命的是,有些部分需要你的同事帮助填写,于是你把文件Copy到U盘里给她(也可能通过Email发送一份给她),然后,你继续修改Word文件。一天后,同事再把Word文件传给你,此时,你必须想想,发给她之后到你收到她的文件期间,你做了哪些改动,得把你的改动和她的部分合并,真困难。
于是你想,如果有一个软件,不但能自动帮我记录每次文件的改动,还可以让同事协作编辑,这样就不用自己管理一堆类似的文件了,也不需要把文件传来传去。如果想查看某次改动,只需要在软件里瞄一眼就可以,岂不是很方便?
这个软件用起来就应该像这个样子,能记录每次文件的改动:
版本 | 文件名 | 用户 | 说明 | 日期 |
---|---|---|---|---|
1 | service.doc | 张三 | 删除了软件服务条款 | 7/12 10:38 |
2 | service.doc | 张三 | 增加了License人数限制 | 7/12 18:09 |
3 | service.doc | 李四 | 财务部门调整了合同金额 | 7/13 9:51 |
4 | service.doc | 张三 | 延长了免费升级周期 | 7/14 15:17 |
记录每一次文件变更:你可以随时回到历史上的任何一个版本。
支持多人协作:轻松合并多个人的工作,并解决冲突。
创建“平行宇宙”(分支):在不影响主线的情况下进行新功能开发或Bug修复。
这样,你就结束了手动管理多个“版本”的史前时代,进入到版本控制的20世纪。
安装Git
在Linux上安装Git
首先,你可以试着输入git
,看看系统有没有安装Git:
$ gitThe program 'git' is currently not installed. You can install it by typing:sudo apt-get install git
像上面的命令,有很多Linux会友好地告诉你Git没有安装,还会告诉你如何安装Git。
Linux 软件包管理工具
Debian及其衍生版(如Ubuntu):
APT (Advanced Package Tool)
APT是Debian及其衍生版的主要软件包管理工具。
常用的命令有:
sudo apt update
:更新软件包列表。sudo apt upgrade
:升级所有已安装的软件包。sudo apt install <package>
:安装软件包。sudo apt remove <package>
:移除软件包。
Fedora、RHEL、CentOS(现在CentOS已经转为使用RPM):
DNF (The Next Generation of YUM)
DNF是Fedora和RHEL 8及更高版本中的新一代软件包管理工具,替代了旧的YUM工具。
常用的命令有:
sudo dnf update
:更新所有软件包。sudo dnf install <package>
:安装软件包。sudo dnf remove <package>
:移除软件包。
对于CentOS 7及之前版本,通常使用YUM:
sudo yum update
:更新所有软件包。sudo yum install <package>
:安装软件包。sudo yum remove <package>
:移除软件包。
Arch Linux:
pacman
pacman是Arch Linux的包管理器。
常用的命令有:
sudo pacman -Syu
:更新并升级所有软件包。sudo pacman -S <package>
:安装软件包。sudo pacman -R <package>
:移除软件包。
Debian系仅需通过一条sudo apt install git
就可以直接完成Git的安装,非常简单。
如果是其他Linux版本,请参考发行版说明,例如,RedHat Linux可以通过命令sudo yum install git
安装。没有包管理器的发行版可以自行下载源码编译安装,仅适合老鸟。
在Windows上安装Git
在Windows上使用Git,有两种安装方法。
直接安装
第一种是直接从Git官网直接下载安装程序,然后按默认选项安装即可。安装完成后,在开始菜单里找到“Git”->“Git Bash”,蹦出一个类似命令行窗口的东西,就说明Git安装成功。
包管理器
第二种是先安装一个包管理器(winget、scoop、choco),推荐Scoop,然后在PowerShell中通过scoop install git
安装Git。
配置Git
安装好Git后,还需要最后一步: 设置你的身份信息。这个信息会附加到你的每一次提交上,告诉别人这是谁干的。在命令行输入:
git config --global user.name "Your Name"git config --global user.email "email@example.com"
TIP—global 表示这是全局配置,这台电脑上所有的 Git 仓库都会使用这个配置。
因为Git是分布式版本控制系统,所以,每个机器都必须自报家门:你的名字和Email地址。你也许会担心,如果有人故意冒充别人怎么办?这个不必担心,首先我们相信大家都是善良无知的群众,其次,真的有冒充的也是有办法可查的。
注意git config
命令的--global
参数,用了这个参数,表示你这台机器上所有的Git仓库都会使用这个配置,当然也可以对某个仓库指定不同的用户名和Email地址。
身份标识 和 权限验证
你可以把这两件事想象成寄一封信:
git config 的用户名和邮箱
:这就像你在信封上写的 “寄件人”信息。
它的作用是标识。当这封信(你的代码提交 commit)被收到后,别人一看就知道这封信是谁写的。
理论上,你可以在“寄件人”那里写任何名字和地址,比如写成“超人”。这封信本身依然会被寄出去。
为什么需要它:在团队协作中,当大家看到 git log 时,能够清晰地知道每一次修改是谁、在什么时候完成的。它是一个纯粹的元数据(Metadata)。
GitHub 账号和凭证
:这就像你用来打开邮筒投递信件的 “钥匙”。
它的作用是验证权限。你必须拥有正确的钥匙,才能打开属于你自己的那个邮筒(你的 GitHub 仓库),把信投进去。 如果你没有钥匙,或者钥匙不对,就算信封上写的是你的名字,邮局(GitHub 服务器)也不会让你投递。
能决定你是否可以修改远程仓库的,是你推送时使用的凭证(PAT 或 SSH 密钥),而不是你 git config 里设置的用户名和邮箱。
常见问题
Q1: 我的 git config 需要和我的 GitHub 账号一样吗?
答案:不强制要求,但强烈推荐 user.email 保持一致。
user.name:可以是你 GitHub 的用户名,也可以是你的真实姓名。建议保持一致,方便辨认。
user.email:强烈建议设置为你 GitHub 账号绑定的邮箱地址。
为什么?
因为 GitHub 会使用你提交记录中的 email 地址来匹配 GitHub 用户。
如果匹配成功:在 GitHub 网站上,这次提交旁边会显示你的头像,并且这次提交会被计入你的贡献图(小绿格)。
如果匹配不成功:提交依然会成功(只要你有推送权限),但在 GitHub 网站上,这次提交的作者旁边会是一个灰色的默认头像,并且不会链接到你的个人主页,也不会计入贡献图。
隐私提示:如果你不想暴露你的真实邮箱,可以在 GitHub 的 邮箱设置 页面勾选
"Keep my email addresses private"
,然后 GitHub 会提供一个 ID+username@users.noreply.github.com 格式的私密邮箱。你可以将这个私密邮箱设置到你的 git config 中。
Q2: 如果不一样,如何控制我的仓库不被随意修改?
答案:通过管理你仓库的访问权限和保护你的个人凭证。
保护你的凭证:你的 Personal Access Token (PAT) 或 SSH 私钥就是你仓库的钥匙。绝对不要泄露它们! 只要别人没有你的钥匙,他们就无法向你的仓库 push 代码,无论他们的 git config 设置成什么。
管理协作者 (Collaborators):在你的 GitHub 仓库设置中,有一个 “Collaborators” 选项。只有被你添加为协作者的人,才有权限向你的仓库推送代码。陌生人是无法推送的。
使用组织和团队 (For larger projects):在组织中,你可以更精细地设置每个团队或每个成员对不同仓库的读、写、管理权限。
分支保护规则 (Branch Protection Rules):你可以设置规则,例如,不允许任何人直接向 main 分支 push 代码,所有修改必须通过 Pull Request 的方式进行,并且需要至少一个其他成员审查通过后才能合并。这是团队协作的最佳实践。
创建版本库
什么是版本库呢?版本库又名仓库(Repository),你可以简单理解成一个目录,这个目录里面的所有文件都可以被Git管理起来,每个文件的修改、删除,Git都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以“还原”。
- 首先,选择一个合适的地方,创建一个空目录:
$ mkdir demo$ cd demo$ pwd
TIP
pwd
用于显示我当前的目录
CAUTION如果你使用Windows系统,为了避免遇到各种莫名其妙的问题,请确保目录名(包括父目录)不包含中文。
- 第二步,通过
git init
命令把这个目录变成Git可以管理的仓库:
$ git initInitialized empty Git repository in /home/p1ume/demo/.git/
通过以上操作Git就把仓库建好了,而且告诉你是一个空的仓库(empty Git repository),细心的读者可以发现当前目录下多了一个.git的目录,这个目录是Git来跟踪管理版本库的,没事千万不要手动修改这个目录里面的文件,不然改乱了,就把Git仓库给破坏了。
如果你没有看到.git目录,那是因为这个目录默认是隐藏的,用ls -ah命令就可以看见
也不一定必须在空目录下创建Git仓库,选择一个已经有东西的目录也是可以的。不过,如果使用自己正在开发的公司项目来学习Git…
如果想要从远程服务器克隆一个现有仓库又该怎么做呢?
- 首先,选择一个合适的地方,创建一个空目录:
$ mkdir demo$ cd demo$ pwd
- 把整个项目(包括所有历史记录)下载 (clone) 到你的本地。
$ git clone https://github.com/git/git.gitor$ git clone --depth 1 https://github.com/git/git.git
TIP遇到大项目clone很慢时,可以使用
--depth 1
来加速,指定了 depth 1 的时候,就是只保留了最新的入口,历史入口就没下载了,即无法随意的使用git reset回滚到历史commits
我一般在 clone 类原生仓库时使用该参数,这极大地降低了我的下载时间,且需要用到其他commits和branch的场景并不多
这样非常轻松地完成了项目的复刻。
把文件添加到版本库
首先这里再明确一下,所有的版本控制系统,其实只能跟踪文本文件的改动,比如TXT文件,网页,所有的程序代码等等,Git也不例外。版本控制系统可以告诉你每次的改动,比如在第5行加了一个单词“Linux”,在第8行删了一个单词“Windows”。而图片、视频这些二进制文件,虽然也能由版本控制系统管理,但没法跟踪文件的变化,只能把二进制文件每次改动串起来,也就是只知道图片从100KB改成了120KB,但到底改了啥,版本控制系统不知道,也没法知道。
不幸的是,Microsoft的doc格式是二进制格式,因此,版本控制系统是没法跟踪Word文件的改动的,前面我们举的例子只是为了演示,如果要真正使用版本控制系统,就要以纯文本方式编写文件。
因为文本是有编码的,比如中文有常用的GBK编码,日文有Shift_JIS编码,如果没有历史遗留问题,强烈建议使用标准的UTF-8编码,所有语言使用同一种编码,既没有冲突,又被所有平台所支持。
CAUTION千万不要使用Windows自带的记事本编辑任何文本文件。原因是Microsoft开发记事本的团队使用了一个非常弱智的行为来保存UTF-8编码的文件,他们自作聪明地在每个文件开头添加了0xefbbbf(十六进制)的字符,你会遇到很多不可思议的问题,比如,网页第一行可能会显示一个“?”,明明正确的程序一编译就报语法错误,等等,都是由记事本的弱智行为带来的。
具体操作
初始化一个Git仓库,使用git init
命令。
添加文件到Git仓库,分两步:
使用命令git add <file>
,注意,可反复多次使用,如git add 1.txt 2.txt
,添加多个文件;
使用命令git commit -m <message>
,完成。
疑难解答
Q:输入git add readme.txt,得到错误:fatal: not a git repository (or any of the parent directories)。
A:Git命令必须在Git仓库目录内执行(git init除外),在仓库目录外执行是没有意义的。
Q:输入git add readme.txt,得到错误fatal: pathspec ‘readme.txt’ did not match any files。
A:添加某个文件时,该文件必须在当前目录下存在,用ls或者dir命令查看当前目录的文件,看看文件是否存在,或者是否写错了文件名。
协作
远程仓库基本概念:
- origin: Git 默认给远程仓库起的名字。当你 git clone 一个项目时,Git 会自动将克隆的地址设为 origin。
- push: 将本地的提交“推送”到远程仓库。
- pull: 从远程仓库“拉取”最新的变更并与本地分支合并。
- fetch: 仅从远程仓库“获取”最新的变更,但不自动合并。
常用协作流程
场景:你已经有了一个本地仓库,想把它推送到 GitHub 上。
在 GitHub 上创建一个新的空仓库(不要勾选 “Initialize this repository with a README”)。
复制仓库地址,例如 https://github.com/your-username/your-repo.git。
在你的本地仓库中,关联这个远程仓库:
git remote add origin https://github.com/your-username/your-repo.git
将本地的 main 分支推送到 origin:
# -u 参数会把本地的 main 分支和远程的 main 分支关联起来,以后推送可以直接用 git pushgit push -u origin main
日常协作流程:
开始工作前,先拉取最新代码,确保你的本地版本是最新的:
git pull origin main
创建自己的分支进行开发:
git switch -c my-feature
在 my-feature 分支上进行修改、添加、提交。 推送你的功能分支到远程仓库:
git push origin my-feature
在 GitHub 或 GitLab 网站上,发起一个 Pull Request (或 Merge Request),请求团队成员审查你的代码并将其合并到主分支。
实用技巧
.gitignore 文件
项目里总有一些文件你不想让 Git 管理,比如编译产物、日志文件、密码配置等。在项目根目录下创建一个名为 .gitignore
的文件,把这些文件或文件夹的名字写进去即可。
示例 .gitignore
:
# 忽略所有 .log 文件*.log# 忽略 node_modules 目录/node_modules/# 忽略密码文件credentials.json
撤销操作
情况一:修改了工作区的文件,但还没 add,想撤销。
# 撤销对单个文件的修改git restore <file_name># 或者旧命令: git checkout -- <file_name>
情况二:已经 add 到了暂存区,想撤销 add,放回工作区。
# 将文件从暂存区撤出git restore --staged <file_name># 或者旧命令: git reset HEAD <file_name>
情况三:已经 commit 了,想修改最后一次提交。 (比如提交信息写错了,或者漏掉了一个文件)
# 1. 如果有漏掉的文件,先 git add <漏掉的文件># 2. 然后执行git commit --amend# 这会打开一个编辑器让你修改提交信息,保存退出即可。
情况四:彻底撤销最近的提交(危险操作!)
# 回退到上一个版本,并丢弃之后的所有修改(工作区和暂存区都会被重置)git reset --hard HEAD^ # HEAD^ 表示上一个版本# 回退到指定的 commit IDgit reset --hard <commit_hash>
WARNING
--hard
参数会彻底删除你的工作,请谨慎使用,尤其不要对已经推送到远程的提交使用。
临时保存现场
当你正在一个分支上开发,突然需要去另一个分支修复一个紧急 Bug,但当前的工作还没完成,不想提交。这时 git stash 就派上用场了。
# 1. 将当前工作区和暂存区的修改保存起来git stash# 2. 现在你的工作区是干净的,可以切换到其他分支干活了git switch hotfix-branch# ...修复bug,提交...git switch maingit merge hotfix-branch# 3. 活干完了,切回原来的分支git switch my-feature# 4. 恢复之前保存的工作现场git stash pop
TODO
Git 常用命令
git init
git clone
git remote add origin ***.git
git push -u origin master
- 推送到远程仓库的dev分支:
git push origin dev
git log
git log --graph --pretty=oneline --abbrev-commit
git status
git diff
git add *
git commit -m "message"
- commit之后又改了一个小bug,但是又不想增加一个commit,可以用:
git commit --amend --no-edit
,直接将改动添加到上一次的commit中 git push
git pull
touch .gitignore
Git 标签管理
- 首先切换到需要打标签的分支上,然后使用
git tag v1.0
就可以在当前commit打上v1.0的标签 git tag v1.0 commitID
对特定commit打标签- 打标签时加上message:
git tag -a <tagname> -m "message"
git tag
查看所有标签git show [tagname]
查看标签详细信息git push origin <tagname>
可以推送一个本地标签到远程仓库git push origin --tags
可以推送全部未推送过的本地标签git tag -d <tagname>
可以删除一个本地标签git push origin :refs/tags/<tagname>
可以删除一个远程标签(先从本地删除)
Git 撤销与回滚
- 暂存区:
git add
之后commit之前存在的区域;工作区:git commit
之后存在的区域;远程仓库:git push
之后; - 作了修改,但还没
git add
,撤销到上一次提交:git checkout -f -- filename
;git checkout -f -- .
- 作了修改,并且已经
git add
,但还没git commit
:- 先将暂存区的修改撤销:
git reset HEAD filename
/git reset HEAD
;此时修改只存在于工作区,变为了 “unstaged changes”; - 再利用上面的checkout命令从工作区撤销修改
- 先将暂存区的修改撤销:
git add
之后,作了修改,想丢弃这次修改:git checkout -f --filename
会回到最近一次git add
- 作了修改,并且已经
git commit
了,想撤销这次的修改:git revert commitID
. 其实,git revert
可以用来撤销任意一次的修改,不一定要是最近一次git reset --hard commitID
/git reset --hard HEAD^
(HEAD表示当前版本,几个^表示倒数第几个版本,倒数第100个版本可以用HEAD~100);参数--hard
:强制将暂存区和工作区都同步到指定的版本git reset
和git revert
的区别是:reset是用来回滚的,将HEAD的指针指向了想要回滚的版本,作为最新的版本,而后面的版本也都没有了;而revert只是用来撤销某一次更改,对之后的更改并没有影响- 然后再用
git push -f
提交到远程仓库
Git 分支管理
- 创建分支:
git branch test
- 切换分支:
git checkout test
- 创建并切换分支:
git checkout -b test
- 将test分支的更改合并到master分支:先在test分支上commit、push,再:
git checkout master
;git merge test
- 如果合并时产生冲突:先手动解决冲突,再合并
- 删除分支:
git branch -d test
git stash
- 如果当前分支还有任务没有做完,也不想提交,但此时需要切换或者创建其它分支,就可以使用stash将当前分支的所有修改(包括暂存区)先储藏起来;然后就可以切换到其它分支
- 在其它分支工作完成之后,首先切换回原来的分支,然后使用
git stash list
命令查看 - 可以使用
git stash apply <stash number>
恢复之前储藏的工作现场,再使用git stash drop <stash number>
删除掉储藏的内容 - 也可以直接用
git stash pop
恢复并删除内容
- 如果在其它分支上做了一个修改(比如修复了一个bug,这次修改有一个commitID),想要将这次修改应用到当前分支上,可以使用:
git cherry-pick commitID
,可以复制一个特定的提交到当前分支
RESTful API
REST指Representational State Transfer,可以翻译为“表现层状态转化”
主要思想
- 对网络上的所有资源,都有一个统一资源标识符 URI(Uniform Resource Identifier);
- 这些资源可以有多种表现形式,即REST中的“表现层”Representation,比如,文本可以用txt格式表现,也可以用HTML格式、XML格式、JSON格式表现。URI只代表资源的实体,不代表它的形式;
- “无状态(Stateless)”思想:服务端不应该保存客户端状态,只需要处理当前的请求,不需了解请求的历史,客户端每一次请求中包含处理该请求所需的一切信息;
- 客户端使用HTTP协议中的 GET/POST/PUT/DELETE 方法对服务器的资源进行操作,即REST中的”状态转化“
设计原则
- URL设计
- 最好只使用名词,而使用 GET/POST/PUT/DELETE 方法的不同表示不同的操作;比如使用
POST /user
代替/user/create
- GET:获取资源;POST:新建/更新资源;PUT:更新资源;DELETE:删除资源;
- 对于只支持GET/POST的客户端,使用
X-HTTP-Method-Override
属性,覆盖POST方法; - 避免多级URL,比如使用
GET /authors/12?categories=2
代替GET /authors/12/categories/2
; - 避免在URI中带上版本号。不同的版本,可以理解成同一种资源的不同表现形式,所以应该采用同一个URI,版本号可以在HTTP请求头信息的Accept字段中进行区分
- 最好只使用名词,而使用 GET/POST/PUT/DELETE 方法的不同表示不同的操作;比如使用
- 状态码:服务器应该返回尽可能精确的状态码,客户端只需查看状态码,就可以判断出发生了什么情况。
- 服务器回应:在响应中放上其它API的链接,方便用户寻找
HTTP请求有哪些常见状态码?
2xx状态码:操作成功。200 OK
3xx状态码:重定向。301 永久重定向;302临时重定向
4xx状态码:客户端错误。400错误请求;401未经授权;403禁止;404未找到;
5xx状态码:服务端错误。500服务器内部错误;501服务不可用