Git: разворачиваем репозиторий для группы разработчиков

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

Исходные
  1. Есть NN разработчиков
  2. Есть сервер на котором эти разработчики все вместе трудятся каждый под своей учетной записью
  3. Необходимо на этом сервере разместить git репозиторий с общим доступом для всех этих разработчиков
Порядок действий
  1. Создаем отдельного пользователя
    useradd git
    
    Задаем ему зубодробильный пароль и радостно забываем его.
  2. Теперь надо авторизоваться под этим пользователем (вообще можно и от рута все сделать, но так проще будет) и создать первый общий репозиторий
    su -l git # авторизуемся как пользователь git (тут по идее нужен рутовый пароль)
    mkdir my-repo-name # создаем папку под репозиторий
    chmod 770 my-repo-name # полный доступ владельцу и группе
    chmod +s my-repo-name # устанавливаем в этой папке наследование прав для группы
    cd my-repo-name # заходим в папку репозитория
    git init --bare --shared # инициализировали репозиторий (см. git help init)
    
    С репозиторием все. Теперь нужно раздать доступ.
  3. Всех пользователей, которые будут иметь доступ к git нужно добавить в соответствующую группу. Для этого поглядим что за группа у пользователя git
    whoami # git
    groups # git
    
    Теперь каждого кому нужен доступ в git добавляем в эту группу, делать это нужно с рутовыми привилегиями понятное дело
    usermod -a -G git user_login # добавляем пользователя в группу (см. usermod --help)
    
    Если решим у пользователя доступ к git отобрать надо будет просто его убрать из этой группы, вот так:
    deluser user_login git # удаляем пользователя из группы (см. deluser --help)
    groups user_login # смотрим в каких группах сейчас выбранный пользователь состоит
    
Все, по паролю доступ к git уже есть, то есть вот в такой манере
mkdir test-repo # создали папку под репозиторий
cd test-repo # зашли в неё
git clone ssh://user_login@my-host.com/home/git/test-repo . # клонируем репозиторий
git remote -v # куда смотрит локальный репозиторий (см. git --help remote)
Правда, при каждом push или pull запросе нужно будет вводить пароль. Казалось бы и так нормально, но..

ЕСТЬ СПОСОБ УДОБНЕЕ :)

Чтобы не вводить на каждый pull или push пароль, можно прикрутить доступ по публичному ключу. Для этого каждый разработчик должен сгенерить на своей локальной машине публичный ключ и прислать его нам. Сделать это можно так
ssh-keygen -t rsa # тут нас попросят задать имя ключа и ключевую фразу, нужно её запомнить
После этой процедуры сгенерится два ключа ~/.ssh/key-name и ~/.ssh/key-name.pub который собственно и является публичным ключем. Вот эти публичные ключи и должны прислать вам разработчики. Теперь на сервере их нужно добавить к авторизованным ключам для пользователя git.
su -l git # заходим под пользователем гита
cd ~ # переходим в домашнюю папку
mkdir .ssh # создаем папку для конфигов если её нет
cat key-name.pub >> .ssh/authorized_keys # добавляем в конец файла авторизованных ключей
Теперь на стороне разработчика нужно один раз авторизоваться с указанием конкретного ключа
ssh git@dev-host.com -i ~/.ssh/key-name # попросят ввести пароль от ключа, который был задан при генерации
Все, после этой процедуры постоянно вводить пароль для авторизации не потребуется. Все разработчики будут авторизовываться по ключу как пользователь git, а значит заморока с наследованием прав на папки больше не нужна. И теперь для добавления нового репозитория достаточно просто сделать следующее
su -l git # вошли как пользователь git
cd ~ # перешли в хомяк
mkdir new-repo # папка под репозиторий
cd new-repo # зашли в папку
git init --bare # создали пустой репозиторий
Такой репозиторий тут же станет доступен всем пользователям которые ходят по публичному ключу. Для разработчиков это будет выглядеть вот так:
mkdir new-project
cd new-project
git clone git@dev-host.com:new-repo .
Осталось добавить, что нужно репозитории регулярно резервировать, "во избежание" так сказать. Чтобы отобрать у пользователя доступ к гиту во втором случае (с публичным ключем), нужно определить его публичный ключ и удалить его из ~/.ssh/authorized_keys на сервере. Все ключи подписаны, так что возможно получится вычислить его по подписи. Если же не удастся.. то придется либо перезаписывать всем доступы, либо последовательно их отключать и смотреть у кого отвалится доступ. Как-то так :) В заметке нет ничего нового, почти все это можно прочитать здесь и здесь.
p.s.: хорошо бы заглушить вход в консоль для пользователя git.
sudo vim /etc/passwd
Ищем строку вида: git:x:1000:1000::/home/git:/bin/sh и приводим её к вот такому виду git:x:1000:1000::/home/git:/usr/bin/git-shell. Надо это затем, чтобы пользователь git не мог зайти в консоль и погрохать все репозитории, например.

