Как я обучаю использованию Git

0
795
views

Перевод статьи «How I teach Git».

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

Кстати, с тех пор у нас на работе появились внутренние тренинги, в которых используется тот же (или похожий) подход. При этом я ничего не изобретал. Я в значительной степени вдохновлялся тем, что писали другие люди, включая книгу «Pro Git». Но материал я подаю не в том же порядке, и это, как мне кажется, может иметь значение.

Я пишу эту статью потому, что на протяжении многих лет постоянно вижу, как люди используют Git, не понимая, что они делают.

Пользователи Git бывают зациклены на очень специфическом рабочем процессе, которому им сказали следовать, и не могут адаптироваться к другому, который, скажем, используется в open source проекте. Это также относится к мейнтейнерам открытого исходного кода, не понимающим, как внешние контрибьюторы используют Git.

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

На написание этой статьи меня вдохновил (возобновившийся) интерес Джулии Эванс к Git, поскольку она иногда обращается за комментариями в социальных сетях.

Моя цель — не научить вас пользоваться Git, а поделиться своим подходом к обучению Git, чтобы другие люди, которые будут учить начинающих, могли вдохновиться.

Так что если вы изучаете Git, эта статья была написана не для вас (извините). Исходя из этого, она не может служить самодостаточным учебником, но, надеюсь, ссылки на другие обучающие ресурсы позволят заполнить пробелы и сделают мою статью также полезным обучающим ресурсом.

Если вы «визуал», то упомянутые внешние учебные ресурсы вам, вероятно, больше понравятся, так как они иллюстрированы или даже ориентированы на визуальное обучение.

Содержание

Ментальная модель

Предположим, мы уже выяснили, зачем мы используем СКВ (систему контроля версий), где изменения записываются в коммиты (или, другими словами, где мы делаем коммиты наших изменений в историю — я предполагаю, что вы знакомы с этой терминологией). Теперь давайте рассмотрим Git более конкретно.

От редакции Techrocks: у нас есть очень интересная статья о том, как был изобретен Git и что было до него, — «История происхождения Git».

Я считаю, что для понимания Git очень важно получить точную ментальную модель концепций, лежащих в его основе.

Начнем с того, что на самом деле Git записывает не изменения, а снимки наших файлов. По крайней мере, концептуально. Git использует пакфайлы для эффективного хранения вещей и фактически лишь в некоторых случаях сохраняет изменения — diffs. Также Git будет генерировать diffs по требованию.

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

Теперь давайте рассмотрим некоторые концепции Git и то, как Git реализует некоторые общие концепции СКВ.

Коммит

Коммит в Git — это:

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

Каждому коммиту присваивается идентификатор, определяемый путём вычисления SHA1-хэша этой информации. Измените запятую, и вы получите другой SHA1, другой объект коммита. (К сведению, Git постепенно переходит на SHA-256 в качестве функции хэширования).

Отступление: как вычисляется SHA1?

В хранилище Git каждый объект хранится с именем, которое напрямую вытекает из его содержимого, в виде его SHA1-хэша.

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

Дерево — это текстовый файл, в котором перечислены файлы в каталоге с их именами, режимами и SHA1 блобов, представляющих их содержимое, а также их подкаталоги с их именами и SHA1 их деревьев.

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

Коммит и его дерево (источник: Pro Git)

Родительские коммиты в коммите создают направленный ациклический граф, который представляет нашу историю. Этот граф состоит из узлов (наши коммиты), связанных между собой направленными рёбрами (каждый коммит связан со своими родительскими коммитами, т.о. есть направление, следовательно, он направлен) и не может иметь циклов (коммит никогда не будет своим предком и ни один из его предков не будет ссылаться на него как на родительский коммит).

Коммиты и их родительские коммиты (источник: Pro Git)

Ссылки, ветки и теги

Работа с SHA1-хэшами неудобна для людей. И хотя Git позволяет нам работать с уникальными SHA1-префиксами вместо полного SHA1-хэша, нам нужны более простые имена для наших коммитов. Тут мы вводим понятие ссылки.

