0004-Git不完全操作指南之远程操作4——push

Git不完全操作指南之远程操作4------push

既然有pull让我们可以从远程仓库中拉取数据,那么肯定也会有一个命令,让我们把本地的一些变更推送的远程仓库。这个命令就是git push

git push 负责将你的 变更上传到指定的远程仓库,并在远程仓库上合并你的新提交记录。一旦你讲本地变更push上去之后,其他小伙伴就可以查看到你所做的变更,可以拉取你的变更或者将你的变更合并到他们的分支上。

注意: git push 不带任何参数时的行为与 Git 的一个名为 push.default 的配置有关。它的默认值取决于你正使用的 Git 的版本,我使用的Git版本是 upstream。 这没什么太大的影响,但是在你的项目中进行推送之前,最好检查一下这个配置。

可以通过以下命令查看:

bash 复制代码
git help config
# 然后输入以下命令搜索
/push\.default

push.default Defines the action git push should take if no refspec is given (whether from the command-line, config, or elsewhere). Different values are well-suited for specific workflows; for instance, in a purely central workflow (i.e. the fetch source is equal to the push destination), upstream is probably what you want. Possible values are:

sql 复制代码
       •   nothing - do not push anything (error out) unless a refspec is given. This is primarily meant for people who want to avoid mistakes by always being explicit.

       •   current - push the current branch to update a branch with the same name on the receiving end. Works in both central and non-central workflows.

       •   upstream - push the current branch back to the branch whose changes are usually integrated into the current branch (which is called @{upstream}). This mode only makes sense if you are
           pushing to the same repository you would normally pull from (i.e. central workflow).

       •   tracking - This is a deprecated synonym for upstream.

       •   simple - in centralized workflow, work like upstream with an added safety to refuse to push if the upstream branch's name is different from the local one.

           When pushing to a remote that is different from the remote you normally pull from, work as current. This is the safest option and is suited for beginners.

           This mode has become the default in Git 2.0.

       •   matching - push all branches having the same name on both ends. This makes the repository you are pushing to remember the set of branches that will be pushed out (e.g. if you always
           push maint and master there and no other branches, the repository you push to will have these two branches, and your local maint and master will be pushed there).

           To use this mode effectively, you have to make sure all the branches you would push out are ready to be pushed out before running git push, as the whole point of this mode is to allow
           you to push all of the branches in one go. If you usually finish work on only one branch and push out the result, while other branches are unfinished, this mode is not for you. Also
           this mode is not suitable for pushing into a shared central repository, as other people may add new branches there, or update the tip of existing branches outside your control.

           This used to be the default, but not since Git 2.0 (simple is the new default).

命令详解

如图,假如说我们有这样一个项目,你本地开发了一些内容,产生了C2的变更,而我们远程仓库还没有这个变更。

此时,如果我们执行以下命令:

bash 复制代码
git push

那么,git会将我们本地的更改推送到远程仓库,并同时更新我们本地远程分支o/main的指针,让它指向C2,而我们的远程仓库的main分支也会移动到我们最新的一个提交的C2

远程库提交历史偏离

举个栗子:

假设你周一克隆了一个仓库,然后开始研发某个新功能。到周五时,你新功能开发测试完毕,可以发布了。但是 ------ 天啊!你的同事这周写了一堆代码,还改了许多你的功能中使用的 API,这些变动会导致你新开发的功能变得不可用。但是他们已经将那些提交推送到远程仓库了,因此你的工作就变成了基于项目旧版的代码,与远程仓库最新的代码不匹配了。

这种情况下, git push 就不知道该如何操作了。如果你执行 git push,Git 应该让远程仓库回到星期一那天的状态吗?还是直接在新代码的基础上添加你的代码,亦或由于你的提交已经过时而直接忽略你的提交?

因为这情况(历史偏离)有许多的不确定性,Git 是不会允许你 push 变更的。实际上它会强制你先合并远程最新的代码,然后才能分享你的工作。

如上图,你基于C1版本提交了一次C3,但此时,远程仓库因为其他同事提交代码,已经更新到了C2了,此时如果你想要将本地代码push到远程仓库的话,是会被拒绝的,因为你本地的提交历史已经跟远程仓库的历史发生了偏移,你需要先将远程仓库的代码合并过来,解决可能存在的冲突后,才能提交并推送。

上面这种情况,在我们实际开发工作中非常常见,这就是经常说的"冲突了"。

那么遇到这种情况,我们要怎么解决冲突,顺利将代码提交到远程仓库呢?

