У меня есть две git фиксации, которые выглядят одинаково после внесенной поправки - PullRequest
0 голосов
/ 29 мая 2020

У меня есть ветка git, над которой я работаю локально. После редактирования некоторого кода и выполнения фиксации и pu sh в удаленном репо я заметил, что у меня была ошибка в моем сообщении о фиксации. Я использовал поправку, чтобы исправить это. Сначала я попытался сделать пу sh, но это было невозможно, поэтому я сделал тягу, а затем пу sh. Все вроде заработало. Когда я смотрю на репо, я вижу две фиксации, по одной для каждого сообщения (и правильные, и неправильные, поэтому я предполагаю, что мое исправление было бесполезным, лол.) Затем я заметил, что оба коммита содержат одинаковые изменения кода. Это кажется странным, потому что я не думаю, что оба должны содержать изменения кода. Значит ли это, что изменения применяются дважды? Это проблема, которая может вызвать проблемы позже? Это нужно исправить? Если да, то как мне это исправить?

=====

команды

  1. (IDE) Изменения внесены, добавлены и зафиксированы
  2. git pu sh -u origin BRANCH_NAME
  3. (IDE) Выполнено изменение
  4. git pu sh (ошибка: не удалось выполнить pu sh некоторые ссылки)
  5. git тяга
  6. git пу sh

Ответы [ 3 ]

3 голосов
/ 29 мая 2020

В git нет такой вещи, как отредактированный коммит. Коммит никогда не может быть изменен , если он существует.

«Изменение» начинает с существующего исходного коммита и создает новый фиксация, содержащая те же файлы (что вы неправильно называете "изменения кода"), что и исходная фиксация, и указывающая на того же родителя, что и исходная фиксация.

Исходная фиксация не изменяется и не уничтожается этим процессом, хотя если на него не указывает ссылка, go через некоторое время он может перестать существовать как недостижимый.

0 голосов
/ 30 мая 2020

Вместо изменения, вероятно, вы только что сделали еще одну фиксацию.
Вероятно, ваша ситуация выглядит так: c1 -> c2 -> c3 (где c2 - неправильная фиксация, c3 не требуется фиксация)
Вам нужно * Параметр 1003 * -f , откройте терминал и попробуйте следующее:

1) git checkout my_branch
2) git reset --hard c2
3) изменить исходный код
4) git add.
5) git commit --amned
6) теперь ваш wim редактор откроется, и вы можете изменить сообщение фиксации
7) сохраните изменения в вашем редакторе vim и выйдите
8) git log (убедитесь, что есть c1 -> c2 и больше ничего)
9) git pu sh origin my_branch -f

В будущих исправлениях делайте следующее:
1) git checkout my_branch
2) git fetch
3) git pull
4) изменить источник code
5) git add.
6) git commit --amend
7) теперь ваш vim редактор откроется, и вы можете изменить сообщение фиксации
8) сохраните изменения в редакторе vim и выйдите
9) git pu sh origin my_branch -f

Будьте осторожны "force" опция рискованна. Когда Вы его используете, это означает, что Вы на 100% уверены, что знаете, что делаете.

0 голосов
/ 30 мая 2020

Как Мэтт сказал , вы не можете изменить фиксацию. Что делает git commit --amend, так это делает новую фиксацию, которая не расширяет текущую ветку.

Я думаю, что больше людей получают это, когда они могут видеть визуально, что это значит. Но это означает, что вам сначала нужно научиться рисовать графики фиксации. Поскольку вы используете IDE, возможно, вы уже узнали об этом, но вы не упоминаете, какую IDE (да и я сам обычно не использую IDE). Кроме того, очень сложно ввести графику в сообщение StackOverflow. :-) Итак, рассмотрим эту текстовую версию рисунка графа фиксации:

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

Здесь мы имеем серию коммитов, все в красивой аккуратной строке, с фиксацией, ha sh это H - H здесь означает какое-то большое уродливое Git ha sh ID - это последний в цепочке. Некоторые IDE рисуют это вертикально, с фиксацией H вверху:

H  <commit subject line>
|
G  <commit subject line>
|
:

, но для целей StackOverflow мне нравятся горизонтальные рисунки.

Обратите внимание, как каждая фиксация «указывает назад» на свой немедленная родительская фиксация. Внутренне это означает, что каждая фиксация хранит полный идентификатор ha sh своего родителя, потому что Git на самом деле находит коммитов по их идентификаторам ha sh.