Ссылки — это ярлыки для наших коммитов, которые выбираем мы (а не Git).

Существует несколько видов ссылок:

  • ветки — перемещаемые ссылки (обратите внимание, что main или master не являются какими-то особенными ветками, их название — лишь условность)
  • теги — неизменяемые ссылки
  • HEAD — это специальная ссылка, которая указывает на текущий коммит. Обычно она указывает на ветку, а не непосредственно на коммит (позже мы увидим, почему). Когда ссылка указывает на другую ссылку, это называется символической ссылкой.
  • Существуют и другие специальные ссылки (FETCH_HEAD, ORIG_HEAD и т. д.), которые Git будет настраивать для вас во время некоторых операций.
Ветка и ее история коммитов (источник: Pro Git)

Три состояния

Когда вы работаете в репозитории Git, файлы, с которыми вы работаете и которые записываете в историю Git, находятся в вашем рабочем каталоге. Чтобы создать коммиты, вы размещаете файлы в индексе или стейджинге. После этого вы прикрепляете сообщение коммита и перемещаете свои подготовленные файлы в историю.

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

Рабочее дерево, стейджинг и директория Git (источник: Pro Git)

Отступление: игнорирование файлов

Не все файлы нуждаются в отслеживании их истории. Например, не нужно отслеживать файлы, которые генерируются вашей системой сборки (если таковая имеется). Также не стоит отслеживать файлы, специфичные для вашего редактора, операционной системы или другого рабочего окружения.

Git позволяет задавать шаблоны имен файлов или каталогов, которые следует игнорировать. Это не означает, что Git не сможет их отслеживать. Просто если они не отслеживаются, то некоторые операции Git не будут показывать их вам или работать с ними. Но вы можете вручную добавить их в историю, и с момента добавления они больше не будут игнорироваться.

Игнорирование файлов осуществляется путём помещения их путей в файлы ignore:

  • Файлы .gitignore в любом месте вашего репозитория определяют шаблоны игнорирования для каталога. Эти файлы отслеживаются в истории как средство обмена ими между разработчиками. В файлах .gitignore вы будете указывать файлы, созданные вашей системой сборки (build/ для проектов Gradle, _site/ для сайта Eleventy и т. д.).
  • .git/info/excludes — это локальный репозиторий на вашей машине. Используется редко, но иногда бывает полезен, поэтому о нём полезно знать.
  • И, наконец, ~/.config/git/ignore — глобальный файл для машины (для вашего пользователя). В нем вы будете указывать файлы, специфичные для вашей машины, например, специфичные для используемых вами редакторов или вашей операционной системы (например, .DS_Store на macOS или Thumbs.db на Windows).

Подведение итогов

Вот еще одно представление всех этих концепций:

Коммиты, ссылки и зоны (источник: A Visual Git Reference, Mark Lodato)

Основные операции

Здесь мы начинаем говорить о командах Git и о том, как они взаимодействуют с графом:

  • git init для инициализации нового репозитория
  • git status для получения сводки о состоянии ваших файлов
  • git diff для отображения изменений между любыми двумя вашими рабочими директориями, индексом, HEAD или вообще между любыми коммитами
  • git log для вывода вашей истории и поиска в ней
  • создание коммитов
    • git add для добавления файлов в индекс
    • git commit для преобразования индекса в коммит (с добавлением сообщения коммита)
    • git add -p для интерактивного добавления файлов в индекс: можно выбрать, какие изменения добавить, а какие оставить только в рабочей директории
  • управление ветвями
    • git branch, чтобы показать ветки или создать ветку
    • git switch (также git checkout) для смены ветки
    • git switch -b (также git checkout -b) как сокращение для git branch и git switch
  • git grep для поиска в вашем рабочем каталоге, индексе или любом коммите. Это что-то вроде расширенного grep -R, который знает о Git
  • git blame, чтобы найти последний коммит, изменивший ту или иную строку данного файла (т. е., позволяет выяснить, кто виноват в появлении бага (англ. blame — обвинять, возлагать ответственность))
  • git stash, чтобы отложить незафиксированные изменения (сюда входят как файлы в стейджинге, так и отслеживаемые файлы из рабочей директории), а позже вернуть их.

