Unix 'rm' в ветке проекта репозитория git удаляет файл из каталога главной ветки - PullRequest
0 голосов
/ 17 марта 2020

Ищите причину «почему» в следующей ситуации, которая привела к непредвиденному поведению - в частности, с помощью команды unix «rm» для удаления файла в ветви проекта в моем репозитории git также был удален файл из мастера. филиал. Ниже я приведу сводку команд, а затем полную консоль.

Сводка команд:

  1. git init
  2. touch file1.txt file2.txt
  3. git add * .txt
  4. git commit -m "Добавить file1.txt и file2.txt"
  5. git checkout -b myBranch
  6. rm file1.txt
  7. git status => показывает удаленный file1.txt, не подготовленный для фиксации
  8. git мастер проверки
  9. git status => показывает удаленный файл file.txt, не подготовленный для фиксации
  10. ls => показывает, что file1.txt был удален из каталога (мастер)
  11. git извлечение myBranch
  12. git rm file1.txt
  13. git commit -m "Удалить file1.txt"
  14. git мастер проверки
  15. ls => показывает, что file1.txt находится в каталоге (master)

Вопросы, приведенные в приведенном выше резюме: строки 9, 10 удалены из файла в master, но возвращены в строку 15.

Детали консоли (Примечание, май есть дополнительные записи для отображения)

ec2-user:~/environment/TestGit $ git init
Initialized empty Git repository in /home/ec2-user/environment/TestGit/.git/

ec2-user:~/environment/TestGit (master) $ touch file1.txt file2.txt
ec2-user:~/environment/TestGit (master) $ git add *.txt
ec2-user:~/environment/TestGit (master) $ git commit -m "Add file1.txt and file2.txt"
[master (root-commit) 531ed48] Add file1.txt and file2.txt
 Committer: EC2 Default User <ec2-user@ip-172-31-37-27.ec2.internal>
Your name and email address were configured automatically based
on your username and hostname. Please check that they are accurate.
You can suppress this message by setting them explicitly:

    git config --global user.name "Your Name"
    git config --global user.email you@example.com

After doing this, you may fix the identity used for this commit with:

    git commit --amend --reset-author

 2 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 file1.txt
 create mode 100644 file2.txt

ec2-user:~/environment/TestGit (master) $ git checkout -b myBranch
Switched to a new branch 'myBranch'

ec2-user:~/environment/TestGit (myBranch) $ ls
file1.txt  file2.txt

ec2-user:~/environment/TestGit (myBranch) $ rm file1.txt
ec2-user:~/environment/TestGit (myBranch) $ ls
file2.txt

ec2-user:~/environment/TestGit (myBranch) $ git status
On branch myBranch
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        deleted:    file1.txt

no changes added to commit (use "git add" and/or "git commit -a")

ec2-user:~/environment/TestGit (myBranch) $ git checkout master
D       file1.txt
Switched to branch 'master'

ec2-user:~/environment/TestGit (master) $ ls
file2.txt

ec2-user:~/environment/TestGit (master) $ git status
On branch master
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        deleted:    file1.txt

no changes added to commit (use "git add" and/or "git commit -a")

ec2-user:~/environment/TestGit (master) $ git checkout myBranch
D       file1.txt
Switched to branch 'myBranch'

ec2-user:~/environment/TestGit (myBranch) $ git status
On branch myBranch
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        deleted:    file1.txt

no changes added to commit (use "git add" and/or "git commit -a")

ec2-user:~/environment/TestGit (myBranch) $ git rm file1.txt
rm 'file1.txt'

ec2-user:~/environment/TestGit (myBranch) $ git status
On branch myBranch
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        deleted:    file1.txt

ec2-user:~/environment/TestGit (myBranch) $ git commit -m "Remove file1.txt"
[myBranch 6585980] Remove file1.txt
 Committer: EC2 Default User <ec2-user@ip-172-31-37-27.ec2.internal>
Your name and email address were configured automatically based
on your username and hostname. Please check that they are accurate.
You can suppress this message by setting them explicitly:

    git config --global user.name "Your Name"
    git config --global user.email you@example.com