Чтобы найти фиксацию H конкретно (и быстро), Git также должен где-то хранить свой ha sh ID. Git может использовать H, чтобы найти G ha sh ID, а затем G, чтобы найти F ha sh ID, и так далее, но Git требуется на начать с H га sh ID. Место Git находит H га sh ID - это имя вашей ветки:

...--F--G--H   <-- branch (HEAD)

имя ветки позволяет Git найти H легко, потому что само имя ветки содержит необработанный sh ID фиксации. Затем коммит H позволяет Git легко находить G, что позволяет Git легко находить F и т. Д. Это наша обратная цепочка коммитов.

Обратите внимание, когда у вас есть несколько веток, они имеют тенденцию воссоединяться где-то в прошлом:

          I--J   <-- branch1
         /
...--G--H   <-- master
         \
          K--L   <-- branch2 (HEAD)

Здесь мы сделали branch1 два коммиты, которых еще нет в master, и сделанные branch2, имеют две фиксации, которых еще нет в master. Коммиты до H находятся на всех трех ветвях . Имя master конкретно означает commit H, но также - при необходимости - означает все коммиты до H включительно. Имя branch1 означает коммиты до J включительно или просто J, в зависимости от контекста; а имя branch2 означает фиксацию до H плюс K-L или просто L, в зависимости от контекста.

Я нарисовал имя HEAD здесь, прикрепленное к branch2 чтобы показать, что у нас есть branch2 и, следовательно, фиксация L, проверенная в данный момент. Общая идея с Git такова:

  • Git разрешит HEAD в фиксацию, найдя имя ветки и следуя стрелке имени ветки до фиксации.
  • Это текущая фиксация , которую вы проверили.
  • Каждая фиксация находит предыдущую (родительскую) фиксацию, которая определяет цепочку коммитов, которую мы называем «ветвью» .
  • Но слово ветка неоднозначно, потому что иногда оно означает имя ветки , иногда фиксацию на конце ветки, как указано to по имени , а иногда это означает фиксация подсказки плюс некоторые или все предыдущие фиксации .

Когда люди используют слово ветка , иногда они означают серию коммитов, включая коммит, который не идентифицируется именем ветки . Помимо обычных имен веток, Git имеет теги имена и имена удаленного отслеживания , например origin/master и origin/branch1 и так далее. Все эти имена в конечном итоге просто указывают на одну конкретную c фиксацию - точно так же, как имя ветки, - но только имена ветки имеют возможность присоединять к ним HEAD.

Теперь рассмотрим, как обычно работает добавление фиксации

Предположим, у нас есть:

...--G--H   <-- branch (HEAD)

У нас есть файлы из фиксации H извлечены, потому что H - это текущий коммит . Мы вносим некоторые изменения в эти файлы, git add обновленные файлы, чтобы скопировать изменения поверх копий этих файлов в Git индексе или промежуточной области , и запустить git commit. Git теперь замораживает файлы, которые находятся в его индексе, добавляет правильные метаданные - наше имя и адрес электронной почты, наше сообщение журнала, родительский ha sh ID H и т. Д. - и тем самым создает новую фиксацию, который получает новый уникальный большой уродливый ha sh ID, но мы просто назовем его I:

...--G--H   <-- branch (HEAD)
         \
          I

Поскольку I теперь последний коммит, Git теперь выполняет один трюк, который делает имена веток отличными от любых других имен: Git сохраняет I 's ha sh ID в name branch, потому что это имя который HEAD прилагается. Мы получаем:

...--G--H
         \
          I   <-- branch (HEAD)

, который мы можем исправить сейчас:

...--G--H--I   <-- branch (HEAD)

Вот как git commit работает:

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

После записи новой фиксации изменяется текущая фиксация - идентификатор ha sh, который HEAD означает, что теперь фиксация I вместо фиксации H - но снова текущий моментальный снимок фиксации соответствует файлам в Git индексе , который, если вы git add -ed все - также сопоставьте файлы в вашем рабочем дереве.

Теперь мы можем увидеть, как git commit --amend работает

Когда вы используете git commit --amend, Git идет через все те же шаги, что и для любого коммита, за одним исключением: родительский нового коммита (или родители, во множественном числе, если текущая фиксация является фиксацией слияния) взяты из текущего фиксация вместо , являющейся текущей фиксацией. То есть, вместо того, чтобы делать:

