簡體   English   中英

如何將本地存儲庫推送到 Github 上另一個存儲庫的分支

[英]How to push a local repository to a branch of another repository on Github

是否可以將僅存儲在我的本地計算機上的項目添加到 Github 上現有項目的分支中。

例如添加:

local-project

到 Github 回購:

https://github.com/myusername/my-project/local-branch.git

可以這樣做還是應該首先將本地存儲庫推送到它自己的 Github 存儲庫?

附加信息:根據我目前收到的評論,這是一些額外信息。 所以基本上我有一個 Github 存儲庫(我的項目),其中包含一個當前正在生產的 Next.js 項目。 我想將此項目更新為一種完全不同的語言(REACT NATIVE),該語言目前仍在開發中。 我一直在使用的 React Native 版本僅在我的計算機上本地,因為我克隆了一個 repo 並且尚未將其推送到 Github。 我的最終目標是將 React Native 版本作為(my-project)Github repo 中的一個分支(local-branch)。 然后當本地分支准備好生產時,我可以簡單地將它合並到主分支。

首先:您可以推送分支,而不是存儲庫。

現在:

git remote add remote-repo <remote-repo-url>
git push remote-repo local-branch:<remote-new-branch-name>

首先要澄清一些術語,您不能“推送本地存儲庫”。 相反,您可以將分支從本地倉庫推送到遠程倉庫。 首先,您需要創建一個遙控器:

git remote add <name> <url>

例如

git remote add my-remote https://github.com/myusername/my-project/local-branch.git

默認情況下, git clone另一個 repo 時,git 將創建一個遠程命名origin

一旦你有了遙控器,你就可以推送你的分支:

git push my-remote my-branch

雖然您可以對任何分支和任何存儲庫執行此操作,但通常只有兩個存儲庫具有共同的歷史記錄時才有意義,例如當您在本地克隆 github 存儲庫時,或者如果您在本地創建新存儲庫並希望將分支推送到github 上的新空倉庫。

您需要從明確的定義開始。 尤其是:

  • Git 存儲庫的核心是一個提交集合——一個包含 Git 所稱的提交對象的數據庫,以及使提交真正有用的支持對象(使它們包含文件)——以及一個輔助數據庫來幫助您和Z0BCC50105ADE247B760提交。

  • 這反過來意味着 Git 完全是關於commits 這與文件無關! 提交包含文件,這就是我們使用提交的原因。 但是 Git 並不完全關心這些文件,至少在高層次查看時是這樣。 而且,它也不是關於分支的,但我們組織並——最重要的是——通過分支名稱找到提交,這是分支進入圖片的方式。

Git 並沒有真正的項目概念。 這個概念由你來定義。 Git 有存儲庫的概念,存儲庫由提交和其他對象組成。 因此,如果您有一個現有的存儲庫,那么您就有一堆現有的提交(以及它們的其他對象)。

在您的更新中,您說:

我想將此項目更新為一種完全不同的語言(REACT NATIVE),該語言目前仍在開發中。

你還在談論一個“項目”。 Git 沒有這些,所以你必須 map 你自己的“項目”概念到 Git 擁有的東西上:包含提交的存儲庫。

我一直在使用的 React Native 版本僅在我的計算機上本地,因為我克隆了一個存儲庫 [sitory]...

在這里,您提到了另一個——顯然不同的——存儲庫 這是 Git 明白的。 您現在似乎至少有三個存儲庫:

  • GitHub 上的一個;
  • 您從某個地方克隆的另一個(GitHub 上的其他地方?);
  • 第三個與第二個密切相關。

並且尚未將其推送到 Github。

這里的代詞“它”似乎是指您的第三個存儲庫。 但是你不能——不能——推送一個存儲庫 您推送一些提交(及其支持對象),它們存在於存儲庫中,您和 Git 使用分支名稱和其他名稱找到它們。

現在是時候多談談存儲庫、提交和克隆了。

存儲庫作為一對數據庫

數據庫采用許多 forms,但構成任何 Git 存儲庫的大部分都是簡單的鍵值存儲 鍵值數據庫(在 Wikipedia 鏈接中查看更多信息)將key作為其輸入,並使用它來檢索value

