Архив рубрики: Git

Устанавливаем, настраиваем и начинаем работаь с Git

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

Установка
Скачать последнюю версию дистрибутива для Windows можно здесь. Ставил в режиме далее-далее-далее. Если будут какие то особенности, то потом допишу.

Настройка
Прописать имя пользователя и почту в настройки пользователя:
$ git config --global user.name "Alexey Losev"
$ git config --global user.email myemail@domen.ru
Кроме --global (настройки текущего пользователя), есть еще --system (системные настройки) и без ключа, это значит настройки текущего репозитария.
Проверить что все записалось нормально, можно командами:
$ git config --global user.email
$ git config --global user.name
Или даже:
$ git config user.email
$ git config user.name
Ну или все настройки доступны командой:
$ git config --list

Справка
Получить справку по команде можно командами:
$ git config --global user.email
$ git config --global user.name
Справка выполнена в виде html, которые при установке сохраняются на диск и доступна без подключения к интернету.
Кстати, команда:
$ man git-<команда>
В Windows не работает.

Создание репозитория
Для путешествия по папкам через Git Bush можно пользоваться стандартными досовскими командами (cd, dir). Только вот есть пара отличий:
1. При переходе на другой диск необходимо вместо d:/ писать /d/
2. Восклицательный знак в пути - это проблема
Ну а так, все стандартно:
$ cd /d/git/robotsbattle
$ dir

Все, создаем репозиторий:
$ git init
Для добавления в репозиторий существующих файлов можно воспользоваться командой:
$ git add *.sln
Фиксация изменений происходит командой
$ git commit -m 'комментарий к коммиту'
Получение того, что у нас твориться в репозитории:
$ git status
Чтобы посмотреть не просто список, а что изменилось в файлах, можно вызвать:
$ git diff
Получим аналог Create Patch из SVN. Пример как это работает:
У команды git diff, есть модификация:
$ git diff --cached
Которая позволяет понять одну магию. В коммит попадают только проиндексированные файлы. Т.е. если файл изменился, то для того, чтобы он попал в коммит его необходимо проиндексировать командой 
add:
$ git add RobotsBattle/Cell.cs
И здесь есть тонкость, хотя Windows игнорирует регистр символов, а вот Git Bash - нет. Т.е. предыдущая команда отработает корректно, а вот эта нет:
$ git add robotsbattle/cell.cs
После добавления файла в кэш, git diff без ключей его игнорирует, а вот указанный ключ 
--cached позволяет посмотреть, что же мы закешировали.
Если заниматься индексацией каждый раз не хочется, то можно вызвать коммит для всех измененных файлов не зависимо от того, закешированы они или нет следующей командой:
$ git commit -a -m "Добавлен комментарий"
Ну и так как у нас в папках проектов всегда есть файлы, которые добавлять в хранилище не надо, то можно создать файл с расширением  .gitignore 
К шаблонам в файле .gitignore применяются следующие правила:
  • Пустые строки, а также строки, начинающиеся с #, игнорируются.
  • Можно использовать стандартные glob-шаблоны.
  • Можно заканчивать шаблон символом слэша (/) для указания каталога.
  • Можно инвертировать шаблон, использовав восклицательный знак (!) в качестве первого символа.
Вот так может выглядеть этот файл, чтобы игнорировать файлы и папки начинающиеся с точки, а также папке bin и obj в первом уровне вложенности относительно корня:

Glob — шаблоны в .gitignore

Для определения какие файлы в папке должны игнорироваться git-ом, создается файл .gitignore. В котором применяются glob - шаблоны. То что под катом, это краткая дока по glob-шаблонам для тех, кто не работает в *nix системах. Т.е. для меня.