Создание коммита, переключение ветвей и HEAD

Когда вы создаёте коммит (с помощью git commit), Git не только создаёт объект коммита, но и перемещает HEAD, чтобы он указывал на него.

Если HEAD указывает на ветку, как это обычно бывает, Git переместит эту ветку в новый коммит (а HEAD продолжит указывать на эту ветку).

Если текущая ветка является предком другой ветки (коммит, на который указывает ветка, также является частью другой ветки), создание коммита переместит HEAD туда же, и ветки разойдутся.

Когда вы переключаетесь на другую ветку (с помощью git switch или git checkout), HEAD перемещается на новую текущую ветку, а ваш рабочий каталог и индекс настраиваются так, чтобы соответствовать состоянию этого коммита (незафиксированные изменения предварительно сохраняются; если Git не может этого сделать, он откажется от переключения).

За более подробной информацией и визуальными представлениями обращайтесь к разделам commit и checkout в книге Марка Лотато «A Visual Git Reference». Имейте в виду, что этот справочник был написан много лет назад, когда git switch и git restore еще не существовали, а git checkout был всем, что у нас было. Поэтому раздел checkout охватывает немного больше, чем git switch.

Конечно, книга «Pro Git» также является хорошим справочником с визуальными представлениями. Подраздел «Branches in a Nutshell» охватывает большую часть всего вышеперечисленного.

Отступление: Git консервативен

Как мы видели выше, любое «изменение» коммита (например, с помощью git commit --amend) на самом деле превратит его в другой коммит (с другим SHA1).

Старый коммит не исчезнет сразу. Git использует сборку мусора, чтобы со временем удалить коммиты, которые недоступны ни по одной ссылке. Это означает, что многие ошибки можно восстановить, если вам удастся найти SHA1 коммита. Здесь может помочь git reflog или нотация <branch-name>@{<n>}, например main@{1} для последнего коммита, на который указывал main перед тем, как он изменился.

Работа с ветвями

Выше мы видели, как ветви могут расходиться.

Но расхождение требует в конечном итоге слияния изменений обратно (с помощью git merge). Git очень хорош в этом (как мы увидим позже).

Особый случай слияния — когда текущая ветка является предком ветки, в которую нужно влиться. В этом случае Git может выполнять fast-forward merge.

Поскольку операции между двумя ветками, скорее всего, всегда будут направлены на одну и ту же пару веток, Git позволяет настроить ветку для отслеживания другой ветки. Эта другая ветка будет называться upstream-ом (восходящим потоком) ветки, которая её отслеживает.

При настройке git status, например, сообщит вам, насколько обе ветки разошлись друг с другом. При этом текущая ветка может соответствовать своей upstream-ветке, отставать от неё с возможностью перевода вперёд (fast-forward), опережать ее на некоторое количество коммитов. Также ветки могут разойтись, каждая на некоторое количество коммитов. Благодаря этой информации можно получить хорошие значения по умолчанию для параметров других команд.

Для интеграции изменений из другой ветки вместо слияния можно выбрать один коммит без его истории. Делается это при помощи команды cherry-pick (англ. cherry-pick — «отбирать только самое лучшее, захватывать лакомые куски, снимать сливки»). Git вычислит изменения, внесённые этим коммитом, и применит их к текущей ветке, создав новый коммит, аналогичный исходному.

Если вы хотите узнать больше о том, как Git делает это на самом деле, читайте статью Джулии Эванс «How git cherry-pick and revert use 3-way merge».

Наконец, еще одна команда в вашем арсенале — rebase.

Вы можете рассматривать ее как способ сделать много «черри-пиков» за один раз, но на самом деле она гораздо мощнее (как мы увидим ниже). Однако в базовом варианте это именно выборка диапазона коммитов.

Вы задаёте диапазон между любым коммитом в качестве начальной точки и существующей веткой в качестве конечной точки (по умолчанию — текущей веткой) и указываете ветку-цель. rebase вставляет все эти коммиты поверх указанной целевой ветки и в итоге обновляет ее.

