Git имеет репутацию запутывающего инструмента. Пользователи натыкаются на терминологию и формулировки, которые вводят в заблуждение. Это более всего проявляется в "перезаписывающих" историю командах, таких как git cherry-pick или git rebase. По моему опыту, первопричина путаницы — интерпретация коммитов как различий, которые можно перетасовать. Однако коммиты — это не различия, а снимки! Я считаю, что Git станет понятным, если поднять занавес и посмотреть, как он хранит данные репозитория. Изучив модель хранения данных мы посмотрим, как новый взгляд помогает понять команды, такие как git cherry-pick и git rebase.
Если хочется углубиться по-настоящему, читайте главу о внутренней работе Git (Git internals) книги Pro Git. Я буду работать с репозиторием git/git версии v2.29.2. Просто повторяйте команды за мной, чтобы немного попрактиковаться.
Хеши — идентификаторы объектов
Самое важное, что нужно знать о Git-объектах, — это то, что Git ссылается на каждый из них по идентификатору объекта (OID для краткости), даёт объекту уникальное имя.
Чтобы найти OID, воспользуемся командой git rev-parse. Каждый объект, по сути, — простой текстовый файл, его содержимое можно проверить командой git cat-file -p.
Мы привыкли к тому, что OID даны в виде укороченной шестнадцатеричной строки. Строка рассчитана так, чтобы только один объект в репозитории имел совпадающий с ней OID. Если запросить объект слишком коротким OID, мы увидим список соответствующих подстроке OID.
$ git cat-file -t e0c03
error: short SHA1 e0c03 is ambiguous
hint: The candidates are:
hint: e0c03f27484 commit 2016-10-26 - contrib/buildsystems: ignore irrelevant files in Generators/
hint: e0c03653e72 tree
hint: e0c03c3eecc blob
fatal: Not a valid object name e0c03
Блобы — это содержимое файлов
На нижнем уровне объектной модели блобы — содержимое файла. Чтобы обнаружить OID файла текущей ревизии, запустите git rev-parse HEAD:<path>, а затем, чтобы вывести содержимое файла — git cat-file -p <oid>.
$ git rev-parse HEAD:README.md
eb8115e6b04814f0c37146bbe3dbc35f3e8992e0
$ git cat-file -p eb8115e6b04814f0c37146bbe3dbc35f3e8992e0 | head -n 8
[![Build status](https://github.com/git/git/workflows/CI/PR/badge.png)](https://github.com/git/git/actions?query=branch%3Amaster+event%3Apush)
Git - fast, scalable, distributed revision control system
=========================================================
Git is a fast, scalable, distributed revision control system with an
unusually rich command set that provides both high-level operations
and full access to internals.
Если я отредактирую файл README.md на моём диске, то git status предупредит, что файл недавно изменился, и хэширует его содержимое. Когда содержимое файла не совпадает с текущим OID в HEAD:README.md, git status сообщает о файле как о "модифицированном на диске". Таким образом видно, совпадает ли содержимое файла в текущей рабочей директории с ожидаемым содержимым в HEAD.
Деревья — это списки каталогов
Обратите внимание, что блобы хранят содержание файла, но не его имя. Имена берутся из представления каталогов Git — деревьев. Дерево — это упорядоченный список путей в паре с типами объектов, режимами файлов и OID для объекта по этому пути. Подкаталоги также представлены в виде деревьев, поэтому деревья могут указывать на другие деревья!
Воспользуемся диаграммами, чтобы визуализировать связи объектов между собой. Красные квадраты — наши блобы, а треугольники — деревья.
$ git rev-parse HEAD^{tree}
75130889f941eceb57c6ceb95c6f28dfc83b609c
$ git cat-file -p 75130889f941eceb57c6ceb95c6f28dfc83b609c | head -n 15
100644 blob c2f5fe385af1bbc161f6c010bdcf0048ab6671ed .cirrus.yml
100644 blob c592dda681fecfaa6bf64fb3f539eafaf4123ed8 .clang-format
100644 blob f9d819623d832113014dd5d5366e8ee44ac9666a .editorconfig
100644 blob b08a1416d86012134f823fe51443f498f4911909 .gitattributes
040000 tree fbe854556a4ae3d5897e7b92a3eb8636bb08f031 .github
100644 blob 6232d339247fae5fdaeffed77ae0bbe4176ab2de .gitignore
100644 blob cbeebdab7a5e2c6afec338c3534930f569c90f63 .gitmodules
100644 blob bde7aba756ea74c3af562874ab5c81a829e43c83 .mailmap
100644 blob 05f3e3f8d79117c1d32bf5e433d0fd49de93125c .travis.yml
100644 blob 5ba86d68459e61f87dae1332c7f2402860b4280c .tsan-suppressions
100644 blob fc4645d5c08bd005238fc72cfa709495d8722e6a CODE_OF_CONDUCT.md
100644 blob 536e55524db72bd2acf175208aef4f3dfc148d42 COPYING
040000 tree a58410edddbdd133cca6b3322bebe4fb37be93fa Documentation
100755 blob ca6ccb49866c595c80718d167e40cfad1ee7f376 GIT-VERSION-GEN
100644 blob 9ba33e6a141a3906eb707dd11d1af4b0f8191a55 INSTALL
Деревья дают названия каждому подпункту и также содержат такую информацию, как разрешения на файлы в Unix, тип объекта (blob или tree) и OID каждой записи. Мы вырезаем выходные данные из 15 верхних записей, но можем использовать grep, чтобы обнаружить, что в этом дереве есть запись README.md, которая указывает на предыдущий OID блоба.
$ git cat-file -p 75130889f941eceb57c6ceb95c6f28dfc83b609c | grep README.md
100644 blob eb8115e6b04814f0c37146bbe3dbc35f3e8992e0 README.md
При помощи путей деревья могут указывать на блобы и другие деревья. Имейте в виду, что эти отношения идут в паре с именами путей, но мы не всегда показываем эти имена на диаграммах.
Само дерево не знает, где внутри репозитория оно находится, то есть указывать на дерево — роль объектов. Дерево, на которое ссылается <ref>^{tree}, особое — это корневое дерево. Такое обозначение основано на специальной ссылке из вашего коммита.
Коммиты — это снапшоты
Коммит — это снимок во времени. Каждый содержит указатель на своё корневое дерево, представляющее состояние рабочего каталога на момент снимка.
В коммите есть список родительских коммитов, соответствующих предыдущим снимкам. Коммит без родителей — это корневой коммит, а коммит с несколькими родителями — это коммит слияния.
Коммиты также содержат метаданные, которые описывают снимки, например автора и коммиттера (включая имя, адрес электронной почты и дату) и сообщение о коммите. Сообщение о коммите для автора коммита — это возможность описать цель коммита по отношению к родителям.
Например, коммит в v2.29.2 в Git-репозитории описывает этот релиз, также он авторизован, а его автор — член команды разработки Git.
$ git rev-parse HEAD
898f80736c75878acc02dc55672317fcc0e0a5a6
/c/_git/git ((v2.29.2))
$ git cat-file -p 898f80736c75878acc02dc55672317fcc0e0a5a6
tree 75130889f941eceb57c6ceb95c6f28dfc83b609c
parent a94bce62b99be35f2ee2b4c98f97c222e7dd9d82
author Junio C Hamano <gitster@pobox.com> 1604006649 -0700
committer Junio C Hamano <gitster@pobox.com> 1604006649 -0700
Git 2.29.2
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Заглянув немного дальше в историю при помощи git log, мы увидим более подробное сообщение о коммите, оно рассказывает об изменении между этим коммитом и его родителем.
$ git cat-file -p 16b0bb99eac5ebd02a5dcabdff2cfc390e9d92ef
tree d0e42501b1cf65395e91e22e74f75fc5caa0286e
parent 56706dba33f5d4457395c651cf1cd033c6c03c7a
author Jeff King <peff@peff.net> 1603436979 -0400
committer Junio C Hamano <gitster@pobox.com> 1603466719 -0700
am: fix broken email with --committer-date-is-author-date
Commit e8cbe2118a (am: stop exporting GIT_COMMITTER_DATE, 2020-08-17)
rewrote the code for setting the committer date to use fmt_ident(),
rather than setting an environment variable and letting commit_tree()
handle it. But it introduced two bugs:
- we use the author email string instead of the committer email
- when parsing the committer ident, we used the wrong variable to
compute the length of the email, resulting in it always being a
zero-length string
This commit fixes both, which causes our test of this option via the
rebase "apply" backend to now succeed.
Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Круги на диаграммах будут представлять коммиты:
Квадраты — это блобы. Они представляют содержимое файла.
Треугольники — это деревья. Они представляют каталоги.
Круги — это коммиты. Снапшоты во времени.
Ветви — это указатели
В Git мы перемещаемся по истории и вносим изменения, в основном не обращаясь к OID. Это связано с тем, что ветви дают указатели на интересующие нас коммиты. Ветка с именем main — на самом деле ссылка в Git, она называется refs/heads/main. Файлы ссылок буквально содержат шестнадцатеричные строки, которые ссылаются на OID коммита. В процессе работы эти ссылки изменяются, указывая на другие коммиты.
Это означает, что ветки существенно отличаются от Git-объектов. Коммиты, деревья и блобы неизменяемы (иммутабельны), это означает, что вы не можете изменить их содержимое. Изменив его, вы получите другой хэш и, таким образом, новый OID со ссылкой на новый объект!
Ветки именуются по смыслу, например, trunk [ствол] или my-special-object. Ветки используются, чтобы отслеживать работу и делиться её результатами. Специальная ссылка HEAD указывает на текущую ветку. Когда коммит добавляется в HEAD, HEAD автоматически обновляется до нового коммита ветки. Создать новую ветку и обновить HEAD можно при помощи флага git -c:
$ git switch -c my-branch
Switched to a new branch 'my-branch'
$ cat .git/refs/heads/my-branch
1ec19b7757a1acb11332f06e8e812b505490afc6
$ cat .git/HEAD
ref: refs/heads/my-branch
Обратите внимание: когда создавалась my-branch, также был создан файл (.git/refs/heads/my-branch) с текущим OID коммита, а файл .git/HEAD был обновлён так, чтобы указывать на эту ветку. Теперь, если мы обновим HEAD, создав новые коммиты, ветка my-branch обновится так, что станет указывать на этот новый коммит!
Общая картина
Посмотрим на всю картину. Ветви указывают на коммиты, коммиты — на другие коммиты и их корневые деревья, деревья указывают на блобы и другие деревья, а блобы не указывают ни на что. Вот диаграмма со всеми объектами сразу:
Время на диаграмме отсчитывается слева направо. Стрелки между коммитом и его родителями идут справа налево. У каждого коммита одно корневое дерево. HEAD указывает здесь на ветку main, а main указывает на самый недавний коммит.
Корневое дерево у этого коммита раскинулось полностью под ним, у остальных деревьев есть указывающие на эти объекты стрелки, потому что одни и те же объекты доступны из нескольких корневых деревьев! Эти деревья ссылаются на объекты по их OID (их содержимое), поэтому снимкам не нужно несколько копий одних и тех же данных. Таким образом, объектная модель Git образует дерево хешей.
Рассматривая объектную модель таким образом, мы видим, почему коммиты — это снимки: они непосредственно ссылаются на полное представление рабочего каталога коммита!
Вычисление различий
Несмотря на то, что коммиты — это снимки, мы часто смотрим на коммит в его историческом представлении или видим его на GitHub как diff. На самом же деле сообщение о коммите часто ссылается на различие. генерируемое динамически из данных снимка путём сравнения корневых деревьев коммита и его родителя. Git может сравнить не только соседние снимки, но и два любых снимка вообще.
Чтобы сравнить два коммита, сначала рассмотрите их корневые деревья, которые почти всегда отличаются друг от друга. Затем в поддеревьях выполните поиск в глубину, следуя по парам, когда пути для текущего дерева имеют разные OID.
В примере ниже корневые деревья имеют разные значения для docs, поэтому мы рекурсивно обходим их. Эти деревья имеют разные значения для M.md, таким образом, два блоба сравниваются построчно и отображается их различие. Внутри docs N.md по-прежнему тот же самый, так что пропускаем их и возвращаемся к корневому дереву. После этого корневое дерево видит, что каталоги things имеют одинаковые OID, так же как и записи README.md.
На диаграмме выше мы заметили, что дерево things не посещается никогда, а значит, не посещается ни один из его достижимых объектов. Таким образом, стоимость вычисления различий зависит от количества путей с разным содержимым.
Теперь, когда понятно, что коммиты — это снимки, можно динамически вычислять разницу между любыми двумя коммитами. Почему тогда этот факт не общеизвестен? Почему новые пользователи натыкаются на идею о том, что коммит — это различие?
Одна из моих любимых аналогий — дуализм коммитов как дуализм частиц, при котором иногда коммиты рассматриваются как снимки, а иногда — как различия. Суть дела в другом виде данных, которые не являются Git-объектами — в патчах.
Подождите, а что такое патч?
Патч — это текстовый документ, где описывается, как изменить существующую кодовую базу. Патчи — это способ самых разрозненных команд делиться кодом без коммитов в Git. Видно, как патчи перетасовываются в списке рассылки Git.
Патч содержит описание изменения и причину ценности этого изменения, сопровождаемые выводом diff. Идея такова: некий разработчик может рассматривать рассуждение как оправдание применения патча, отличающегося от копии кода нашего разработчика.
Git может преобразовать коммит в патч командой git format-patch. Затем патч может быть применён к Git-репозиторию командой git apply. В первые дни существования открытого исходного кода такой способ обмена доминировал, но большинство проектов перешли на обмен коммитами непосредственно через пул-реквесты.
Самая большая проблема с тем, чтобы делиться исправлениями, в том, что патч теряет родительскую информацию, а новый коммит имеет родителя, который одинаков с вашим HEAD. Более того, вы получаете другой коммит, даже если работаете с тем же родителем, что и раньше, из-за времени коммита, но при этом коммиттер меняется! Вот основная причина, по которой в объекте коммита Git есть разделение на "автора", и "коммиттера".
Самая большая проблема в работе с патчами заключается в том, что патч трудно применить, когда ваш рабочий каталог не совпадает с предыдущим коммитом отправителя. Потеря истории коммитов затрудняет разрешение конфликтов.
Идея перемещения патчей с места на место перешла в несколько команд Git как "перемещение коммитов". На самом же деле различие коммитов воспроизводится, создавая новые коммиты.
Если коммиты — это не различия, что делает git cherry-pick?
Команда git cherry-pick создаёт новый коммит с идентичным отличием от <oid>, родитель которого — текущий коммит. Git в сущности выполняет такие шаги:
Вычисляет разницу между <oid> коммита и его родителя.
Применяет различие к текущему HEAD.
Создаёт новый коммит, корневое дерево которого соответствует новому рабочему каталогу, а родитель созданного коммита — HEAD.
Перемещает ссылку HEAD в этот новый коммит.
После создания нового коммита вывод git log -1 -p HEAD должен совпадать с выводом git log -1 -p <oid>.
Важно понимать, что мы не "перемещали" коммит так, чтобы он был поверх нашего текущего HEAD, мы создали новый коммит, и его вывод diff совпадает со старым коммитом.
А что делает git rebase?
Команда git rebase — это способ переместить коммиты так, чтобы получить новую историю. В простой форме это на самом деле серия команд git cherry-pick, которая воспроизводит различия поверх другого, отличного коммита.
Самое главное: git rebase <target> обнаружит список коммитов, доступных из HEAD, но недоступных из <target>. С помощью команды git log --online <target>...HEAD вы можете отобразить их самостоятельно.
Затем команда rebase просто переходит в местоположению <target> и выполняет команды git cherry-pick в этом диапазоне коммитов, начиная со старых. В конце мы получили новый набор коммитов с разными OID, но схожих с первоначальным диапазоном.
Для примера рассмотрим последовательность из трёх коммитов в текущей ветке HEAD с момента разветвления target. При запуске git rebase target, чтобы определить список коммитов A, B, и C, вычисляется общая база P. Затем поверх target они выбираются cherry-pick, чтобы создать новые коммиты A', B' и C'.
Коммиты A', B' и C' — это совершенно новые коммиты с общим доступом к большому количеству информации через A, B и C, но они представляют собой отдельные новые объекты. На самом деле старые коммиты существуют в вашем репозитории до тех пор, пока не начнётся сбор мусора.
С помощью команды git range-diff мы даже можем посмотреть на различие двух диапазонов коммитов! Я использую несколько примеров коммитов в репозитории Git, чтобы сделать rebase на тег v2.29.2, а затем слегка изменю описание коммита.
$ git checkout -f 8e86cf65816
$ git rebase v2.29.2
$ echo extra line >>README.md
$ git commit -a --amend -m "replaced commit message"
$ git range-diff v2.29.2 8e86cf65816 HEAD
1: 17e7dbbcbc = 1: 2aa8919906 sideband: avoid reporting incomplete sideband messages
2: 8e86cf6581 ! 2: e08fff1d8b sideband: report unhandled incomplete sideband messages as bugs
@@ Metadata
Author: Johannes Schindelin <Johannes.Schindelin@gmx.de>
## Commit message ##
- sideband: report unhandled incomplete sideband messages as bugs
+ replaced commit message
- It was pretty tricky to verify that incomplete sideband messages are
- handled correctly by the `recv_sideband()`/`demultiplex_sideband()`
- code: they have to be flushed out at the end of the loop in
- `recv_sideband()`, but the actual flushing is done by the
- `demultiplex_sideband()` function (which therefore has to know somehow
- that the loop will be done after it returns).
-
- To catch future bugs where incomplete sideband messages might not be
- shown by mistake, let's catch that condition and report a bug.
-
- Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
- Signed-off-by: Junio C Hamano <gitster@pobox.com>
+ ## README.md ##
+@@ README.md: and the name as (depending on your mood):
+ [Documentation/giteveryday.txt]: Documentation/giteveryday.txt
+ [Documentation/gitcvs-migration.txt]: Documentation/gitcvs-migration.txt
+ [Documentation/SubmittingPatches]: Documentation/SubmittingPatches
++extra line
## pkt-line.c ##
@@ pkt-line.c: int recv_sideband(const char *me, int in_stream, int out)
Обратите внимание: результирующий range-diff утверждает, что коммиты 17e7dbbcbc и 2aa8919906 "равны", а это означает, что они будут генерировать один и тот же патч. Вторая пара коммитов различается: показано, что сообщение коммита изменилось, есть правка в README.md, которой не было в исходном коммите.
Если пройти вдоль дерева, вы увидите, что история коммитов всё ещё существует у обоих наборов коммитов. Новые коммиты имеют тег v2.29.2 — в истории это третий коммит, тогда как старые имеют тег v2.28.0 — болеее ранний, а в истории он также третий.
$ git log --oneline -3 HEAD
e08fff1d8b2 (HEAD) replaced commit message
2aa89199065 sideband: avoid reporting incomplete sideband messages
898f80736c7 (tag: v2.29.2) Git 2.29.2
$ git log --oneline -3 8e86cf65816
8e86cf65816 sideband: report unhandled incomplete sideband messages as bugs
17e7dbbcbce sideband: avoid reporting incomplete sideband messages
47ae905ffb9 (tag: v2.28.0) Git 2.28
Если коммиты – не отличия, тогда как Git отслеживает переименования?
Внимательно посмотрев на объектную модель, вы заметите, что Git никогда не отслеживает изменения между коммитами в сохранённых объектных данных. Можно задаться вопросом: "Откуда Git знает, что произошло переименование?"
Git не отслеживает переименования. В нём нет структуры данных, которая хранила бы запись о том, что между коммитом и его родителем имело место переименование.
Вместо этого Git пытается обнаружить переименования во время динамического вычисления различий. Есть два этапа обнаружения переименований: именно переименования и редактирования.
После первого вычисления различий Git исследует внутренние различия, чтобы обнаружить, какие пути добавлены или удалены. Естественно, что перемещение файла из одного места в другое будет выглядеть как удаление из одного места и добавление в другое. Git попытается сопоставить эти действия, чтобы создать набор предполагаемых переименований.
На первом этапе этого алгоритма сопоставления рассматриваются OID добавленных и удалённых путей и проверяется их точное соответствие. Такие точные совпадения соединяются в пары.
Вторая стадия — дорогая часть вычислений: как обнаружить файлы, которые были переименованы и отредактированы? Посмотреть каждый добавленный файл и сравните этот файл с каждым удалённым, чтобы вычислить показатель схожести в процентах к общему количеству строк. По умолчанию что-либо, что превышает 50 % общих строк, засчитывается как потенциальное редактирование с переименованием. Алгоритм сравнивает эти пары до момента, пока не найдёт максимальное совпадение.
Вы заметили проблему? Этот алгоритм прогоняет A * D различий, где A — количество добавлений и D — количество удалений, то есть у него квадратичная сложность! Чтобы избежать слишком долгих вычислений по переименованию, Git пропустит часть с обнаружением редактирований с переименованием, если A + D больше внутреннего лимита. Ограничение можно изменить настройкой опции diff.renameLimit в конфигурации. Вы также можете полностью отказаться от алгоритма, просто отключив diff.renames.
Я воспользовался знаниями о процессе обнаружения переименований в своих собственных проектах. Например, форкнул VFS for Git, создал проект Scalar и хотел повторно использовать большое количество кода, но при этом существенно изменить структуру файла. Хотелось иметь возможность следить за историей версий в VFS for Git, поэтому рефакторинг состоял из двух этапов:
Эти два шага позволили мне быстро выполнить git log --follow -- <path>, чтобы посмотреть историю переименовывания.
$ git log --oneline --follow -- Scalar/CommandLine/ScalarVerb.cs
4183579d console: remove progress spinners from all commands
5910f26c ScalarVerb: extract Git version check
...
9f402b5a Re-insert some important instances of GVFS
90e8c1bd [REPLACE] Replace old name in all files
fb3a2a36 [RENAME] Rename all files
cedeeaa3 Remove dead GVFSLock and GitStatusCache code
a67ca851 Remove more dead hooks code
...
Я сократил вывод: два этих последних коммита на самом деле не имеют пути, соответствующего Scalar/CommandLine/ScalarVerb.cs, вместо этого отслеживая предыдущий путь GVSF/GVFS/CommandLine/GVFSVerb.cs, потому что Git распознал точное переименование содержимого из коммита fb3a2a36 [RENAME] Rename all files.
Не обманывайтесь больше
Теперь вы знаете, что коммиты — это снапшоты, а не различия! Понимание этого поможет вам ориентироваться в работе с Git.
И теперь мы вооружены глубокими знаниями объектной модели Git. Не важно, какая у вас специализация, frontend, backend, или вовсе fullstack — вы можете использовать эти знания, чтобы развить свои навыки работы с командами Git'а или принять решение о рабочих процессах в вашей команде. А к нам можете приходить за более фундаментальными знаниями, чтобы иметь возможность повысить свою ценность как специалиста или вовсе сменить сферу.
Узнайте, как прокачаться в других специальностях или освоить их с нуля:
Другие профессии и курсы
ПРОФЕССИИ
КУРСЫ
joffer
Если честно — понятнее не стало
тогда коммиты — это снимки, которые можно перетасовать. Ещё непонятнее. Снимки как набор каких-то состояний/параметров, у разных снимков они разные и будут = то есть всё в итоге свелось к diff.
Git кажется большинству пользователей запутанным не потому, что люди не понимают, из чего сделан его основной кирпич, а потому, что операции с этими «кирпичами» неочевидны, неинтуитивны и имеют побочные эффекты.
Самый простой пример — стандартный флоу в гите:
То есть, мы берём файлы, которые хотим залить, помещаем их в такой себе буфер с сообщением «вот эти файлы с изменениями для задачи такой-то» и затем это всё отправляем на удалённую ветку — чтобы там эти файлы тоже сталы доступны.
И вот после этого мы поняли, что закоммитили и запушили ошибочно — что нужно сделать, чтобы вернуть всё, как было? Мы не можем написать что-то вроде 'git origin revert && git last_commit revert' и быть счастливы, нам нужно разбираться, что мы сделали и как это вернуть, и каким способом.
Например, можно вернуть все изменения в файлах, закоммитить верные и запушить «верный вариант». А что, если изменений было много, например, в 30 местах? И мы закоммитили, например, 20? Нужно перебрать все 20 мест, но это ещё нужно понимать, какие, вернуть там всё, собрать всё до кучи перед коммитом, проверить и запушить — всё это время в удалённой ветке лежит «плохой» код.
То есть, быстро отменить мы не можем, а чтобы исправить ситуацию, нам нужно или подготовить новый верный коммит+пуш, либо грохнуть последний пуш на удалённую ветку — при этом пропадают наши 20 изменений. А в попытке восстановить какое-то эталонное состояние ветки мы натыкаемся на то, что для переключения ветки нам нужно оставшиеся 10 изменений куда-то положить, в какой-то стеш.
После этого ещё стоит вспомнить, что у каждого коммита есть хеши, а у ветки есть голова, которая HEAD, поведение которой супернеинтуитивно — вот есть у нас ветка, в которой 5 коммитов. И мы можем передвинуть «голову» с 5 на 4ый, или на 3ий, или на 2й, мы можем. А что это даёт? Можем ли мы туда коммитить/пушить? И как тогда передвинуть голову обратно? Что произойдёт с коммитами?
При этом некоторые операции имеют в побочных эффектах какие-то вот эти операции с «головой», при этом у нас есть хеши коммитов и последний коммит — какие выгоды дают операции с «головой»? Понадобится какое-то время почитать, чтобы понять, что это за «голова» и почему есть reset с её участием и без.
Я попытался смоделировать самые обычные ситуации, которые возникали у меня и моих собратов по цеху, особенно у новичков, у джунов. У нас не было проблемы в понимании, что такое ветка, коммит, снимок, блоб — у нас проблема в том, что на любую нестандартную ситуацию нужно идти на СтэкОверФлоу и читать, как посмотреть логи, как посмотреть, что ты сделал, как посмотреть, что у тебя было, как посмотреть, что у тебя стало и как из этого сделать то, что тебе нужно, не потеряв того, что тебе нужно, и всё это желательно в флоу разработки, когда ветка твоей фичи ответвлялась от ветки «develop» 2 недели назад, а заливать на «тестовый сервер» нужно уже вот сейчас/через час/сегодня — и у тебя там ещё 5 минут назад всё было хорошо и понятно.
п.с: а как гит отслеживает переименования — честно, это знание поможет мизерно. Понимание работы карбюратора мало поможет в навыках управлением автомобилем, по крайней мере видятся какие-то такие метафоры
Mingun
Ну, если rebase разрешен, то починить описанную ситуацию с неверным коммитом просто. Сташим свою работу, ресетим ветку на коммит назад и
push --force
.Затем возвращаем ветку локально к своему первоначальному коммиту, вытаскиваем работу из стеша и работаем дальше, как ни в чем не бывало.
В нормальном GUI клиенте это все за несколько секунд делается. В консольке тоже все довольно просто, даже для новичка (наверное самое сложное — откатится на коммит назад, а потом вернуться вперед, т.к. это нужно представлять дерево в голове).
А вообще, конечно, незачем пушить работу, если не уверен. Работай локально, полируй, ребейзь,
жди ответного звонкаи проталкивай, когда все готово.nckma
«когда все готово» — ну такое, очень расплывчатое понятие. Бывает ли такое в жизни вообще?
Mingun
Тут на усмотрение разработчика. У меня "все готово" — когда каждый коммит делает ровно одну вещь, описан и очевиден. А в процессе работы это часто не так — бывает и куча разрозненных вещей в одном коммите, кучи коммитов-фиксов, пока все не устаканится. Но это остается только у меня в локальной истории. Фактически, 1/4 времени — написание кода и 3/4 — пара десятков перебазирований, чтобы сделать нормальную историю.
Barbaresk
А вообще, в гите есть куча других проблем. Например, невозможность нормально, без костылей, закомитить пустую папку. А также отсутствие независимого версионирования различных папок. Вместо последнего предполагаются компоненты. Из-за чего возникают проблемы, когда в репозитории есть проект А и проект В, оба зависят от библиотеки С, но от разных её версий…
enree
А зачем пустую папку комитить?
Ну а для версионирования сабмодули можно использовать.
Barbaresk
Затем. Иногда бывает ситуации, когда папка тоже что-то значит в проекте. Например туда складываются какие-то файлы, а папка уже обязана существовать.
maximgorbatyuk
Радикально. Если папка необходима в коде приложения, то можно создавать динамически:
Если во время инициализации или иных операций, не связанных с кодом, то через bash-команду.
Зачем гит-то заполнять пустыми папками?
vvzvlad
Русский форум какой-то. Говоришь «я хочу так», тебе отвечают «тебе это не нужно». Нужно. Я хочу так, так мне удобнее работать.
Банальный пример — создаю папку для подключаемых модулей и пишу коллегам «с этого времени подключаемые модули кладутся в эту папку». Можно ли сделать по-другому? Можно. Можно положить туда заготовку модуля. Можно ридми. Можно написать «создайте папку, если хотите добавить модуль». Но я хочу так, а мне это запрещают сделать.
Еще один пример — заранее созданная структура, в которой договора, например, клиентов-юрлиц раскладываются по папкам. Вот я создал десять папок, и в пять из них положил договора. А остальные пять я создать не могу? Но у меня есть сущность — клиенты, в логической структуре репозитария ей соответствует сущность «папка». Которую я не могу создать.
enree
С одной стороны вы, конечно, правы, инструмент по хорошему должен позволять делать так, как удобнее работать. Но с другой стороны, должен ли позволять багтрекер, например, создавать тикет без заголовка и описания? Кому-то это может быть удобно, но в общем случае это вредно.
С договорами для клиентов все, имхо, просто: появился клиент, сложили шаблон с реквизитами юрлица. И папка не пустая, и смысл в ней появился.
vvzvlad
Вы опять пытаетесь решить проблему, которая служит для иллюстрации, что требования принципиально возникнуть могут. Я могу выдвинуть вам дополнительные уточнения (это же легко, ситуация изначально придумана), которые объяснят, почему туда нельзя складывать шаблоны, а вы можете в ответ придумать новое решение проблемы. Собственно, проблема в этом случае решается файлом .gitkeep, в других — действительно созданием папки при работы программы или иначе.
Но я не о том, как именно решить конкретную проблему, я о том, что у гита есть архитектурный проеб, который не позволяет закомиттить пустую папку, и это решается только костылями. Это не возможность создавать тикет без заголовка и описания, потому что пустая папка — это совершенно нормальная штука в любых операционных и встраиваемых системах, к которой пользователи привыкли. И когда гит без веских на то причин отказывается добавить в репозиторий пустую папку — это ломает привычные пользовательские сценарии и запутывает пользователя, так как весь его опыт работы с компьютерами говорит о том, что пустая папка — это совершенно нормальная вещь.
И сама проблема не так важна, на самом деле. Гораздо хуже то, что гит делает это совершенно тихо — созданная папка просто не попадет в репозитарий, без генерирования ошибки и тем более вопроса «вы создали папку, но она не будет сохранена в коммите, хотите создать в ней файл .gitkeep?».
Разработчик, неосведомленный об этой особенности, легко может заложить мину замедленного действия в проект, полагаясь на то, что папка всегда есть, и не делая дополнительных проверок на ее наличие. И в какой-то момент, когда из папки исчезнут файлы, она просто не создастся на клиенте при выполнении git clone, что сломает программу.
Это произойдет не потому, что разработчик плохой, а потому что люди, разрабатывающие гит, сначала допустили архитектурную проблему при проектировании, а потом не смогли обмазать ее достаточным слоем UI, чтобы она хотя бы не била так больно пользователей по пальцам, потому что отказываются признать совершенную ошибку, ссылаясь на «ну вы просто не делайте так, и проблем не будет».
Именно от этого у меня бомбит, когда мне в ответ на мое недоумение отвечают «вам не нужны пустые папки в гите». Когда вы знаете о том, что с ними проблема — вы можете обойтись без них. Но чаще всего это знание дается через вопль «ну какого хрена оно не работает», и хорошо, если это случается на локальной машине и не затрагивает сотни пользователей вашего продукта.
enree
Простите, но ваш пример очень странный, я не знаю ни одного разработчика, который не делает перед комитом git status или аналог, чтоб посмотреть, что пойдет в комит. Поэтому как можно подумать, что папка ушла в репу, когда она при этом не ушла? Я просто исхожу из опыта (это конечно, не очень сильный аргумент), но за много лет ни разу не встречал ситуации, когда нужна была пустая папка (проекты c++/Java/go). У вас был не гипотетический, а реальный случай, когда она понадобилась?
vvzvlad
Какие только аргументы не придумывают люди, чтобы не признавать косяк в гите.
Во-первых, проблема может возникнуть не при коммите добавления. Я создал папку, добавил туда пять файлов, спустя полгода эти файлы в ходе переработки проекта начали удаляться. Удаляется последний файл — папка пропадает и при git clone не создается. Каждый коммит абсолютно корректен, но в результате поведение нерасчетное и неожидаемое.
Во-вторых, у меня в гите хранится не только код, но и документация, и маркдаун для сайтов, например. И я не всегда проверяю досконально, что именно уходит в коммит, потому что я и так знаю, как я изменил репозиторий по сравнению с предыдущим состоянием. Исправил пару файлов, создал папку, нажал коммит в интерфейсе, все. Файлы ушли, папка нет.
Из реального случая, когда я на это напоролся — генератор документации сломался, потому что у него была инструкция «включить все файлы из этой папки», и он нормально относится к отсутствию файлов, а вот к отсутствию папки нет. Локально все ок, потому что папка-то была в фс у меня, а вот при клонировании она исчезла.
В-третьих, вы упорно игнорируете саму проблему, низводя ее до конкретных кейсов, которые вы с блеском разбиваете.
— Папку нельзя создать! —Нет, можно, с пустым скрытым файлом
— Можно создать папку и не заметить, что она не закоммитилась — Нет, я и все мои знакомые всегда смотрят на коммит!
— А если кто-то не посмотрит? — А в моей практике никому не нужна была пустая папка.
— Ну а если она все-таки нужна приложению? — Тогда создавайте ее в коде, проблема-то?
Да, каждый конкретный кейс решается. Но если бы не было глобальной проблемы, решать эти кейсы не потребовалось бы, они бы вообще не возникли. И вместо того, чтобы сказать «да, если косяк», вы зачем-то начинаете спорить, что пустые папки вовсе не нужны.
enree
Но это же как раз означает, что в реальности вы не знаете, как изменили репозиторий с предыдущим состоянием. Я тоже пару раз напарывался на то, что открыл проект в другой IDE, получил пачку файлов, которых я не создавал. После второго раза приучился смотреть, что реально пойдёт в репу.
Но я понял вашу точку зрения, вам, наверное, действительно нужны пустые папки. Спасибо за пример.
vvzvlad
За исключением папок, знаю. Ну и опять же, дело не в том, что мне нужны папки, или кому-то нужны: я привел пример, в котором можно легко напороться на пропадание папки, даже если все делать правильно. Это не проблема папок, это проблема софта.
KvanTTT
По ворнингу — можно попробовать поискать таску в трекере, что разработчики по этому поводу пишут, почему не добавили.
Barbaresk
А если она нужна на этапе компиляции /публикации в каком-то инструменте, в котором я не могу вставить bash команду? А если эта папка часть структуры проекта? Менять инструменты, запилить баг репорт в инструмент, да? Пустая папка имеет столько же прав и причин на жизнь, как и пустой файл. В теме на SO на вопрос "как добавить пустую папку" тоже был ответ, что "вам не нужна пустая папка в гите". Только вот такой ответ набрал +20, а ответная реплика про бессмысленность такого ответа и про то, что папка нужна, как бы там кто не думал, набрала +300. Из этого можно сделать вывод, что таки нужна пустая папка в гите.
tommyangelo27
ВЫ не прошли ревью XD
Полагаю, должно быть !Directory.Exists() и ещё одной скобки не хватает.
UPD. Смысл этого комментария в том, чтобы показать, что даже такой элементарный код нужно:
— написать
— протестировать
— поддерживать.
А зачем это всё, если можно элементарно закоммитить пустую папку?
maximgorbatyuk
Если писать приложение, используя "элементарные" хаки, то скоро можно обнаружить, что система ведет себя неожиданно и непрозрачно для его разработчиков
KvanTTT
Вызов
Directory.Exists
вообще не нужен, т.к. при вызовеDirectory.Create
папка просто не создастся если она существует.enree
Вы какие-то сгенерированные файлы в source-tree складываете? А что с ними потом делать? Все равно придётся как-то их вычистить, чтоб папка осталась пустой. Опять какой-то скрипт
Barbaresk
Зачем папке оставаться пустой? В папке лежат файлы, но эти файлы не отслеживаются. А сама папка при этом является частью проекта. Не в любое средство разработки можно вставить дополнительный скрипт для генерации папок и проверки её наличия.
enree
Откуда эти файлы берутся? Ну вот я новый разработчик, сделал git clone, получил пустую папку. Есть какие-то инструменты, которые а) что-то генерируют в папку и б) не умеют при этом папку создавать? Я не спорю, мне правда интересно, что это за инструментарий такой.
beDenz
пустую папку можно закомитить с помощью .gitkeep, ну а в остальном полностью согласен
Barbaresk
Да это понятно, такой костыль собственно и использую. Но тут надо понимать, что папка в таком случае все-таки не пустая, а с файлом .gitkeep. И в некоторых случаях это может помешать, когда нужна именно пустая папка.
tommyangelo27
Mingun
Это не поддержание истории изменений, а приведение мыслей в порядок, чтобы они были не только мне понятны, но и человеку, который этот код читает и (если это мейнтейнер, вставший сегодня не с той ноги) он не мог отмахнуться от моего PR вида, тут у вас скобки не так стоят, или отступы неверные, а этот комментарий мне не нравится, перепишите как-нибудь.
Рабочий код уже, как правило, есть после этой 1/4, но там бывает недостает документации, или в процессе я обнаруживаю, что можно немного порефакторить, а в итоге этот рефакторинг становится вообще слабо связан с основной целью изменений в этой ветке и его приходится отсаживать в новую. И такой процесс может продолжаться бесконечно — у меня такая рекурсия довольно часто может глубоко заходить.
Иногда выгоднее сделанный рефакторинг предложить в PR раньше, чем собственно начальные изменения. А после его слияния перебазироваться поверх него.
По-моему, подмодули как раз это и есть (хотя каждый подмодуль будет отдельным проектом)
joffer
а как это узнать-то?) и как понять, что тебе это нужно? вот вы ведёте разработку и у вас в команде за полгода 0 ребейзов было, всё ведётся только на мерджах (реальный кейс) — и когда возникает такое, у вас не получится быстро понять, что «ну, если ребейзы разрешены, то вот это, а если нет, то вот это». И интуитивно это не понять. И знание про снимки и блобы тоже не помогут. И как вообще заподозрить, что путь восстановления ветки зависит от того, разрешён ли rebase?
И почему для push в данном случае нужен будет флаг --force? Почему раньше пушили без него? А если сейчас сделать просто push, без --force — что-то не сработает?
Потому гит и считается неинтуитивным — в нём нельзя просто что-то делать и радоваться результатам. Гит как программирование на «С» — всё здорово, пока не доходит до выделения памяти и планирования своих переменных — и тут люди сливаются и переходят на JavaScript)
Либо, если хотите, можно вспомнить vim, который тоже славится своей самобытностью. Им просто невозможно пользоваться, не изучив хоткеи и не зная, как они работают и что произойдёт.
новый слоган Git) «Не уверен — не обганяй/не пушируй/не программируй, жди 100%ой ситуации»)
zhaparoff
И это мы еще до тэгов не дошли и до сквешей в фиче-бранчах...
На самом деле не могу не согласиться что разбираться приходится долго и упорно. Вполне возможно из-за того что в большенстве источников разжевывается "как это происходит под капотом", а не "как мне починить бранч самым простым способом". И явно не всем заходят игры в деревья в голове когда нужно срочно выкатывать правки на стейдж, а у тебя мердж конфликт. Так что нужно приложить некоторые усилия чтобы постичь дзен.
Но все же оно того стоит, и, вероятно, просто стоит загодя продумать стратегии бранчевания, мерджей и вообще всей этой кухни. Не брать переусложненный гитфлоу, если он вам не нужен. Поиграться на тестовых бранчах и запилить пару инструкций для команды, для не совсем типичных ситуаций, чтобы не искать решения в горячке.
А насчет картинки — гит сейчас везде, а алгоритмы и 50% не нужны для крудов всяких и формочек.
Mingun
Ну
--force
— это удаление некорректного коммита. Просто так же вы его не удалите. Конечно, нужно знать, что он делает, но так в чуть других системах вы же тоже знаете какую-то особую команду для удаления неверного коммита. И точно также каждый день ей не пользуетесь.Форсированный пуш провалится, если он запрещен (не говоря о том, что в политике коммитов это должно бы быть прописано).
Как-то не верится, локально по-любому есть. Вон, я выше написал типичный сценарий моей разработки — после собственно реализации потом больше времени трачу на приведение истории в порядок, в первую очередь, чтобы другим было понятно, почему сделано так, а не иначе.
Тут вы хотели сказать, "никто никогда не пушил форсированно" (т.е. не исправлял историю). Ну так вот, даже если вы этим никогда не пользовались, найти описание флага
--force
и почитать, что он делает — не такая большая проблема. И само название довольно интуитивное, и в нормальных инструментах эта галочка на видном месте.Не знаю, может там большинство вопросов возникают с консольным клиентом, но никто же не заставляет им пользоваться, если у вас vim отвращение вызывает (у кого не вызывает, наверное даже рады). Я вот пользуюсь GUI клиентами.
Кроме того, git как бы сейчас в любом проекте (ну, в 90%), а прочие технологии распределены гораздо неравномернее. Естественно, то, что больше используется, больше вопросов и вызывает.
joffer
пока у вас всё хорошо — вы можете месяцами вести разработку, в принципе используя 5 — 6 команд. И когда возникает впервые некорректный коммит, это выглядит как то, что пуш почему-то не работает, хотя работал. И с этого места нужно начинать разбираться. И это не выглядит как то, что нужно почитать, что делают флаги, даже будь они вполне стандартны — это нужно читать кейсы на стекОверФлоу просто чтобы узнать, что тебе эти флаги нужны. Можно полгода вести свою ветки разработки, в принципе не зная, что команды гита имеют какие-либо флаги вообще, но когда тебе это понадобится — это не будет вопросом 5 минут
master, dev, ветки-фичи, сливания только через мердж, проекту 2ой год, бекенд на пхп7 для систем типа «Умный дом». Ребейз, кстати, и в текущем проекте не использую, не нравится количество усилий для ведения ветки
обычно у тебя просто работает pull и push, и когда они перестают работать — приходят интерны и говорят «у меня 3 стеша, у меня 4 ветки, я не могу переключиться, не могу запушить, не могу запулить, в конфликтах 30 классов, в стеше наработок 2 недели, так вышло, всегда всё было хорошо, а сегодня вот. Да, ошибся, не подумал, но сейчас чего делать-то?» (реальный диалог). Представьте любой проект, в котором есть джуны/интерны и хотя бы средняя текучка кадров в компании — и вы получите такие диалоги минимум несколько раз в месяц
у меня вим не вызывает отвращение) привёл его как пример системы, в которую нельзя зайти интуитивно, нельзя просто посидеть полчаса, тыкая все хоткеи подряд и понять, как в нём работать, нужно читать хоткеи и кейсы. Вот, например, самый простой кейс — скопировать кусок текста из вима в вим. Не зная, как это делается, это не найти интуитивно.
И возьмите тот же Gimp — в нём через 5 минут можно уже рисовать и делать коллажи и мэмасики — потому что там есть инструменты, палитра, «выделить область — вставить область»
mayorovp
То есть интерны "варятся" в своём соку по 2 недели, без какого бы то ни было видимого прогресса, советов и код-ревью, а потом гит виноват?
joffer
ну вот человек получил задачу, работал над ней 2 недели, на стенд-апах всё норм, раз в неделю смотрим его ветку — ну вроде что-то делается, делал себе такой джун коммиты, пушил в свою ветку. А потом, например, узнаёт, что в ветке «develop» появились какие-то нужные изменения, не мудрствуя лукаво и ещё не зная про чери-пик, человек берёт и мерджит «девелоп» в свою ветку — чтобы получить нужный функционал. Это вполне логично и интуитивно. Пока.
Бывает, что всё норм на автомердже, а бывает, что есть кофликты, кофликты, допустим, человеку показывает ИДЕ, он их исправляет. Но если попытаться это всё запушить в свою ветку — вряд ли это закончится хорошо хотя бы потому, что его ветка не получала мердж с ветки develop. Здесь не виноват «гит» — просто со стороны это выглядит как то, что гит 2 недели работал и буквально после 2х — 3х команд мы получаем патовую ситуацию, в которой непонятно, что куда отменять и как это разрулить.
Само собой, что это не проблема git и git не виноват, просто получается, что чтобы получить нужный функционал нельзя было делать мердж ветки девелоп в свою локальную ветку. Для большинства людей это супернеинтуитивно, это как если бы перестал работать copy-paste — тебе нужен текст, ты его копируешь, вставляешь — и у тебя ломается файл с текстом и буфер обмена, и оказывается, в этот файл нельзя было вот так копировать, а надо было по-другому.
Здесь нету чьей-то вины, здесь просто ситуация такая, что выполняя, как тебе кажется, обычные и логичные действия довольно легко получить ситуацию, разрулить которую может понадобиться куча времени и нервов
mayorovp
Ну так если у него коммиты есть в его ветке — значит, работа за эти две недели не потеряна. А исходная история звучала так, как будто всё пропало.
Вы уж там определитесь...
Мерж develop в свою ветку — это правильное решение (для "длинных" веток, разумеется), оно работает (после разрешения конфликтов, разумеется), и никак не ломает push.
Mingun
А еще лучше ребейзить свою работу над develop — история сохраняется максимально линейной и понятной. Это особенно удобно, если над веткой только один человек работает, что как раз описано в случае. А если каждый коммит еще и маленький, то вообще риск, что где-то случится конфликт, с которым разбираться полдня, минимален
kukovik
Проще все избавиться от неверного коммита — отменить его следующим коммитом (git revert). Тогда не нужен форсированный пуш.
А как бы вы отменили неверный коммит в другой, гораздо более понятной и логичной системе контроля версий?
Mingun
Ну,
git stash
должен работать. Сташим работу, переключаемся на мастер, обновляем рабочую копию (git pull
), пытаемся ребейзить свою ветку. Если ветка слишком длинная, в срединном коммите организуем промежуточную ветку, ребейзим сначала ее, затем основную уже поверх нее. Таким образом, если после половины ребейза встретитесь с хитрым конфликтом, что всю процедуру перебазирования придется прервать, чтобы снова начать с чистого листа, то вы потеряете не всю проделанную работу по перебазированию, а максимум половину.Если
git pull
вернул слишком много коммитов, то можно ребейзить сначала куда-то в середину новой порции коммитов, а потом уже наmaster
.Конечно, все еще зависит от инструмента, я привел рабочий сценарий для TortoiseGit, как я сам делаю.
Таким образом малыми итерациями исправляете ситуацию. Если у вас привычка, что ни один коммит не ломает сборку/тесты, то процесс очень контролируемый.
В конце делаем
git stash pop
. Его тоже можно делать после каждого промежуточного ребейза, чтобы исправлять возможные конфликты малыми порциями, а затем снова сташить. Повторять, пока не перебазируемся на самый свежий мастер.terrapod
"незачем пушить работу, если не уверен"
А разве commit не просто у меня на диске складывает в реп. изменения? Я боюсь что завтра будет плохо моему диску, и такое бывало не раз, не хочу терять работу, пушу в бренч автоматом перед каждым перекуром. А перед пул реквестом просто все комиты делаю одним. Не помню что там за команды, мне хватает ui в vs code.
Mingun
Ну в качестве бекапа в свою личную ветку(и) пушить конечно можно. И переписывать там историю хоть по сто раз на дню, если так удобно. Тут эта фраза относилась скорее к тому, что незачем пушить все в мастер, пока еще работу не закончил — а ведь именно это и случилось. Что-то запушили в мастер, в итоге пришлось судорожно все исправлять. По хорошему вообще в мастере только мерджи должны быть из других веток, уже протестированные.
mayorovp
Вообще-то можем, примерно так:
git push --force-with-lease origin refs/remotes/origin/master@{1}:refs/heads/master
Но у меня ни разу не возникало проблем с тем, чтобы вспомнить что именно я сделал и как это отменять
…совершенно интуитивно.
oxidmod
А как же revert?
klirichek
Ну от оставляет след в истории. Ещё один коммит, "обратный" исходному.
А если их несколько, то путаницы можно внести ещё больше.
andreishe
git-репозиторий — это append-only граф снепшотов и пачка указателей на коммиты (их еще ветками называют). Единственная разрешенная операция с графом — добавлять коммиты (куда угодно, но только добавлять). Соответственно, все исправления — это добавление новых коммитов в разные места этого дерева и манипуляции с указателями (причем набор указателей у вас локально и в удаленном репозитории — это два независимых набора).
В локальном репозитории у вас полная свобода, но удаленные репозитории могут налагать запрет на некоторые операции с указателями (
git push --force
), так что исправляя косяки надо иметь в виду, что это потом будет отсылаться в удаленный репозиторий.KvanTTT
А как же дропание коммитов при рибейзе?
kogemrka
Так ребейз — это добавление новых коммитов к тому месту, начиная с которого ребейзитесь ;) Если я ничего не путаю, старое дерево комитов при этом не удаляется, сборщик мусора до них не доберётся ещё 30 дней. При необходимости можно сделать git reflog, увидеть хэш сталой ветки (до rebase'а), перейти в это состояние и сделать с ним что угодно (навесить указатель ветки, посмотреть историю и найти коммит, который ранее скипнули, cherry-pick'нуть этот комит в новую ветку и т.д.)
kukovik
Или навесить указатель до ребейза, получив старую и новую версии, если по какой-то причине это необходимо.
andreishe
Кстати, еще одно неочевидное свойство графа (я назвал его деревом в одном месте, это граф на самом деле) коммитов: он не обязан быть связным.
zloddey
Проблема с хождением на SO в том, что ситуацию в найденном ответе ещё надо умудриться правильно смаппить на своб собственную — да так, чтобы ничего не испортить. Необдуманные эксперименты на рабочем репозитории без понимания собственных действий часто лишь ухудшают ситуацию. Не надо так.
Подсказываю лайфхак, как решать 90% таких ситуаций безопасно. В любой непонятной ситуации делайте ещё один клон и экспериментируйте на нём. Это же распределённая система контроля версий, так? Значит, у репозитория может быть сколько угодно равноценных копий. Клонируйте свой рабочий репозиторий в другое место на своём компьютере, прогоняйте там какие угодно команды — хоть из головы, хоть из мануала, хоть из интернетов — и смотрите на результат. Дело пошло не туда? Не вопрос, грохнули временную репу целиком и пересоздали заново. Дело пошло в правильном направлении? Отлично, просто запускаем ту же самую команду на рабочей репе. Надо разобраться, как будут вести себя
push
иpull
? Тоже несложно: делаем обычную копию, а рядомbare
копию, и в обычной копии делаемgit remote add origin <путь к bare репе>
. Дело готово, можно проводить любые эксперименты без риска попортить рабочую репу.Безопасность экспериментов на таком сетапе рождает смелость в действиях. Смелость позволяет экспериментировать больше и разнообразнее. Эксперименты дают опыт. Опыт приводит к пониманию, как работает система. Когда есть понимание, ответы на вопросы приходят сами.
AnthonyMikh
А как же
git reflog
?zloddey
reflog
тоже полезен, но есть нюансы:Он позволяет разобраться с имеющимся бардаком, а эксперимент на клоне даёт шанс избежать появления сего бардака в основном рабочем проекте.
Это тоже инструмент самого
git
, который надо дополнительно осваивать. Приём с клонами проще тем, что использует только самые базовые техники.В общем, знать и уметь использовать
reflog
не повредит, но это не замена варианта с клонами, а дополнение к нему.Bytamine
NN1
Я пользуюсь гитом через различные GUI и делаю любые сложные действия произвожу легко и непринуждённо.
В то время как коллеги мучаются с командами из консоли.
В консоли тоже периодически бывает работаю с гитом и для этого очень советую oh-my-zsh или oh-my-posh.
VokaMut
oh-my-god
На первых порах тоже пользовался всяким GUI, но потом перешел на консольные команды без всяких сокращений так как в GUI ощущения не те, нет полной власти и контроля над репозиторием.
mayorovp
Простите, а куда эта самая полная власть девается? И кто в таком случае мешает использовать GUI для просмотра текущего статуса, а консоль — для его изменения?
KvanTTT
У меня противоположные ощущения — в гуи я вижу полноценное дерево в удобном виде.