Начну с того, что строка может содержать имя файла или директории. В этом случае шаблоном она являться не будет. Например, если у меня в папке с решением лежит папка .vs, то я могу в файле .gitignore прямо так и написать .vs. И эта папка будет игнорироваться.
Glob шаблоном является строка содержащая один из символов "?", "*" или "[".
"?", "*" пока они не в квадратных скобках работают весьма привычно. "?" - любой символ, "*" - любая последовательность символов. А вот квадратные скобки задают классы символов.
Класс символов позволяет задать что на этой позиции будет находится символ принадлежащий классу или (если после открывающейся скобки идет восклицательный знак "!") не принадлежащий классу.
Классы задаются перечислением, либо диапазонами.
[abc] - любой из символов a, b, c.
[a-c] - любой из символов находящихся между a и c. Т.е. те же три символа a, b, c.
[a-c1-3] - любой из символов a, b, c или цифра от 1 до 3.
[!abc] - любой символ кроме букв a, b, c.
Если в класс должны входить закрывающаяся скобка "]", то она должна идти первой в классе:
[]a-c] - закрывающаяся скобка или любой из символов a, b, c.
Если в классе необходимо использовать "-" именно как символ, то он должен стоять на первой или последней позиции.
[]-] - соответствует двум символам: "]" и "-"
[--/] - соответствует  трем символами: "-", "." и "/".
Ну и пара примеров.
Игнорируем все что начинается с точки (файлы и папки):
В папках RobotsBattle и RobotsBattleConsole есть папки bin и debug. Мне эти папки надо исключить:

На BitBucket опубликованы исходники проекта по работе с proxy в AutoCAD

Закинул в свой профиль на BitBucket исходники проекта по работе с прокси в AutoCAD. Проект в открытом доступе. Там же опубликован и результат компиляции.

UPD (27.06.2016) Программа переведена из категории открытой в категорию бесплатной. Отныне доступны MSI-инсталляторы, но не исходный код. Подробнее здесь.

Издательство "Питер" представило русский перевод книги Pro Git 2.

На днях издательство "Питер" представило перевод книги Pro Git 2. В англоязычном варианте книга имеется в свободном доступе (официальный сайт книги здесь). Да, я в курсе о том, что группой энтузиастов давно выполняется её перевод, но судя по текущему состоянию этого перевода - он не сильно продвинулся с тех пор, как я смотрел его в последний раз весной (во всяком случае ряд интересующих меня тем так и не переведён до сих пор). К сожалению, издательство "Питер" продаёт только электронную версию книги (epub, pdf). Кроме того, за электронную версию 400 рублей - это несколько завышенная цена (на мой взгляд). Покупать или нет - это уж каждый пусть решает для себя сам.

Некоторые мысли по поводу заметки Алексея Кулика о Git.

Опубликовываю последовательность действий, оформленных в виде скрипта, согласно тому, как обещал в комментариях здесь. Как я уже писал, изложенный в ссылке пример совместной работы, на мой взгляд, является, к сожалению, плохим. Я считаю, что Алексей показал даже скорее пример того, как НЕ НАДО делать (буду называть вещи своими именами), т.к. работая с 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
$

Сайт о Git для Windows.

Некоторое время назад создал сайт, на котором выкладываю материалы по работе с Git для Windows. Дополнительно присутствует материал о различных "подводных камнях", с которыми мне пришлось столкнуться на практике, а так же о способах их обхода. Ресурс редактируется и дополняется по мере оформления материала. Адрес сайта: http://www.git-for-win.red-bee.ru

Git & Unicode

Начиная с версии V1.7.10 Git for Windows поддерживает кодировку Unicode.

Имена файлов и каталогов, выполненные кирилицей, в Git for Windows могут отображаться, например так:


Чтобы это исправить, следует в консоли Git установить используемым шрифт TrueType, например Lucida Console или Consolas:


И произвести ряд изменений в настройках Git, посредством запуска команд:

git config --global core.quotepath off
git config --global --unset i18n.logoutputencoding
git config --global --unset i18n.commitencoding
git config --global --unset svn.pathnameencoding

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



Источник информации здесь.

Англоязычный вариант книги Pro Git здесь (pdf формат).
Русский перевод книги Pro Git здесь (pdf формат).

P.S. Если вам потребовалось в Git for Windows для операции git commit назначить иной текстовый редактор, путь к exe файлу которого содержит пробелы - в этом случае, при указании полного пути к exe файлу, следует внутри кавычек одного типа, дополнительно указывать кавычки другого типа. Т.е. либо одинарные кавычки упаковывать в двойные, либо наоборот - двойные кавычки упаковывать в одинарные. В общем, в подобных случаях следует использовать любой из следующих вариантов:

git config --global core.editor "'C:Program FilesNotepad++notepad++.exe'"
git config --global core.editor '"C:Program FilesNotepad++notepad++.exe"'