...--G--H--I--J   <-- branch (HEAD)

с новой фиксацией J, указывая на существующую фиксацию I, Git делает следующее:

          I   ???
         /
...--G--H--J   <-- branch (HEAD)

Фактически, Тогда-текущий коммит I теперь «убран с дороги», чтобы поместить новый коммит в конец цепочки , не увеличивая цепочку .

Существующий коммит I все еще существует. У него просто больше нет имени .

Когда вы задействуете другой репозиторий Git, вы обмениваетесь коммитами с ними

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

Одна из этих двух команд - git fetch. Это тот, который вызывает их Git и находит, какие коммиты у них есть, а у вас нет: он загружает их коммиты в ваш собственный Git репозиторий.

Другая команда - git push: с git push ваш Git вызывает их Git и отправляет коммиты. Однако эти двое не совсем симметричны. Давайте сначала посмотрим на git fetch, потому что именно отсюда имена удаленного отслеживания вроде origin/master и origin/branch.

Мы уже видели Git находит коммиты , беря имя - возможно, имя ветки - чтобы найти последний коммит, а затем работая в обратном направлении. Тем временем ваш Git звонит другому Git. Другой Git имеет имена веток B1, B2, B3, ..., каждое из которых указывает последний идентификатор ha sh фиксации для этого Git

Это их ветки, а не ваши ветки. У вас могут быть или не быть ветки с одинаковыми именами, но это их имена, указывающие на их последние коммиты. Ваш git fetch не касается ваших имен веток.

Предположим, например, что мы начинаем с:

...--G--H   <-- master (HEAD)

, но получили фиксацию H из origin. Тогда у нас действительно есть:

...--G--H   <-- master (HEAD), origin/master

То есть в их Git репозитории, их имя master также выбирает фиксацию H. Итак, наш Git записал их Git имя master как наше origin/master; затем мы сделали наш master из их origin/master, и теперь оба имени указывают на существующий коммит H.

Если мы теперь сделаем наш собственный новый коммит I, наш master теперь указывает на фиксацию I. Наш origin/master по-прежнему указывает на H, как и раньше:

...--G--H   <-- origin/master
         \
          I   <-- master (HEAD)

Между тем, предположим, что они - кем бы они ни были - делают свою новую фиксацию. Он получит какой-то большой уродливый уникальный ha sh ID; мы просто назовем его J. Их коммит J находится в их репозитории:

...--G--H--J   <-- master [in their Git]

Мы запускаем git fetch, и наш Git вызывает их Git и обнаруживает, что у них есть новый коммит чего мы никогда раньше не видели. Наш Git получает его от своего Git и помещает в наш репозиторий. Чтобы запомнить идентификатор J ha sh, наш Git обновляет наш собственный origin/master:

          I   <-- master (HEAD)
         /
...--G--H--J   <-- origin/master

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

Теперь у нас есть своего рода проблема. Наша фиксация I и их фиксация J образуют две ветки, в зависимости от того, что мы подразумеваем под словом ветка:

          I   <-- master (HEAD)
         /
...--G--H
         \
          J   <-- origin/master

Нам нужно как-то их объединить, в какой-то момент. Мы можем сделать это с помощью git merge или git rebase, чтобы скопировать существующую фиксацию I в новую улучшенную фиксацию - назовем ее I' - которая расширяет их J:

          I   ??? [abandoned]
         /
...--G--H--J   <-- origin/master
            \
             I'  <-- master (HEAD)

Мы отказываемся от нашего I в пользу нашего нового и улучшенного I', который дополняет существующие коммиты. Теперь мы можем git push origin master. Или мы используем git merge, чтобы объединить работу в новый коммит, со снимком, сделанным немного сложным процессом, включающим сравнение снимка коммита H с каждым из двух снимков в I и J:

          I
         / \
...--G--H   M   <-- master (HEAD)
         \ /
          J   <-- origin/master

Теперь мы снова можем git push origin master.

Почему pu sh не является симметричным c с выборкой

Допустим, мы есть только это:

          I   <-- master (HEAD)
         /
...--G--H
         \
          J   <-- origin/master