對於提交 object,鍵是提交的名稱。 提交的“名稱”是一個大的、丑陋的、隨機的(但實際上不是隨機的) hash ID ,例如4af7188bc97f70277d0f10d56d5373022b1fa385 這些 hash ID 特別神奇,因為:

  • 它們是獨一無二的:沒有兩個不同的提交具有相同的 hash ID;
  • 宇宙中的每個 Git 系統都以相同的方式計算它們,因此即使您尚未提交,一旦您提交並獲得一些 hash ID,宇宙中的每個 Z0BCC70105AD279503E31FE7B3F47 系統都會同意系統您剛剛提交的提交,應該得到並獲得了 hash ID ,因為您已經接受了它,所以沒有其他提交可以擁有它。

這種深奧的魔法以各種方式與密碼學相關,而 Git [ab] 使用它的方式實際上不能永遠有效。 hash ID 空間的絕對大小是為了確保它的工作時間足夠長,以至於沒人關心。 但您真正需要知道的是 hash ID(Git 正式稱為object ID或 OID)是提交的“真實名稱”。 Git 確實需要這個 hash ID 才能在其所有對象的大數據庫中找到提交。

如果這是 Git 擁有的唯一數據庫,我們都必須記住這些看似隨機的 hash ID。 這將是非常糟糕的,因為人類不擅長這些事情。 所以 Git 有第二個數據庫。 第二個數據庫也是一個簡單的鍵值對存儲,但其中的鍵是諸如分支和標簽名稱之類的東西,它們是人類可讀的並且對人類有意義:諸如mainmasterdevelop等名稱。 Git 將在第二個數據庫中的分支名稱下的某個分支中存儲最新提交的 hash ID。

這意味着只需要記住您正在使用的分支名稱。 你說“給我最新的main提交”或“給我最新的dev提交”,Git 從名稱數據庫中找出 hash ID,然后使用它從大型全對象數據庫中找出提交。

換句話說,您不需要記住 hash ID。 仍然需要知道提交是編號的,使用這些有趣的奇怪的十六進制OID 或 hash ID 事物,並且每個提交都有一個。 但是您不必記住其中任何一個:例如,只需運行git log , Git 本身就會顯示給您,您可以使用鼠標剪切和粘貼 Z0800FC577294C34E0B28AD245 ,你會時不時地需要一個——可能很少,可能一周一次或一天兩次或其他什么,但總有一天,可能)。

關於提交的知識

除了像這樣編號之外,還有一個提交:

  • 完全只讀。 這是編號系統正常工作所必需的,但這意味着一旦提交,您實際上無法更改任何有關提交的內容。 這並不是什么大問題,因為 Git 中的提交通常幾乎不會占用任何空間。

  • 存儲兩個東西:

    • 每個提交都有每個文件的完整快照

    • 每個提交都有一些元數據,或者關於提交本身的信息。

第一個子要點似乎與提交通常很小的說法相矛盾。 如果每個提交都包含每個文件,存儲庫數據庫不會膨脹嗎? 它會,除了一些非常聰明的 Git 技巧。 第一個技巧是提交中的文件一種特殊的、只讀的、僅限 Git 的格式存儲(實際上作為那個大型全對象數據庫中的對象),其中重復的文件內容被刪除了重復

由於大多數提交主要重復使用之前提交的大部分文件,並且這些文件會自動重復數據刪除,因此它們不占用空間! 只有更改的文件實際上占用了任何空間。 Git 稍后(不是馬上)也壓縮這些 只要它們是普通文本或編程語言內容,這通常會非常有效。 (不過,對於二進制文件,它通常會失敗,這就是為什么 Git 不適合存儲大多數二進制文件的原因。)

因此,作為 Git 對象的壓縮和去重文件是Git在每次提交中存儲每個文件的方式,而不是在每次提交中實際存儲每個文件,因此不會讓存儲庫變得非常胖。 你不需要知道這一點來使用 Git,你只需要知道每個文件似乎都被永久存儲在每次提交中。 也就是說,進行一次提交,你就可以永遠取回所有文件——或者更確切地說,只要你能找到那個提交。 您將需要它的 hash ID,(您知道為什么 hash ID 很重要嗎?現在?)