Команда здесь имеет вид git rebase --onto=<target> <start> <end>. Как и во многих других командах Git, аргументы могут быть опущены и будут иметь значения по умолчанию и/или специфические значения.

Таким образом, git rebase — это сокращение для git rebase --fork-point upstream, где upstream — это upstream текущей ветки (я проигнорирую --fork-point, т.к. его влияние тонко и не так важно в повседневном использовании).

При этом git rebase --fork-point upstream является сокращением для git rebase upstream HEAD (где HEAD должен указывать на ветку).

А git rebase upstream HEAD является сокращением для git rebase --onto=upstream upstream HEAD.

И последняя команда тоже является сокращением, уже для git rebase --onto=upstream $(git merge-base upstream HEAD) HEAD.

Эта команда будет переписывать все коммиты между последним общим предком upstream и текущей ветки с одной стороны и текущей веткой с другой стороны (т.е. все коммиты с момента их расхождения), и заново применит их поверх upstream, а затем обновит текущую ветку, чтобы она указывала на новые коммиты.

Явное использование --onto (со значением, отличным от начальной точки) на самом деле встречается редко, см. мой предыдущий пост об одном случае использования.

Говоря о git rebase, нельзя не упомянуть ее интерактивный вариант git rebase -i. Эта команда имеет такое же поведение, как и ее неинтерактивный вариант, но после вычисления того, что нужно сделать, она позволит вам редактировать результат (как текстовый файл в редакторе, по одному действию на строку).

По умолчанию будут выбраны все коммиты из диапазона, но вы сможете изменить их порядок, пропустить некоторые или даже объединить несколько в один коммит. Вы можете выбрать коммит, который изначально не был выбран, и даже создать слияние коммитов, полностью переписав всю историю!

Наконец, вы также можете остановиться на коммите, чтобы отредактировать его (используя git commit --amend и/или, возможно, создав новые коммиты перед продолжением rebase).

Также вы сможете выполнить заданную команду между двумя коммитами. Последняя опция настолько полезна (например, для подтверждения того, что вы не сломали проект в каждой точке истории), что вы можете передать эту команду в опции --exec, и Git будет выполнять её между всеми перемещаемыми коммитами.

Это работает и при неинтерактивном rebase. Но в интерактивном режиме вы увидите строки выполнения, вставленные между строками cherry-pick, когда вам будет предоставлена возможность редактировать сценарий rebase.

Для получения более подробной информации и визуальных представлений смотрите разделы «Merge», «Cherry pick» и «Rebase» в книге Марка Лодато «A Visual Git Reference», а также подразделы «Basic Branching and Merging», «Rebasing» и «Rewriting History» в книге «Pro Git».

Вы также можете посмотреть на диаграммы «Branching and merging» из книги Дэвида Драйсдейла «Git Visual Reference».

Совместная работа с другими людьми

До сих пор мы работали только локально в нашем хранилище. Но Git был специально создан для работы с другими людьми.

Позвольте мне представить вам remotes (удаленные репозитории).

Удалённые репозитории

Когда вы клонируете репозиторий, он становится удалённым (remote) от вашего локального репозитория, который называется origin. Как и в случае с веткой main, в имени origin нет ничего особенного, кроме того, что оно иногда используется в качестве значения по умолчанию, когда аргумент команды опущен.

Затем вы начнёте работать, создавая локальные коммиты и ветки (соответственно, форки от remote). А тем временем remote, вероятно, получит ещё несколько коммитов и веток от своего автора. Поэтому вы захотите синхронизировать эти удаленные изменения с вашим локальным репозиторием. Также вам понадобится возможность быстро узнать, какие изменения вы сами внесли в локальный репозиторий по сравнению с удаленным.

Git решает эту задачу, записывая состояние известных ему удалённых ресурсов (в основном, веток) в специальное пространство имён: refs/remote/. Это т. н. удалённые отслеживаемые ветки (remote-tracking branches).

