- 有時我在第二個分支中寫了一些更改,但我還不想提交,
- 在那里我回到 master 我發現 master 分支也改變了,為什么?
$ mkdir tmp && cd $_ && git init && touch file.txt && echo "text wite on master" > file.txt
$ git add file.txt && git commit -m"init"
- 結帳第二個分支
$ git checkout -b seconde-branch && echo "text wite on seconde branch" >> file.txt
- 在不提交更改的情況下切換回 master
$ git checkout -b master
$ cat file.txt
text wite on master
text wite on seconde branch
- 它應該是輸出:
text wite on master僅 - 同樣,當我在 master 中使用 git restore 時,我丟失了在第二個分支中寫入的更改,這使我避免使用分支,因為它令人困惑
uj5u.com熱心網友回復:
您在作業樹中看到和處理的檔案不在任何分支中。
理解這一點的方法是記住以下規則:
- Git 是關于commits 的。
- Git 與files無關,盡管提交持有檔案。
- Git 與分支無關,盡管分支名稱可以幫助我們(和 Git)找到提交。
Git 關心的是——Git存盤什么并傳輸到其他 Git 存盤庫——是提交。
每次提交:
被編號:每個提交都有一個大的、丑陋的、隨機的、唯一的哈希 ID。某些提交的哈希 ID 是 Git 知道該提交就是該提交的方式。您的 Git 會將這個哈希 ID 呈現給其他一些 Git 軟體;如果使用其存盤庫的其他 Git 軟體使用此編號進行提交,則它具有此提交。如果它沒有此編號,則它需要從您的 Git 存盤庫中獲取此提交(如果您的 Git 提供)。(在另一個方向也是如此,當您讓 Git 向您的存盤庫添加新提交時,這些提交是從其他 Git 存盤庫獲得的。)
是只讀的:任何提交都不能更改,甚至 Git 本身也不能更改。
存盤兩件事:當您或任何人提交時 Git 知道的每個檔案的完整快照;和一些元資料,或者關于提交的資訊,比如誰做的,什么時候做的。請注意,存盤在提交中的檔案以特殊的、只讀的、僅限 Git 的格式保存,并進行了壓縮和重復資料洗掉。您的計算機無法讀取這些檔案(好吧,它可以讀取原始資料,但無法理解它們)并且沒有任何東西——甚至 Git 本身——可以覆寫這些檔案(因為用于提交)。
由于提交及其檔案只能由 Git 本身讀取,因此 Git 必須先提取提交,然后才能對其進行任何操作。這就是git checkout它的作用:它提取提交。
當你從一個分支切換到另一個分支時——無論是 withgit checkout還是較新的git switch——你可能是在告訴 Git 從一個提交切換到另一個提交。在這種情況下,Git 必須洗掉您正在使用的提交中產生的檔案,并將它們替換為您將使用的提交中產生的檔案。不過,在 Git 執行此操作之前,它會檢查以確保它洗掉和替換的任何檔案實際上都沒有被修改。這樣你就不會失去你已經完成但尚未提交的作業。
如果你已經完成了作業,但還沒有提交,那么到目前為止你所做的作業不在 Git 中。只是在 Git 之前提取的檔案中,您從那時起進行了更改。因此從一個分支切換到另一個分支不會顯示任何內容,因為這些檔案不在 Git 中。
整個系統可能非常混亂,所以讓我們多說一點。
提交已編號,并鏈接到較早的提交
每當您或任何人進行新提交時,新提交都會獲得一個新的、唯一的、隨機的哈希 ID。該散列 ID 與宇宙中任何地方的每個其他提交的散列 ID 不同,而且必須如此。1 新提交被寫出,從那時起,它就永遠無法更改。
新提交在寫出時可以將一些舊提交的哈希 ID 存盤在其中。這使得新提交“指向”舊提交。一旦我們重復了幾次這個技巧,我們就有了一個提交鏈。如果我們用單個大寫字母來稱呼它們——這對人類來說更容易理解——我們得到的圖是這樣的:
A <-B <-C
C最新的提交在哪里。我們說,提交B,里面傳來之前C,是C的父。提交C 指向其父級B。CommitC還保存了每個檔案的完整快照。B當然,Commit也是一次提交,它保存每個檔案的完整快照并指向其父檔案A。提交A是一個提交并保存一個快照,但由于提交A是第一次提交,它不能向后指向任何更早的提交,所以它只是沒有。
通過從最新的提交開始并向后作業,Git 可以找到所有提交。所以我們只需要記住最后一次提交的哈希 ID。
1這在技術上是不可能的,總有一天 Git 將無法作業。大尺寸的哈希 ID 試圖將這一天推遲到很遠的將來,以至于我們不在乎它實際上不會永遠有效:在出現問題之前,我們都會死很久。至少,這是一個想法,但我們已經遇到了一些其他的小問題,所以 Git 正在獲得一個新的更大的哈希 ID 方案。
分支名稱幫助我們(和 Git)找到提交
The system above works fine as long as we remember the actual hash ID of commit C. But who can remember some big ugly hexadecimal number like that? I can't, and you probably can't. We could write these down, perhaps ... but hey, wait a minute, we have a computer. Let's have the computer store the number of the latest commit. We'll put it in a small database of names. Let's call them branch names and tag names and the like.
Now that we have names, we can add them to our drawing. Each name points to some commit:
A--B--C <-- master
Here, the branch name master points to commit C. Let's add another branch name, seconde-branch, that also points to commit C, like this:
A--B--C <-- master, seconde-branch
We now need a way to remember which name we are using. Let's use the special name HEAD for this:
A--B--C <-- master (HEAD), seconde-branch
This indicates that we are using commit C as our current commit, via the name master. If we now:
git checkout seconde-branch
we get:
A--B--C <-- master, seconde-branch (HEAD)
We're still using commit C, but now we're using it via the name seconde-branch.
When we change branches like this, we're not changing which commit we're using. So Git does not have to remove-and-replace any files at all, and therefore, Git doesn't bother. This lets us switch to the other branch, in case we forgot and started editing files too soon.
Git's index and your working tree
As I mentioned above, when we first check out or switch to some branch, Git will—if needed—extract all the files from the snapshot in the commit as found by the branch name. These files are in some weird Git-only format, compressed and de-duplicated, but now they're regular everyday files.
These files go in a work area. Git calls this our working tree or work-tree. The files here came out of a commit but are not actually in Git: they're just ordinary files in ordinary folders. Git has no control over these files: you can do anything you want with them.
When you have done something with them, you'd typically like to save the things you did. For this purpose you'll need to make a new commit. In other version control systems, you'd run their commit verb (e.g., hg commit or svn commit) and they'd scan your working tree, find what you changed, and make the new commit. Git, however, is different. Git makes you run git add.
What git add does is copy the updated file back into a secret—well, not really secret, but invisible—Git area that, in effect, sits between a commit and your working tree. This area is extremely important in Git, at least if you ever plan to make any new commits. (If you don't need new commits, you can mostly ignore it.) Because it is so important, and/or because it is badly named, this area has three names: Git calls it the index, the staging area, and—rarely these days—the cache.
(You can—to a limited extent—get by with git commit -a instead of git add. Don't do this! You'll be able to ignore the index for a while, but eventually, Git will whack you over the head with its index. Learn about the index. Embrace it. Some people find it useful: there are clever tricks you can do with it. Some find it annoying, but it's there, in the way, and you need to know about it so you don't trip over it.)
Git's index is a complicated thing, but it plays one pretty constant role, and can therefore be described in one line this way: The index holds your proposed next commit. The initial git checkout or git switch that you run extracts the commit's files to Git's index.
The files in Git's index are in the compressed and de-duplicated form that Git uses internally. The key difference between these files, and the files in a commit, is that the commit cannot be changed, but the index contents can be changed. Running git add tells Git: Make the index copy of this file look like the work-tree copy.
What this means is that after git add, you've updated your proposed next commit. When you first check out commit C, or are on commit C with modified working tree files like this:
A--B--C <-- master, seconde-branch (HEAD)
the index still holds the original files that were extracted from commit C. Until you run git commit—which will write out the index's files into a permanent form in a new commit—the index copies are just sitting around ready to go into a new commit.
Running git add updates the index copies, making them match the working tree copies. So this means that with, e.g., file.txt, there are three copies:
HEAD index work-tree
--------- --------- ---------
file.txt file.txt file.txt
As you modify the work-tree copy, nothing happens to the other two copies. If we put version numbers in the table above, we get:
HEAD index work-tree
----------- ----------- -----------
file.txt(1) file.txt(1) file.txt(2)
When you run git add file.txt, Git updates the index copy to match the work-tree copy:
HEAD index work-tree
----------- ----------- -----------
file.txt(1) file.txt(2) file.txt(2)
Note that you can change the work-tree copy again, without using git add, and at this point all three copies will differ.
(Note that if you run git add on a new file, that isn't in the index yet, Git will copy this new file into the index. This adds the new file to the proposed commit. It's not yet in any commit, but it's now ready to be committed. Or, you can run git rm on a file to remove it from both the index and your working tree. Now it's gone from the index, so it won't be in the next commit. This does not affect any existing commits: those cannot be changed.)
When you run git commit, this is what happens:
- Git gathers any metadata it needs, such as your name and email address, and the current date-and-time. It may collect a log message from you, or use the
-margument to get the log message. - Git uses the current commit's hash ID to go into the metadata for the new commit.
- Git writes out whatever files are in the index.
- Git turns the above into a commit. This creates the commit's unique hash ID. (One reason for the date-and-time-stamp is that since this is always changing, the hash ID will differ from that of any other commit that is otherwise exactly the same.)
- This is the sneaky bit. Now that the new commit exists, Git writes the new commit's hash ID into the current branch name.
This means that if you currently have:
A--B--C <-- master, seconde-branch (HEAD)
and you run git commit and it successfully makes a new commit, you now have:
A--B--C <-- master
\
D <-- seconde-branch (HEAD)
Note how master still points to commit C, but seconde-branch now points to new commit D. Commit D points back to existing commit C as its parent. No commits have changed, but there is now a new commit in the repository.
If you now run:
git checkout master # or git switch master
Git must now remove the commit-D files and replace them with the commit-C files. Git has to do this for all the files that are different. It can cheat a bit, and for any file that is the same in commits C and D, it can leave that file alone in the index and working tree.
(A degenerate case of "leave the file alone" occurs when switching from commit C to commit C: there's no change at all, so all files can be left alone. That's the case you're seeing in your example, and it's always true for the kind of git checkout -b you are using.)
But if some files are different, Git will have to remove-and-replace those. Here Git will first make sure that these files in your working tree aren't changed; if they are, Git will refuse to switch commits. You can force the switch anyway, telling Git throw away my changes. (Since those changes are in your working tree, which is not in Git, Git will not be able to help you recover from this. So don't ignore Git's complaints about files that would be overwritten. Figure out why you haven't saved them!)
Let's switch to commit C again:
A--B--C <-- master (HEAD)
\
D <-- seconde-branch
We can now make a new commit on master, by changing some files, or adding new files, or removing files, or some combination of all three. We git add any updates if necessary and run git commit, and after it succeeds, we have a new commit, with a new big ugly hash ID, but we'll just call it E:
E <-- master (HEAD)
/
A--B--C
\
D <-- seconde-branch
Now, think about this: Which commits are on which branches? In particular, which branch(es) hold commits A-B-C? Remember that Git always starts with the last commits—of which there are now two, commits D and E—and works backwards.
Working backwards from E, we also traverse commits C, then B, then A. So these commits should all be on master. Working backwards from D, we also traverse C, then B, then A. So these commits should all be on seconde-branch.
So: which branch(es) are commits A-B-C on? I'll leave this as an exercise, but will note that Git's answer is very different from, say, Mercurial's.
uj5u.com熱心網友回復:
未提交的更改不系結到任何分支。
它們只是本地檔案系統中的本地更改。
當您在分支之間切換時,Git 通常會保留這些本地更改(除非您通過強制檢出來丟棄它們)。
如果您想存盤更改以備后用,但又不想將它們提交到任何分支,請使用git stash save -u將本地更改保存到本地 git stash。
然后您可以稍后使用 git stash apply
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/378136.html
標籤:混帐