但是我剛才說你不需要記住 hash ID,這是真的。 那么這是如何工作的呢? 好吧,讓我們更仔細地看看那個元數據。 每個提交都存儲有關自身的信息,這些信息包括提交人姓名和 email 地址,以及一些日期和時間戳等。 但是這里有一個關於Git 本身的重要信息:每個提交都存儲了之前提交的 hash ID。

更准確地說,每個提交都有一個先前提交 hash ID 的列表 但是這個列表通常只有一個元素長。 那一個元素,一個列表條目,保存父提交的hash ID。 這意味着提交具有父/子關系,大多數提交只有一個父(母親?父親?在這里選擇你喜歡的任何東西,Git 與性別無關)。

因為提交是完全只讀的,所以提交可以記住其父提交的“名稱”(哈希 ID),因為當我們創建子提交時父提交存在。 但是父母記得它的孩子的名字,因為它的孩子——如果有的話——還不存在。 孩子一出生,就被低溫冷凍,無法得知未來孩子的名字。

我們說子節點指向它的父節點,如果我們願意,我們可以通過這種方式繪制一些提交。 使用單個大寫字母代表真實的 hash ID,我們將調用分支H (代表 Hash)中的最后一個提交,並將其繪制如下:

            <-H

H伸出的那個箭頭是H如何在其元數據中使用其父 hash ID 存儲來指向其父。 我們現在將其父級繪制為字母G ,因為它位於H之前:

        <-G <-H

當然G指向其父級:

... <-F <-G <-H

正如你所看到的,這形成了一個無限的向后指向鏈,除了當我們回到有史以來的第一次提交時,歷史最終會耗盡。 第一次提交沒有父母,就像某種處女出生但甚至沒有母親一樣,所以那個提交——我們稱之為A畢竟並沒有向后指向:

A--B--...--G--H

我們可以變得懶惰並停止將箭頭繪制為箭頭,因為我們知道它們是提交的一部分並且無法更改,因此必須向后指向。

分支名稱查找提交

上面的問題是你仍然要記住 hash ID H ,鏈中的最后一次提交:鏈的尖端 不過,我們已經看到 Git 存儲庫包含第二個名稱數據庫,並且分支名稱包含上次提交的 hash ID。 與指向較早提交的提交一樣,我們說分支名稱指向提示提交,我們將其繪制如下:

...--G--H   <-- branch

要將新提交添加到分支,Git 將簡單地寫出新提交及其完整快照和元數據,在此過程中獲取新的唯一 hash ID。 hash ID 看起來是隨機的; 我們在這里將其稱為I ,即H之后的下一個字母。 提交I指向提交H ,任何提交都必須向后指向其父級:

...--G--H   <-- branch
         \
          I

並且因為新提交I提交鏈的新尖端,所以 Git 現在會將I的 hash ID 寫入數據庫中的分支名稱,因此名稱現在指向I

...--G--H    branch
         \  ↙︎
          I

(我在這里使用的箭頭有點蹩腳;這就是為什么我對提交之間的箭頭變得懶惰)。 畢竟,我們不需要I在單獨的行上:將其繪制為更有意義:

...--G--H--I   <-- branch

請注意,從分支名稱中伸出的箭頭可以並且確實一直在移動 這使得它與從提交中伸出、指向提交的父級的剛性、向后指向的箭頭非常不同。 Git 簡單地將分支名稱定義為“這是最后一次提交”。 也就是說,無論 hash ID 在分支名稱中,這是分支上的最后一次提交。 因此,要更改哪個提交是最后一次提交,您需要將 Git 將新的 hash ID 填充到分支名稱中。 這不僅可以讓您分支添加提交,還可以讓您分支中刪除提交:

       H--I   ???
      /
...--G   <-- branch

如果我們在分支名稱中有 Git 存儲G的 hash ID,我們已經從分支中刪除了提交HI 它們仍在存儲庫中,但現在您需要知道它們的 hash ID! 如果您不知道提交I的 hash ID,您將永遠找不到它。 Git 可以向后工作——給定一個指向提交I的名稱branchGit可以為您遵循提交到父級的箭頭; 這就是git log的工作原理,但 Git無法向前工作。 沒有前向箭頭!