After doing this, you may fix the identity used for this commit with:

    git commit --amend --reset-author

 1 file changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 file1.txt

ec2-user:~/environment/TestGit (myBranch) $ git status
On branch myBranch
nothing to commit, working tree clean

ec2-user:~/environment/TestGit (myBranch) $ ls
file2.txt

ec2-user:~/environment/TestGit (myBranch) $ git checkout master
Switched to branch 'master'

ec2-user:~/environment/TestGit (master) $ ls
file1.txt  file2.txt

Ответы [ 2 ]

0 голосов
/ 18 марта 2020

У вас неверная ментальная модель работы Git. (Не волнуйтесь, что вы делаете - я делал, когда начал с Git, более десяти лет go.) Чтобы исправить свою ментальную модель, вам нужно знать следующие вещи:

  • Git магазины коммиты . Он не хранит файлы - не на том уровне, на котором вы будете его использовать, во всяком случае, - скорее, целые коммиты.

  • Коммиты сами делают файлы для хранения, так что вот как вы получить файлы, но они находятся на уровне коммита: у вас либо есть коммит (и все его файлы), либо нет (у вас нет ни одного из его файлов). Каждый коммит хранит полный и полный снимок из всех файлов (ну, все из его файлов; см. Ниже).

  • Коммиты также хранят некоторые метаданные: информацию о коммите, например, кто его сделал, когда и почему (сообщение журнала). Важнейшим элементом метаданных в каждом коммите является «номер» коммита, который приходит до этого коммита.

  • коммит "числа" большие и некрасивые и случайный вид ha sh ID. Каждый коммит получает уникальный идентификатор ha sh. Вот как вы (или ваш Git) узнаете, есть ли у вас коммит. Каждый Git повсюду соглашается, что этот конкретный коммит получает этот конкретный га sh ID , и никакой другой коммит, прошлый или будущий, никогда не сможет иметь этот ID. Для этого идентификатор ha sh представляет собой криптографическую c контрольную сумму содержимого коммита - это означает, что ни одна из частей любого существующего коммита не может когда-либо измениться.

  • Ни один человек не может на самом деле запомнить эти га sh идентификаторы. К счастью, нам не нужно: у нас есть компьютер, чтобы запомнить их для нас.

  • A название филиала , что большинство людей (включая меня) часто будет сокращенно "ветвь", содержит только один идентификатор ha sh. Идентификатор ha sh в имени , например, это идентификатор last commit в ветви. Вот почему каждый коммит ссылается на свой родительский или предыдущий коммит: так что Git может начинаться с конца и работать в обратном направлении.

  • A коллекция коммитов то, что вы получаете, начиная с конца и работая в обратном направлении, также называется «ветвью». Поэтому, когда кто-то говорит, например, branch master, важно подумать о том, означает ли это последний коммит в master, сохраненный в имени master или серия коммитов, заканчивающаяся последним коммитом в master.

Теперь тот факт, что каждый когда-либо сделанный коммит является только для чтения , означает что то, что мы делаем с репозиторием, обычно просто добавляет новые коммиты . Но чтобы сделать новый коммит, мы должны иметь возможность изменять файлов: открывать их в наших редакторах, вносить в них изменения и сохранять их обратно. Файлы внутри коммитов не могут быть изменены . Поэтому мы не работаем и не можем работать с совершенными файлами. Сами коммиты, которые хранят моментальные снимки всех ваших файлов, являются всего лишь архивами.

Чтобы архивы не становились очень толстыми очень быстро, Git хранит зафиксированные файлы в специальном, только для чтения, Git -только сжатый формат. Только Git действительно может использовать их. (Конечно, вы могли бы написать свои собственные программы для их чтения, но существует более одного формата, и уже есть Git сантехническая команда , то есть то, что пользователям не нужно использовать, чтобы читать необработанный объект, используя git cat-file -p. Это может читать больше, чем просто файлы, но он может читать файлы внутри коммита.) Новые коммиты могут делиться файлами из существующих коммитов - это, очевидно, безопасно все доступны только для чтения - и на самом деле все это происходит автоматически.

