Опубликовываю последовательность действий, оформленных в виде скрипта, согласно тому, как обещал в комментариях здесь. Как я уже писал, изложенный в ссылке пример совместной работы, на мой взгляд, является, к сожалению, плохим. Я считаю, что Алексей показал даже скорее пример того, как НЕ НАДО делать (буду называть вещи своими именами), т.к. работая с Git использовать стиль работы Subversion — это значит ограничивать себя (ИМХО). :((( Тем, кому интересна тема коллективной работы с использованием Git настоятельно рекомендую прочесть эту главу.

UPD: Добавляю информацию о такой новости.

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

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

Ещё мне очень не понравилось то, что в своём примере Алексей заливал в центральный репозиторий не конечный результат работы пользователей, а каждый их «чих» (т.е. промежуточные состояния веток, не являющихся основными и в конечном счёте подлежащие слитию с веткой master). Я считаю, что в центральном репозитории должна храниться стабильная («устаканившаяся») версия проектов. А все коммиты и ветки, предшествующие появлению этой самой стабильной версии заливать в центральный репозиторий не следует (для этого у каждого девелопера должны быть собственные рабочий и публичный репозитории), т.к. это его замусоривает. Делая git fetch я не хочу видеть разнообразие посторонних веток (находящихся на различных промежуточных стадиях разработки), которые туда напихали др. программисты. Мне (да наверное и всем остальным) не хочется заливать себе в репозиторий весь этот лишний (поскольку не мой) набор дополнительных, промежуточных веток, над которыми я не работаю. Когда девелоперы доделают свою работу, то зальют результат в основную ветку — именно этот самый конечный результат мне интересен, когда я заливаю себе код с центрального репозитория.

А для того, чтобы test1 мог тестировать ветки юзеров dev1 и dev2, они просто дают ему ссылки на свои public репозитории и говорят, какие ветки нужно протестировать. test1 спокойно тестирует эти ветки по отдельности. Если dev1 и dev2 хотят взаимодействовать между собой, в процессе разработки, то они делают это по тому же принципу — через свои public репозитории. Нет необходимости загаживать центральный репозиторий лишним хламом. ИМХО.

Далее скрипт с подробными пояснениями, о котором я упоминал в комментарии здесь, а так же консольный вывод:

# test.sh
# Кодировка ANSI.
# © Андрей Бушман
# ВАЖНО! Этот скрипт следует запускать из консоли Git Bash.
# Скрипт относится к моему комментарию со странички
# http://autolisp.ru/2015/04/27/sourcetree-branches-2/
# Этот код показывает, как test1 получил кашу вместо возможности тестировать
# отдельные ветки индивидуально и то, как это должно было делаться.

# Все наши эксперименты будем выполнять в подкаталоге ./kulik
cd kulik
# Создаём голый серверный репозиторий и на его основе 4 рабочих.
git init —bare server
git clone server dev1
git clone server dev2
git clone server dev3
git clone server test1

# юзер dev1 в ветке master создаёт файл Dev1master.txt, редактирует его, делает
# коммит и заливает изменения на центральный репозиторий
cd dev1
echo Dev1 \| master > Dev1master.txt
git add Dev1master.txt
git commit -m «dev1 master»
git push origin master

# юзеры dev2, dev3 и test1 синхронизируют свои рабочие репозитории с центральным
cd ../dev2
git pull —all
cd ../dev3
git pull —all
cd ../test1
git pull —all

# юзер dev1 создаёт ветку Dev1Br#1 и новый файл (Dev1br1.txt) в ней. Делает
# коммит, после чего новую ветку заливает на центральный репозиторий.
cd ../dev1
git checkout -b Dev1Br#1
echo 2>Dev1br1.txt
git add Dev1br1.txt
git commit -m «Dev1Br#1 commit#1»
git push origin Dev1Br#1

# юзер dev3 создаёт ветку Dev3Br#1 и новый файл (Dev3br1.txt) в ней. Делает
# коммит, после чего новую ветку заливает на центральный репозиторий.
cd ../dev3
git checkout -b Dev3Br#1
echo 2>Dev3br1.txt
git add Dev3br1.txt
git commit -m «Dev3Br#1 commit#1»
git push origin Dev3Br#1

# юзер test1 забирает все изменения из центрального репозитория (origin) для
# тестирования, но не заливает их в свой рабочий репозиторий (при желании, он
# мог бы и залить их в рабочий репозиторий, но в данном случае это не
# обязательно). Тем более он не делает никаких слияний (как это сделал Алексей).
# test1 будет лишь тестировать ветки origin/Dev1Br#1 и origin/Dev3Br#1 ПО
# ОТДЕЛЬНОСТИ, дабы выдавать замечания отдельно для dev1 и отдельно для dev3.
cd ../test1
git fetch origin
# переключился на ветку Dev1Br#1
git checkout origin/Dev1Br#1
# смотрит состав файлов
echo test1: files of the origin/Dev1Br#1 branch:
ls
# переключился на ветку Dev3Br#1
git checkout origin/Dev3Br#1
# смотрит состав файлов
echo test1: files of the origin/Dev3Br#1 branch:
ls

# ==============================================================================
# Как видим — мы не получили той каши, которую получил Алексей для test1,
# непонятно зачем объединив ветки Dev1Br#1 и Dev3Br#1 с веткой origin своего
# рабочего репозитория.
#
# Продемонстрированный мною выше код не выполняет этого слияния (за
# ненадобностью). test1 спокойно переключается между ветками origin/Dev1Br#1 и
# origin/Dev3Br#1 выполняя их раздельное тестирование.
#
# test1 может потестить ветки не заливая их себе в рабочий репозиторий. Он так
# же может выполнять в них коммиты и, если в дальнейщем примет такое решение —
# залить их себе в рабочий репозиторий не объединяя их при этом (за
# ненадобностью) со своей веткой origin.
# ==============================================================================

# смотрим список ВСЕХ веток , доступных test1 в его рабочем репозитории (в т.ч.
# и ветки центрального репозитория, не слитые в рабочий репозиторий).
git branch -a

# Если test1 захотел бы залить себе в рабочий репозиторий все текущие изменения,
# присутствующие на сервере, то он вместо команды
#   git fetch origin
# выполнил бы сразу команду
#   git pull —all
# А если бы он к тому же захотел бы сделать то, что сделал Алексей, т.е. слить
# ветки origin/Dev1Br#1 и origin/Dev3Br#1 в ветку origin, то затем test1
# выполнил бы следующее:
#   git checkout master
#   git merge origin/Dev1Br#1
#   git merge origin/Dev3Br#1
# В результате он бы получил то же самое, что и Алексей, т.е. полную кашу…

# юзер dev2 вносит изменение в файл Dev1master.txt ветки master. Затем делает
# коммит и заливает изменения в центральный репозиторий.
cd ../dev2
git pull —all
echo Dev2 \| master >> Dev1master.txt
git commit -am «dev2master commit#1»
git push origin master
# смотрим содержимое файла Dev1master.txt текущей ветки (origin) рабочего
# репозитория dev2
cat Dev1master.txt

# юзер dev1 заливает себе все текущие изменения с центрального репозитория.
# Затем переключается на ветку master и смотрит содержимое файла Dev1master.txt
cd ../dev1
git checkout master
git pull —all
cat Dev1master.txt

# юзер dev3 заливает себе все текущие изменения с центрального репозитория.
# Затем переключается на ветку master и смотрит содержимое файла Dev1master.txt
cd ../dev3
git checkout master
git pull —all
cat Dev1master.txt

# юзер test1 заливает себе все текущие изменения с центрального репозитория.
# Затем переключается на ветку master и смотрит содержимое файла Dev1master.txt
cd ../test1
git checkout master
git pull —all
cat Dev1master.txt 

Консольный вывод выглядит следующим образом:

bushm@DESKTOP-ISD2NUH MINGW64 /d/sandbox
$ /d/sandbox/test.sh
Initialized empty Git repository in D:/sandbox/kulik/server/
Cloning into ‘dev1’…
warning: You appear to have cloned an empty repository.
done.
Cloning into ‘dev2’…
warning: You appear to have cloned an empty repository.
done.
Cloning into ‘dev3’…
warning: You appear to have cloned an empty repository.
done.
Cloning into ‘test1’…
warning: You appear to have cloned an empty repository.
done.
warning: LF will be replaced by CRLF in Dev1master.txt.
The file will have its original line endings in your working directory.
[master (root-commit) a685643] dev1 master
warning: LF will be replaced by CRLF in Dev1master.txt.
The file will have its original line endings in your working directory.
 1 file changed, 1 insertion(+)
 create mode 100644 Dev1master.txt
Counting objects: 3, done.
Writing objects: 100% (3/3), 235 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To D:/sandbox/kulik/server
 * [new branch]      master -> master
Fetching origin
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From D:/sandbox/kulik/server
 * [new branch]      master     -> origin/master
Fetching origin
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From D:/sandbox/kulik/server
 * [new branch]      master     -> origin/master
Fetching origin
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From D:/sandbox/kulik/server
 * [new branch]      master     -> origin/master
Switched to a new branch ‘Dev1Br#1’

[Dev1Br#1 8d3df6c] Dev1Br#1 commit#1
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 Dev1br1.txt
Counting objects: 3, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 283 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To D:/sandbox/kulik/server
 * [new branch]      Dev1Br#1 -> Dev1Br#1
Switched to a new branch ‘Dev3Br#1’

[Dev3Br#1 959a19b] Dev3Br#1 commit#1
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 Dev3br1.txt
Counting objects: 3, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 285 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To D:/sandbox/kulik/server
 * [new branch]      Dev3Br#1 -> Dev3Br#1
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 5 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (5/5), done.
From D:/sandbox/kulik/server
 * [new branch]      Dev1Br#1   -> origin/Dev1Br#1
 * [new branch]      Dev3Br#1   -> origin/Dev3Br#1
Note: checking out ‘origin/Dev1Br#1’.

You are in ‘detached HEAD’ state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b <new-branch-name>

HEAD is now at 8d3df6c… Dev1Br#1 commit#1
test1: files of the origin/Dev1Br#1 branch:
Dev1br1.txt  Dev1master.txt
Previous HEAD position was 8d3df6c… Dev1Br#1 commit#1
HEAD is now at 959a19b… Dev3Br#1 commit#1
test1: files of the origin/Dev3Br#1 branch:
Dev1master.txt  Dev3br1.txt
* (HEAD detached at origin/Dev3Br#1)
  master
  remotes/origin/Dev1Br#1
  remotes/origin/Dev3Br#1
  remotes/origin/master
Fetching origin
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 5 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (5/5), done.
From D:/sandbox/kulik/server
 * [new branch]      Dev1Br#1   -> origin/Dev1Br#1
 * [new branch]      Dev3Br#1   -> origin/Dev3Br#1
Already up-to-date.
warning: LF will be replaced by CRLF in Dev1master.txt.
The file will have its original line endings in your working directory.
[master warning: LF will be replaced by CRLF in Dev1master.txt.
The file will have its original line endings in your working directory.
363d1f3] dev2master commit#1
warning: LF will be replaced by CRLF in Dev1master.txt.
The file will have its original line endings in your working directory.
 1 file changed, 1 insertion(+)
Counting objects: 3, done.
Writing objects: 100% (3/3), 275 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To D:/sandbox/kulik/server
   a685643..363d1f3  master -> master
Dev1 | master
Dev2 | master
Switched to branch ‘master’
Your branch is up-to-date with ‘origin/master’.
Fetching origin
remote: Counting objects: 6, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 6 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (6/6), done.
From D:/sandbox/kulik/server
   a685643..363d1f3  master     -> origin/master
 * [new branch]      Dev3Br#1   -> origin/Dev3Br#1
Updating a685643..363d1f3
Fast-forward
 Dev1master.txt | 1 +
 1 file changed, 1 insertion(+)
Dev1 | master
Dev2 | master
Switched to branch ‘master’
Your branch is up-to-date with ‘origin/master’.
Fetching origin
remote: Counting objects: 6, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 6 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (6/6), done.
From D:/sandbox/kulik/server
   a685643..363d1f3  master     -> origin/master
 * [new branch]      Dev1Br#1   -> origin/Dev1Br#1
Updating a685643..363d1f3
Fast-forward
 Dev1master.txt | 1 +
 1 file changed, 1 insertion(+)
Dev1 | master
Dev2 | master
Previous HEAD position was 959a19b… Dev3Br#1 commit#1
Switched to branch ‘master’
Your branch is up-to-date with ‘origin/master’.
Fetching origin
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From D:/sandbox/kulik/server
   a685643..363d1f3  master     -> origin/master
Updating a685643..363d1f3
Fast-forward
 Dev1master.txt | 1 +
 1 file changed, 1 insertion(+)
Dev1 | master
Dev2 | master

bushm@DESKTOP-ISD2NUH MINGW64 /d/sandbox
$