同步操作将从 deepinwiki/wiki 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
git 是现在最常用的同步和版本管理工具。同步就是将两地的文档版本同步,一个中心(git 仓库网站/服务器),多地编辑。版本管理是每一次修改,提交,都会形成一个新的版本,新旧版本直接可以跳转回溯,甚至生成分支。
git init 目录
:普通仓库git init --bare 目录
:裸仓库git clone 源 目录
:克隆创建所谓的仓库,实际上是一个配置了 .git 子目录的普通目录,里面的内容是任意的,仓库的信息存在 .git 子文件夹。普通仓库会显示文档内容,可以对文档进行编辑。
而所谓的裸仓库,就是只有 .git 信息内容的目录(即目录内容就是普通仓库的 .git 子目录),即只有信息,而没有文件视图,不能对存储的文档进行编辑。
仓库服务器,一般是创建裸仓库(bare repo),因为它的作用是给异地的普通仓库同步用的。
git 的管理逻辑是:
仓库和仓库之间,严格来说内容是一样的(在同步之后),这种架构叫分布式版本管理。
流程:
所以裸仓库的作用就是同步和保存信息,而不是编辑文档内容。而普通仓库的作用是提取当前本地仓库需要修改的版本到目录中,用户可以直接编辑和更改文件内容。
例子:
# 普通仓库,可以编辑文档
git init proj-a
# 裸仓库,用于同步共享多个普通仓库的版本信息
git init --bare shared-proj-a
# 查看服务器上的远程仓库
# -h 只列出分支信息
git ls-remote -h https://github.com/ratfactor/ziglings
# 将远程仓库克隆到本地 proj-b 目录下
# proj-b 成为一个普通仓库
# proj-b/.git 是本地仓库的所有信息,它内容等同于远程的仓库
# --depth=1 只要目标版本,而不下载其他关联版本,提高下载速度
# -b 分支
git clone --depth=1 -b main https://github.com/ratfactor/ziglings proj-b
# ./.git/config 的一个例子
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
[user]
name = htqx
email = xxx@sina.com
[remote "deepin.wiki"]
url = git@github.com:deepinwiki/wiki.wiki.git
fetch = +refs/heads/*:refs/remotes/deepin.wiki/*
[branch "master"]
remote = deepin.wiki
merge = refs/heads/master
[remote "gitee"]
url = git@gitee.com:deepinwiki/wiki.git
fetch = +refs/heads/*:refs/remotes/gitee/*
# ~/.gitconfig 的例子
[user]
email = xxx@163.com
name = rufeng
[core]
quotepath = false
[http]
proxy = socks5h://127.0.0.1:1080
# 使用命令来设置配置
git config --global user.name "htqx"
git config --global user.email "xxx@qq.com"
# 取消某个设置
git config --unset http.proxy
# 编辑配置文件
git config --edit
工作模式:git 关联远程仓库并不会自动处理同步,一切都要靠用户自己主动提交信息和同步信息。
例子:
cd proj-a
git remote -v
# 和本地文件夹的裸仓库建立联系
git remote add shared ../share
# 查看远程源设置的相关命令
git remote -h
# 配置文件发生变化
# .git/config
[remote "shared"]
url = ../shared-proj-a/
fetch = +refs/heads/*:refs/remotes/shared/*
以上说明只是建立 url 关联,它可以获取共享目录的信息。但现在,本地目录,和共享目录还是空的。所以我们实际上要创建分支,然后关联分支,才能同步版本。
我们需要做的事情是将本地仓库的 master 绑定到远程仓库,即 origin 远程仓库上的 master 分支。
分支的名字是可以随意命名的,也不要求本地仓库的分支名字和远程仓库的分支名字一致。但建议使用 master 来表示当前的主要分支。
# 默认工作目录会创建一个 master 分支,只需要添加文件即可
touch a # 创建 a 文件
git add a # 添加到 git 仓库,否则 git 并不会跟踪这个文件的变化
git commit -m "create a" a # 提交文件 a 的当前版本(状态)到本地仓库,否则 git 不会认为发生了变化
# -u 关联远程分支。
# 也就是将本地 master --> shared/master 推送,并建立默认关联
# 这样下次在 master 分支下,只需要 git push 就推送最新版本到共享目录
# git pull 反向操作
git push -u shared master
也可以直接修改配置文件:
# .git/config
# 本地 master 分支的配置
[branch "master"]
remote = shared # 远程仓库
merge = refs/heads/master # 关联的远程仓库上的分支
注意。工作目录(文档视图)和 .git 仓库子目录是两个地方。用户编辑的文件首先要提交给 .git 子目录,它相当于在本地的一个版本管理系统。和异地同步是后面的事。
当你添加一个新的文件到目录中,.git 是还没有该文件的信息,这种状态称之为”未跟踪“。.git 有一个该文件的版本,这种情况为已跟踪。
git add 标注一下这次要更新的文件(新建文件或发生变化的文件),一次更新即 git commit 可以是多个文件。
git 是不会自动将文档的变化提交到 .git ,每次修改后,都需要用户主动 git add 标记和主动提交 git commit。
因此,这就造成了目录下的文档存在这几种情况:
事实上,git 的正式提交是分阶段的:
主目录(工作区 Working Directory) --> 暂存区(Staging Area/索引 Index) --> .git(Local Repository) --> 远程仓库(Remote Repository)
可能现在还是对这些术语了解不深,因为还没有掌握相关的操作命令,但是最终会形成这个模型,而命令就是将数据从这些对象中搬来搬去的作用。因为是翻译文档,中文版的说法可能不是很统一,所以也要关注英文的写法是什么,方便以后在互联网上搜索信息。
当然现在只需要记住需要使用这两个命令来更新也可以了。
为了查看目录处于那种状态,git status 命令将会给出信息,并且还会给出相关命令的提示。
# 追加内容到 a
echo "aaa" >> a
git status # 查看本地仓库状态,这是个好习惯
# 这时会提示让你 git add 修改后的 a
git add a
# -m 记录在当前版本的简短描述
git commit -m "add aaa"
# 将上述两步合并到一起的命令
git commit -m "add aaa" - a
这里可以直接用 git commit 的参数来提交,省去 add 这一步:
可以针对一个文件提交,也可以针对几个文件一起进行提交。git 能够分别记录每一个文件的版本变化。一次提交就会生成一个版本。git 接下来可以回溯到旧的版本,这就是 git 的主要目的。
echo "bbb" >> a
git add a
echo "ccc" >> a
git status # 这时,工作区和暂存区(staged)不一致,工作区比暂存区(staged)更新
这时,git status 会提示你:
注意: 在 git 中,什么操作是危险的,什么操作是安全的?当你修改后提交(commit),形成了一个版本记录后,这个修改的内容就会永久(几乎)的被记录下来,你再怎么修改,删除,都能通过某些手段恢复当前的版本,这种操作就是安全的。
反之,当还没有提交前的修改,变了就是变了,删了就是删了,就无法再次恢复。
git restore a
cat a # a 没了 ccc
git commit -m "add bbb" # 新的版本,添加了 bbb
相关命令:
git add 文档/目录
git add -u
:添加当前修改的所有文档git restore 文档
(危险)git commit -m 提交说明
git restore --staged 文档
git restore -WS 文档
(危险)git rm 文档
git rm --cached 文档
:删除暂存区(staged)的文档,并取消跟踪,但保留工作区的文档git mv 文档 新名字
git status
git diff
当一个文件被跟踪后,应该是用 git rm 删除,而不应该使用 rm 删除。因为 rm 只是从目录中删除, .git 本地仓库中是缺乏这种信息(那么你就无法提交到远程仓库中,让其他用户知道你删除了文件)。
重命名实质是 git rm 旧文件,然后 git add 新文件。
版本的概念:
没创建版本的本地修改可能会丢失,而一旦提交,创建了版本,就不会丢失数据。
版本跳转会影响:
意思是,如果改变版本,就等于仓库头指针 HEAD 指向某个版本,获得这个版本下所有文件的状态和内容。改变仓库版本很容易理解,但如果当前工作区,暂存区(staged)和 .git 的版本不一致(说明用户进行了修改),那么就要结合暂存区(staged)和目录就需要仔细思考其中的差异:
git reset --soft 提交
,其中“提交”是 git commit 之后得到的一个散列值,用来标识一个版本。
git reset 提交
git reset --hard 提交
知道工作原理,还要知道应用场景。
当我们想切换版本,最好先保证三者统一(意思就是先提交修改 git commit,创建新版本,三者自然统一在这个新版本之下,然后再跳转版本),这样就不需要考虑工作区修改,或者暂存区(staged)修改的情况了。
任何时候:跳转安全与否,在于当前的修改有没有被提交。每一次提交都有一个代表该版本的哈希值,只要你能记住,永远可以依赖这个哈希值,回到那个版本。这也是版本管理的基本抓手。
相关命令:
git reset --hard 提交
(危险)git checkout -d 提交
git log
HEAD
: 头指针开始HEAD~2
: 前两个版本shared/master..master
: 同步到最新的历史^HEAD
: 不包含 HEAD 的历史-- a
: 针对特定文件的历史-L 1,3:a
: 文件 a 的几个版本的变化历史--oneline
: 一行内显示--pretty="%an %ar:[ %s ]-- %h"
: 格式化输出git 向前回溯历史是很简单的,但是向后移动版本就比较难,如果用 git reset 回到老版本,他会同时将分支指向那个老版本,那么怎么回去当前版本是个问题。
如果你在当前分支下进行版本操作,最好用 git checkout -b 提交
这种方式,强制头指针 HEAD 单独指向某一个版本,而不是关联某个分支,它不会导致分支指针的变化。
git status # 做跳转前,最好先观察文件状态
cat a
git checkout shared/master # 回到上一次同步的位置
cat a # 文件已经清空
git checkout master # 回到当前分支顶端
cat a # 内容又回来了,这就是版本跳转的神奇之处
git checkout HEAD~1 # 表示头指针指向的上一个版本,
cat a # aaa
git log --oneline # 查看历史,可以看到每个提交对应一个 5e8daa6... 之类的一串数字
git checkout 5e8daa6 # 提供若干位即可跳转到指定提交版本
git checkout master # 恢复到当前分支顶端
如果我们跳到某个历史版本中,又修改了文件,将会发生什么?这就是接下来要说的分支。
仓库头指针 HEAD 指向当前的分支,而分支指向当前分支最近的版本,这是一般状态。
分支本质就是一个指针,它指向一个提交(commit),当有新的提交,就更新这个分支指针,指向新的提交。一个提交就是一个版本,提交记录了上一个提交的位置信息,所以可以回溯历史(用上一节的 checkout),分支只需要记录终点版本。
为何需要分支?比如一些大型项目,它都是同时开发几个版本的,一些版本比较激进,一些版本比较稳定,面对不同的消费者。
另一个例子,如果历史中的某个版本,发现了错误,那么就需要添加修补补丁。这些版本的消费者不愿意升级到最新版本,但是开发者还是需要提供修补错误的补丁的。
再一个例子,对当前版本的前景不明朗,可能会尝试一些新的方法,但是又想保留当前分支,可以作为后退的立足点。这时候就可以新建一个分支作为尝试。
诸如此类的例子,说明我们业务上确实需要分支。
首先需要理解分支的状态。分支就相当于版本的一组序列,有起点和终点。分支的集合,就类似一颗多个枝条的大树,在某个地方分叉出一个新的分支。
具体形态有:
git status # 做操作前先了解当前文件状态
# 创建 test 分支(始于当前位置)
git branch test
git switch test
# 设置上游(远程仓库)
# shared 是远程仓库的标识
# test 是想推送的本地分支
# :release 是想对送到远程仓库的哪个分支
# -u 设置绑定关系,即 test <--> shared/release
git push -u shared test:release
# 现在就有了一个新的分支
echo "ccc" >> a
git commit -m "add ccc" a # 做一些修改工作,生成新的版本
# 后悔了,切换回主分支
git status
git switch master
echo "ddd" >> a
git commit -m "add ddd" a # 在主分支上创作,延展一些新的版本
# 从而两个分支出现了独立发展的趋势
用户可以随意在两个分支中切换(前提是先保存好当前状态,避免丢失数据)。
相关命令:
git branch 分支名 起点
git branch -d 分支名
git branch -D 分支名
:强制删除,哪怕该分支还没合并(注意)git branch -m 旧名 新名
git branch -c 老分支 新分支
git branch -u 上游分支 本地分支名
: 修改关联
git fetch 远程仓库
: 下载远程仓库的分支信息(远程已有时)
git checkout -b 本地分支 远程仓库/分支
: 快捷创建对应的本地分支git push -u 远程仓库 本地分支:远程分支
: 设置关联(先在本地创建时)git branch -avv
git switch 分支
git checkout 分支
创建分支很简单,但是要合并分支就要处理相关的合并切入点和内容冲突:
git merge
或 git pull
会开启合并流程,不能快速合并会提示用户人工干预。git merge --ff-only
只尝试快速合并。git merge --abort
,如果出错,可以用这个取消整个合并git rebase master
:当前分支基于 master 主分支变基git mergetool
图形工具(需要配置)git merge --continue
或 git commit
提交结果以上文字说明,可能比较迷糊。以图片说明对照一下:
可见变基会将共同祖先之后的每个当前分支版本,应用到另一个分支顶点上,因此需要依次处理合并冲突。因为等于在基底分支上新增了整个当前分支,那么自然就可以去除当前分支了。
而三方合并虽然只需要考虑 c2,c4,c5 的冲突,但是c4 或c5 是怎么来的?这个信息也必须保留起来(否则没有路径)。好处是哪怕分支的路径再复杂,也只需要处理三个点。
# 三方合并
git status
git switch master
git merge test # 产生合并冲突,因为不能快速合并
# a 文件会变成代解决冲突的内容
# 类似:
aaa
bbb
<<<<...
ddd
====...
ccc
>>>>...
# 可以用任何文本编辑器来将冲突部分改成你需要的内容
# 如:
aaa
bbb
ccc
ddd
# 即可
# 产生冲突时,会产生 MERGE_HEAD 指针,所以 git 能判断当前是合并状态
git add a # 表示解决冲突了
git merge --continue # 继续合并,会产生一个新的版本,是两个当前分支的直接后续
git log --oneline --graph # 查看分支图
gitk # 图形界面看得更清晰
可以配置 pull 默认采取的策略:
git config pull.rebase false # 三方合并(默认)
git config pull.rebase true # 变基
git config pull.ff only # 只支持快进合并
变基合并是为了解决分支图过于繁琐,删除一些过时的分支。
git switch master
git rebase # 清理多余分支,产生冲突,按上一节处理即可
nano a
git add a
git rebase --continue # 解决冲突后继续
git log --oneline --graph
git switch test # 将分支定位到主分支上
git reset --hard master # 这样都在一条分支线上了
rebase 还能指定另一个分支和基底进行变基: git rebase --onto 当前分支 修改的分支
。
注意:变基实际上在改变历史记录,因此在多人协作时会有一点麻烦(要求其他人也应该用 rebase 合并本地仓库的分支)。
reset 命令一般很少用,因为我们的分支应该自然而然的变化(随着提交),reset 区操控它,会让这种历史的变化变得很跳跃。我们要窥探历史,只需要 checkout,它并不改变分支(只改变 HEAD)。但,这里 reset 是有用的,因为变基实际上将 test 从 master 分支树中去除了。我不想维护两个不相干的分支树,所以将 test 重设为当前 master。
.git 目录下:
工作目录:
git 分三个点管理版本:目录、暂存区(staged)、.git。其中.git 子目录保存了所有版本,而目录保存当前工作的版本,暂存区(staged)就是中间过度的版本。
当我们要切换版本的时候,经常面临的窘迫是怎么保存现在正在工作,但却还不想提交的工作版本。
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
vscode 或者开源版本的 vscodium 可以作为 diff 工具使用。difftool 是将修改前和修改后的文件进行对比,让用户选择那些最终修改的版本,这在 git 中是一个比较关键的编辑环节。
git config --global diff.tool vscodium
git config --global difftool.prompt false
git config --global difftool.vscodium.cmd 'codium --wait --diff "$LOCAL" "$REMOTE" '
# 然后可以使用(如果文档修改了,就会出现对比图)
git difftool 文档
对应的配置内容:
# ~/.gitconfig
[diff]
tool = vscodium
[difftool "vscodium"]
cmd = codium --diff --wait \"$LOCAL\" \"$REMOTE\"
[difftool]
prompt = false
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。