git 是现在最常用的同步和版本管理工具。同步就是将两地的文档版本同步,一个中心(git 仓库网站/服务器),多地编辑。版本管理是每一次修改,提交,都会形成一个新的版本,新旧版本直接可以跳转回溯,甚至生成分支。
所谓的仓库,实际上是一个普通目录,里面的内容是任意的,仓库的信息存在 .git 子文件夹。而所谓的裸仓库,就是只有 .git 文件夹,即只有信息,而不能对文档进行编辑。仓库服务器,一般是创建裸仓库,因为它是给异地的普通仓库同步用的,普通仓库会显示文档内容,可以对文档进行编辑。
git 的管理逻辑是:
一个裸仓库可以对应多个普通仓库,普通仓库会显示文档文件,用户编辑文档之后执行同步命令,就和 .git 子目录的仓库信息进行交互和同步,用户执行远程同步命令,就会提交新版本到服务器上的裸仓库。另一个普通仓库的用户执行同步命令,就会从服务器上的裸仓库下载新版本,然后和本地个 .git 子文件夹对比,合并新版本信息,并把新版本的内容替换现有的旧文档,用户就能及时的看到新的文档内容。
所以裸仓库的作用就是同步和保存信息,而不是编htqx辑文档内容。
; ./.git/config 的一个例子
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
[user]
name = htqx
email = [email protected]
[remote "deepin.wiki"]
url = [email protected]:deepinwiki/wiki.wiki.git
fetch = +refs/heads/*:refs/remotes/deepin.wiki/*
[branch "master"]
remote = deepin.wiki
merge = refs/heads/master
[remote "gitee"]
url = [email protected]:deepinwiki/wiki.git
fetch = +refs/heads/*:refs/remotes/gitee/*
; ~/.gitconfig 的例子
[user]
email = [email protected]
name = rufeng
[core]
quotepath = false
[http]
proxy = socks5h://127.0.0.1:1080
工作模式:git 关联远程仓库并不会自动处理同步,一切都要靠用户自己主动提交信息和同步信息。
因此,这就造成了目录下的文档存在这几种情况:
事实上,git 的正式提交是分阶段的:
目录(工作区) --> 缓存区(索引) --> .git
相关命令:
git add 文档/目录
git add -u
:添加 .git 当前已跟踪到的,并有修改的所有文档git restore --staged 文档
git reset 文档
git rm --cached 文档
git commit -m 提交说明
git restore 文档
(危险)
git checkout 文档
(危险)git restore -S -W 文档
(危险)
git checkout HEAD 文档
(危险)git rm 文档
:
git rm --cached 文档
:删除缓存区的文档,并取消跟踪,保留工作区的文档git mv 文档 新名字
git status
: 查看文件状态版本的概念:
版本跳转会影响:
意思是,如果改变版本,就等于仓库指向某个版本,获得这个版本下所有文件的状态和内容。改变仓库版本很容易理解,但是结合缓存区和目录就需要仔细思考其中的差异:
git reset --soft 提交
,其中“提交”是 git commit 之后得到的一个散列值,用来标识一个版本。表现为:仓库变,目录不变,缓存区保存了需要提交的更改。git reset 提交
,表现为:仓库变,目录不变,缓存区为空(即需要git add,因为仓库和缓存区一样)。git rest --hard 提交
,表现为:三者统一,跳转到指定版本,但之前对目录中文档的修改都丢失(危险),所以跳转版本前,首先应该保证三者统一(意思就是先提交修改 git commit,创建新版本,三者自然统一在这个新版本之下,然后再跳转版本)跳转安全与否,在于当前的修改有没有被提交。每一次提交都有一个代表该版本的哈希值,永远可以依赖这个哈希值,回到那个版本。这也是版本管理的基本抓手。
相关命令:
git reset --hard 提交
(危险)git checkout -d 提交
git 向前回溯历史是很简单的,但是向后移动版本就比较难,如果用 git reset 回到老版本,他会同时将分支指向那个老版本,那么怎么回去当前版本是个问题。
而使用 git checkout -b 提交
这种方式,强制头指针 HEAD 单独指向某一个版本,而不是关联某个分支,它不会导致分支指针的变化。
仓库头指针 HEAD 指向当前的分支,而分支指向当前分支最近的版本,这是一般状态。
分支本质就是一个指针,它指向一个提交(commit),当有新的提交,就更新这个分支指针,指向新的提交。一个提交就是一个版本,提交记录了上一个提交的位置信息,所以可以回溯历史,分支只需要记录终点版本。
为何需要分支?协同工作中,不同的人基于一个版本,编写新的内容,这自然就会有多个分支。他们基于自己编写的新内容又发展出自己的下一个版本,这就是分支潜在需求。最后不同的分支汇合起来,形成最终的版本。
git 使用分支的场合:
首先需要理解分支的状态。分支就相当于版本的序列,有起点和终点:
相关命令:
git branch 分支名 起点
git branch -d 分支名
git branch -D 分支名
:强制删除,哪怕该分支还没合并(注意)git branch -m 旧名 新名
git branch -c 老分支 新分支
git branch -u 上游分支 本地分支名
git branch -av
git switch 分支
git checkout 分支
git reset --hard 提交
git checkout -d 提交
创建分支很简单,但是要合并分支就要处理相关的合并切入点和内容冲突:
git merge
或 git pull
会开启合并流程,不能快速合并会提示用户人工干预。git merge --abort
git rebase master
:当前分支基于 master 主分支变基git mergetool
图形工具(需要配置)git merge --continue
或 git commit
提交结果可以配置 pull 默认采取的策略:
git config pull.rebase false # 三方合并(默认)
git config pull.rebase true # 变基
git config pull.ff only # 只支持快进合并
git 分三个点管理版本:目录、缓存区、.git。其中.git 子目录保存了所有版本,而目录保存当前工作的版本,缓存区就是中间过度的版本。
当我们要切换版本的时候,经常面临的窘迫是怎么保存现在正在工作,但却还不想提交的工作版本。
git 给出了两个方案:
git stash push -m 注释
:把工作目录的修改保存在暂存区栈
git stash pop
:应用上一个暂存区的该更git worktree add ../tmp
:创建一个新分支 tmp,并将工作目录设置在../tmp 目录
git worktree remove ../tmp
: 删除多余的工作目录git 主要目的是为了共享一个远程的同步仓库,方便协作或备份。和本地 .git 目录相比,主要是设置和远程仓库的上游关系,包括拉取信息和提交信息。
首先需要设置拉取和提交的配置:
git config push.default=upstream # 表示默认提交到上游
git coinfg pull.ff=only # 拉取合并的模式,只支持快速合并
远程仓库基本配置:
git remote add 仓库名字 ../test.git
,设置地址../test.git,支持多种地址格式。默认的名字:origingit remote set-branches origin test2
git remote set-branches --add origin test
,添加另一个远程分支git branch --set-upstream-to=origin/test
,将当前分支和上游的 origin/test 分支关联。如果上游分支不存在:
git push -u origin HEAD:test2
:提交到 origin 仓库,HEAD 指向当前分支,test2 是希望对应的远程分支,意思就是将 HEAD 指向的分支上传到远端test2 分支。 -u 是同时设置默认上游关系,如果没有默认值,上传的时候就需要指定上游。git fetch
git pull
, pull 相当于 fetch + merge(合并分支)git push
git ls-remote
git 中的远程仓库,和本地仓库其实分别并不大,因为它的设计是任意一个仓库,都保存所有的信息(在同步的基础上)。因此,有时候远程仓库没有的东西,就用 git push 推送,git push 相当于操作远程仓库,你可以推送一些编辑信息给远程仓库,比如删除,添加分支等。
远程仓库是需要配置的,它并不会默认同步所有信息,你需要添加关联的分支,命令指定同步的内容等等,这对于同步效率是有意义的。
创建仓库时会自动生成默认的主分支 master,在远程同步中会经常被假定使用的名字,如果要改名字或删除,这会和其他工具产生冲突,建议保留。
和远程仓库有关的概念:
从远程下载东西,它默认并不会直接合并到当前分支,而是存放在 FETCH_HEAD,后续才对这个远程头的内容进行合并。
实际工作中,经常会出现项目之间相互依赖的关系。这时候不能将两个项目合并在一个仓库,因为有些人不关心其中一部分,如果放在一个仓库,那么两个项目的任意改变,都会影响到所有人。
git 使用一下方案解决这类问题:
意思是在远程建立两个独立的仓库,然后在本地设置其中一个仓库的子模块是另一个仓库。
对于本地来说,主仓库和子仓库并不是平等的,子仓库就相当于主仓库的一个模块,一个功能组件。
然后要注意的是主仓库和子模块之间的依赖关系,它被认为是存在一下关系的:
比如主仓库是版本 1 依赖子仓库版本 2,但是另一个人本地有主仓库版本1,子仓库版本1,这样就是无效的。进而可以发现实际上,是有几种应用情况:
情况1 比较常见于依赖外部项目,情况2 主要是内部自己的管理的项目。根据这两个情况,可能有不同的更新策略。主仓库默认是不会主动提交子模块的更新的。
子模块配置:
# 设置 test1 子模块跟踪远程 stable 分支
# -f .gitmodules 在模块配置中设置(可远程共享),而非在本地 .git
git config -f .gitmodules submodule.test1.branch stable
# 显示子模块的更改详情
git config status.submodulesummary 1
# 自动推送子模块更新(可能存在问题)
git config push.recurseSubmodules on-demand
相关功能:
git submodule add ./test1 test1
git submodule init
,如果是克隆主仓库,子模块需要单独初始化
git submodule sync
:模块路径发生变化时git submodule update --remote --merge
或进入子模块目录执行git pull
git push
,但配置自动更新子模块时
git submodule foreach 'git push'
在子模块执行更新命令标签和分支差不多,它也是一个指向特定版本的指针,只是用途不一样,它只是一个标记。
标签种类:
git log 日志记录了每一次的版本更新,是查看历史记录的强大工具:
# 单行模式 图形模式 显示最近20条记录
git log --oneline --graph -20
图形工具:
# 需要先安装 tk 组件,这个工具才能正常使用
# 推荐使用
gitk