Другими словами, мы еще не перекомпилировали и не объединились. Наше имя master указывает на совершение I; наше имя origin/master, представляющее master на origin, указывает на фиксацию J. Мы можем попробовать запустить:

git push origin master

, который вызовет их Git, отправит им наш коммит I - у них его еще нет, потому что мы не дали это им раньше - а затем попросите их установить их master, чтобы они указывали на фиксацию I.

Помните, что их master в настоящее время указывает на (общий, скопированный в оба Gits) коммит J. Если они сделают то, что мы просим, ​​у них получится:

          I   <-- master
         /
...--G--H
         \
          J   ??? [abandoned]

То есть они потеряют зафиксируют J полностью. Git находит коммиты, начиная с имени ветки, например master, и работая в обратном направлении. Их master использовали, чтобы найти J; и если они воспользуются нашей просьбой, чтобы вместо этого master указывало на I, они больше не смогут найти J.

Вот почему они просто отклоняют нашу вежливую просьбу , говоря , а не перемотка вперед . Мы исправляем эту проблему, используя git rebase или git merge, чтобы сделать I' или какой-нибудь коммит слияния. Затем мы либо отправляем им I' и просим их установить master так, чтобы они указывали на I', что нормально, потому что I' идет после J и, следовательно, сохраняет фиксацию J в изображение; или мы отправляем им M (и снова I, если они его уронили) и просим их установить их master так, чтобы они указывали на M, что нормально, потому что и I и J предшествует M, чтобы они могли найти J.

Иногда мы действительно хотим, чтобы они выбросили фиксацию

Когда мы используем git commit --amend, мы берем такую ​​цепочку:

...--H--I   <-- branch (HEAD)

и превращаем ее в такую:

       I   ??? [abandoned]
      /
...--H--J   <-- branch (HEAD)

, что делает фиксацию I видимой go. На самом деле он остается на некоторое время - по крайней мере, около месяца - на случай, если мы захотим его вернуть, через механизм, который Git вызывает reflogs . Но он ушел из повседневного представления, так как нет имени, указывающего прямо на него, и нет другого имени, указывающего на некоторую фиксацию, которая в конечном итоге указывает на I.

Но что, если бы мы отправили фиксацию I другому Git? Что, если, в частности, мы запустили:

git push origin branch

, так что теперь у нас есть:

       I   <-- origin/branch
      /
...--H--J   <-- branch (HEAD)

, где наш origin/branch представляет origin s branch, который теперь указывает на нашу старую фиксацию I?

Если мы просто запустим:

git push origin branch

, это сообщит их Git: Здесь: иметь новую фиксацию J. Теперь, пожалуйста, если все в порядке, настройте branch так, чтобы он запомнил фиксацию J. Они скажут «нет» по той же причине, по которой они сказали «нет» в нашем другом примере: это потеря фиксация I, в их репозитории Git.

Но это именно то, что мы хотим. Мы хотим, чтобы они потеряли фиксацию I в своей ветке branch. Чтобы это произошло, мы отправляем такую ​​же операцию - еще одну git push - но мы меняем наш последний вежливый запрос на более сильную команду.

У нас есть два варианта:

  • Мы можем сказать: Задайте свое имя branch так, чтобы оно указывало на фиксацию J! Это просто говорит им удалить все коммиты, которые могут быть отброшены таким образом , даже если это сейчас I и K тоже.

  • Или мы можем сказать: Я думаю, ваш branch определяет фиксацию . Если да, измените его, чтобы вместо этого идентифицировать фиксацию J. В любом случае, дайте мне знать, что произошло.

Первый простой git push --force. Второй - git push --force-with-lease. Наш Git заполнит часть «Я думаю» commit I ha sh из нашего origin/branch и, конечно же, получит идентификатор J ha sh ID таким же образом, как и всегда.

Опасность любого git push --force, с частью -with-lease или без нее, заключается в том, что мы говорим какому-то другому Git выбросить некоторые коммиты . Конечно, это то, что мы хотим, чтобы не было опасным, пока мы знаем, что просим выкинуть коммиты. Но если мы git push используем репозиторий GitHub, есть ли другие люди, которые используют этот репозиторий GitHub для git fetch откуда? Возможно, они взяли наш коммит I и используют его. Они могли вернуть фиксацию I. Или, может быть, мы делаем для них дополнительную работу, так что им придется переделать свои коммиты, чтобы использовать фиксацию J вместо фиксации I.