К слову, локальные ветки хранятся в пространстве имен refs/heads/, а теги — в refs/tags/ (теги из remote обычно импортируются прямо в refs/tags/, так что, например, вы теряете информацию о том, откуда они взялись).

Вы можете иметь столько удаленных репозиториев, сколько необходимо, и у каждого будет свое имя.

Обратите внимание, что удаленные репозитории не обязательно живут на других машинах, они могут находиться на той же машине и доступ к ним может осуществляться непосредственно из файловой системы. Так что вы можете играть с remote без необходимости что-то настраивать.

Получение изменений (fetching)

Всякий раз, когда вы производите выборку из удалённого хранилища (с помощью git fetch, git pull или git remote update), Git обращается к нему, чтобы загрузить коммиты, о которых он ещё не знает, и обновляет remote-tracking branches удалённого хранилища.

Точный набор ссылок, которые нужно извлечь, и место, куда их нужно извлечь, передаются команде git fetch (как refspecs). Значение по умолчанию определяется в файле .git/config вашего репозитория. Настраивается с помощью команд git clone или git remote add по умолчанию на получение всех веток (всего, что находится в refs/heads/ удаленного репозитория) и помещение их в refs/remote/<remote> (т. е. refs/remote/origin/ для удалённого origin), с тем же именем (так что refs/heads/main удаленного репозитория становится refs/remote/origin/main локального).

Удаленные репозитории и удаленные отслеживаемые ветви (источник: Pro Git)

Затем вы будете использовать команды, связанные с ветками, чтобы перенести изменения из удаленной отслеживаемой ветки в вашу локальную (git merge или git rebase), или git pull, который является не более чем сокращением для git fetch с последующим git merge или git rebase.

Кстати, в ряде ситуаций Git автоматически настроит удалённую отслеживаемую ветку как upstream локальной ветки, когда вы её создадите (он сообщит вам об этом, когда это произойдёт).

Передача изменений (pushing)

Чтобы ваши изменения попали к другим людям, они могут добавить ваш репозиторий как удаленный и сделать pull из него (подразумевается право доступа к вашей машине по сети). Или вы можете сделать push ваших изменений в удаленный репозиторий.

Если вы просите кого-то взять изменения из вашего удаленного хранилища, это называется… пул-реквест (pull request). Этот термин наверняка встречался вам на GitHub или других подобных сервисах.

Pushing похож на fetching, только наоборот: вы отправляете свои коммиты на remote и обновляете его ветку, чтобы она указывала на новые коммиты.

В качестве меры безопасности Git разрешает только fast-forward удалённых веток. Если вы хотите отправить изменения, которые обновят удалённую ветку не быстрым способом, вам придётся сделать это принудительно, используя git push --force-with-lease.

Можно использовать и git push --force. Но будьте осторожны: --force-with-lease сначала проверит, обновлена ли ваша remote-tracking branch, чтобы убедиться, что никто не внёс изменения в ветку с момента последнего извлечения. А --force не будет выполнять эту проверку, делая то, что вы ему говорите, на ваш страх и риск.

Как и в случае с git fetch, вы передаёте ветви для обновления команде git push, но Git предоставляет хорошее поведение по умолчанию, если вы этого не сделаете. Если вы ничего не укажете, Git будет считать удаленным репозиторием upstream текущей ветки. Поэтому в большинстве случаев git push эквивалентен git push origin.

На самом деле git push origin — это сокращение для git push origin main (при условии, что текущая ветка — main), которое само по себе является сокращением для git push origin main:main, которое, в свою очередь, является сокращением для git push origin refs/heads/main:refs/heads/main. Последняя команда означает push локальной refs/heads/main в refs/heads/main удалённого origin.

См. мою предыдущую статью о некоторых случаях конкретизации refspecs с разными источниками и назначениями.

git push (источник: Git Visual Reference, David Drysdale)

Более подробную информацию и наглядные представления можно найти в подразделах «Remote Branches», «Working with Remotes» и «Contributing to a Project» книги «Pro Git», а также в диаграммах «Dealing with remote repositories» из книги Дэвида Драйсдейла «Git Visual Reference».