В любом случае, чтобы выполнить любую работу new в каком-либо существующем репозитории, вы должны сначала выбрать существующий коммит и Git извлеките его куда-нибудь. Это «где-то» - ваше рабочее дерево (или рабочее дерево или какой-либо вариант с этим именем). Извлеченная область рабочего дерева содержит обычные файлы в обычных повседневных форматах.

Вы и ваш компьютер можете работать с этими файлами рабочего дерева. Это то, что вы делаете, например, в шагах 2 и 6.

Git не не использует эти файлы рабочего дерева вообще. Он создает их для вас (извлекая их из коммитов), и он будет смотреть на них, когда вы скажете это, но он не использует их для создания коммитов . Они существуют для того, чтобы вы могли использовать для выполнения своей работы. Вы должны скопировать их в файлы, которые использует Git, что и было шагом 3. Здесь все становится немного сложнее.

Индекс

На шаге 1 вы создали новый пустой Git репозиторий. В этом хранилище еще нет коммитов. Он имеет пустое рабочее дерево, в котором вы можете работать со своими файлами. И он имеет пустой индекс . Эта вещь - этот индекс - довольно сложная, но вы можете думать о ней как о , где вы строите следующий коммит, который вы сделаете . Вы можете думать об этом как о хранении копий каждого из ваших файлов.

Ваш шаг 2:

touch file1.txt file2.txt

, который создал два (пустых) файла в вашей работе -tree. Эти файлы еще не включены в ваш индекс. Ваш шаг 3, однако, был:

git add file1.txt file2.txt

Это приводит к копированию содержимого файлов в индекс. 1 Git теперь говорит, что эти файлы постановка для коммита . Это приводит к другому, альтернативному имени индекса: он также называется промежуточной областью . Это просто синонимы: указатель или промежуточная область - это только одно. 2

Наконец, на шаге 4 вы запустили git commit. Это сделало новый коммит из файлов, которые были в индексе , а не в рабочем дереве. Эти два индексных файла были копиями из рабочего дерева.

Теперь у вас есть коммит. Этот коммит является самым первым коммитом в репозитории, поэтому он немного особенный: он не записывает ни одного предыдущего коммита. (Конечно, не может; никаких предыдущих коммитов нет.) Я понятия не имею, что у вас есть sh ID вашего коммита: он зависит не только от файлов в коммите (которые я знаю) и вашего сообщение в журнале (которое я видел в вашей команде), но также на ваше имя и адрес электронной почты и в ту самую секунду, когда ваш Git создал коммит (а я этого не знаю). Однако я знаю, что он имеет уникальный идентификатор ha sh, отличный от всех других идентификаторов ha sh в вашем репозитории или любом другом репозитории Git, с которым вы будете разговаривать в будущем. 3


1 Технически, индекс содержит режимы файлов, их имена и - для каждого файла - ссылку на внутренний Git объект, который содержит содержимое. Этот объект BLOB-объекта имеет идентификатор ha sh, как и коммит (хотя в отличие от коммита, объект BLOB-объекта можно использовать повторно). Идентификатор пустого файла ha sh: e69de29bb2d1d6434b8b29ae775ad8c2e48c5391, который можно найти, запустив git hash-object -t blob --stdin </dev/null. Если и когда Git перейдет к SHA-2 вместо SHA-1, идентификаторы каждого объекта изменятся, что будет очень интересным временем для Git. Мы можем надеяться, что Git скрывает здесь все болезненные части для нас.

2 Технически, индекс в основном представляет собой просто файл в .git с именем .git/index. «В основном» здесь только потому, что Git имеет режим, называемый индекс разделения . Все это, однако, внутренние детали, которые могут измениться. Единственное внешнее обещание состоит в том, что вы можете установить переменную среды с именем GIT_INDEX_FILE, чтобы Git использовал другой индекс. Некоторые Git программы делают это для специальных целей: например, git stash, когда это был скрипт оболочки, делал это при выполнении некоторых коммитов sta sh, чтобы избежать перезаписи нормального индекса.

3 Это зависит от уникальности идентификаторов ha sh. В присутствии злоумышленников это, в свою очередь, зависит от силы криптографии. См. Как влияет недавно обнаруженное столкновение SHA-1 на Git?


Подробнее об именах ветвей

Мы уже упоминали, что имена ветвей, например, master, держите га sh ID коммита. Пока у вас нет идентификаторов ha sh, у вас не может быть названий ветвей. Таким образом, создание этого начального коммита - это то, что создало name master. Это имя содержит фактический идентификатор ha sh, что бы это ни было. Когда что-то содержит идентификатор sh, мы говорим, что это указывает на коммит. Итак, в настоящее время - после того, как шаг 4 создает первый коммит - у вас есть коммит с каким-то большим уродливым идентификатором ha sh, но давайте просто назовем его «commit A» и нарисуем его так: *

Имя master указывает на (содержит га sh ID) commit A.

Теперь мы go переходим к шагу 5:

git checkout -b myBranch

Это создает новое имя , myBranch, которое также содержит идентификатор ha sh существующего коммита A. Давайте обновим наш чертеж:

A   <-- master, myBranch

Git также необходимо знать какое название ветви мы используем , поэтому давайте прикрепим имя HEAD, написанное заглавными буквами, к одно из этих двух названий веток. Имя ветви, которое мы хотим использовать - созданное этим git checkout -b - является новым, так что:

A   <-- master, myBranch (HEAD)

Оба имени указывают на один и тот же коммит. Это совершенно нормально в Git: коммит A теперь в обе ветви . Текущее имя равно myBranch, а текущее значение - это значение A.

Теперь давайте посмотрим, что происходит в шагах 6, 7 и 8:

rm file1.txt

Удаляет файл из вашего рабочего дерева . Git index , который по-прежнему соответствует фиксации A - Git made commit A из индекса - в нем по-прежнему есть два файла.

git status

Это выполняет два отдельных сравнения. Один сравнивает текущий коммит, commit A, с индексом. Они имеют одинаковые файлы с одинаковым содержимым, поэтому эта часть git status ничего не говорит. Второе сравнение - index-vs-work-tree. Здесь индекс имеет file1.txt, а рабочее дерево - нет, поэтому это сравнение говорит о том, что file1.txt удаляется из рабочего дерева, но не из индекса, говоря, что это удаление not staged for commit.

git checkout master

Это сообщает Git, что вы хотите изменить текущий коммит и / или ветвь . Текущая ветвь myBranch, а текущая фиксация A. Имя выбранной ветви master и его фиксация A. Таким образом, Git может пропустить изменение коммитов , при этом прикрепляя специальное имя HEAD к имени master сейчас: 4

A   <-- master (HEAD), myBranch

Ничего больше не происходило: в индексе по-прежнему есть два файла, в текущем коммите все еще сохраняется A, а в рабочем дереве по-прежнему отсутствует один файл. Шаг 9 - еще один git status - скажет вам, что ваша текущая ветвь теперь master, но будет выполнять те же сравнения: зафиксировать A против индекса и индекс против рабочего дерева. Результат здесь будет таким же. Шаг 10 просто смотрит на рабочее дерево, которое, как мы знаем, отсутствует file1.txt.

Шаг 11 просит Git снова присоединить HEAD к master. Больше ничего не меняется: индекс не затрагивается, а рабочее дерево не затрагивается.

На шаге 12, однако, вы запускаете:

git rm file1.txt

Это меняет index . Команда git rm удаляет файл как из индекса, так и из рабочего дерева. Оно уже ушло из рабочего дерева, так что ничего не изменилось, но теперь в index больше нет file1.txt.

На шаге 13 вы запускаете git commit снова. Это делает новый коммит из того, что находится в индексе: то есть коммит, в котором есть только пустой file2.txt. Вы также получаете все обычные метаданные: ваше имя и адрес электронной почты, а также сообщение в журнале, почему вы сделали этот коммит. родитель этого нового коммита, который мы будем называть B, а не пытаться угадать идентификатор ha sh, - это существующий коммит A: новый коммит B указывает на существующий коммит A.

Последний шаг git commit предназначен для Git, чтобы записать идентификатор ha sh нового коммита в имя, к которому прикреплен HEAD. Поскольку шаг 11 привязан HEAD к myBranch, результат будет следующим:

A   <-- master
 \
  B   <-- myBranch (HEAD)