克隆

除了上述之外,Git 還提供克隆存儲庫的能力。 以下是克隆的工作原理:

  • 你運行git clone並給它一個 URL。
  • Git 創建了一個新的、完全空的存儲庫:兩個數據庫(用於提交和其他對象,以及用於分支和標記和其他名稱),但它們都沒有任何內容。
  • 您的 Git 軟件將此 URL 保存在新存儲庫中(在輔助“數據庫”中,實際上只是一個簡單的文件,如INI 文件)。
  • 您的 Git 軟件使用您提供的 URL 調用其他一些 Git 軟件。 他們通過列出所有分支和標簽以及其他名稱以及 go 與這些名稱的 hash ID 來響應。
  • 您的 Git 軟件說“給我所有這些對象及其所有父母和祖父母以及構成歷史的一切”——即,他們的提交和其他對象數據庫的全部內容。
  • 他們將所有這些東西都發送過來,然后您的 Git(您的軟件與您的存儲庫一起工作)將其粘貼到您的數據庫中。
  • 對於它們的每個分支名稱,您的 Git 軟件會將這些名稱更改為您的遠程跟蹤名稱 他們的mainmaster成為您的origin/mainorigin/master 他們的develop ,如果他們有,成為你的origin/develop 等等。

你最終會得到你的兩個數據庫充滿了東西:

  • 你有一個他們的對象數據庫的完整副本(嗯,大部分是完整的:如果他們有一些他們找不到的對象,例如由於從分支末尾刪除提交,他們不需要向你發送這些)。
  • 沒有分支名稱 (您是否有分支取決於您所說的分支一詞的含義。)您有以origin/開頭的遠程跟蹤名稱,而不是分支名稱。 這些是你現在如何找到提交的方法。

作為最后一步,您的 Git 現在創建一個您自己的分支名稱 例如,如果您告訴git clonegit clone -b develop ,您的 Git 將創建您自己的分支名稱develop 如果您沒有使用-b選項(大多數人不使用),您的 Git 會詢問他們的 Git他們推薦什么名稱,並創建該名稱。

在任何一種情況下,您的新分支所具有的提交,作為它的提示提交,都是相同的提交 hash ID 在他們的名字中,在他們這邊拼寫相同的方式 也就是說,如果您讓您的 Git 創建main因為他們推薦main ,那么您的main作為其最后一次提交的提交與您的origin/main選擇的提交相同,這與您運行git clone他們的main選擇的提交相同git clone

