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

  • Отменить ошибки
  • Быстро находите ошибки
  • Очистите историю коммитов
  • Интегрируйте сторонний код хорошо структурированным способом
  • …и многое другое!

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

Но теперь: пошли!

Использование Reflog для исправления ошибок

«Reflog», хотя и чрезвычайно полезен, является довольно малоизвестной функцией Git. Короче говоря, вы можете думать о Reflog как о «дневнике» Git; Git использует его для протоколирования всех перемещений указателя HEAD, которые происходят в вашем локальном репозитории. Это означает, что каждый раз, когда вы фиксируете, перебазируете, объединяете, сбрасываете или выбираете вишенку, Git будет отмечать это в этом специальном журнале. Такой протокол ваших важнейших действий, конечно же, прекрасная подстраховка на случай ошибки!

Давайте посмотрим на простой пример: вы недовольны своими последними коммитами и хотите от них избавиться. Вы выполняете «сброс», откатывая историю коммитов на пару коммитов - и тем самым удаляя эти нежелательные коммиты. Предположим также, что через пару минут вы понимаете, что не должны были этого делать, и теперь отчаянно хотите вернуть эти коммиты! 😱

Вот где вам пригодится Reflog! Взгляните на следующее небольшое видео, в котором показаны как наша ошибка (выполнение «сброса»), так и использование Reflog для исправления этой ошибки:

Примечание. В приведенном выше примере я использую клиент Tower Git для упрощения визуализации рабочего процесса.

Обратите внимание, как я снова использовал «git reset», чтобы вернуть старое состояние. В качестве альтернативы я мог бы также использовать «git branch», если бы я предпочел сохранить старое состояние в отдельной ветке.

Если вы хотите узнать больше о исправлении ошибок с помощью Git, посмотрите (бесплатную) серию видеороликов Аптечка первой помощи для Git.

Использование Bisect для поиска ошибок

Я почти уверен, что следующая ситуация будет вам знакома. Вы знаете, что ваш код работал некоторое время, но вы не знаете точно, когда (в каком коммите) вы внесли неприятную ошибку. ! Как найти коммит, содержащий ошибку? Ответ Git на этот вопрос - его инструмент Bisect.

Bisect требует, чтобы вы указали ему один хеш фиксации, где все было определенно в порядке. Как только вы скармливаете ему хэш этого безопасного коммита, Git знает, что где-то между этим и этим «хорошим» коммитом есть «плохая» фиксация. То, что Git делает сейчас, довольно просто, но при этом довольно эффективно; он разделяет диапазон коммитов пополам и проверяет фиксацию посередине.

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

Git Bisect на практике

Так как же «git bisect» работает на практике? Первый шаг - явно запустить процесс:

$ git bisect start

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

$ git bisect bad HEAD

Вторая информация, исправление, которое определенно «хорошая», может потребовать немного покопаться. Вы можете начать с проверки более старой версии, где вы вполне уверены, что все было в порядке. Допустим, мы нашли такую ​​«хорошую» доработку:

$ git bisect good fcd61994

Затем Git запустит фактический процесс «деления пополам» и проверит ревизию в середине этого диапазона коммитов между «хорошей» и «плохой»:

Bisecting: 3 revisions left to test after this (roughly 1 step)
 [0023cdddf42d916bd7e3d0a279c1f36bfc8a051b] Changing page structure

Мяч теперь на нашей стороне. После того, как Git проверил эту фиксацию в середине, мы должны запустить или собрать наше приложение и проверить, присутствует ли ошибка по-прежнему или нет. И мы должны сообщить Git результат нашего небольшого теста с помощью «git bisect bad» или «git bisect good».

В зависимости от нашего ответа Git затем повторит процесс и разделит либо первую, либо вторую половину исходного диапазона фиксации - и снова проверит фиксацию посередине и предложит вам протестировать. Повторив этот процесс пару раз, вы довольно быстро определите плохой коммит!

Как только вы найдете виновника, вы можете завершить процесс деления пополам:

$ git bisect reset

Это проинструктирует Git завершить деление пополам и вернет вас к предыдущей фиксации HEAD.

Управление большими файлами с помощью Git LFS

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

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

LFS положила конец «раздуванию больших файлов»

Ситуация стала намного лучше, когда несколько лет назад было выпущено расширение «Git LFS». «LFS» означает «Хранилище больших файлов» и направлено на улучшение обработки больших двоичных файлов. LFS делает это, добавляя два важных новых компонента: локальный кэш LFS и удаленное хранилище LFS.

  • Локальный кэш LFS: как только вы сообщаете Git, что определенный файл (или файл типа) должен управляться через LFS, эти файлы сохраняются только как указатели в локальном репозитории. Затем фактические данные файла находятся где-то еще: в локальном кэше LFS, который теперь сопровождает ваш локальный репозиторий Git.
  • Удаленное хранилище LFS. На удаленном компьютере новое хранилище LFS сохраняет и доставляет все эти большие файлы по запросу.

Всякий раз, когда Git встречает управляемый LFS файл в вашем локальном репозитории, он находит только указатель, а не фактические данные файла. Затем Git попросит локальный кэш LFS доставить его. Для этого локальный кэш LFS ищет файл по указателю - и, если его еще нет, запрашивает его из удаленного хранилища LFS.

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

Git LFS на практике

Так как же Git LFS работает на практике?

1. Установка: прежде чем вы сможете использовать LFS, вам необходимо установить соответствующее расширение. На GitHub есть подробное описание, включая ссылки для скачивания.
2. Файлы отслеживания. Затем вам нужно указать Git, какие файлы или типы файлов вы хотите отслеживать с помощью LFS:

# Tracking a single, specific file:
$ git lfs track “design.psd”
 
# Tracking certain file types:
$ git lfs track “*.mov”

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

Если вы хотите узнать больше о LFS и о том, как с ней работать, я рекомендую вам прочитать статью Работа с большими файлами с помощью LFS.

Rebase

Git предлагает два основных способа интеграции одной ветки в другую: слияние и перебазирование. Хотя Rebase больше не является «секретной» или «профессиональной функцией», лишь немногие люди досконально понимают, как это работает.

Прежде чем мы углубимся в детали, давайте проясним важный момент: Rebase не «лучше», чем Merge. Есть много команд, которые сознательно выбирают Merge вместо Rebase по уважительным причинам. Есть и другие команды, которые в определенных ситуациях предпочитают Rebase. Все сводится к личным предпочтениям и четким руководящим принципам для вашей команды.

История коммитов как прямая линия

Давайте сначала поговорим о почему может понадобиться использовать Rebase вместо Merge. Вот классический пример результата использования «git merge»:

Типичный конечный результат - это «фиксация слияния», которая объединяет две ветви. В то время как некоторые команды на самом деле предпочитают иметь эту автоматическую фиксацию, другим командам это не нравится - и поэтому они используют Rebase:

Rebase создаст прямую линию истории, в отличие от Merge. Нет ни видимого «узла», ни фиксации слияния, которые указывали бы на то, что две ветки действительно были объединены. Как было сказано ранее, некоторые команды в некоторых ситуациях предпочитают этот способ интеграции.

Но как на самом деле работает Rebase?

Механика перебазирования

Первым шагом, конечно же, является фактический запуск процесса Rebase, указав Git, какую ветку вы хотите интегрировать в текущую ветку HEAD:

$ git rebase branch-B

Теперь в нашей текущей ветке HEAD Git «отменит» все коммиты, которые произошли после его разветвления. Однако не о чем беспокоиться, поскольку Git не отбрасывает эти коммиты! Вместо этого вы можете думать об этих коммитах как о «временно припаркованных»:

Затем Git применит коммиты, которые мы хотим интегрировать из ветки B. На этом этапе, в середине операции Rebase, обе ветви выглядят одинаково:

На последнем этапе «припаркованные» коммиты на ветке A теперь применяются повторно, но теперь поверх интегрированных коммитов из ветки B. Они основаны на новом предке - отсюда и термин «перебазирование».

Конечный результат выглядит так, как если бы развитие происходило по прямой. И вместо фиксации слияния (которая будет содержать объединенные изменения) была сохранена исходная структура фиксации.

Конечно, можно еще многое сказать о перебазировании! Если вам интересно, прочтите статью Rebase as an Alternative to Merge.

Подмодули

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

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

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

  • Чистое разделение: что наиболее важно, у вас есть четкое разделение между кодом вашего проекта и дополнительным кодом из других источников.
  • Обновление стороннего кода. Кроме того, становится очень легко обновить сторонний код. Вы просто открываете вспомогательный репозиторий и вносите новые изменения, как и в любом другом репозитории Git!
  • Стандартные репозитории Git. Вдобавок ко всему, подмодули - это просто стандартные репозитории Git! Это означает, что вы можете вносить изменения, фиксировать или тянуть, как обычно.

Добавление подмодуля в проект

Чтобы добавить подмодуль в свой проект, просто перейдите в папку, в которую вы хотите его загрузить, и используйте команду «git submodule add»:

# Move into the folder where you want the Submodule files:
$ cd lib
 
# Add the Submodule repository from its remote URL:
$ git submodule add https://github.com/djyde/ToProgress
 
# Adding a Submodule, like any other change, must be committed to the repo:
$ git commit -m “Add Submodule to project”

Это загрузит проект в указанное место и создаст необходимую конфигурацию в вашем локальном репозитории Git. Git управляет конфигурацией подмодуля в нескольких местах; в основном в .gitmodules, но также в вашем локальном файле .git / config и во внутренней папке .git / modules. Конфигурация субмодулей - довольно сложная и шаткая тема, поэтому я хотел бы дать вам один совет: никогда не связывайтесь с субмодулями вручную! Всегда используйте соответствующие команды или используйте графический интерфейс рабочего стола, например Башня за такую ​​работу!

Клонирование удаленного проекта с помощью подмодулей

Допустим, вы хотите клонировать существующий проект, содержащий подмодули. Если бы вы использовали простую команду «git clone», вы были бы удивлены, увидев, что после завершения клонирования ни одного из ваших файлов подмодуля нет. Вы можете исправить это одним из следующих способов:

(a) Вы можете просто добавить параметр --recurse-submodules в команду «git clone». Это проинструктирует Git также инициализировать все подмодули после завершения клонирования.

(b) В качестве альтернативы, если вы использовали простую команду «git clone» без этой опции, вам придется затем вручную инициализировать подмодули с помощью git submodule update --init --recursive.

В любом случае ваш клонированный проект будет также содержать файлы всех подключенных субмодулей.

Получение ревизии в подмодуле

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

Представьте, что произойдет, если подмодули, как и обычные репозитории Git, будут проверяться в ветвях . Природа веток состоит в том, что их фактическое содержимое со временем меняется; по мере поступления новых коммитов они автоматически перемещают свои указатели на самую новую фиксацию. Однако в субмодуле вам этого часто не нужно. Скорее, вы хотите быть на 100% уверены в точном коде, который подмодуль привносит в ваш проект. Вот почему подмодули всегда указывают на конкретную ревизию, что гарантирует, что у вас всегда будет правильный код в вашем основном проекте.

Обучение работе с подмодулями

Поскольку мы не можем вдаваться в подробности работы с субмодулями, я настоятельно рекомендую вам копнуть немного глубже и прочитать это руководство по субмодулям Git.

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

Заключение

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

об авторе

Тобиас Гюнтер - генеральный директор Tower, популярного настольного клиента Git, который помогает более 100 000 разработчиков по всему миру работать с Git более продуктивно.