Существующее имя master не изменилось вообще. HEAD все еще прикреплен к myBranch, но имя myBranch теперь указывает на новый коммит B. Индекс по-прежнему имеет то, что имел до того, как вы запустили git commit: то есть он содержит только пустой file2.txt. Коммит B имеет стрелку, указывающую назад, или содержит идентификатор ha sh - коммит A, поэтому, если вы сейчас запустите git log, ваш Git начнется с HEAD, find myBranch, find B, show commit B, следуйте стрелке, чтобы зафиксировать A, и show commit A.


4 Технически Git выполняет это, записывая имя ветви master в файл в .git с именем .git/HEAD. Вы можете посмотреть на этот файл, но когда вы хотите обновить его, вы должны использовать различные Git инструменты, потому что при различных условиях Git может использовать некоторые другие файл. В частности, поскольку Git 2.5, Git теперь имеет git worktree add, что добавляет новую пару индекс-и-дерево-работа. Каждое добавленное рабочее дерево также должно иметь свое собственное отдельное HEAD, поэтому, как только вы добавите несколько рабочих деревьев, индекс уже не всегда будет .git/index, а HEAD - не всегда .git/HEAD. .


Сводка

Всегда помните о следующих пунктах:

  • Git это все о совершает . Имена ветвей - и другие имена, как только вы доберетесь до этой точки - просто служат для find коммитов.

  • Каждый коммит имеет уникальный идентификатор ha sh, и за исключением некоторых новых незавершенных функций («частичных клонов»), у вас всегда либо полный коммит, либо ни одного из коммитов.

  • Каждый коммит ссылается на один или несколько предшественников или parent коммиты, за исключением особых случаев, таких как самый первый коммит в каком-либо хранилище. Эти связи - или цепочки коммитов - образуют то, что люди называют ветвями (одно из нескольких значений слова «ветвь»).

  • Чтобы сделать новый коммит, вам необходимо обновить Git индекс . Когда вы впервые git checkout делаете какой-то коммит, которого у вас еще нет, Git заполняет индекс - и, конечно, ваше рабочее дерево - из этого коммита. Вы работаете с файлами в вашем рабочем дереве, а Git работает с его индексом.

  • Индекс и ваше рабочее дерево не копируются: когда вы git clone, или git fetch, или git push, вы передадите коммитов . Индекс и дерево работы здесь не имеют значения (ну, есть некоторые условия для git push, в other Git, который получает git push).

  • Коммиты навсегда заморожены (и в основном постоянные - от них немного сложно избавиться, даже если вы захотите, иногда). Копии файлов в вашем индексе и рабочем дереве являются временными.

  • Добавление новых коммитов обновляет названия вашей ветви. Обновленное имя ветви - это имя, к которому вы прикрепили HEAD.

  • В Git 2.23 или более поздней версии вы можете использовать git switch, чтобы выбрать, где HEAD идет и / или создает новые имена веток и git restore для извлечения указанных c файлов из указанных c коммитов; в более ранних версиях Git оба задания объединялись в одну команду git checkout.

  • Когда вы приступите к использованию второго репозитория Git, помните, что до git push эти коммиты в этого другого хранилища, ваш Git - единственный, который имеет ваш новый коммит. Это позволяет легко (и хорошо) «переписывать историю», заменяя некоторые коммиты некоторыми новыми и улучшенными версиями (например, git rebase -i или git commit --amend). Как только вы отправили коммитов в другое место, вы все равно можете заменить коммиты на новые и улучшенные версии, просто у other Git теперь есть коммиты, которые вы отправили ранее, так что эти все становится сложнее - иногда намного сложнее.

0 голосов
/ 17 марта 2020

Хорошо ... Мне кажется, я понимаю, что ты не понимаешь. В пунктах 9 и 10 вы играли , играя на на мастере .... однако затем вы переключаетесь обратно на myBranch, и тогда вы делаете коммит. Итак ... мастер остался там, где он был (первый коммит с двумя файлами, он не перемещается), и вы наконец зафиксировали на myBranch удаление файла. Вот почему, когда вы возвращаетесь к мастеру, оба файла находятся там.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...