git - 不同步源和主
问题描述
我有一个 git repo,上面有两个文件master
(例如,当我访问 GitHub.com 时)和一个本地计算机上的 git repo(origin
),里面有很多很多文件。有几个文件origin
不在 master 上,我已经对 README 进行了更改,但master
我还没有在origin
.
我想git push
选择从源到主的脚本。
但是,我得到了错误
hint: Updates were rejected because the remote contains work that you do not have locally."
所以,我想要git pull
我所拥有的,master
但我很担心这样做,因为我不想失去我在当地拥有的所有工作。
我正在考虑将我所有的工作保存在origin
其他地方,做到git pull
原点,然后将工作移回原点并做git push <files>
。
还有其他不那么迂回的方法吗?
如果有的话,我宁愿从中删除所有内容,master
因为那里实际上有两个文件,然后添加我需要的所有内容origin
,但我不知道是否有方向地基于 git 的工作方式会没问题。
谢谢你!
解决方案
这是我的建议:不要以git pull
. 开始git fetch
。使用git fetch
和其他 Git 命令一段时间,直到你真正“了解”git fetch
和其他 Git 命令做什么。 然后你就可以使用了git pull
,如果你愿意的话,因为这git pull
意味着:
- 运行
git fetch
;然后 - 通常运行第二个 Git 命令,
git merge
但您也可以选择git rebase
。
这是git pull
一个方便的快捷方式,因为事实证明,在 之后git fetch
,您经常需要一个git merge
or git rebase
。但我经常喜欢在 之间git log
添加一个或其他命令,而使用 时,你不能这样做。事实证明,便利命令是......不方便。git pull
新手遇到的更大问题git pull
是它似乎是魔术。这使得人们无法思考到底发生了什么。大约 15 年前,当我第一次使用 Git 时,我曾经遇到过同样的问题。了解我可以分离git pull
成git fetch
+ 第二个命令是理解 Git 的关键之一。
要知道的事情
您需要了解的内容包括:
Git 不是关于文件,而是关于提交。因此,您需要查看您有哪些提交,以及其他 Git 存储库(例如 over on )有哪些提交。
origin
提交包含文件。事实上,每个提交都有每个文件的完整快照。它们以压缩的、只读的、仅限 Git 的格式存储,只有 Git 可以读取,并且几乎没有任何东西(甚至 Git 本身)可以覆盖。一旦文件保存在提交中,那么在该提交中它是完全安全的。 没有什么可以改变它。只要你还有那个commit,你也仍然有那个文件。
但这反过来意味着您可以查看和处理/使用的文件不在 Git 中。您看到和处理的文件是普通的读/写文件。由于 Git 提交中的文件不是普通文件,因此它们必须是不同的文件。他们就是这样。当您使用
git checkout
或 newfangledgit switch
时,您会从某个分支中挑选出一些 Git 应该复制的提交。Git 找到该提交中的所有文件并将它们复制到您的工作区,Git 将其称为您的工作树或工作树。
一旦你牢牢掌握了这一点,你就需要了解更多的事情:
Git 做出新的提交,不是来自你工作树中的内容,而是来自Git在Git 的 index中的内容。这个东西——“索引”——还有两个名字:它也被称为暂存区,有时——现在很少见,主要是在像缓存这样的标志
--cached
中。(为什么它有三个名字有点神秘,但是暂存区是三个名字中最新的,也是最具描述性的,所以很明显,至少部分原因是原来的名字不好。)您可以将 Git 的索引视为您提议的下一次提交。
git checkout
当您第一次使用or签出某个特定提交时git switch
,Git 会将整个提交复制到索引/暂存区域。所以现在提议的下一次提交、当前提交和您的工作树都具有相同的文件。因此,每个文件都有三个副本:- 当前提交中不可更改的、永久保存的副本,因为它在提交中,所以完全不受更改的影响;
- Git 索引中提出的下一个提交副本,您可以更改该副本;和
- 您可以在工作树中查看和编辑的副本。
这意味着您在工作树中进行工作,然后
git add
在您更新的任何文件上运行。你可以在每个文件上运行它git add .
(或者您可以直接在刚刚更改的文件上运行它。这里所做的是将文件的更新的工作树副本复制回 Git 的 index。这会更改提议的下一次提交。git add -u
git add
当你运行
git status
:- Git 将当前提交 (
HEAD
) 与索引/暂存区域进行比较。这两者之间的任何区别是staged for commit
,即,这就是和提议的下一次提交之间的区别。HEAD
新的提交仍然是每个文件的完整快照,但只有一些文件被更改。 - 另外,Git 将索引/暂存区域与您的工作树进行比较。这两者之间的任何差异都是
not staged for commit
. 也就是说,您可以运行git add
这些更改,以更新文件的建议下一个提交副本,这可能会更改早期HEAD
-vs-index 差异的输出。
- Git 将当前提交 (
当你运行时
git commit
,Git 只是打包当时Git 索引中的任何内容,并使用它来进行新的提交。像just 这样的分支名称
master
可以帮助 Git 找到一些特定的提交。当您启动master
并运行git commit
时,Git:- 将文件打包到其索引中;
- 添加您的姓名、电子邮件地址等,以对所有这些进行提交;
- 写出新的提交;和
- 使名称
master
标识新提交。
新的提交链接回之前的最后一次提交。
提交的心理形象
这意味着我们可以画出正在发生的事情,我觉得这很有帮助。假设我们从一个只有几个提交的小型存储库开始,只有一个分支名称,master
或者main
,它标识此存储库中的最后一次提交:
... <-F <-G <-H <--master
每个提交都有一个number,一个大而难看的十六进制数字,对于那个特定的提交来说是唯一的。我们称这个数字为哈希 ID。进行提交会为新提交分配一个哈希 ID。 在任何 Git 存储库(您的或其他任何人的)中,没有其他提交可以拥有该哈希ID。 这就是为什么这个数字如此之大,如此丑陋,以至于没有人可以处理它。
因此,我没有处理数字,而是使用一个大写字母来代替数字。这是 branch 中最后一次提交H
的哈希 ID 。提交具有每个文件的永久、安全保存的快照。它也有一些元数据——一些东西说你做了它(如果你做了它),你什么时候做的,你的日志信息,等等。在该元数据中,Git 添加了早期commit的原始哈希 ID 。master
H
G
我们说 commitH
指向较早的 commit G
。CommitG
当然有快照和元数据。元数据告诉我们谁做了G
,什么时候,为什么——他们的日志消息——并在其中包含更早提交的哈希 ID F
。所以G
指向 F
.
当然, CommitF
具有所有这些相同的东西,所以它指向一些更早的提交,等等。这在整个历史中一直重复——只不过是这些提交——直到我们得到第一个提交。它不能向后指向某个较早的提交,所以它只是没有,这就是 Git 知道历史在这里结束(呃,开始?)的方式。
请注意,在所有这些中,Git 是向后工作的。它必须这样做,因为分支名称会找到最后一次提交。最后一个提交找到一个更早的提交,它找到一个更早的提交,依此类推。
请注意,分支名称指向最后一次提交。然后,要添加一个新的提交,我们只需要这个提交字符串:
...--F--G--H <-- master
并创建一个I
指向现有提交的新提交H
:
...--F--G--H
\
I
如果我们在执行此操作时在分支上master
,这会告诉 Git:现在我们有了新的 commit I
,让名称master
指向I
,这意味着我们得到了:
...--F--G--H
\
I <-- master
现在没有理由画I
一条单独的线:
...--F--G--H--I <-- master
意思是一样的。
git fetch
什么git fetch
是调用其他 Git 并从其他 Git 获取提交。要调用其他 Git,我们需要一个 URL:,ssh://git@github.com/user/repo.git
或https://github.com/user/repo.git
,或其他任何东西。我们让 Git 为我们保存该 URL,名称为 ,这样origin
我们就不必一直输入它。
然后我们运行:
git fetch origin
甚至:
git fetch origin master
这让我们的 Git通过保存在 name 下的 URL调用他们的origin
Git 。他们的 Git 为我们的 Git 列出了他们所有的分支(以及标签和其他东西)。1 我们的 Git 然后挑选出任何有趣的——它们都很有趣,除非我们使用第二种形式,我们说只关注它们master
——并检查他们的 Git 以查看他们是否有任何我们想要的提交并且没有。
(这涉及使用提交编号,这就是为什么提交编号必须是唯一的。但这是另一个讨论的主题。)
一般来说,Git 是贪婪的:他们有任何我们没有的提交,我们想要!我们的 Git 使用 fetch 协议从他们那里获取他们所有的新提交。当然,我们所有的提交,我们已经拥有,以及我们之前从他们那里得到的所有提交,我们已经拥有。我们的 Git 和他们的 Git 谈话以及我们的 Git 找到了一种只获取新内容的好方法,即使我们已经拥有这些文件,即使在新提交中也避免重新下载任何文件。(也就是说,获取速度很快并且不会获取我们已经拥有的东西,即使在新的提交中也是如此。不过,这也是另一个讨论的话题。)
最后,一旦我们有了这些新的提交,我们的 Git 会更新一些名称,以帮助我们的 Git跟踪这些新的提交。这些是我们的远程跟踪名称,例如2的形式origin/master
。
假设,当我们进行 commitI
时,他们获得了一些新的 commit J
。因此,在我们做任何事情之前——进行提交或运行git fetch
——<em>我们在我们的存储库中拥有:
...--G--H <-- master, origin/master
我们做出新的承诺并拥有:
...--G--H <-- origin/master
\
I <-- master
(现在有理由将I
andmaster
放在单独的一行上,这样我们origin/master
仍然可以指向H
。)
如果我们现在运行git fetch
,我们会发现他们有一个新的提交,我们将获得并绘制为J
:
J <-- origin/master
/
...--G--H
\
I <-- master
请注意,提交H
都在这两个链上;它只是I
和J
那个分歧。我们也可以在这里画一条线,也可以画I
在J
同一条线上H
:这次我只是选择画origin/master
一条单独的线,以强调提交的共享性质,包括H
.
请注意,所有这些提交都是只读的,它们都不是我们的工作树文件。 如果我们I
检查了提交,那很好:这意味着 commitI
是我们当前的提交,并且 Git 的索引和我们的工作树匹配 commit I
,或者正在为新的提交做准备,或者其他什么。 通过添加提交J
到存储库git fetch
对我们现在正在进行的任何工作都没有影响。git fetch
是相当安全 的。我们可以随时做这部分。
1在具有大量分支和标签的存储库中,这可能会花费大量时间并涉及数兆字节的数据,仅用于列表,因此在现代 Git 中,git fetch origin master
版本可以限制列表并加快速度。这对于大多数存储库来说无关紧要,它们大多只有十几个分支和几百或几千个标签;它是类似于 Chrome 的存储库,具有 50+ MiB 的分支和标签名称,这是一个问题。
2 Git 调用这些远程跟踪分支名称。它们是我们的 Git 对其 Git 分支名称的记忆。我发现分支这个词在这里是多余的,所以我只使用短语remote-tracking names。Git 有很多种名称:分支名称、标签名称、注释名称、refs/stash
它在 期间临时使用的名称git bisect
,等等。远程跟踪名称只是另一种名称。每个名称仅包含一个哈希 ID — 这就是它们的工作方式,无论它们是什么类型的名称。
这是与我们的工作树文件混淆的第二个命令
现在我们有了,说:
J <-- origin/master
/
...--G--H
\
I <-- master (HEAD)
我们可以运行第二个Git 命令,就像git pull
会做的那样。第二个命令会弄乱我们的文件——我们工作树中的文件。
无论我们选择git merge
还是git rebase
,这两个命令都需要能够覆盖我们的工作树。所以现在我们当然需要运行git status
并确保它说一切都是“干净的”。
我们选择的命令——merge vs rebase——以及它的作用,都是有趣的问题。不过,StackOverflow 上已经有很多关于它们的答案。知道git pull
运行git fetch
- 这是安全的 - 然后第二个命令在同样意义上不“安全”,会让你走得很远。
推荐阅读
- c++ - C ++“错误:'class std::result_of < ... >中没有名为'type'的类型”
- java - 从字符串中提取整数到数组中
- tomcat - 如何用tomcat编写一个groovy-filter?
- javascript - Angular 不在控制台上显示 JavaScript 错误
- web - solid 和 ipfs 的区别
- c++ - DCMTK 了解“DIMSE No valid Presentation Context ID”错误
- sql - SQL,出生日期到当前年龄
- python - 将多个用户名和密码保存在 python 的外部文件中
- sql - MsSQL LEFT JOIN 与双 GROUP BY
- email - Jenkins SMTP 配置 - 535 身份验证失败