到目前為止——可能是seconds ,這在計算機上是很多時間——它們的分支名稱可能是 select 不同的提交。 但是在您運行git clone時,它們的分支名稱選擇了特定的提交。 您的Git 使用您的存儲庫的遠程跟蹤origin/*名稱記住所有這些。 您的存儲庫有您自己的分支名稱,您可以隨意創建和更新它們,它們與它們的分支名稱不同,即使您使用相同的拼寫。 那是因為他們的數據庫不是您的數據庫。

你和他們共享的是commits Those are read-only—neither of you can change them—and have hash IDs that every Git in the universe agrees are the right hash IDs, via the hash ID magic. 因此,您對原始( origin )存儲庫的克隆與該原始存儲庫非常密切相關。 您有自己的分支名稱,但您共享提交

獲取和推送

給定任意兩個存儲庫——通常是相關的,盡管一開始這實際上並不是必需的——我們可以將提交從一個存儲庫轉移到另一個存儲庫:

  • 從他們那里獲得提交的方法是使用git fetch
  • 給他們提交的方法是使用git push

在這兩種情況下,我們都必須給我們的 Git(我們的軟件與我們的存儲庫一起運行)提供另一個 Git(他們的軟件和他們的存儲庫)的 URL。 如果我們有一個通過克隆創建的密切相關的存儲庫,我們已經有了 URL,因為我們的 Git 將它保存在名稱origin下。 所以我們只運行:

git fetch origin

我們的 Git 調用他們的 Git。 They list out their names and hash IDs, and our Git can tell whether we already have some commits—because hash IDs are the true names of objects, and the commits have unique hash IDs—or whether we need them. 如果我們需要它們,我們的 Git 會要求它們,它會自動要求發件人發送父 hash ID,以便我們可以查看是否也有這些 ID。 通過這種方式,我們的 Git 可以從他們那里獲得所有我們沒有的提交 我們要求我們已經擁有的任何提交。 Git 使用這些信息不僅可以確定哪些提交對我們來說是新的,還可以確定這些提交中的哪些文件是新的,然后只向我們發送新內容。

我們的 Git 然后將所有提交和支持對象粘貼到我們的對象數據庫中。 我們現在擁有了他們所有的提交,以及我們從未提供給他們的任何提交。 Then our Git updates our remote-tracking names because we know what hash IDs their branch names held at the time we ran git fetch and we have all those commits, so our Git can update our memory of their branch names.

In fact, this git fetch is how the main part of git clone works: all git clone does is create the empty repository, stash the URL away under the name origin , run git fetch , and run one final operation to create and check out a分店名稱。 因此,如果您使用git fetch連接兩個不相關的存儲庫,則一旦完成提取,這兩個存儲庫現在是相關的。

git push命令與 Git 接近git fetch的相反。 ( Remember this: git push 's opposite is git fetch , not git pull . This was a mistake in naming that Git made early on and we're just stuck with it now. You just have to memorize it: push/fetch, not推/拉。)雖然有一些很大的區別:

  • 使用git fetch ,操作是“獲取提交並更新遠程跟蹤名稱”。 默認值為所有新提交和所有遠程跟蹤名稱。

  • 使用git push ,操作是發送新的提交。 我們必須選擇我們一方將從哪個提交開始,為他們提供新的提交。 所以我們在這里運行git push origin somebranch ,向他們提供我們發現的分支名稱為somebranch的任何新提交。 我們將首先為他們提供一個提示提交。 他們可能沒有它,所以他們會說“哦,是的,發送那個”,這意味着我們必須提供它的父母。 如果他們沒有,他們也會要求,這意味着我們提供祖父母,等等。 最終我們回到他們已經擁有的一些提交——因為我們通過克隆從他們那里得到了它——或者我們 go 一直追溯到我們的歷史並得到我們第一次提交,它沒有父提交,所以我們說“就是這樣有”。

    一旦我們知道哪些提交對他們來說是新的,我們的 Git 會使用它的智能來 package 只添加那些提交和對他們來說也是新的文件,然后我們發送這些東西,然后他們將其粘貼到存儲庫的對象中數據庫。 但現在我們,或者他們,有一點進退兩難的境地。 他們將如何找到這些對象? 他們需要一個名字

    當我們使用git fetch時,我們創建或更新了一個遠程跟蹤名稱以記住其分支名稱之一的提示提交。 使用git push ,他們沒有我們的遠程跟蹤名稱。 相反,我們要求他們(默認情況下禮貌地)看看他們是否可以,請,創建或更新他們的一個分支名稱以記住我們最近的提交。

    如果somebranch是他們的新名稱,他們可以創建該名稱。 這不會打擾他們現有的任何名字。 所以這很容易,他們只需說“好”,就去做,我們都很好。 但是如果somebranch是他們的分支名稱之一,他們現在要做的就是檢查以確保我們只是向他們的分支添加提交

讓我們 go 回到我們之前的插圖。 假設我們有這些名稱:

...--G--H   <-- main
         \
          I   <-- develop

讓我們進一步假設我們有幾秒鍾、幾小時或幾天前origin站獲得的遠程跟蹤名稱

...--G--H   <-- main, origin/main
         \
          I   <-- develop, origin/develop

我們現在使用git switch develop來選擇名稱develop作為我們當前的分支名稱:

...--G--H   <-- main, origin/main
         \
          I   <-- develop (HEAD), origin/develop

這個技巧,將特殊名稱HEAD添加到繪圖並將其“附加”到其中一個分支名稱,是我們如何顯示我們在自己的 Git 存儲庫中使用的分支名稱

現在讓我們以通常的方式在我們的 Git 存儲庫中進行新的提交(編輯文件, git addgit commit ,編寫提交消息等)。 我們得到這個:

...--G--H   <-- main, origin/main
         \
          I   <-- origin/develop
           \
            J   <-- develop (HEAD)

我們現在運行git push origin develop 我們會給他們提交J ,他們會說:嗯,新的 hash ID,好的,發給我J 接下來我們將向他們提供提交I ,因為那是J的父母。 他們會說不,謝謝,已經有了。 我們現在知道所有或幾乎所有的提交! 他們有I ,但這意味着他們有H ,這意味着他們有G ,依此類推,一直回到第一個提交!

因此,我們的 Git 將 package 向上提交J和任何不與H中的文件重復的新到J文件(也許我們將文件恢復到以前的方式)。 我們會將它發送過來,然后我們會禮貌地問他們:如果可以,請將您的 name develop設置為指向提交J (當然是提交J的真實 hash ID)。

如果他們的develop ——正如我們的 memory 所代表的, origin/develop ——仍然指向提交I ,提交J添加到他們的develop 所以他們會說OK,完成了我們的禮貌請求,我們會知道他們的develop now 名稱 commit J ,我們的 Git 將像這樣更新我們的圖片:

...--G--H   <-- main, origin/main
         \
          I--J   <-- develop (HEAD), origin/develop

但是假設在這幾秒鍾、幾小時或幾天內,其他人他們的develop中添加了一些其他新的提交K 也就是說,他們有:

...--G--H   <-- main
         \
          I--K   <-- develop

他們的存儲庫中(這些是他們的名字,所以我們不必知道或關心他們的HEAD在哪里,而且他們還沒有J )。

我們向他們發送一個新的提交J ,他們將其保存在他們的數據庫中:

...--G--H   <-- main
         \
          I--K   <-- develop
           \
            J

然后我們禮貌地請他們請 - 如果可以 - 將他們的develop移動到J 但是這次不行! 如果他們這樣做,他們將“丟失”他們的提交K 所以他們會說:不,如果我這樣做,我會失去一些東西。 (Git 稱之為“非快進”,這是 Git 通常難以理解的方式。)我們會得到一個錯誤:

 ! [rejected]    develop -> develop (non-fast-forward)

對於這個錯誤,我們需要做的通常是運行git fetch ,這將使我們提交K ,所以我們現在有:

...--G--H   <-- main, origin/main
         \
          I--K   <-- origin/develop
           \
            J   <-- develop (HEAD)

一旦我們有了它,我們就可以決定:

  • 提交K好嗎? 或者,我們應該要求他們把它扔掉嗎?
  • 如果提交K是好的,我們想對此做什么?

“我們想要做的”的主要選擇是使用git rebasegit merge 運行git fetch然后想運行git rebasegit merge是很常見的。 這就是git pull存在的原因:它運行git fetch ,然后運行git rebasegit merge git pull的缺點很多:

  • 我們無法查看提交K 我們只是假設它是好的。
  • 在決定是合並還是變基之前,我們不會查看提交K
  • 我們甚至不知道,如果我們是 Git 的初學者,我們正在做這一切。
  • 我們不知道git mergegit rebase是什么!
  • git mergegit rebase無法完成時,我們不知道該怎么辦,這種情況經常發生,很重要。
  • 我們甚至不知道我們選擇了git mergegit rebase中的哪一個! 我們知道下一步該做什么,因為我們不知道發生了什么。

因此,如果您是 Git 的新手,請不要使用git pull (還)。 稍后,您可能想使用它,一旦您牢記所有這些內容。 我自己仍然大多不使用它,而且我已經使用 Git 將近二十年了。 我很不喜歡git pull (在過去一年左右的時間里,它發展了一種新模式,可以滿足我的需求,但我仍然寧願只運行兩個命令。)

結論

在您擔心是否應該將另一個項目推入存儲庫之前(您當然可以,但也許您不應該,這是一個判斷問題),了解存儲庫為您做了什么。 決定是否要將 map“項目”到“存儲庫”一對一、多對多、多對一、一對多或其他。 “monorepo”(一個存儲庫中的所有內容)和“polyrepo”(一個或多個項目的多個存儲庫)方法各有利弊。 你不會總是第一次就搞定這一切,但請注意,這就是你在這里所做的。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM