Git, SourceTree, ветки и несколько пользователей.
Автор: Кулик Алексей aka kpblc | Дата: 27 Апрель 2015 · 7 коммент.
На работе возникла ситуация, что немного запутались с ветками в системе контроля версий. Несколько разработчиков, несколько тестировщиков и масса пользователей, которым эти разборки глубоко фиолетово. Необходимость такой "шпаргалки" встала в полный рост.
Я отчетливо понимаю, что "тру-программеры", суровые линуксоиды, а также приверженцы "нормального Git" порвут меня как Тузик грелку... Но тем не менее.
Рассматриваются вопросы и подробности работы с Git на примере SourceTree. Для примера имитируется следующая ситуация:
- Над проектом работает 4 человека
- 3 человека — разработчики. Могут и постоянно вносят изменения в серверный репозиторий
- 1 человек — тестировщик. В серверный репозиторий изменения не вносит, только считывает
- Кроме всего прочего, существует еще один репозиторий ("пользовательский"), с которым (впоследствии) будут работать конечные пользователи. Синхронизацию этого репозитория с серверным выполняет любой из разработчиков
- Разработчики и тестировщики могут использовать любые ветки. Пользовательский репозиторий использует и синхронизирует только основную ветку — master
Для упрощения работы используется только физический компьютер. Все репозитории располагаются внутри каталога c:\Repositories
Вводим команду
1 | git init --bare c:/Repositories/Server |
Перед bare указывается двойной минус. Путь к “серверному” репозиторию указывается прямыми слешами, а не обратными, как мы привыкли в Windows. В случае использования абсолютного пути закон не меняется.
После выполнения команды вводим команду
1 | exit |
Окно терминала закроется.
Аналогичным образом создаются репозитории для Dev2, Dev3, Test и User:
В качестве лирического отступления: Что такое вообще фиксация изменений? Зачем она нужна?
Все файлы, которые располагаются в репозитории, с точки зрения системы контроля версий делятся на два типа: те, состояние которых отслеживать надо; и те, состояние и изменение которых отслеживать не надо. Чтобы не усложнять ситуацию, считаем, что отслеживать надо все файлы и каталоги, которые попадают в репозиторий (как прописывать исключения – это отдельная тема, не хочется сейчас отвлекаться).
Если файл появился, был изменен или удален – система контроля версий в лучшем случае скажет автору, что “файл изменен”. Об этих изменениях ни один другой пользователь системы не узнает. Чтобы сообщить об этом, а заодно и предоставить обновленную версию файла (файлов), и надо выполнять фиксацию изменений (коммит).
При выполнении фиксации (коммита) система записывает к себе новое состояние файла. При отправке коммита на внешнее хранилище в этом внешнем хранилище также фиксируется актуальное состояние файла. Другие пользователи выполняют операцию получения (push) – и у них появляется свежая версия файла.
Теперь вид SourceTree для Dev1 выглядит таким образом:
Обратите внимание, установлена галочка Показать внешние ветки, и в перечне изменений показывается origin/master и master. Первое – это название той ветки, с которой синхронизируется текущее состояние дел. Второе – имя ветки, с которой работает пользователь.
Любой другой пользователь получает сообщение (как – неважно: по телефону, через почту и т.д.) о том, что в серверный репозиторий были внесены изменения. Изменения коснулись ветки master.
Пользователь активирует свой SourceTree со своим репозиторием (например, это будет пользователь Test1) и нажимает кнопку |
|
В диалоговом окне выбирается синхронизируемая ветка (в данном случае – master) и указывается, что после слияния выполнить фиксацию изменений |
Теперь у пользователя Test1 вид окна SourceTree полностью аналогичен окну Dev1:
Аналогичные действия выполняют все разработчики, синхронизируя свои репозитории с серверным. И у всех пользователей в каталоге репозитория появляется файл Dev1master.txt с содержимым
1 | Dev1 | master |
Dev1 создает отдельную ветку Dev1Br#1 и в этой ветке создает отдельный файл Dev1br1.txt, а Dev3 – свою ветку Dev3Br#1 с файлом Dev3br1.txt. Создание ветки автоматически переводит соответствующего клиента на эту ветку. При фиксации изменений галочка “Сразу отправить изменения в…” установлена. В результате состояние дел у пользователей будет таким:
Имя пользователя | Вид окна SourceTree |
Dev1 | |
Dev3 | |
Остальные пользователи |
Обращаем внимание на то, что установлена галочка “Показать внешние ветки”. Если она не будет установлена, вид немного упростится: будут показываться только те ветки и коммиты, которые существуют в текущем репозитории.
Таким образом, сейчас два разработчика внесли свои изменения в (например) исходные коды программ. Возможно, даже в компилированные версии программ. Но, поскольку репозиторий User не был обновлен, конечные пользователи системы даже не подозревают обо всех внутренних изменениях.
Подчеркну: и Dev1, и Dev3 успешно создали собственные ветки и “залили” их в серверный репозиторий. Теперь Dev1 решает, что пора бы и протестировать внесенные изменения. Он отправляет пользователю Test1 сообщение с указанием названия ветки, которую надо получить: Dev1Br#1. Каковы будут действия пользователя Test1?
Пользователь Test1 активирует SourceTree и нажимает кнопку |
|
В выпадающем списке выбрать нужную ветку – Dev1Br#1 | |
Результирующий вид окна SourceTree для пользователя Test1 |
Обратите особое внимание на несколько моментов:
- У пользователя Test1 до сих пор только одна его ветка – master:
Импорт ветки Dev1Br#1 не изменил название локальной ветки, не создал новую ветку – ни-че-го! Изменения из внешней ветки origin\Dev1Br#1 просто были помещены поверх старого состояния репозитория - Состояние репозитория в результате получения изменений из Dev1Br#1 стало таким:
- SourceTree (точнее, Git) считает, что ему есть что отправить в серверный репозиторий:
Хотя, конечно, это не так. Просто обновилась локальная ветка master, и Git предполагает, что ее надо срочно синхронизировать с серверной. Ни в коем случае этого делать нельзя! Ветку master в серверном (общем) репозитории обновляют только разработчики (ну или специально обученные люди, обладающие достаточной квалификацией и правами).
Теперь Dev3 сообщает пользователю Test1, что у него тоже есть что “обновить”. Действия пользователя Test1 ничем не отличаются от уже описанных, только выбираемая ветка будет Dev3Br#1. Правда, результат будет совсем другим:
А вот что будет твориться в репозитории с точки зрения файловой системы:
Таким образом, получается, что Test1 “подхватил” новые файлы и из ветки Dev1Br#1, и из ветки Dev3Br#1. При этом сам пользователь Test1 находится на своей собственной ветке master. Фактически произошло слияние (merge) веток Dev1Br#1 и Dev3Br#1 в локальной ветке master пользователя Test1.
А теперь в игру вступает Dev2, про которого мы почти забыли – и вносит изменения в начальный файл Dev1master.txt, добавляя строку
1 | Dev2 | master |
и выполняет коммит без создания новой ветки: изменения настолько критичны, что касаются всех и сразу.
Естественно, что Dev2 сообщает об изменениях всем причастным (каким образом – неважно) и в обязательном порядке называет имя ветки, которую надо обновлять. Каковы будут действия пользователей системы и как это отразится на состоянии из репозиториев?
Пойдем, пожалуй, по алфавиту: Dev1, Dev3, Test1, User. Пользовательский репозиторий затрагивается обязательно: изменения касаются все, еще раз повторюсь.
Рассмотрим последовательность действий пользователя Dev1:
Пользователю не надо ничего отправлять в серверный репозиторий, поэтому он нажимает кнопку |
|
Поскольку известно, что надо обновлять ветку master, в выпадающем списке выбираем именно ее. Для гарантии безошибочности работы настоятельно рекомендуется нажать кнопку |
|
Обратите внимание: обновление осуществляется в локальную ветку Dev1Br#1, как нам и требуется. Если в процессе работы пользователь “перескакивал” между ветками, то синхронизация будет выполняться с текущей. | |
Таким образом будет выглядеть окно SourceTree. Обратите внимание: активирована ветка Dev1Br#1 |
А что же будет твориться в каталоге репозитория Dev1?
Во-первых, оба файла на месте:
Во-вторых, проверим измененявшийся файл Dev1master.txt:
1 2 | Dev1 | master Dev2 | master |
Отлично, сработало! Теперь такие же действия выполняет Dev3, Test1 и User, расписывать уже не буду – картина в результате будет полностью аналогична.
В контекстном меню выбирается “Перейти на master”, либо двойной клик на имени локальной ветки, на которую планируется перейти | |
Вызвать контекстное меню для ветки Dev1Br#1 и выбрать |
|
В диалоговом окне нажать ОК |
Если удаление не происходит, это означает, что предварительно необходимо для этой ветки выполнить коммит и отправить его во внешнее хранилище – Git не позволит просто так уничтожить ветку.
Чтобы ветка во внешнем хранилище не мешалась, не помешает удалить и ее:
Естественно, что о таких действиях тоже надо сообщить всем пользователям системы.
Другие пользователи могут увидеть внешнюю ветку, но при выполнении первого же обновления с серверного репозитория она перестанет отображаться.
Теперь возьмем пользователя Dev3: его ветка оказалась настолько удачной и нужной, что ее следует “внедрить” в рабочую (master). На данный момент SourceTree для Dev3 выглядит так:
Dev3, во-первых, отправляет свои изменения ветки на внешнее хранилище (если этого еще не было сделано). Описывать уж не стану – выше это было рассмотрено достаточно подробно.
Далее – он получает ветку master с внешнего хранилища и активирует ее:
Выполняет слияние своей ветки с master:
И удаляет свою ветку.
- Отработанные ветки крайне желательно удалять. Лишняя информация
- Имена веткам стоит задавать более-менее осмысленные: через полгода вспомнить, о чем была ветка “Ветка1634″ – нереально
- Из отслеживания крайне желательно убирать backup- и debug-файлы и каталоги (это отдельная песня, в качестве подсказки – гугл, gitignore)
- Использовать кнопку
[Слияние] я бы не стал (по крайней мере пока): неочевидно, что с чем сливается и что станет в результате
Исправил несколько опечаток.
>Ветка master существует в любом репозитории и считается “главной”. Ее нельзя уничтожить, ее нельзя переименовывать.
Ветка master ничем не отличается от любых других веток и набор операций к ней может быть применён такой же, как и к любой другой ветке.
Переименование ветки:
git branch -m
Удаление ветки:
git branch -d
>…и выполняет фиксацию внесенных изменений в своем репозитории. При выполнении фиксации изменений (коммите) обязательно пишется комментарий и устанавливается галочка
Я не понимаю тебя: ты дважды употребляешь слово "фиксация" но применительно к двум совершенно разным операциям: к добавлению изменений в staged area, а так же к последующему выполнению commit. Это "четыре совершенно разных человека".... Кстати, я вообще не вижу упоминания о staged area, которая является одним из ключевых понятий git. Вот хорошая картинка по теме: https://git-scm.com/book/en/v2/book/02-git-basics/images/lifecycle.png
>Любой другой пользователь получает сообщение (как – неважно: по телефону, через почту и т.д.) о том, что в серверный репозиторий были внесены изменения.
Имхо - лучше обезопасить себя от получения подобных SMS в 2 часа ночи. Гораздо целесообразней было бы выполнять git fetch --all тогда, когда разработчик сам захочет проверить состояние оригинального репозитория. Решать о том, когда именно выполнять git pull - ему так же виднее, чем повесить это на автоматизацию.
>Просто обновилась локальная ветка master, и Git предполагает, что ее надо срочно синхронизировать с серверной. Ни в коем случае этого делать нельзя!
Раз в год и палка стреляет. Т.о. у тестеровщика не должно быть прав для записи к обозначенному "серверному" репозиторию. имхо.
>Имена веткам стоит задавать более-менее осмысленные: через полгода вспомнить, о чем была ветка “Ветка1634″ – нереально
По хорошему ветка должна быть одна. Дополнительные ветки являются временными и в конце концов сливаются с основной, после чего удаляются. Имя ветки вроде "issue #123" вполно информативное - это ветка, исправляющая баг, зарегистрированная в багтрекере под указанным номером. Вряд ли дополнительные ветки будут жить долго, но информативные имена важны и для того, чтобы если ты вдруг выиграешь миллион евро и положишь болт на работу, то подхвативший упавшее знамя программист смог бы разобраться что к чему. Кстати, ключевые этапы разработки принято помечать тэгами через git tag. Затем тэги легко просматривать, фильтровать и т.п.
Андрей, у меня стояла задача использовать консольный вариант в минимальном объеме. Поэтому, к сожалению, команды приходится отодвигать в сторону.
В остальном спорить не стану - у тебя больше опыта по использованию систем контроля версий хотя master я бы не трогал в смысле переименования
>Поэтому, к сожалению, команды приходится отодвигать в сторону.
Консольные команды я использовал, чтобы показать, что те или иные операции возможны. Поскольку обозначенные операции распространены, то скорее всего они реализованы и в SourceTree посредством тех или иных кнопок.
>хотя master я бы не трогал в смысле переименования
Согласен. Я показывал не то, как надо поступать с master, а то, что она не отличается от др. веток и переименовать/удалить её так же возможно.
>у тебя больше опыта по использованию систем контроля версий
Я этого не говорил. На данный момент я сам только изучаю Git по книжке и документации.
> 3 человека — разработчики. Могут и постоянно вносят изменения в серверный репозиторий
Фатальная ошибка: центральный репозиторий, доступный для редактирования всем разработчикам - это отстой. Хаос гарантирован. Лёша, если тебя реально интересует процесс организации взаимодействия и предоставлении различных уровней доступа (при использовании Git), то рекомендую прочесть эту главу: http://git-scm.com/book/en/v2/Distributed-Git-Distributed-Workflows
> Разработчики и тестировщики могут использовать любые ветки. Пользовательский репозиторий использует и синхронизирует только основную ветку — master
Этих фраз я вообще не понял... Тем более, что далее по тексту заметки ты используешь не только master, но и ветки Dev1Br#1, Dev3Br#1.
> Ниже приведена последовательность создания репозитория для разработчика (Dev1).
Ну, если уж ты зашёл в консоль для создания пустого серверного репозитория, то и свои Dev1..3, а так же test1 мог бы сразу там же создать (обрати внимение на то, как указаны пути):
git init --bare /c/Repositories/Server
git clone /c/Repositories/Server /c/Repositories/Dev1
git clone /c/Repositories/Server /c/Repositories/Dev2
git clone /c/Repositories/Server /c/Repositories/Dev3
git clone /c/Repositories/Server /c/Repositories/Test1
ну и затем открывать их в SourceTree. В итоге: меньше кликов мышки и получение результата быстрее.
> Чтобы не усложнять ситуацию попусту, будем работать с текстовыми файлами – их и контролировать проще, и менять.
Это не "чтобы не усложнять ситуацию", а единственный тип файлов, для которого может выполняться СЛИЯНИЕ. Для НЕ текстовых файлов выполняется ЗАМЕНА. Т.о. работать СОВМЕСТНО над НЕ текстовыми файлами не получится.
> Ветка master существует в любом репозитории и считается “главной”. Ее нельзя уничтожить, ее нельзя переименовывать.
Как я уже писал в предыдущем комменте - это не так. На всякий случай показываю пример того, как master можно переименовать или удалить:
================================================================================
bushm@DESKTOP-ISD2NUH MINGW64 /d/sandbox/sample (master)
$ git branch
* master
bushm@DESKTOP-ISD2NUH MINGW64 /d/sandbox/sample (master)
$ git branch -m master basic
bushm@DESKTOP-ISD2NUH MINGW64 /d/sandbox/sample (basic)
$ git branch
* basic
bushm@DESKTOP-ISD2NUH MINGW64 /d/sandbox/sample (basic)
$ git branch -m basic master
bushm@DESKTOP-ISD2NUH MINGW64 /d/sandbox/sample (master)
$ git branch
* master
bushm@DESKTOP-ISD2NUH MINGW64 /d/sandbox/sample (master)
$ git checkout -b main
Switched to a new branch 'main'
bushm@DESKTOP-ISD2NUH MINGW64 /d/sandbox/sample (main)
$ git branch
* main
master
bushm@DESKTOP-ISD2NUH MINGW64 /d/sandbox/sample (main)
$ git branch -d master
Deleted branch master (was 4b74f49).
bushm@DESKTOP-ISD2NUH MINGW64 /d/sandbox/sample (main)
$ git branch
* main
bushm@DESKTOP-ISD2NUH MINGW64 /d/sandbox/sample (main)
================================================================================
> При выполнении фиксации изменений (коммите) обязательно пишется комментарий и устанавливается галочка [Сразу отправить изменения в ...]
"Сразу отправить изменения в ..." - плохой подход. Вспоминаем всё ту же ссылку которую я показал в начале этого коммента.
> Если файл появился, был изменен или удален – система контроля версий в лучшем случае скажет автору, что “файл изменен”. Об этих изменениях ни один другой пользователь системы не узнает. Чтобы сообщить об этом, а заодно и предоставить обновленную версию файла (файлов), и надо выполнять фиксацию изменений (коммит).
Нет, ты лепишь вместе два понятия: git commit и git push... Первая команда фиксирует изменения в твоём локальном репозитории, а вторая отправляет изменения на сервер. Лёша, выполнение первой операции без второй не достаточно для того, чтобы твои сокамерники увидели изменения (если они не подключены к твоему локальному репозиторию через git remote). Любой человек, имеющий доступ к репозиторию, может легко получить информацию обо всех изменениях как на уровне файловой системы, с пояснением о том, какой файл добавлен/изменён/удалён, так и полную информацию о том, что именно было изменено в каждом (или интересующем тебя) файле, т.е. просмотреть патч файла.
> Другие пользователи выполняют операцию получения (push) – и у них появляется свежая версия файла.
Неверно. Команда git push заливает изменения, а не скачивает их. "Другим пользователям" нужно либо выполнить git fetch дабы закачать себе обновления и иметь возможность потестить их, после чего могут принять решения о том, стоит ли выполнять слияние; либо сразу выполнить команду git pull, которая закачает изменения и тут же автоматом попытается выполнить слияние.
> Любой другой пользователь получает сообщение (как – неважно: по телефону, через почту и т.д.) о том, что в серверный репозиторий были внесены изменения.
Пользователь и сам может проверить состояние серверного репозитория (поскольку ты в него лепишь всё без разбору) и увидеть все изменения, выполненные с момента его последнего получения оттуда данных (при условии, что серверный репозиторий подключен к локальному посредством git remote, обычно по умолчанию ему назначается имя origin; посмотреть подключенные репозитории можно с помощью команды git remote -v):
git remote show origin
> В диалоговом окне выбирается синхронизируемая ветка (в данном случае – master) и указывается, что после слияния выполнить фиксацию изменений
А если закачиваемый контент содержит чепуху? Лучше сначала потестить то, что скачал с удалённого репозитория и только убедившись, что изменённая версия работает корректно, заливать себе в локальный репозиторий и затем, при желании, выполнять слияние. Результатом слияния будет новый коммит.
> Dev1 создает отдельную ветку Dev1Br#1
Не самое информативное имя. Имхо - имя ветки должно отображать смысл того, ради чего она создана.
> При фиксации изменений галочка “Сразу отправить изменения в…” установлена.
Как я уже писал выше, сразу всё отправлять в центральный репозиторий - очень плохое решение.
> Он отправляет пользователю Test1 сообщение с указанием названия ветки, которую надо получить: Dev1Br#1.
Насколько я знаю, "получает" Test1 по любому полную версию сетевого репозитория а не только ветку Dev1Br#1, т.к. git fetch качает не какую-то отдельную ветку, но весь репозиторий в целом.
> У пользователя Test1 до сих пор только одна его ветка – master:
Всё правильно. А ты ожидал другого?
> Импорт ветки Dev1Br#1 не изменил название локальной ветки, не создал новую ветку – ни-че-го!
Ты так удивляешься... :))))) С какого перепугу ты ожидал получить изменения в локальной? На скрине у тебя показано, что локальная ветка master отстаёт от ветки origin\Dev1Br#1 на один коммит. Решение о переименовании или слиянии принимает юзер. Ты пока не давал ни одной из этих команд.
> Импорт ветки Dev1Br#1 не изменил название локальной ветки, не создал новую
> ветку – ни-че-го! Изменения из внешней ветки origin\Dev1Br#1 просто были
> помещены поверх старого состояния репозитория
> Состояние репозитория в результате получения изменений из Dev1Br#1 стало таким:
Маленький ликбез: у тебя произошло переключение на на ветку origin\Dev1Br#1 - это НЕ ЛОКАЛЬНАЯ ветка. Переключение на ветку автоматом заменяет содержимое рабочего дерева (working tree), т.е. содержимое каталога, который ты смотришь на скрине. В этот момент у тебя HEAD указывает на последний коммит ветки origin\Dev1Br#1. Так что ничего удивительного не произошло.
> SourceTree (точнее, Git) считает, что ему есть что отправить в серверный репозиторий:
Я не знаю, чего там "считает" SourceTree, но возможно ты не верно интерпретируешь то, что видишь на показанном скрине.
> Хотя, конечно, это не так. Просто обновилась ЛОКАЛЬНАЯ ветка master, и Git предполагает, что ее надо срочно синхронизировать с серверной.
Нет, ЛОКАЛЬНАЯ ветка master не обновилась. Как я уже писал выше, произошло ПЕРЕКЛЮЧЕНИЕ на ветку origin\Dev1Br#1 (т.е. HEAD указывает теперь на origin\Dev1Br#1).
> Ни в коем случае этого делать нельзя! Ветку master в серверном (общем) репозитории обновляют только разработчики
А нефиг кому попало раздавать права для записи в центральный репозиторий! Настрой ACL для каталога так, чтобы тестер имел права только на чтение. Конкретно В ДАННОМ случае ничего не случится страшного даже если юзер нажмёт кнопку записи на сервер - он просто не сможет залить что-либо на сервер до тех пор, пока предварительно не синхронизируется с текущим состоянием сервера. На данный момент его локальный репозиторий отличается от серверного.
> Таким образом, получается, что Test1 “подхватил” новые файлы и из ветки Dev1Br#1, и из ветки Dev3Br#1. При этом сам пользователь Test1 находится на своей собственной ветке master. Фактически произошло слияние (merge) веток Dev1Br#1 и Dev3Br#1 в локальной ветке master пользователя Test1.
Маленькая поправочка: не "Test1 "подхватил"", а ты своими шаловливыми пальчиками натворил чего не следовало... Согласно содержимому каталога, которое ты показываеш на скрине, очень пхоже на то, что ты действительно выполнил слияние. Вот только вопрос: ЗАЧЕМ??? Я очень сомневаюсь что Test1 хотел именно этого...
>В общем и целом, сейчас ситуация, кажется, нормализовалась: у всех пользователей именно то, что необходимо, все синхронизировано и все работает.
Ничего не нормализовалось: у test1 в ветке каша,как мы помним и проскакивать этот момент не следует... Все дальнейшие перечисленные шаги повествуют о том, как делать НЕ НАДО. Вспоминаем всю ту же ссылку, приведённую мною в начале комментария.
Давай вернёмся к тому моменту, когда test1 получил кашу: слияние трёх веток вместо того, чтобы иметь возможность протестировать каждую в отдельности. Рассмотрим это более подробно, дабы показать что ты по факту сделал и что же нужно было сделать на самом деле... Сделаем так: я оформлю это в виде подробно комментированного sh-скрипта, который следует запускать в Git Bash. Скрипт выложил у себя в блоге, т.к. в комментах данной заметки это будет нечитабельно. Ссылка на скрипт: http://bushman-andrey.blogspot.ru/2015/09/blog-post.html#more
> Разработчики и тестировщики могут использовать любые ветки. Пользовательский репозиторий использует и синхронизирует только основную ветку — master
Всё, до меня дошла эта фраза.
Я подумал, что пожалуй стоит добавить некоторые пояснения по части своей позиции в обозначенном вопросе и добавил их в обозначенную мною выше запись блога (на всякий случай).