简体   繁体   English

简单地将本地 repo 添加到远程 repo 的主分支(不是 master)

[英]Simple add local repo to remote repo's main branch (not master)

This is driving me up the wall!这让我上墙了!

This is what I've done:这就是我所做的:

  1. Created a folder with some code (local repo)用一些代码创建了一个文件夹(本地存储库)
  2. Created a repo in Github eg my_repo在 Github 中创建了一个 repo,例如my_repo
  3. Issued these commands (and trial and error'ed hella other stuff too):发出这些命令(以及反复试验和其他东西):
git init && \
git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/main && \
git remote add origin https://github.com/my_github/my_repo.git && \
git checkout -b init_branch && \
git add * && \
git commit -m "Initial commit" && \
git push origin init_branch && \
git checkout main && \
git merge init_branch

The error I'm seeing:我看到的错误:

 ! [rejected]        init_branch -> init_branch (fetch first)
error: failed to push some refs to 'https://github.com/my_github/my_repo.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

On occasions getting this error (not sure why):有时会收到此错误(不知道为什么):

error: pathspec 'main' did not match any file(s) known to git

What I've tried:我试过的:

  1. Switching the default branch in Github from main to master and pushing straight to master - that worked but want to keep it as main .将 Github 中的默认分支从main切换到master并直接推送到master - 可行,但希望将其保留为main
  2. Not creating a new branch and attempting to push straight to main - doesn't work.不创建新分支并尝试直接推送到main分支 - 不起作用。
  3. Having git remote add origin ... straight after git init - doesn't work.git remote add origin ... git init之后直接让git remote add origin ...不起作用。
  4. Removed the symbolic-ref line, added git config --global init.defaultBranch main and git pull origin main .删除了symbolic-ref行,添加了git config --global init.defaultBranch maingit pull origin main New error:新错误:
* branch            main       -> FETCH_HEAD
 * [new branch]      main       -> origin/main
error: The following untracked working tree files would be overwritten by merge:
        .gitignore
Please move or remove them before you merge.
Aborting

Any help would be much appreciated任何帮助将非常感激

TL;DR TL; 博士

Make sure you create an empty repository on GitHub.确保在 GitHub 上创建一个空的存储库。 Don't put in that initial commit.不要放入那个初始提交。 Then you can get rid of most of this stuff: you'll just create your new repository, commit to create main (and rename to main if necessary), do a git remote add origin url , and git push origin main .然后你可以摆脱大部分这些东西:你只需创建你的新存储库,提交创建main (并在必要时重命名为main ),执行git remote add origin urlgit push origin main

(You can then, if you wish, use git remote set-head origin --auto , although I personally find no value here.) (然后,如果您愿意,可以使用git remote set-head origin --auto ,尽管我个人认为这里没有价值。)

Long

OK, first, don't do this at all:好的,首先,根本不要这样做:

 git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/main && \\

It's not inherently wrong but it has no value.它本质上不是错误的,但它没有价值。 1 It has nothing to do with the errors you're getting, but removing it gets one more step out of the way and removes distractions from your view. 1它与您遇到的错误无关,但删除它可以让您多走一步,并消除您视线中的干扰。

With that said, I think your problem stems from a fundamental misunderstanding of Git: of what it does, why, and how.话虽如此,我认为您的问题源于对 Git 的根本误解:对它的作用、原因和方式的误解。 To fix that, let's take a quick overview of Git repositories.为了解决这个问题,让我们快速浏览一下 Git 存储库。


1 I'm of the opinion that the existence of refs/remotes/origin/HEAD itself has no value to begin with, but even if you disagree, you should be using git remote or git ls-remote to obtain their symbolic HEAD here, rather than hard-coding main . 1我认为refs/remotes/origin/HEAD本身没有任何价值,但即使你不同意,你也应该使用git remotegit ls-remote在这里获取它们的符号HEAD ,而不是硬编码main Your remote-tracking names are supposed to reflect their branch names as they exist now, not as you wish they existed.您的远程跟踪名称应该反映它们现在存在的分支名称,而不是您希望它们存在的名称。 😀 😀


Git's raison d'être is the commit, for which there are databases Git 存在的理由是提交,为此有数据库

A Git repository is, in the end, all about commits .归根结底,一个 Git 存储库就是关于commits 的 To get there, Git has a big database of what it calls objects .为了达到这个目的,Git 有一个庞大的数据库,里面有它所谓的对象 There are four kinds of objects: commits, tags (annotated tags), trees, and blobs.有四种对象:提交、标签(带注释的标签)、树和 blob。 The commits are the ones we (humans) mostly care about;提交是我们(人类)最关心的; the others just have supporting roles and we can mostly ignore them.其他的只是配角,我们基本上可以忽略它们。

Each object—including the crucial commit objects—has a unique hash ID .每个对象——包括关键的提交对象——都有一个唯一的哈希 ID This hash ID is how Git finds the object.这个哈希 ID 是 Git查找对象的方式。 Git stores the objects in a simple key-value database , with the hash ID being the key. Git 将对象存储在一个简单的键值数据库中,哈希 ID 作为键。 So we need the hash ID to retrieve a commit.所以我们需要哈希 ID 来检索提交。 But hash IDs are big and ugly, eg, 5a73c6bdc717127c2da99f57bc630c4efd8aed02 .但是哈希 ID 又大又丑,例如, 5a73c6bdc717127c2da99f57bc630c4efd8aed02 They need to be big and ugly because every commit in the universe has to have its own hash ID.它们需要又大又丑,因为宇宙中的每个提交都必须有自己的哈希 ID。 2 2

But this makes them too difficult for humans to use.但这使得它们对于人类来说太难使用了。 There's a solution for that though.不过有一个解决方案。 Besides the commit-and-other-objects database, each Git repository also has a names database.除了 commit-and-other-objects 数据库,每个 Git 存储库还有一个名称数据库。 Here, the database is still a key-value store, but this time the keys are things like branch and tag names , and the values are big ugly hash IDs.在这里,数据库仍然是一个键值存储,但这次分支和标签名称之类的东西,而值是大而丑陋的哈希 ID。

Each name maps to exactly one hash ID.每个名称都映射到一个哈希 ID。 That's all we need, in Git, because a branch name automatically means "the latest commit on that branch", by storing the hash ID of the latest commit on that branch.这就是我们所需要的,在 Git 中,因为通过存储该分支上最新提交的哈希 ID 分支名称自动表示“该分支的最新提交”。 (We'll skip over how that leads to all the rest of the commits, at least for now.) (我们将跳过这如何导致所有其他提交,至少现在是这样。)

The two databases, then, allow us to:然后,这两个数据库允许我们:

  • supply a name like master or main ;提供一个名称,如mastermain
  • have Git turn that into a commit hash ID;让 Git 将其转换为提交哈希 ID; and
  • have Git use that to retrieve the commit.让 Git 使用它来检索提交。

The commit itself holds two things: a snapshot of every file, as of the form the file had at the time you (or whoever) made it, and some metadata , or information about the commit itself.提交本身包含两件事:每个文件的快照,按照文件在您(或任何人)创建时的形式,以及一些元数据,或有关提交本身的信息。 Again, we won't go into any of the details here just yet.同样,我们不会在这里讨论任何细节。 All we need to know right now is this two-database thing.我们现在需要知道的就是这个两个数据库的东西。


2 This is technically impossible: eventually, two different commits will get the same hash ID. 2这在技术上是不可能的:最终,两个不同的提交获得相同的哈希 ID。 Fortunately, there are two tools we can use here: (1) we can make the hash ID big and ugly so that the "eventually" does not happen for billions of years;幸运的是,我们可以在这里使用两个工具:(1)我们可以使哈希 ID 变得又大又丑,以便“最终”在数十亿年内不会发生; (2) we can avoid introducing two Git repositories to each other if they have what I call doppelgängers : objects that should be different but have the same hash ID. (2) 如果两个 Git 存储库具有我所说的doppelgängers :应该不同但具有相同哈希 ID 的对象,我们可以避免相互引入两个 Git 存储库。 In service of item 1, Git is in the process of moving to bigger hash IDs today.在第 1 项服务中,Git 目前正在转向更大的哈希 ID。


git init

Running git init in a place (folder / directory) where there is as yet no Git repository will create a new, empty repository .在还没有 Git存储库的地方(文件夹/目录)运行git init创建一个新的空存储库 This repository has no commits—its object database is essentially empty—and since names are required to hold valid object hash IDs , with branch names being required to hold commit hash IDs, the names database is also empty.这个存储库没有提交——它的对象数据库本质上是空的——并且由于名称需要保存有效的对象哈希 ID分支名称需要保存提交哈希 ID,名称数据库也是空的。 There are no commits, so there cannot be any branch names.没有提交,因此不能有任何分支名称。

Note that running git init in a place where there is a Git repository is mostly a big and slightly-slow nothing.需要注意的是运行git init在那里一个Git仓库的地方主要是一个大而略慢无。 Git will say that it "re-initialized" the repository: Git did some housekeeping type items, and checked up on some auxiliary items like templated Git hooks and so on. Git 会说它“重新初始化”了存储库:Git 做了一些内务处理类型的项目,并检查了一些辅助项目,如模板化的 Git 钩子等等。 But the important parts of the existing repository—the two databases—are completely undisturbed by a git init .但是现有存储库的重要部分——两个数据库——完全不受git init干扰。