Мы должны организовать продвигайтесь вместе с другими пользователями этого origin Git, чтобы они знали, в каких ветвях могут быть удалены вот так.

Ваш собственный случай

В вашем случае , вы выполнили ошибку git push, а затем git pull. Команда git pull означает запустить git fetch, а затем запустить вторую Git команду . Вторая команда - git merge по умолчанию.

Итак, допустим, вы начали с:

...--G--H   <-- master, origin/master, branch (HEAD)

затем добавили фиксацию I:

...--G--H   <-- master, origin/master
         \
          I   <-- branch (HEAD)

You затем запустил (успешно) git push -u origin branch, что привело к:

          I   <-- branch (HEAD), origin/branch
         /
...--G--H   <-- master, origin/master

(опять же, на этот раз я просто поставил I сверху для эстетики).

Затем вы использовали git commit --amend, который сделал новую фиксацию J, не имеющую I в качестве родителя:

          I   <-- origin/branch
         /
...--G--H   <-- master, origin/master
         \
          J   <-- branch (HEAD)

Вы пробовали обычную git push, которая не удалась с без перемотки вперед: их Git сказал вашему Git, что этот пу sh потеряет коммиты (в частности, I).

Затем вы запустили git pull :

  • Это запустило git fetch, что ничего не дало, потому что у вас уже есть коммиты H и I, и нет никаких изменений, которые нужно вносить ни в одно из ваших origin/* имен.
  • Затем он запустил git merge, чтобы объединить I и J в новую фиксацию слияния.

Я перестану рисовать имена master и origin/master по мере того, как они мешают, но это произошло именно так, как мы теперь ожидали:

          I   <-- origin/branch
         / \
...--G--H   M   <-- branch (HEAD)
         \ /
          J

, а затем вы запустили git push, который отправил им коммиты J и M в добавить к своим branch. Они сказали «ОК», так что ваш Git обновил ваш origin/branch:

          I
         / \
...--G--H   M   <-- branch (HEAD), origin/branch
         \ /
          J

, и это то, что вы теперь видите в своем репозитории.

Вы можете, если хотите, заставьте ваше имя branch указывать на фиксацию J снова напрямую, затем используйте git push --force-with-lease, чтобы попросить другой Git отменить обе фиксации M и I.

Чтобы заставить ваш текущий (HEAD), чтобы указать на одну конкретную c фиксацию, используйте git reset. В этом случае вы можете сначала убедиться, что у вас нет ничего, что может уничтожить git reset --hard, и использовать git reset --hard HEAD~1 для перехода к первому родительскому элементу M. См. Примечание к первому родительскому элементу ниже.

(Чтобы переместить ветку, в которой вы не , используйте git branch -f, для которого требуются два аргумента: имя ветки и фиксация для move-to. Так как операции git reset в ветке, в которой вы выполняете , git reset просто принимает спецификатор фиксации.)

Примечание: --first-parent

Есть одна хитрость, которая плохо отображается на моих рисунках с горизонтальным графиком. Каждый раз, когда вы делаете новое слияние коммит, например M, Git проверяет, что первый из нескольких родителей, выходящих из M, указывает на фиксацию, которая был верхушкой вашей ветки раньше. В данном случае это означает, что первым родителем M является J, а не I.

У вас могут быть git log и другие Git команды, только посмотрите на первый родитель каждого слияния при просмотре коммитов. Если вы сделаете это, картинка будет выглядеть так:

...--G--H--J--M   <-- branch (HEAD), origin/branch

Фактически, M по-прежнему указывает на I как на своего второго родителя.

Это --first-parent Параметр в основном полезен для просмотра ветки типа master, когда функции всегда разрабатываются в своих собственных ветвях:

                  o--o--o   <-- feature2
                 /       \
 ...--●---------●---------●--...   <-- master
       \       /
        o--o--o   <-- feature1

Просмотр master с --first-parent отбрасывает все эти входящие боковые подключения, так что можно увидеть только коммиты solid. Но само понятие имеет значение всякий раз, когда вы имеете дело с фиксацией слияния: M^1 означает первый родительский элемент M, а M^2 означает второй родительский элемент M. Обозначение суффикса тильды ведет отсчет назад только через ссылки первого родителя, так что M~1 означает шаг назад на одну ссылку первого родителя .

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