Git: командная работа

Все приличные команды разработки работают с таск-трекерами, сейчас как мне кажется стандартом стал Jira (ну или другой трекер, суть та же для примера). Поэтому здесь для примера буду писать "таск" или "тикет" подразумевая задачу в Jira. Хорошим тоном сейчас считается указание идентификатора таска в каждом коммите который для решения этого таска делается. Не претендую на истину в последней инстанции, но как один из возможных вариантов опишу здесь свой опыт. Могу дополнить только что для SVN пользовался практически той же схемой, за исключением того, что ветвление на каждую задачу значительно безопаснее как мне кажется, но об этом ниже.

Итого:
  1. В команде есть NN разработчиков
  2. Все разработчики используют единый репозиторий git
  3. В репозитории существуют две (может быть для каких-то целей и больше) основные ветки master и dev
  4. Ветка master существует исключительно для стабильных версий продукта, работающих только в боевой (продакшн) зоне. То есть эта ветка считается последней стабильной версией ПО с которой работают конечные пользователи. Перед каждым релизом на эту ветку ставится тег, с которого собственно и выкатывается стабильная версия. В случае каких-то проблем на боевой откатывается на предыдущий стабильный тег этой ветки (если такой откат возможен конечно после внесенных изменений в БД или какие-то другие жизненно важные части по).
  5. Ветка dev (development) служит для аккумулирования изменений всех разработчиков и собственно обмена последними стабильными изменениями между разработчиками.
  6. Для каждой новой задачи разработчик создает новую ветку от текущей dev и работает в ней, с нее же получая для своей задачи обновления по необходимости
  7. При передаче в тест разработчик синхронизирует свою ветку с текущей dev и отдает на тестирование свою задачу именно на ветке этой задачи
  8. После тестирования разработчик мерджит свою ветку в dev и либо сам проверяет что ничего не сломалось уже на dev либо это опять проверяют тестеры
  9. Перед релизом dev ветка мерджится на master, после чего на мастере ставится тег с которого производится деплой свежего релиза
Вот таким вот нехитрым способом все происходит. Схема вполне рабочая, но возможно можно что-то улучшить, сейчас - как есть.
Что касается git, то описанный процесс происходит примерно так:
  1. Клонируем репозиторий в папку (если необходимо)
    cd /my/project/folder
    git clone ssh://luke@tatuin.com/git/project-repo .
    
  2. Переключаемся на dev ветку и обновляем ее
    git checkout dev
    git pull origin dev
    
  3. Ветвимся от dev в ветку под конкретную задачу (для примера имя ветки здесь это ключ от таска в Jira)
    git checkout -b task-123
    git status
    
    Статус должен показать что вы теперь на ветке task-123
  4. Теперь спокойно работаем на этой ветке и все коммиты по задаче идут на неё
    git commit -am 'task-123 my task commit'
    
  5. Если задача затянулась, то можно подтянуть на ветку обновления от других разработчиков
    git pull origin dev
    
    Для того чтобы сделать pull в этой ситуации, нужно либо все свои изменения закомитить на ветку, либо воспользоваться git stash чтобы спрятать свои изменения, а потом git stash apply чтобы достать свои изменения назад. При каждом коммите нужно тщательно смотреть diff.
  6. Когда задача закончена, и все коммиты по ней отправлены на соответствующую ветку отправляем свою ветку в общий репозиторий
    git push origin task-123
    
    Таким образом ветка станет доступна другим разработчикам и тестерам. Все, в этот момент останавливаем прогресс в таск-трекере и передаем задачу в тестирование.
  7. Посмотреть все лоакльные ветки можно вот так
    git branch
    
    Посмотреть все ветки в удаленном репозитории можно вот так
    git remote show origin
    
  8. Теперь задачу нам вернули из теста, с ней все хорошо, нужно вмерджить свои изменения в dev ветку
    git checkout dev
    git pull origin dev
    git merge task-123
    
    Тут мы переключаемся на dev ветку, обновляем её содержимое из удаленного репозитория и собственно мерджим в ветку dev свою ветку task-123. При необходимости все конфликты разрешаем ручками и коммитим, но уже на dev. Теперь осталось только отправить наши изменения на dev-e в удаленный репозиторий
    git push origin dev
    
  9. Теперь желательно прибрать за собой, то есть убрать больше не нужные ветки. Сначала удалим ветку из удаленного репозитория
    git push origin :task-123
    
    А теперь ветку в локальном репозитории
    git branch -d task-123
    
На этом собственно и все. В начале работы по такой схеме часто возникают типовые ошибки:
  • Работа на другой ветке (забыл переключиться в спешке), тут может выручить stash. С его помощью можно спрятать изменения, а потом выложить их на другой ветке после переключения на неё, важно внимательно смотреть в diff при коммитах чтобы не потереть чужое.
  • Пуш в другую ветку, тут нужно просто быть внимательным при каждом push-e.
  • Ошибки связанные с мерджем, тут тоже нужно помнить что нужно сначала перейти в ветку в которую будем мерджить свои изменения, а уже потом мерджить в неё свою ветку. Нужно просто быть внимательным.
При внешней сложности через пару недель все это прочно оседает в голове и делается почти автоматически. Важно просто быть внимательным в начале работы по этой схеме чтобы не наломать дров в общем репозитории. Особо внимательно стоит смотреть на диффы и во время пушей, что, откуда и куда пушим. Если не сильно торопиться то проблем не будет.

Git: как посмотреть что лежит в stash?

Смотрим все прятки
git stash list
это нам даст список вида
stash@{0}: lalala
stash@{1}: lalala
stash@{2}: lalala
Чтобы посмотреть файлы лежащие в конкретном стэше
git stash show stash@{2}
Чтобы посмотреть правки в этих файлах делаем так
git stash show -p stash@{2}
Ну и чтобы применить выбранный стэш
git stash apply stash@{2}
Еще подробности тут и тут

Express.js: листинг файлов папки/директории

В Api.Reference есть пример который показывает как разрешить листать все директории внутри public.
app.use(express.directory('public'))
app.use(express.static('public'))
Чтобы лучше понять как это работает можно посмотреть исходник
node_modules/express/node_modules/connect/lib/middleware/directory.js
Возникает вопрос, как быть если я не хочу разрешать листать весь public, а только конкретные папки? Решение может быть таким
// обычный static для public
app.use(express.static(path.join(__dirname, 'public')));
// маунтим роут на папку которую будет обслуживать directory 
// + передаем опции отображения
app.use('/files-list', express.static(path.join(__dirname, 'public', 'files')));
app.use('/files-list', express.directory(path.join(__dirname, 'public', 'files'), {hidden: true, icons: true}));
Или в случае если имя папки на диске совпадает с роутом (тоже самое, только с реальным именем папки для правильных ссылок для загрузки файлов)
app.use('/files', express.directory(path.join(__dirname, 'public', 'files'), {icons: true}));
Нашелся скринкаст на эту тему:

Express.js: .jade template to string

Наверное не нужно объяснять зачем это нужно.

test1.js
exports.test1 = function(req, res){
  res.render('test1', { title: 'test1' }, function(err, body) {
    console.log(body);
  });

  res.send('wooo');
};

test1.jade
div
  = title
p hello world!