В главе «Contributing to a Project» книги «Pro Git» также рассказывается о контрибуции в проекты с открытым исходным кодом на таких платформах, как GitHub, где вам нужно сначала форкнуть репозиторий, а затем вносить свой вклад с помощью pull request (или merge request).

Лучшие практики

Эти рекомендации адресованы новичкам и, надеюсь, не слишком спорны.

Старайтесь сохранять историю чистой:

  • Разумно используйте коммиты слияния
  • Пишите четкие и качественные сообщения коммитов (см. commit guidelines в «Pro Git»)
  • Делайте атомарные коммиты: каждый коммит должен компилироваться и выполняться независимо от коммитов, следующих за ним в истории.

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

В локальном режиме делайте все, что хотите. Новичкам я бы посоветовал следующее:

  • Не работайте напрямую над main (или master, или любой удаленной веткой, которой вы не владеете). Вместо этого создавайте локальные ветки. Это помогает разделить работу над разными задачами. Собираетесь начать работу над другим багом или фичей, пока ждёте дополнительных деталей по инструкциям для текущей? Переключитесь на другую ветку, а затем вернитесь к предыдущей, переключившись обратно. Это также облегчает обновление с удалённой ветки: если ваши локальные ветки — это просто копии удалённых с теми же именами, без каких-либо локальных изменений, вы можете быть уверены, что у вас не будет конфликтов.
  • Не стесняйтесь переписывать историю коммитов (git commit --amend и/или git rebase -i), но не делайте этого слишком рано. Вполне допустимо накапливать множество мелких коммитов во время работы и переписывать/очищать историю только перед тем, как поделиться ею.
  • Аналогично, не стесняйтесь перемещать (rebase) свои локальные ветки, чтобы интегрировать изменения из upstream. Но это пока вы не поделились веткой. Поделившись, придержавайтесь правил проекта относительно работы с ветками.

Если вдруг возникла проблема и вы заблудились, мой совет — используйте gitk или gitk HEAD @{1}, а также, возможно, gitk --all, чтобы визуализировать историю Git и попытаться понять, что произошло. Я использую gitk, но вы можете использовать любой инструмент по своему вкусу.

Разобравшись в произошедшем, вы можете откатиться к предыдущему состоянию (git reset @{1}) или попытаться исправить ситуацию (выбрать коммит при помощи cherry-pick и т. д.). А если вы находитесь в середине rebase или, возможно, неудачного слияния, вы можете прервать их и откатиться к предыдущему состоянию с помощью таких команд, как git rebase --abort или git merge --abort.

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

Продвинутые концепции

Это только некоторые, есть ещё много других, которые нужно изучить!

  • Отсоединённый HEAD: на странице man git checkout есть хороший раздел по этой теме, также смотрите мой предыдущий пост, а для наглядного представления смотрите раздел «Committing with a Detached HEAD» в книге Марка Лодато «A Visual Git Reference».
  • Хуки: это исполняемые файлы (чаще всего shell-скрипты), которые Git будет запускать в ответ на операции с репозиторием. Люди используют их для линтинга кода перед каждым коммитом (прерывая коммит в случае неудачи), генерации или постобработки сообщений коммитов, а также для запуска действий на сервере после того, как кто-либо внесёт данные в репозиторий (запуск сборки и/или развёртывания).
  • Несколько редко используемых команд, которые порой могут сэкономить вам часы:
    • git bisect — продвинутая команда, помогающая определить, какой коммит внёс ошибку, путём тестирования нескольких коммитов (вручную или с помощью скриптов). При линейной истории это можно сделать вручную с помощью bisection, но как только у вас появляется много слитых коммитов, это становится намного сложнее, и хорошо, если git bisect сделает всю эту тяжелую работу.
    • git filter-repoсторонняя команда, фактически заменяющая собственный filter-branch в Git. Позволяет переписать всю историю репозитория, чтобы удалить ошибочно добавленный файл или помочь извлечь часть репозитория в другой.

Мы закончили.

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

ОСТАВЬТЕ ОТВЕТ

Please enter your comment!
Please enter your name here