With an empty repository, though, there are no commits and therefore no branches .但是,对于一个空的存储库,没有提交,因此没有分支 And yet, even in an empty repository like this, you're "on" some branch: git status will say on branch master or on branch main or whatever.然而,即使在这样的空存储库中,您也“在”某个分支上: git status会说on branch masteron branch main或其他任何地方。 What's going on here?这里发生了什么?

More about the commit metadata有关提交元数据的更多信息

It's time to talk a bit more about commit data-and-metadata.是时候多谈谈提交数据和元数据了。 We already mentioned that each commit has two parts:我们已经提到每个提交有两个部分:

  • the data: a full snapshot of every file, frozen for all time;数据:每个文件的完整快照,一直冻结;
  • the metadata: information such as the name and email address of the author of the commit.元数据:提交作者的姓名和电子邮件地址等信息。

The snapshot is actually stored indirectly, through supporting objects.快照实际上是通过支持对象间接存储的。 This allows the files stored inside commits to be de-duplicated .这允许对存储在提交中的文件进行重复数据删除 That way, since most commits mostly re-use files that are already in existing commits, the new commits don't need any space to store the files.这样,由于大多数提交主要重用现有提交中已经存在的文件,因此新提交不需要任何空间来存储文件。 They just latch on to the already-existing files.他们只是锁定已经存在的文件。 Since no part of any commit can ever be changed —not even by Git itself—it's quite safe to re-use pieces of old commits.由于没有任何部分可以提交永远甚至Git的-而不是改变自己,这是相当安全的老提交的再利用碎片。

The metadata in a commit include things like when you made the commit and your log message: stuff that git log shows.提交中的元数据包括诸如提交时间和日志消息之类的内容: git log显示的内容。 But for Git's own use, each commit stores a list of the raw hash IDs—the big ugly "true names"—of previous commits.但是对于 Git 自己的使用,每个提交都存储了一个原始哈希 ID 列表——丑陋的“真实姓名”——以前的提交。 Most commits store exactly one entry in this list, which we call the parent commit.大多数提交在这个列表中只存储一个条目,我们称之为提交。 We say that these hash IDs point to the parent.我们说这些哈希 ID指向父级。

In a non-empty repository with, say, three commits, we could draw them like this:在一个非空的存储库中,比如说,三个提交,我们可以这样绘制它们:

A <-B <-C

