В этой статье речь пойдет о специфике применения Git в iOS-разработке и распространенных проблемах.

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

Настройка .gitignore

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

Как правило, туда добавляются следующие виды объектов:

  • Файлы и каталоги, создаваемые в результате сборки проекта;
  • Каталоги с внешними зависимостями;
  • Разного рода временные файлы, создаваемые различными инструментами.

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

Чтобы не заполнять этот файл самостоятельно, имеет смысл обратиться к готовому файлу от GitHub.

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

Файл xcodeproject/project.pbxproj и merge-конфликты

В нем перечислены файлы, добавленные в проект, а также зависимости и настройки сборки — в общем, файл важный и нужный. Проблема в том, что он часто становится причиной конфликтов слияния (merge conflict), которые вручную не всегда удается разрулить. А удалять или добавлять его в .gitignore нельзя. Решение заключается в использовании сторонних утилит.

Одна из таких утилит — это mergepbx. Прежде всего, стоит сказать, что это не серебряная пуля и в некоторых ситуациях конфликты неизбежны (пример из документации — если один человек переименует файл, а другой удалит его же, то конфликт неизбежен). Но в большинстве случаев это работает. Утилита парсит файл и «понимает», что нужно исправить, чтобы изменения обоих ветвей не потерялись и при этом файл остался синтаксически корректным. Этот инструмент наиболее удобен в использовании, если настроить его на автоматическое срабатывание при конфликте.

Шаг 1: клонируем репозиторий с GitHub

$ git clone https://github.com/simonwagner/mergepbx.git
$ cd mergepbx

Шаг 2: собираем

$ ./build.py

В рабочей директории появится файл mergepbx. Запомните путь к этому файлу.

Шаг 3: настраиваем Git

В файл ~/.gitconfig добавьте следующее (/path/to/mergepbx замените на ваш путь к этому файлу):

#driver for merging Xcode project files
[merge "mergepbx"]
        name = Xcode project files merger
        driver = /path/to/mergepbx %O %A %B

В вашем проекте добавьте в .gitattributes:

*.pbxproj merge=mergepbx

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

Файл xcodeproject/project.pbxproj и CocoaPods

С этим файлом связана еще одна проблема — после запуска CocoaPods этот файл конвертируется в XML-формат, и хотя он по-прежнему остается валидным для Xcode, тут mergepbx уже не поможет, если кто-то отправит такую версию файла в репозиторий. Таким образом, Xcode и CocoaPods могут «воевать» между друг другом, конвертируя файл то в один, то в другой формат.
Прежде всего, можно дать общий совет — перед каждым коммитом смотреть diff, чтобы случайно в коммит не попало лишнего (о том, почему это важно, расскажу ниже). Однако, для этой проблемы существует простое решение — достаточно поставить утилиту xcproj.

$ brew install xcproj

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

Как найти коммит, который внес баг в проект

Если у вас в проекте сотни или тысячи коммитов, перебирать их всех крайне утомительно. У Git есть инструмент, который предназначен как раз для решения данной проблемы — git bisect. Он использует двоичный поиск, что позволяет быстро найти виновника. Вам понадобится только хэш какого-либо коммита, где бага точно нет. Используется очень просто (вместо <good-commit> — хэш или тэг «хорошего» коммита):

$ git bisect start
$ git bisect bad
$ git bisect good <good-commit>

Дальше алгоритм такой:

  1. Собираем и запускаем проект.
  2. Если баг есть — пишем git bisect bad, иначе — git bisect good.
  3. Если git написал что-то вроде «Bisecting: 2 revisions left to test after this», то переходим к п. 1. Если Git выдал хэш коммита («<hash> is first bad commit»), то мы нашли виновника, и можно приступить к исправлению бага.

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

Не включайте в коммит лишнего

Если вы, к примеру, изменили размер шрифта в UI приложения, то не нужно включать в коммит «Change font size» изменения сетевого модуля или чего-либо еще, которые вы делали до этого. Нужно лишь следить за тем, что вы добавляете в индекс.

Почему это важно:

  • У вас должна быть возможность найти коммит, в котором вы поменяли/добавили/удалили что-либо. Никто и не подумает искать изменения сетевого модуля в коммите с сообщением «Change font size»;
  • Будет проблематично отменить (git revert) только изменения, связанные c UI, ведь заодно откатится что-нибудь еще, что может вообще сломать приложение;

git pull —rebase

Когда вы разрабатываете не в одиночку, то в результате git pull могут появляться коммиты вида «Merge remote-tracking branch ‘origin/master'». Если вы работаете в одной ветви (например, в master), ненужные merge-коммиты делают историю менее читаемой. Чтобы их не было, можете вместо git pull использовать git pull —rebase, тогда вместо слияния ваши коммиты окажутся над коммитами остальных, что позволит вам отправить эти изменения в репозиторий, не создавая merge-коммитов. Впрочем, если вы активно используете ветвление, этот совет не столь актуален.

Добавление части файла в индекс

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

 

Автор фото: @madeawkward

 

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.