方法其实也不止一种,比如说:

  1. push之前,先pull一下,pull本身就有merge的操作,如果此时如果出现没办法自动合并的冲突,那么我们就手动解决冲突后提交。解决完所有冲突后,再执行push操作即可。

  2. push之前,先使用fetch操作,获取到远程仓库最新的变更情况,此时,我们本地的远程分支o/main就已经跟远程仓库同步了,此时我们再让main分支变基为o/main,在变基的过程中可能会出现冲突,我们先解决冲突之后,输入:git rebase --continue继续变基的过程(当然,如果在变基过程中,你后悔不想做这个操作了,你可以输入命令:git rebase --abort

  3. push之前,使用merge进行合并,虽然merge并不会移动你的工作变更,但他会创建一个合并提交,并告诉git你已经合并了远程仓库的所有变更,这是因为远程分支现在是你本地分支的祖先,也就是说你的提交已经包含了远程分支的所有变化。

    如下图中的C4就是我们在合并过程中创建的一个包含了远程仓库所有变更的一次提交。

  4. push之前,执行git pull --rebase,这个与第一点的区别就是没有显示指定用rebase方式的话,默认是采用merge合并的,而此时,我们显示的指定了rebase,那么它就会在执行完fetch操作之后,执行rebase操作了。

相比起merge,个人觉得,使用rebase会更好一点,因为使用rebase不会产生一个额外的合并提交记录,让我们的git变更历史变得更加清晰明了。因此,我们再实际开发过程中,建议使用第2或第4种方法。

远程追踪分支

经过上面几章的学习,不知道大家会不会有一个疑惑,好像我们本地的main分支就该要跟远程的o/main分支关联似得,主要是因为以下几点:

  • pull 操作时, 提交记录会被先下载到 o/main 上,之后再合并到本地的 main 分支。隐含的合并目标由这个关联确定的。
  • push 操作时, 我们把工作从 main 推到远程仓库中的 main 分支(同时会更新远程分支 o/main) 。这个推送的目的地也是由这种关联确定的!

当然,git不会真的简单的仅以命名规则将这两个分支关联起来,而是引入了一个概念:远程追踪分支

直接了当地讲,maino/main 的关联关系就是由分支的"remote tracking"属性决定的。main 被设定为跟踪 o/main ------ 这意味着为 main 分支指定了推送的目的地以及拉取后合并的目标。

你可能想知道 main 分支上这个远程追踪分支属性是怎么被设定的,你并没有用任何命令指定过这个属性呀!好吧, 当你克隆仓库的时候, Git 就自动帮你把这个属性设置好了。

当你克隆时, Git 会为远程仓库中的每个分支在本地仓库中创建一个远程分支(比如 o/main)。然后再创建一个跟踪远程仓库中活动分支的本地分支,默认情况下这个本地分支会被命名为 main

克隆完成后,你会得到一个本地分支(如果没有这个本地分支的话,你的目录就是"空白"的),但是可以查看远程仓库中所有的分支(如果你好奇心很强的话)。这样做对于本地仓库和远程仓库来说,都是最佳选择。

当然,你也可以使用下面的命令修改远程追踪分支:

bash 复制代码
# 修改当前分支的远程最终分支
git branch -u origin/main1
# 如果你当前不是在要设置远程追踪分支的本地分支上,你可以显示指定要给哪个本地分支设置远程
# 追踪分支
git branch -u origin/main1 test1
# 或
git branch --set-upstream-to=origin/main1
# 或者在你检出新分支时就指定
git checkout -b main1 origin/main1

指定推送目标

既然我们已经了解了远程追踪分支是怎么回事了,那么,我们再来看看git push在明确指定推送目标时是如何工作的。

bash 复制代码
# 其中的:
# <remote> 代表远程仓库源,这里默认是:origin
# <place> 代表要被推送的分支,如:main
git push <remote> <place>
# 如:
git push origin main
# 把这个命令翻译过来就是:
# 切到本地仓库中的"main"分支,获取所有的提交,再到远程仓库"origin"中找到"main"分支,将远程仓库中没有的提交记录都添加上去,搞定之后告诉我。
# 我们通过"place"参数来告诉 Git 提交记录来自于 main, 要推送到远程仓库中的 main。它实际就是要同步的两个仓库的位置。
# 需要注意的是,因为我们通过指定参数告诉了 Git 所有它需要的信息, 所以它就忽略了我们所检出的分支的属性!

上面说的例子中,我们要推送的目的地和推送源分支名都是main,那么,假如说我们要推送的目的地和推送源不一样,又要如何推送呢?

bash 复制代码
git push origin <source>:<destination>
# 如下命令代表将本地的foo分支父节点的记录提交到远程的main中
git push origin foo^:main
git push origin foo:main
git push origin cf0b:main

# 上述命令都是合法的,也就是说,我们的source和destination只要是git能够识别出来的位置
# 就可以

要同时为源和目的地指定 <place> 的话,只需要用冒号 : 将二者连起来就可以了。

这个参数实际的值是个 refspec,"refspec" 是一个自造的词,意思是 Git 能识别的位置(比如分支 foo 或者 HEAD~1

一旦你指定了独立的来源和目的地,就可以组织出言简意赅的远程操作命令了。

如果推送目的分支不存在的话,git会自动在远程创建这个分支:

bash 复制代码
git push origin foo:newBranch
# 远程仓库不存在newBranch分支,将会自动创建这个分支

同样的,其实我们的git fetch也一样可以类似这样的使用:

bash 复制代码
# 此处需要注意几点:
# 1. 如果你本地当前分支是main,那你不能使用这条命令
# 2. 在这里foo代表远程分支,main代表本地分支
git fetch origin foo:main
相关推荐
安冬的码畜日常2 小时前
【Git 学习笔记】Ch1.1 Git 简介 + Ch1.2 Git 对象
笔记·git·学习
西西o8 小时前
mac中文件夹怎么显示.git隐藏文件
git·macos
JavaDog程序狗9 小时前
【规范】Git分支管理,看看我司是咋整的
git·规范
安冬的码畜日常19 小时前
【Git 学习笔记】1.3 Git 的三个阶段
笔记·git·学习
森叶21 小时前
webstorm 高效查看不同分支差异 摒弃你的git diff手动操作
ide·git·webstorm
wenxiudaren1 天前
针对SVN、GIT版本管理工具进行源代码加密保护
git·安全·svn·系统安全
MonkeyKing_sunyuhua1 天前
ubuntu22.04 操作系统安装 Git LFS
git
T0uken1 天前
【Git】本地版本控制
大数据·git
alvis1 天前
🏷️ Git实操
git·github