Here C is the third and latest commit (with some actual big ugly hash ID, but we're just calling it C for commit ).这里C是第三次也是最新的提交(带有一些实际的大而丑陋的哈希 ID,但我们只是将其称为C以表示commit )。 Inside commit C , in its metadata, we have the stuff that git log shows, plus the raw hash ID of earlier commit B , plus of course the data—the snapshot.在提交C的元数据中,我们有git log显示的内容,加上早期提交B的原始哈希 ID,当然还有数据——快照。 So by reading C , Git can find commit B .所以通过读取C ,Git 可以找到提交B This has a snapshot too.这也有快照。 By comparing the two snapshots, Git can show us what changed between B and C , and that's what git log -p does.通过比较这两个快照,Git 可以向我们展示BC之间的变化,这就是git log -p所做的。

Having shown commit C , though, git log now uses the parent hash ID to step back one hop, to commit B .但是,在显示提交C之后, git log现在使用父哈希 ID 后退一跳,提交B B , like C , has data and metadata, and its metadata point to commit A . BC一样,有数据和元数据,它的元数据指向提交A So git log can use the snapshots in A and B to show us what changed in B ... and then git log can move on—or rather, back—to commit A .所以git log可以使用快照AB向我们展示了什么改变B ...然后git log可以-或移动相当,背犯A

Commit A is a bit special.提交A有点特殊。 It has no parent.它没有父级。 It can't!不能! It was the first commit ever.这是有史以来的第一次提交。 There was no previous commit.之前没有提交。 So it just doesn't have any parents at all.所以它根本没有任何父母。 Git knows that this means that "what changed" in commit A is that every file in it was added from scratch, and git log knows that having shown commit A , it can finally stop going backwards: there's nowhere left to go. Git 知道这意味着提交A中的“变化”是其中的每个文件都是从头开始添加的,并且git log知道在显示提交A ,它终于可以停止向后倒退了:无处可去。

The flaw in the ointment here should be pretty obvious.这里软膏缺陷应该很明显。 We had our git log work by "magically" knowing the hash ID of the last commit C .我们通过“神奇地”知道最后一次提交C的哈希 ID 来让我们的git log工作。 But there is no magic .但是没有魔法 Where did Git get commit C 's hash ID? Git 从哪里得到 commit C的哈希 ID?

The answer is: from a branch name .答案是:来自一个分支名称 We have some name , like master or main , that holds commit C 's hash ID.我们有一些名字,比如mastermain ,它保存了提交C的哈希 ID。 We say that this name points to the commit:我们说这个名字指向提交:

A <-B <-C   <--main

This name-and-value pair is stored in the names database, so Git just has to look that one up to get the hash ID for commit C .这个名称-值对存储在名称数据库中,因此 Git 只需查找该对即可获取提交C的哈希 ID。

Branch names find commits, and commits find commits分支名称查找提交,提交查找提交

What this all means is that we use branch names to find the last commit of a branch, and then use the commits to find other, earlier commits.这一切意味着我们使用分支名称来查找分支最后一次提交,然后使用这些提交来查找其他更早的提交。 That's what a branch name in Git is all about.这就是 Git 中分支名称的意义所在。 But there are several tricks:但是有几个技巧:

  • We can have more than one branch name.我们可以有多个分支名称。
  • We can even have more than one branch name for one commit .我们甚至可以为一次提交拥有多个分支名称。

Let's draw the latter case:我们来画后一种情况:

A--B--C   <-- develop, main

(I've gotten deliberately lazy about drawing the arrows that lead backwards from one commit to the previous one here, mostly because the arrows in arrow-fonts don't always render very well on Stack Overflow, plus they are annoying to type in.) (我故意懒惰在这里绘制从一个提交到前一个提交的箭头,主要是因为箭头字体中的箭头在 Stack Overflow 上并不总是很好地呈现,而且它们输入起来很烦人。 )

We are going to be using commit C , no matter which branch name we pick, but we need to pick a branch name.我们将使用commit C ,无论我们选择哪个分支名称,但我们需要选择一个分支名称。 We need to mark that branch name as "the name we're using", too.我们也需要将该分支名称标记为“我们正在使用的名称”。 To do that, we'll attach the special name HEAD , written in all uppercase like this, to just one branch name:为此,我们将像这样全部大写的特殊名称HEAD附加到一个分支名称:

A--B--C   <-- develop, main (HEAD)

We're now using the name main to find commit C .我们现在使用名称main来查找提交C

If we run git switch develop or git checkout develop , the picture changes slightly:如果我们运行git switch developgit checkout develop ,图片会略有变化:

A--B--C   <-- develop (HEAD), main

We're still using commit C , but now we're using it *through the name develop .我们仍在使用 commit C ,但现在我们使用它 * 通过名称develop

Why does this matter?为什么这很重要? Well, let's make a new commit now.好吧,让我们现在进行新的提交 Without worrying about how we make new commits, we note that when we do make a new commit, Git does the following:不用担心,我们如何做出新的提交,我们注意到,当我们做一个新的提交,Git会执行以下操作:

  1. Git gathers up all the metadata it needs, such as the user.name and user.email settings to put in as the name and email address of the author of the new commit. Git 收集它需要的所有元数据,例如user.nameuser.email设置,作为新提交作者的姓名和电子邮件地址。
  2. Git makes a snapshot of all of the files that should be frozen for all time in this new commit. Git 对所有应该在这个新提交中一直冻结的文件进行快照。
  3. Git adds the current commit to the metadata, as the parent. Git 将当前提交添加到元数据中,作为父项。 Git writes out the commit—the data-and-metadata—which computes a new unique hash ID. Git 写出提交——数据和元数据——它计算一个新的唯一哈希 ID。 That's some big ugly hexadecimal number, but we'll just call it commit D here.这是一个非常丑陋的十六进制数,但我们在此处将其称为 commit D
  4. Git writes D 's hash ID to the current branch name . Git 将D的哈希 ID 写入当前分支名称

Since commit D 's parent is the current commit C in step 3, new commit D points backwards to existing commit C .由于提交D父项是步骤 3 中的当前提交C ,因此新提交D向后指向现有提交C But step 4 updates the name develop , because that's the name HEAD is attached to:但是第 4 步更新了名称develop ,因为这是名称HEAD附加到:

A--B--C   <-- main
       \
        D   <-- develop (HEAD)

HEAD thus provides two things:因此HEAD提供了两件事:

  • it tells us which branch name is the current branch name , and它告诉我们哪个分支名称当前分支名称,以及
  • by having Git read the name , it tells us which commit is the current commit .通过让 Git 读取名称,它告诉我们哪个提交是当前提交

Git can answer either question about HEAD : "what name does it hold" or "what hash ID does it represent". Git 可以回答关于HEAD任一问题:“它拥有什么名称”或“它代表什么哈希 ID”。 Git just needs to ask itself the right question . Git 只需要问自己正确的问题

This gets us back to the anomalous empty repository case这让我们回到异常的空仓库案例

In an empty repository, there are no commits, and therefore there are no branch names.在空存储库中,没有提交,因此没有分支名称。 But if we "attach" HEAD to some non-existent name—in reality, in Git, that means writing the branch name to the file .git/HEAD (but with some complicating exceptions that we won't worry about here)—then running git commit can still do the four steps we showed:但是,如果我们将HEAD “附加”到某个不存在的名称上——实际上,在 Git 中,这意味着将分支名称写入文件.git/HEAD (但有一些我们不会在这里担心的复杂异常)——那么运行git commit仍然可以完成我们展示的四个步骤:

  • gather metadata;收集元数据;
  • make snapshot;制作快照;
  • write metadata-and-snapshot to obtain initial commit;写入元数据和快照以获取初始提交; and
  • write new commit's hash ID into branch name, thereby creating the branch name.将新提交的哈希 ID 写入分支名称,从而创建分支名称。

The last step—writing the hash ID—gives us:最后一步——写哈希 ID——给我们:

A   <-- main (HEAD)

as long as the name stored "in" HEAD was main .只要存储“在” HEAD的名称是main (The metadata for commit A omits any parent hash IDs because Git notices, when reading HEAD , that the name main does not exist: this makes main an unborn branch , as Git usually calls it, or an orphan branch , as Git sometimes, inconsistently, calls it.) (提交A的元数据省略了任何父哈希 ID,因为 Git 在读取HEAD时注意到名称main不存在:这使得main成为未出生的分支,正如 Git 通常所说的,或者孤儿分支,如 Git 有时,不一致,调用它。)

This explains some of your problems这解释了你的一些问题

If you run git init such that it creates a new, empty repository, there are no branch names yet.如果您运行git init创建一个新的空存储库,则还没有分支名称。 But you're "on" some branch anyway.但无论如何你都“在”某个分支。 The branch name you're "on" now determines the name of the branch you will create when you make the first commit.您“打开”的分支名称现在决定了您在第一次提交时将创建的分支的名称。

The default name for a new, empty repository is master .新的空存储库的默认名称是master In very recent version of Git, you can use -b main as arguments to git init to change this to main .在最新版本的 Git 中,您可以使用-b main作为git init参数其更改为main You can also configure a setting, init.defaultBranch , to override the default master .您还可以配置一个设置init.defaultBranch来覆盖默认的master

If you run git init such that it simply re-initializes an existing repository, you are on some branch now, and you're still on that branch.如果您运行git init使其简单地重新初始化现有存储库,那么您现在位于某个分支上,并且您仍在该分支上。 The init.defaultBranch setting, if it exists and would be used, isn't used here. init.defaultBranch设置,如果它存在并且将被使用,则不在此处使用。 You're just on whatever branch you're on: use git status or git symbolic-ref HEAD to find out what that is.你只是在你所在的任何分支上:使用git statusgit symbolic-ref HEAD找出那是什么。

If you're in an empty repository right now, the branch you're "on" does not exist .如果您现在在一个空的存储库中,则您所在的分支不存在 So you can't rename it, at least in older versions of Git: git branch -m main may not work.所以你不能重命名它,至少在旧版本的 Git 中: git branch -m main可能不起作用。 (It might work, as this has been fixed up in new versions of Git: the branch rename code notices that you're on an unborn branch, and renames the unborn branch, which is simple enough to do internally, it's just that nobody thought to do it before.) (它可能会起作用,因为这已在新版本的 Git 中修复:分支重命名代码会注意到您在未出生的分支上,并重命名未出生的分支,这在内部很简单,只是没有人想到之前做。)

There are two procedures that always work, regardless of your Git vintage:无论您的 Git 年份如何,有两个程序始终有效:

  1. git checkout --orphan main will switch the name of the non-existent branch that you are on to main . git checkout --orphan main会将您所在的不存在分支的名称切换到main Your next commit will now create main .您的下一次提交现在将创建main
  2. You can create your first commit, which creates master , then rename master to main with git branch -m main .您可以创建你的第一个承诺,创造master ,然后重命名mastermaingit branch -m main

These two procedures are of course only to be used in that empty-repository case.这两个过程当然仅用于空存储库的情况。 In a non -empty repository, you're still on whatever branch you were on before you ran git init unnecessarily.空存储库中,在不必要地运行git init之前,您仍然在您所在的任何分支上。 Your git init didn't do anything (so why did you bother?).你的git init没有任何事情(所以你为什么要打扰?)。

Using two repositories使用两个存储库

You mention in step 2:您在第 2 步中提到:

  1. Created a repo in Github eg my_repo在 Github 中创建了一个 repo,例如my_repo

When you create a new repository with GitHub's web interface (or, presumably, with their gh command line tool or CLI), you have two options:当您使用 GitHub 的 Web 界面(或者,大概使用他们的gh命令行工具或 CLI)创建新存储库时,您有两个选择:

  • Create a totally empty repository.创建一个完全空的存储库。 This is like using git init .这就像使用git init

  • Create a repository with one initial commit that has some files in it: a README, a LICENSE, a COPYRIGHT, maybe even a .gitignore , and that sort of thing.使用一个初始提交创建一个存储库,其中包含一些文件:自述文件、许可证、版权,甚至可能是.gitignore等等。 This is like using git init and then making a commit .这就像使用git init然后进行 commit

The difference between these is that in the second case, the GitHub repository now has one commit in it , which means it can have branch names .它们之间的区别在于,在第二种情况下,GitHub 存储库现在有一个提交,这意味着它可以有分支名称 In the first case, the GitHub repository has no commits, which means it can't have any branch names .在第一种情况下,GitHub 存储库没有提交,这意味着它不能有任何分支名称

GitHub have, of course, changed their own default initial branch name to main instead of master .当然,GitHub 已经将他们自己的默认初始分支名称更改为main而不是master This affects both the unborn-branch empty-repository case and the one-commit now-the-repository-isn't-empty-so-there's-a-branch case.这会影响 unborn-branch empty-repository 情况one-commit now-the-repository-isn't-empty-so- there's-a-branch 情况。

Unless you have some powerful need for their initial commit, I'd recommend you use the "create with no commits" option.除非您对他们的初始提交有强烈的需求,否则我建议您使用“无提交创建”选项。 This makes your remaining tasks trivial.这使您剩下的任务变得微不足道。

There's another, more complex case, where your GitHub repository has a bunch of commits that you'd like to keep.还有另一种更复杂的情况,您的 GitHub 存储库有一堆您想保留的提交。 I'll touch on this one briefly for completeness.为完整起见,我将简要介绍这一点。 Here, you will make use of the fact that Git repositories share commits , but not branch names .在这里,您将利用 Git 存储库共享提交而不是分支名称这一事实。

Whenever we connect two Gits to each other with git fetch or git push , we're invoking the commit-sharing stuff.每当我们使用git fetchgit push将两个 Git 相互连接时,我们都会调用提交共享的东西。 But there are two important differences between fetch and push here:但是这里的 fetch 和 push 有两个重要的区别:

  • git fetch means get me stuff from them . git fetch意味着从他们那里给我拿东西 That is, I have my Git software, working on my repository ("my Git" collectively) call up their Git software and connect to their repository ("their Git").也就是说,我有我的 Git 软件,在我的存储库(统称为“我的 Git”)上工作,调用他们的 Git 软件并连接到他们的存储库(“他们的 Git”)。 Then my Git has their Git list out all their branch and tag names and the corresponding commit or other hash IDs.然后我的 Git 让他们的 Git 列出他们所有的分支和标签名称以及相应的提交或其他哈希 ID。 My Git then checks: for each hash ID, do I have that object?我的 Git 然后检查:对于每个哈希 ID,我是否有那个对象? If not, my Git gets their objects from their Git, collecting all the commits they have that I don't in the process.如果没有,我的 Git他们的Git 中获取他们的对象,收集他们在此过程中没有的所有提交

    So now I share all their commits.所以现在我分享他们所有的提交。 (I might have some of my own that they don't, though.) Now that I have all their commits, my Git can set remote-tracking names in my repository, to remember their branch names. (不过,我可能有一些我自己的,但他们没有。)现在我有了他们的所有提交,我的 Git 可以在我的存储库中设置远程跟踪名称,以记住他们的分支名称。 My Git will create or update my origin/main and origin/develop to remember the hash IDs they have stored in their main and their develop .我的 Git 将创建或更新我的origin/mainorigin/develop以记住他们存储在他们的main和他们的develop的哈希ID。

    The end result of this is that after the fetch, I have all their commits , but none of my branches have been touched.这样做的最终结果是,在 fetch 之后,我得到了他们所有的commits ,但我的分支都没有被触及。 My branch names are mine , not theirs.我的分支名称是我的,不是他们的。 If I have a main and a fred , their main doesn't affect my main , and if they have a fred it doesn't affect mine either.如果我有一个main和一个fred ,他们的main不会影响我的main ,如果他们有一个fred也不会影响我的。 If they have a barney or flintstone and I don't, I still don't have that branch.如果他们有barneyflintstone而我没有,我仍然没有那个分支。 I share their commits , not their branches .我分享他们的提交,而不是他们的分支

  • The git push command sends from my Git to their Git. git push命令从我的 Git发送到他们的 Git。 I pick something—usually a branch name, but what I really need here is a commit hash ID—in my repository.我在我的存储库中选择了一些东西——通常是一个分支名称,但我真正需要的是一个提交哈希 ID。 I have my Git call up their Git and ask them if they have that hash ID.我让我的 Git 调用他们的 Git 并询问他们是否有那个哈希 ID。 If not, my Git sends over that commit, and any previous commits that might be required that they don't already have, until they have all the commits leading up to that last commit that I just sent them too.如果没有,我的 Git 会发送该提交,以及可能需要但他们还没有的任何先前提交,直到他们拥有导致我刚刚发送的最后一次提交的所有提交。 (That way their git log can find all the commits, for instance.) (例如,这样他们的git log可以找到所有提交。)

    But then, having sent them some of my commits—as many as I chose, but usually I choose by branch name so that I send them all the commits up to the last commit on some branch of mine—then something different happens.但是,在向他们发送了我的一些提交后——我选择了多少,但通常我会按分支名称进行选择,以便我将所有提交发送给他们,直到我某个分支上的最后一次提交——然后就会发生一些不同的事情。 I don't have them set some sort of remote-tracking name.我没有让他们设置某种远程跟踪名称。 Instead, I ask them if they would please set one of their branch names to remember that commit hash ID.相反,我问他们是否愿意设置他们的分支名称之一来记住该提交哈希 ID。

    They can refuse to do this!他们可以拒绝这样做! It's their choice as to whether they will let me set one of their branch names.他们可以选择是否让我设置他们的分支名称之一。 But if they allow it, I've just created or updated a branch name in their Git.但是如果他们允许,我刚刚在他们的Git 中创建或更新了一个分支名称。 I don't have to use the same name on both sides.我不必在双方都使用相同的名称。 I don't even have to use a name on my side at all:我什至不必在身边使用一个名字

     git push origin mybranch:free-the-ocean

    for instance, or:例如,或:

     git push a123456:refs/heads/newbranch

    if a123456 is a valid shortened hash ID for a commit in my repository.如果a123456是我的存储库中提交的有效缩短哈希 ID。

So we share commits .所以我们共享commits We do not share branch names , except to the extent that if I say git push origin mybranch , I mean git push origin mybranch:mybranch : I'm going to ask them to use the same branch name I'm using.我们共享分支名称,除非我说git push origin mybranch ,我的意思是git push origin mybranch:mybranch :我将要求他们使用我正在使用的相同分支名称。 It's still their branch name, though.不过,这仍然是他们的分支名称。 It's not mine to control: I can only ask them to create or update theirs.这不是我可以控制的:我只能要求他们创建或更新他们的。

(GitHub in particular have "protected branches", which won't let people update them with git push . Of course, if I own the GitHub repository, I can use the GitHub web administration pages to de-protect the name and push to it, and then re-protect it. But you can see from this complicated process that I still wind up having to ask permission . It's just that I'm asking myself for permission.) (特别是 GitHub 有“受保护的分支”,它不会让人们用git push更新它们。当然,如果我拥有 GitHub 存储库,我可以使用 GitHub Web 管理页面来解除名称保护并推送到它,然后重新保护它。但是从这个复杂的过程中可以看出,我最终还是要征得许可。只是我在征得自己的许可。)

So what's this about origin/HEAD anyway?那么这到底是怎么回事origin/HEAD呢?

I mentioned:我提到:

git remote set-head origin --auto

up in the TL;DR section.在 TL; DR 部分。 What this does is:它的作用是:

  • call up their Git (via the name origin );调用他们的 Git(通过名称origin );
  • ask them which of their branch names their HEAD is attached to;问他们,他们的分支名称HEAD连接到; and
  • create or update my origin/HEAD name—its full name is refs/remotes/origin/HEAD —so that it is a symbolic ref to the remote-tracking name in my repository that corresponds to the same branch name in their repository.创建或更新我的origin/HEAD名称——它的全名是refs/remotes/origin/HEAD HEAD——以便它是我的存储库中远程跟踪名称的符号引用,对应于他们存储库中的相同分支名称。

Whew, that's a mouthful—or head-full—of concepts.哇,这是一个满嘴或满头的概念。 Let's take it apart a bit:让我们把它拆开一点:

  • their branch names, main and develop for instance他们的分支名称,例如maindevelop
  • are reflected in my remote-tracking names origin/main and origin/develop反映在我的远程跟踪名称origin/mainorigin/develop
  • which my Git maintains when I run git fetch and git push .当我运行git fetchgit push时,我的 Git 会维护它。

That is, I run git fetch origin : my Git connects to their Git and gets from them a list of all their branch names and the commit hash IDs that go with those names.也就是说,我运行git fetch origin :我的 Git 连接到他们的 Git 并从他们那里获取所有分支名称的列表以及与这些名称对应的提交哈希 ID。 My Git makes sure I share all those commits, too.我的 Git 也确保我共享所有这些提交。 Now that I have all their commits , I can create or update remote-tracking names for each of their branch names .现在我有了他们的所有提交,我可以为他们的每个分支名称创建或更新远程跟踪名称 My Git will do this automatically for me.我的 Git 会自动为我做这件事。

If their HEAD holds one of their branch names—and it does—then my origin/HEAD could hold one of my remote-tracking names.如果他们的HEAD保存了他们的分支名称之一——而且确实如此——那么我的origin/HEAD可以保存我的远程跟踪名称之一。 The git fetch command does not maintain this automatically . git fetch命令不会自动维护这一点 3 Running git remote set-head lets you have your Git update it; 3运行git remote set-head让你的 Git 更新它; see the git remote documentation for details.有关详细信息,请参阅git remote文档

The name stored in their HEAD is the name their Git will recommend at git clone time.存储他们的HEAD中的名称是他们的 Git 在git clone时会推荐的名称。 That is, when you run:也就是说,当你运行:

git clone <url>

your git clone operation will:您的git clone操作将:

  1. create an empty directory and do all its work there;创建一个空目录并在那里完成所有工作;
  2. use git init to create a new, empty repository;使用git init创建一个新的空仓库;
  3. use git remote add origin url to create origin ;使用git remote add origin url创建origin
  4. insert any necessary git config operations here;在此处插入任何必要的git config操作;
  5. run git fetch ;运行git fetch
  6. run git checkout -b somebranch --track origin/ somebranch .运行git checkout -b somebranch --track origin/ somebranch

The branch name in step 6 that your Git creates , based on your origin/ name that's based on one of their branch names, is the name you supplied to the -b option to git clone .您的 Git第 6 步中创建的分支名称(基于基于其分支名称之一的origin/名称)是您提供给git clone-b选项的名称。 If you didn't supply a -b option, your Git asks their Git which name they recommend.如果您没有提供-b选项,您的 Git 会询问他们的 Git 推荐的名称。 They recommend whatever name is in their HEAD .他们推荐HEAD任何名称。 So that's what their HEAD is really for: to act as a default recommendation to git clone operations.所以这就是他们的HEAD真正的用途:作为git clone操作的默认建议。

Note that there's no Git protocol for setting HEAD in someone else's repository.请注意,没有用于在其他人的存储库中设置HEAD Git 协议。 GitHub offer a configuration page where you can set your own repositories' HEAD s. GitHub 提供了一个配置页面,您可以在其中设置自己的存储库的HEAD (At least one cloud provider—Google—seem to have failed to set up such a page, which leaves their HEAD s set to master even if they have a main instead.) (至少有一个云提供商——谷歌——似乎没有设置这样一个页面,这使得他们的HEAD设置为master即使他们有一个main 。)


3 I don't really know why. 3我真的不知道为什么。 It seems like it should.好像应该。 It doesn't: you need to run git remote instead.它不是:您需要运行git remote代替。 But Git explicitly lets you set origin/HEAD to any of your own remote-tracking names, regardless of which one they have theirs set to.但Git的明确允许您设置origin/HEAD任何自己的远程跟踪的名字,不管是哪一个,他们他们的一套来。 This is theoretically useful—see the gitrevisions documentation and note step 6 of the name-resolving protocol—but I've never found it useful myself.这在理论上很有用——请参阅gitrevisions 文档并注意名称解析协议的第 6 步——但我自己从未发现它有用。


Stale remote-tracking names陈旧的远程跟踪名称

Note that git fetch does not clean up automatically: if they had a rumplestiltskin branch yesterday, and your Git made origin/rumplestiltskin to match, and today their rumplestiltskin is gone, your left-over origin/rumplestiltskin sits there gathering dust.请注意, git fetch不会自动清理:如果他们昨天一个rumplestiltskin分支,并且您的 Git 使origin/rumplestiltskin匹配,而今天他们的rumplestiltskin不见了,那么您剩余的origin/rumplestiltskin坐在那里收集灰尘。 You can run git fetch --prune or git remote prune origin to fix these up, or you can set fetch.prune to true in your configuration to make git fetch act like git fetch --prune by default.您可以运行git fetch --prunegit remote prune origin来修复这些问题,或者您可以在配置中将fetch.prune设置为true以使git fetch默认情况下像git fetch --prune一样。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM