Подготовка

Если вы никогда ранее не использовали git, для начала вам необходимо осуществить установку. Выполните следующие команды, чтобы git узнал ваше имя и электронную почту.

git config --global user.name "Orkhan Alyshov"
git config --global user.email "[email protected]"

Также параметры установки окончаний строк (Windows):

git config --global core.autocrlf true
git config --global core.safecrlf true

Создание проекта

Начните работу в пустом рабочем каталоге (например, work) с создания пустого каталога с именем "hello", затем войдите в него и создайте там файл с именем hello.html с таким содержанием:

mkdir hello
cd hello
touch hello.html

hello.html:

Hello, World

Теперь у вас есть каталог с одним файлом. Чтобы создать git репозиторий из этого каталога, выполните команду git init:

git init

Теперь давайте добавим в репозиторий страницу "Hello, World"

$ git add hello.html
$ git commit -m "First Commit"
[master (root-commit) 911e8c9] First Commit
 1 files changed, 1 insertions(+), 0 deletions(-)
 create mode 100644 hello.html

Проверка состояния

Используйте команду git status, чтобы проверить текущее состояние репозитория:

$ git status
# On branch master
nothing to commit (working directory clean)

Команда проверки состояния сообщит, что коммитить нечего. Это означает, что в репозитории хранится текущее состояние рабочего каталога, и нет никаких изменений, ожидающих записи.


Внесение изменений

Добавим кое-какие HTML-теги к нашему приветствию. Измените содержимое файла на:

<h1>Hello, World!</h1>

Теперь проверьте состояние рабочего каталога:

$ git status
# On branch master
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   hello.html
#
no changes added to commit (use "git add" and/or "git commit -a")

Первое, что нужно заметить, это то, что git знает, что файл hello.html был изменен, но при этом эти изменения еще не зафиксированы в репозитории.

Также обратите внимание на то, что сообщение о состоянии дает вам подсказку о том, что нужно делать дальше. Если вы хотите добавить эти изменения в репозиторий, используйте команду git add. В противном случае используйте команду git сheckout для отмены изменений.


Индексация изменений

Теперь дайте команду git проиндексировать изменения. Проверьте состояние:

$ git add hello.html
$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   modified:   hello.html
#

Изменения файла hello.html были проиндексированы. Это означает, что git теперь знает об изменении, но изменение пока не перманентно навсегда записано в репозиторий. Следующий коммит будет включать в себя проиндексированные изменения.

Если вы решили, что не хотите коммитить изменения, команда состояния напомнит вам о том, что с помощью команды git reset можно снять индексацию этих изменений.


Индексация и коммит

Отдельный шаг индексации в git позволяет вам продолжать вносить изменения в рабочий каталог, а затем, в момент, когда вы захотите взаимодействовать с версионным контролем, git позволит записать изменения в малых коммитах, которые фиксируют то, что вы сделали.

Предположим, что вы отредактировали три файла (a.html, b.html, и c.html). Теперь вы хотите закоммитить все изменения, при этом чтобы изменения в a.html и b.html были одним коммитом, в то время как изменения в c.html логически не связаны с первыми двумя файлами и должны идти отдельным коммитом.

В теории, вы можете сделать следующее:

git add a.html
git add b.html
git commit -m "Changes for a and b"
git add c.html
git commit -m "Unrelated change to c"

Разделяя индексацию и коммит, вы имеете возможность с легкостью настроить, что идет в какой коммит.


Коммит изменений

Достаточно об индексации. Давайте сделаем коммит того, что мы проиндексировали, в репозиторий.

Когда вы ранее использовали git commit для коммита первоначальной версии файла hello.html в репозиторий, вы включили метку -m, которая делает комментарий в командной строке. Команда commit позволит вам интерактивно редактировать комментарии для коммита. Теперь давайте это проверим.

Если вы опустите метку -m из командной строки, git перенесет вас в редактор по вашему выбору. Редактор выбирается из следующего списка (в порядке приоритета):

  • переменная среды GIT_EDITOR;
  • параметр конфигурации core.editor;
  • переменная среды VISUAL;
  • переменная среды EDITOR;

У меня переменная EDITOR установлена в notepad.

Сделайте коммит сейчас и проверьте состояние:

git commit

Вы увидите в вашем редакторе:

|
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   modified:   hello.html
#

В первой строке введите комментарий: "Added h1 tag". Сохраните файл и выйдите из редактора. Вы увидите:

git commit
Waiting for notepad...
[master 569aa96] Added h1 tag
 1 files changed, 1 insertions(+), 1 deletions(-)

В конце давайте еще раз проверим состояние:

$ git status
# On branch master
nothing to commit (working directory clean)

Рабочий каталог чистый, можете продолжить работу.


Изменения, а не файлы

Большинство систем версионного контроля работают с файлами. Вы добавляете файл в версионный контроль, а система отслеживает изменения файла с этого момента.

Git фокусируется на изменениях в файле, а не самом файле. Когда вы осуществляете команду git add file, вы не говорите git добавить файл в репозиторий. Скорее вы говорите, что git надо отметить текущее состояние файла, коммит которого будет произведен позже.

Измените страницу "Hello, World", чтобы она содержала стандартные теги <html> и <body>:

<html>
  <body>
    <h1>Hello, World!</h1>
  </body>
</html>

Теперь добавьте это изменение в индекс git:

git add hello.html

Теперь добавьте заголовки HTML (секцию <head>) к странице "Hello, World":

<html>
  <head>
  </head>
  <body>
    <h1>Hello, World!</h1>
  </body>
</html>

Проверьте текущий статус:

$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   modified:   hello.html
#
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   hello.html
#

Обратите внимание на то, что hello.html указан дважды в состоянии. Первое изменение (добавление стандартных тегов) проиндексировано и готово к коммиту. Второе изменение (добавление заголовков HTML) является непроиндексированным. Если бы вы делали коммит сейчас, заголовки не были бы сохранены в репозиторий. Давайте проверим.

Произведите коммит проиндексированного изменения (значение по умолчанию), а затем еще раз проверьте состояние:

$ git commit -m "Added standard HTML page tags"
[master 8c32287] Added standard HTML page tags
 1 files changed, 3 insertions(+), 1 deletions(-)
$ git status
# On branch master
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   hello.html
#
no changes added to commit (use "git add" and/or "git commit -a")

Состояние команды говорит о том, что hello.html имеет незафиксированные изменения, но уже не в буферной зоне.

Теперь добавьте второе изменение в индекс, а затем проверьте состояние с помощью команды git status:

$ git add .
$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   modified:   hello.html
#

Второе изменение было проиндексировано и готово к коммиту.

Примечание: В качестве файла для добавления, мы использовали текущий каталог (.). Это самый краткий и удобный путь для добавления всех изменений в файлы текущего каталога и его подкаталоги. Но поскольку он добавляет все, не лишним будет проверить состояние перед запуском add, просто чтобы убедиться, что вы не добавили какой-то файл, который добавлять было не нужно.

Сделайте коммит второго изменения:

git commit -m "Added HTML header"

История

Получение списка произведенных изменений - функция команды git log.

$ git log
commit fa3c1411aa09441695a9e645d4371e8d749da1dc
Author: Orkhan Alyshov <[email protected]>
Date:   Wed Mar 9 10:27:54 2011 -0500

    Added HTML header

commit 8c3228730ed03116815a5cc682e8105e7d981928
Author: Orkhan Alyshov <[email protected]>
Date:   Wed Mar 9 10:27:54 2011 -0500

    Added standard HTML page tags

commit 43628f779cb333dd30d78186499f93638107f70b
Author: Orkhan Alyshov <[email protected]>
Date:   Wed Mar 9 10:27:54 2011 -0500

    Added h1 tag

commit 911e8c91caeab8d30ad16d56746cbd6eef72dc4c
Author: Orkhan Alyshov <[email protected]>
Date:   Wed Mar 9 10:27:54 2011 -0500

    First Commit

Вот список всех четырех коммитов в репозиторий, которые мы успели совершить.

Вы полностью контролируете то, что отображает log. Мне, например, нравится однострочный формат:

$ git log --pretty=oneline
fa3c1411aa09441695a9e645d4371e8d749da1dc Added HTML header
8c3228730ed03116815a5cc682e8105e7d981928 Added standard HTML page tags
43628f779cb333dd30d78186499f93638107f70b Added h1 tag
911e8c91caeab8d30ad16d56746cbd6eef72dc4c First Commit

Есть много вариантов выбора, какие элементы отображаются в логе. Поиграйте со следующими параметрами:

git log --pretty=oneline --max-count=2
git log --pretty=oneline --since='5 minutes ago'
git log --pretty=oneline --until='5 minutes ago'
git log --pretty=oneline --author=<your name>
git log --pretty=oneline --all

Алиасы

Добавьте следующее в файл .gitconfig в вашем $HOME каталоге.

[alias]
  co = checkout
  ci = commit
  st = status
  br = branch
  hist = log --pretty=format:\"%h %ad | %s%d [%an]\" --graph --date=short
  type = cat-file -t
  dump = cat-file -p

Мы уже успели рассмотреть команды commit и status, в предыдущем уроке рассмотрели команду log и совсем скоро познакомимся с checkout. Главное, что стоит запомнить, так это то, что теперь вы можете вводить git st там, где раньше приходилось использовать git status. Аналогичным образом, пишем git co вместо git checkout и git ci вместо git commit. Что лучше всего, команда git hist позволит избежать ввода очень длинной команды log.

Мы добавили несколько алиасов для команд, которых мы еще не рассматривали. С командой git branch разберемся чуть позже, а команда git cat-file используется для исследования git, в чем мы вскоре убедимся.


Получение старых версий

Возвращаться назад в историю очень просто. Команда checkout скопирует любой снимок из репозитория в рабочий каталог.

$ git hist
* fa3c141 2011-03-09 | Added HTML header (HEAD, master) [Orkhan Alyshov]
* 8c32287 2011-03-09 | Added standard HTML page tags [Orkhan Alyshov]
* 43628f7 2011-03-09 | Added h1 tag [Orkhan Alyshov]
* 911e8c9 2011-03-09 | First Commit [Orkhan Alyshov]

Примечание: Вы не забыли задать hist в вашем файле .gitconfig?

Изучите данные лога и найдите хэш для первого коммита. Он должен быть в последней строке данных git hist. Используйте этот хэш-код (достаточно первых 7 знаков) в команде ниже. Затем проверьте содержимое файла hello.html.

$ git checkout 911e8c9
Note: checking out '911e8c9'.

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 911e8c9... First Commit
$ cat hello.html
Hello, World

Обратите внимание на то, что содержимое файла hello.html является значением по умолчанию.

Примечание: Многие команды зависят от хэшевых значений в репозитории. Поскольку ваши хеш-значения будут отличаться от моих, когда вы видите что-то вроде <hash> или <treehash> в команде, подставьте необходимое значение хэш для вашего репозитория.

Вернитесь к последней версии в ветке master:

$ git checkout master
Previous HEAD position was 911e8c9... First Commit
Switched to branch 'master'
$ cat hello.html
<html>
  <head>
  </head>
  <body>
    <h1>Hello, World!</h1>
  </body>
</html>

"master" - имя ветки по умолчанию. Переключая имена веток, вы попадаете на последнюю версию выбранной ветки.


Создание тегов версий

Давайте назовем текущую версию страницы hello первой (v1):

git tag v1

Теперь текущая версия страницы называется v1.

Давайте создадим тег для версии, которая идет перед текущей версией и назовем его v1-beta. В первую очередь нам надо переключиться на предыдущую версию. Вместо поиска до хэш, мы будем использовать ^, обозначающее "родитель v1".

$ git checkout v1^
Note: checking out 'v1^'.

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 8c32287... Added standard HTML page tags
$ cat hello.html
<html>
  <body>
    <h1>Hello, World!</h1>
  </body>
</html>

Эта версия c тегами <html> и <body>, но еще пока без <head>. Давайте сделаем ее версией v1-beta:

git tag v1-beta

Теперь попробуйте попереключаться между двумя отмеченными версиями:

$ git checkout v1
Previous HEAD position was 8c32287... Added standard HTML page tags
HEAD is now at fa3c141... Added HTML header
$ git checkout v1-beta
Previous HEAD position was fa3c141... Added HTML header
HEAD is now at 8c32287... Added standard HTML page tags

Вы можете увидеть, какие теги доступны, используя команду git tag:

$ git tag
v1
v1-beta

Вы также можете посмотреть теги в логе:

$ git hist master --all
* fa3c141 2011-03-09 | Added HTML header (v1, master) [Orkhan Alyshov]
* 8c32287 2011-03-09 | Added standard HTML page tags (HEAD, v1-beta) [Orkhan Alyshov]
* 43628f7 2011-03-09 | Added h1 tag [Orkhan Alyshov]
* 911e8c9 2011-03-09 | First Commit [Orkhan Alyshov]

Вы можете видеть теги (v1 и v1-beta) в логе вместе с именем ветки (master). Кроме того HEAD показывает коммит, на который вы переключились (на данный момент это v1-beta).


Отмена локальных изменений (до индексации)

Убедитесь, что вы находитесь на последнем коммите ветки master, прежде чем продолжить работу:

git checkout master

Иногда случается, что вы изменили файл в рабочем каталоге, и хотите отменить последние коммиты. С этим справится команда checkout.

Внесите изменение в файл hello.html в виде нежелательного комментария:

<html>
  <head>
  </head>
  <body>
    <h1>Hello, World!</h1>
    <!-- This is a bad comment.  We want to revert it. -->
  </body>
</html>

Сначала проверьте состояние рабочего каталога:

$ git status
# On branch master
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   hello.html
#
no changes added to commit (use "git add" and/or "git commit -a")

Мы видим, что файл hello.html был изменен, но еще не проиндексирован.

Используйте команду checkout для переключения в версию файла hello.html в репозитории:

$ git checkout hello.html
$ git status
# On branch master
nothing to commit (working directory clean)
$ cat hello.html
<html>
  <head>
  </head>
  <body>
    <h1>Hello, World!</h1>
  </body>
</html>

Команда status показывает нам, что не было произведено никаких изменений, не зафиксированных в рабочем каталоге. И "нежелательный комментарий" больше не является частью содержимого файла.


Отмена проиндексированных изменений (перед коммитом)

Внесите изменение в файл hello.html в виде нежелательного комментария:

<html>
  <head>
    <!-- This is an unwanted but staged comment -->
  </head>
  <body>
    <h1>Hello, World!</h1>
  </body>
</html>

Проиндексируйте это изменение:

git add hello.html

Проверьте состояние нежелательного изменения:

$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   modified:   hello.html
#

Состояние показывает, что изменение было проиндексировано и готово к коммиту.

К счастью, вывод состояния показывает нам именно то, что мы должны сделать для отмены индексации изменения:

$ git reset HEAD hello.html
Unstaged changes after reset:
M   hello.html

Команда reset сбрасывает буферную зону к HEAD. Это очищает буферную зону от изменений, которые мы только что проиндексировали.

Команда reset (по умолчанию) не изменяет рабочий каталог. Поэтому рабочий каталог все еще содержит нежелательный комментарий. Мы можем использовать команду checkout из предыдущего урока, чтобы удалить нежелательные изменения в рабочем каталоге.

Переключитесь на версию коммита:

$ git checkout hello.html
$ git status
# On branch master
nothing to commit (working directory clean)

Наш рабочий каталог опять чист.


Отмена коммитов

Иногда вы понимаете, что новые коммиты являются неверными, и хотите их отменить. Есть несколько способов решения этого вопроса, здесь мы будем использовать самый безопасный. Мы отменим коммит путем создания нового коммита, отменяющего нежелательные изменения.

Измените файл hello.html на следующий:

<html>
  <head>
  </head>
  <body>
    <h1>Hello, World!</h1>
    <!-- This is an unwanted but committed change -->
  </body>
</html>

Сделайте коммит:

git add hello.html
git commit -m "Oops, we didn't want this commit"

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

$ git revert HEAD --no-edit
[master 45fa96b] Revert "Oops, we didn't want this commit"
 1 files changed, 1 insertions(+), 1 deletions(-)

Так как мы отменили самый последний произведенный коммит, мы смогли использовать HEAD в качестве аргумента для отмены. Мы можем отменить любой произвольной коммит в истории, указав его хэш-значение.

Примечание: команду --no-edit можно проигнорировать. Она была необходима для генерации выходных данных без открытия редактора.

Проверка лога показывает нежелательные и отмененные коммиты в наш репозиторий:

$ git hist
* 45fa96b 2011-03-09 | Revert "Oops, we didn't want this commit" (HEAD, master) [Orkhan Alyshov]
* 846b90c 2011-03-09 | Oops, we didn't want this commit [Orkhan Alyshov]
* fa3c141 2011-03-09 | Added HTML header (v1) [Orkhan Alyshov]
* 8c32287 2011-03-09 | Added standard HTML page tags (v1-beta) [Orkhan Alyshov]
* 43628f7 2011-03-09 | Added h1 tag [Orkhan Alyshov]
* 911e8c9 2011-03-09 | First Commit [Orkhan Alyshov]

Удаление коммиттов из ветки

Revert из предыдущего раздела является мощной командой, которая позволяет отменить любые коммиты в репозиторий. Однако, и оригинальный и отмененный коммиты видны в истории ветки (при использовании команды git log). Часто мы делаем коммит, и сразу понимаем, что это была ошибка. Было бы неплохо иметь команду возврата, которая позволила бы нам сделать вид, что неправильного коммита никогда и не было. Команда возврата даже предотвратила бы появление нежелательного коммита в истории git log.

Мы уже видели команду reset и использовали ее для согласования буферной зоны и выбранного коммита (мы использовали коммит HEAD в нашем предыдущем уроке).

При получении ссылки на коммит (т.е. хэш, ветка или имя тега), команда reset:

  1. Перепишет текущую ветку, чтобы она указывала на нужный коммит;
  2. Опционально сбросит буферную зону для соответствия с указанным коммитом;
  3. Опционально сбросит рабочий каталог для соответствия с указанным коммитом;

Давайте сделаем быструю проверку нашей истории коммитов:

$ git hist
* 45fa96b 2011-03-09 | Revert "Oops, we didn't want this commit" (HEAD, master) [Orkhan Alyshov]
* 846b90c 2011-03-09 | Oops, we didn't want this commit [Orkhan Alyshov]
* fa3c141 2011-03-09 | Added HTML header (v1) [Orkhan Alyshov]
* 8c32287 2011-03-09 | Added standard HTML page tags (v1-beta) [Orkhan Alyshov]
* 43628f7 2011-03-09 | Added h1 tag [Orkhan Alyshov]
* 911e8c9 2011-03-09 | First Commit [Orkhan Alyshov]

Мы видим, что два последних коммита в этой ветке - "Oops" и "Revert Oops". Давайте удалим их с помощью сброса. Но прежде чем удалить коммиты, давайте отметим последний коммит тегом, чтобы потом можно было его найти:

git tag oops

Глядя на историю лога (см. выше), мы видим, что коммит с тегом v1 является коммитом, предшествующим ошибочному коммиту. Давайте сбросим ветку до этой точки. Поскольку ветка имеет тег, мы можем использовать имя тега в команде сброса (если она не имеет тега, мы можем использовать хэш-значение):

$ git reset --hard v1
HEAD is now at fa3c141 Added HTML header
$ git hist
* fa3c141 2011-03-09 | Added HTML header (HEAD, v1, master) [Orkhan Alyshov]
* 8c32287 2011-03-09 | Added standard HTML page tags (v1-beta) [Orkhan Alyshov]
* 43628f7 2011-03-09 | Added h1 tag [Orkhan Alyshov]
* 911e8c9 2011-03-09 | First Commit [Orkhan Alyshov]

Наша ветка master теперь указывает на коммит v1, а коммитов Oops и Revert Oops в ветке уже нет. Параметр --hard указывает, что рабочий каталог должен быть обновлен в соответствии с новым head ветки.

Что же случается с ошибочными коммитами? Оказывается, что коммиты все еще находятся в репозитории. На самом деле, мы все еще можем на них ссылаться. Помните, в начале этого урока мы создали для отмененного коммита тег "oops". Давайте посмотрим на все коммиты:

$ git hist --all
* 45fa96b 2011-03-09 | Revert "Oops, we didn't want this commit" (oops) [Orkhan Alyshov]
* 846b90c 2011-03-09 | Oops, we didn't want this commit [Orkhan Alyshov]
* fa3c141 2011-03-09 | Added HTML header (HEAD, v1, master) [Orkhan Alyshov]
* 8c32287 2011-03-09 | Added standard HTML page tags (v1-beta) [Orkhan Alyshov]
* 43628f7 2011-03-09 | Added h1 tag [Orkhan Alyshov]
* 911e8c9 2011-03-09 | First Commit [Orkhan Alyshov]

Мы видим, что ошибочные коммиты не исчезли. Они все еще находятся в репозитории. Просто они отсутствуют в ветке master. Если бы мы не отметили их тегами, они по-прежнему находились бы в репозитории, но не было бы никакой возможности ссылаться на них, кроме как при помощи их хэш имен. Коммиты, на которые нет ссылок, остаются в репозитории до тех пор, пока не будет запущен сборщик мусора.


Удаление тега oops

Тег oops свою функцию выполнил. Давайте удалим его и коммиты, на которые он ссылался, сборщиком мусора:

$ git tag -d oops
Deleted tag 'oops' (was 45fa96b)
$ git hist --all
* fa3c141 2011-03-09 | Added HTML header (HEAD, v1, master) [Orkhan Alyshov]
* 8c32287 2011-03-09 | Added standard HTML page tags (v1-beta) [Orkhan Alyshov]
* 43628f7 2011-03-09 | Added h1 tag [Orkhan Alyshov]
* 911e8c9 2011-03-09 | First Commit [Orkhan Alyshov]

Внесение изменений в коммиты

Добавьте в страницу комментарий автора:

hello.html:

<!-- Author: Orkhan Alyshov -->
<html>
  <head>
  </head>
  <body>
    <h1>Hello, World!</h1>
  </body>
</html>
git add hello.html
git commit -m "Add an author comment"

После совершения коммита вы понимаете, что любой хороший комментарий должен включать электронную почту автора. Обновите страницу hello, включив в нее email:

hello.html:

<!-- Author: Orkhan Alyshov ([email protected]) -->
<html>
  <head>
  </head>
  <body>
    <h1>Hello, World!</h1>
  </body>
</html>

Мы действительно не хотим создавать отдельный коммит только ради электронной почты. Давайте изменим предыдущий коммит, включив в него адрес электронной почты:

$ git add hello.html
$ git commit --amend -m "Add an author/email comment"
[master 6a78635] Add an author/email comment
 1 files changed, 2 insertions(+), 1 deletions(-)

Просмотр истории:

$ git hist
* 6a78635 2011-03-09 | Add an author/email comment (HEAD, master) [Orkhan Alyshov]
* fa3c141 2011-03-09 | Added HTML header (v1) [Orkhan Alyshov]
* 8c32287 2011-03-09 | Added standard HTML page tags (v1-beta) [Orkhan Alyshov]
* 43628f7 2011-03-09 | Added h1 tag [Orkhan Alyshov]
* 911e8c9 2011-03-09 | First Commit [Orkhan Alyshov]

Мы можем увидеть, что оригинальный коммит "автор" заменен коммитом "автор/email". Этого же эффекта можно достичь путем сброса последнего коммита в ветке, и повторного коммита новых изменений.


Перемещение файлов

- Первый способ:

Сейчас мы собираемся создать структуру нашего репозитория. Давайте перенесем страницу в каталог lib:

$ mkdir lib
$ git mv hello.html lib
$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   renamed:    hello.html -> lib/hello.html
#

Перемещая файлы с помощью git, мы информируем git о 2 вещах:

  • Что файл hello.html был удален;
  • Что файл lib/hello.html был создан;

Оба эти факта сразу же проиндексированы и готовы к коммиту. Команда git status сообщает, что файл был перемещен.

- Второй способ:

Позитивной чертой git является то, что вы можете забыть о версионном контроле до того момента, когда вы готовы приступить к коммиту кода. Что бы случилось, если бы мы использовали командную строку операционной системы для перемещения файлов вместо команды git?

Оказывается, следующий набор команд идентичен нашим последним действиям. Работы здесь побольше, но результат тот же. Мы могли бы выполнить:

mkdir lib
mv hello.html lib
git add lib/hello.html
git rm hello.html

Давайте сделаем коммит этого перемещения:

git commit -m "Moved hello.html to lib"

Подробнее о структуре

Давайте добавим файл index.html в наш репозиторий. Следующий файл отлично подойдет для этой цели.

index.html:

<html>
  <body>
    <iframe src="lib/hello.html" width="200" height="200" />
  </body>
</html>

Добавьте файл и сделайте коммит:

git add index.html
git commit -m "Added index.html."

Теперь при открытии index.html, вы должны увидеть кусок страницы hello в маленьком окошке.


Git внутри: каталог .git

Настало время провести небольшое исследование. Для начала, из корневого каталога вашего проекта:

$ ls -C .git
COMMIT_EDITMSG 
MERGE_RR
config
hooks
info
objects
rr-cache
HEAD
ORIG_HEAD
description
index
logs
refs

Это магический каталог, в котором хранятся все "материалы" git. Давайте заглянем в каталог объектов:

$ ls -C .git/objects
09  24  28  45  59  6a  77  80  8c  97  af  c4  e7  info
11  27  43  56  69  6b  78  84  91  9c  b5  e4  fa  pack

Вы должны увидеть кучу каталогов, имена которых состоят из 2 символов. Имена каталогов являются первыми двумя буквами хэша sha1 объекта, хранящегося в git.

$ ls -C .git/objects/09
6b74c56bfc6b40e754fc0725b8c70b2038b91e  9fb6f9d3a104feb32fcac22354c4d0e8a182c1

Смотрим в один из каталогов с именем из 2 букв. Вы увидите файлы с именами из 38 символов. Это файлы, содержащие объекты, хранящиеся в git. Они сжаты и закодированы, поэтому просмотр их содержимого нам мало чем поможет. Рассмотрим далее каталог .git внимательно.

$ cat .git/config
[core]
    repositoryformatversion = 0
    filemode = true
    bare = false
    logallrefupdates = true
    ignorecase = true
[user]
    name = Orkhan Alyshov
    email = [email protected]

Это файл конфигурации, создающийся для каждого конкретного проекта. Записи в этом файле будут перезаписывать записи в файле .gitconfig вашего главного каталога, по крайней мере в рамках этого проекта.

$ ls .git/refs
heads
tags
$ ls .git/refs/heads
master
$ ls .git/refs/tags
v1
v1-beta
$ cat .git/refs/tags/v1
fa3c1411aa09441695a9e645d4371e8d749da1dc

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

Каталог heads практически аналогичен, но используется для веток, а не тегов. На данный момент у нас есть только одна ветка, так что все, что вы увидите в этом каталоге – это ветка master.

$ cat .git/HEAD
ref: refs/heads/master

Файл HEAD содержит ссылку на текущую ветку, в данный момент это должна быть ветка master.


Git внутри: работа непосредственно с объектами git

Давайте исследуем объекты git с помощью некоторых инструментов.

Поиск последнего коммита. Эта команда должна показать последний коммит в репозиторий. SHA1 хэш в вашей системе, вероятно, отличается от моего, но вы увидите что-то наподобие этого:

$ git hist --max-count=1
* 8029c07 2011-03-09 | Added index.html. (HEAD, master) [Orkhan Alyshov]

С помощью SHA1 хэша из коммита, указанного выше:

$ git cat-file -t 8029c07
commit
$ git cat-file -p 8029c07
tree 096b74c56bfc6b40e754fc0725b8c70b2038b91e
parent 567948ac55daa723807c0c16e34c76797efbcbed
author Orkhan Alyshov <[email protected]> 1299684476 -0500
committer Orkhan Alyshov <[email protected]> 1299684476 -0500

Added index.html.

Примечание: Если вы задали алиасы "type" и "dump", как описано в уроке об алиасах, можете вводить команды git type и git dump вместо длинных команд.

Мы можем вывести дерево каталогов, ссылка на который идет в коммите. Это должно быть описание файлов (верхнего уровня) в нашем проекте (для конкретного коммита). Используйте SHA1 хэш из строки "дерева", из списка выше:

$ git cat-file -p 096b74c
100644 blob 28e0e9d6ea7e25f35ec64a43f569b550e8386f90    index.html
040000 tree e46f374f5b36c6f02fb3e9e922b79044f754d795    lib

Да, я вижу index.html и каталог lib.

Вывод каталога lib:

$ git cat-file -p e46f374
100644 blob c45f26b6fdc7db6ba779fc4c385d9d24fc12cf72    hello.html

Существует файл hello.html.

Вывод файла hello.html:

$ git cat-file -p c45f26b
<!-- Author: Orkhan Alyshov ([email protected]) -->
<html>
  <head>
  </head>
  <body>
    <h1>Hello, World!</h1>
  </body>
</html>

А вот и он. Мы вывели объекты коммитов, объекты деревьев и объекты блобов непосредственно из репозитория git. Это все, что есть - блобы, деревья и коммиты.


Создание ветки

Пора сделать наш hello world более выразительным. Так как это может занять некоторое время, лучше переместить эти изменения в отдельную ветку, чтобы изолировать их от изменений в ветке master.

Давайте назовем нашу новую ветку "style":

git checkout -b style
git status

Примечание: git checkout -b <имяветки> является шорткатом для git branch <имяветки> за которым идет git checkout <имяветки>.

Обратите внимание, что команда git status сообщает о том, что вы находитесь в ветке "style".

Добавьте файл стилей style.css:

touch lib/style.css

lib/style.css:

h1 {
  color: red;
}
git add lib/style.css
git commit -m "Added css stylesheet"

Обновите файл hello.html, чтобы использовать стили style.css:

<!-- Author: Orkhan Alyshov ([email protected]) -->
<html>
  <head>
    <link type="text/css" rel="stylesheet" media="all" href="style.css" />
  </head>
  <body>
    <h1>Hello, World!</h1>
  </body>
</html>
git add lib/hello.html
git commit -m "Hello uses style.css"

Обновите файл index.html, чтобы он тоже использовал style.css:

<html>
  <head>
    <link type="text/css" rel="stylesheet" media="all" href="lib/style.css" />
  </head>
  <body>
    <iframe src="lib/hello.html" width="200" height="200" />
  </body>
</html>
git add index.html
git commit -m "Updated index.html"

Навигация по веткам

Теперь в вашем проекте есть две ветки:

$ git hist --all
* 07a2a46 2011-03-09 | Updated index.html (HEAD, style) [Orkhan Alyshov]
* 649d26c 2011-03-09 | Hello uses style.css [Orkhan Alyshov]
* 1f3cbd2 2011-03-09 | Added css stylesheet [Orkhan Alyshov]
* 8029c07 2011-03-09 | Added index.html. (master) [Orkhan Alyshov]
* 567948a 2011-03-09 | Moved hello.html to lib [Orkhan Alyshov]
* 6a78635 2011-03-09 | Add an author/email comment [Orkhan Alyshov]
* fa3c141 2011-03-09 | Added HTML header (v1) [Orkhan Alyshov]
* 8c32287 2011-03-09 | Added standard HTML page tags (v1-beta) [Orkhan Alyshov]
* 43628f7 2011-03-09 | Added h1 tag [Orkhan Alyshov]
* 911e8c9 2011-03-09 | First Commit [Orkhan Alyshov]

Просто используйте команду git checkout для переключения между ветками:

$ git checkout master
Switched to branch 'master'
$ cat lib/hello.html
<!-- Author: Orkhan Alyshov ([email protected]) -->
<html>
  <head>
  </head>
  <body>
    <h1>Hello, World!</h1>
  </body>
</html>

Сейчас мы находимся на ветке Master. Это заметно по тому, что файл hello.html не использует стили style.css.

Вернемся к ветке style:

$ git checkout style
Switched to branch 'style'
$ cat lib/hello.html
<!-- Author: Orkhan Alyshov ([email protected]) -->
<html>
  <head>
    <link type="text/css" rel="stylesheet" media="all" href="style.css" />
  </head>
  <body>
    <h1>Hello, World!</h1>
  </body>
</html>

Изменения в ветке master

Пока вы меняли ветку style, кто-то решил обновить ветку master. Они добавили README:

Создайте файл README в ветке master:

git checkout master

README:

This is the Hello World example from the git tutorial.

Сделайте коммит изменений README в ветку master:

git add README
git commit -m "Added README"

Просмотр отличающихся веток

Теперь у нас в репозитории есть две отличающиеся ветки. Используйте следующую лог-команду для просмотра веток и их отличий:

$ git hist --all
* 6c0f848 2011-03-09 | Added README (HEAD, master) [Orkhan Alyshov]
| * 07a2a46 2011-03-09 | Updated index.html (style) [Orkhan Alyshov]
| * 649d26c 2011-03-09 | Hello uses style.css [Orkhan Alyshov]
| * 1f3cbd2 2011-03-09 | Added css stylesheet [Orkhan Alyshov]
|/
* 8029c07 2011-03-09 | Added index.html. [Orkhan Alyshov]
* 567948a 2011-03-09 | Moved hello.html to lib [Orkhan Alyshov]
* 6a78635 2011-03-09 | Add an author/email comment [Orkhan Alyshov]
* fa3c141 2011-03-09 | Added HTML header (v1) [Orkhan Alyshov]
* 8c32287 2011-03-09 | Added standard HTML page tags (v1-beta) [Orkhan Alyshov]
* 43628f7 2011-03-09 | Added h1 tag [Orkhan Alyshov]
* 911e8c9 2011-03-09 | First Commit [Orkhan Alyshov]

Это наша первая возможность увидеть в действии --graph в git hist. Добавление опции --graph в git log вызывает построение дерева коммитов с помощью простых ASCII символов. Мы видим обе ветки (style и master), и то, что ветка master является текущей HEAD. Общим предшественником обеих веток является коммит "Added index.html". Метка --all гарантированно означает, что мы видим все ветки. По умолчанию показывается только текущая ветка.


Слияние

Слияние переносит изменения из двух веток в одну. Давайте вернемся к ветке style и сольем master с style:

$ git checkout style
Switched to branch 'style'
$ git merge master
Merge made by recursive.
 README |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)
 create mode 100644 README
$ git hist --all
*   5813a3f 2011-03-09 | Merge branch 'master' into style (HEAD, style) [Orkhan Alyshov]
|\  
| * 6c0f848 2011-03-09 | Added README (master) [Orkhan Alyshov]
* | 07a2a46 2011-03-09 | Updated index.html [Orkhan Alyshov]
* | 649d26c 2011-03-09 | Hello uses style.css [Orkhan Alyshov]
* | 1f3cbd2 2011-03-09 | Added css stylesheet [Orkhan Alyshov]
|/  
* 8029c07 2011-03-09 | Added index.html. [Orkhan Alyshov]
* 567948a 2011-03-09 | Moved hello.html to lib [Orkhan Alyshov]
* 6a78635 2011-03-09 | Add an author/email comment [Orkhan Alyshov]
* fa3c141 2011-03-09 | Added HTML header (v1) [Orkhan Alyshov]
* 8c32287 2011-03-09 | Added standard HTML page tags (v1-beta) [Orkhan Alyshov]
* 43628f7 2011-03-09 | Added h1 tag [Orkhan Alyshov]
* 911e8c9 2011-03-09 | First Commit [Orkhan Alyshov]

Путем периодического слияния ветки master с веткой style вы можете переносить из master любые изменения и поддерживать совместимость изменений style с изменениями в основной ветке. Однако, это делает графики коммитов действительно уродливыми. Позже мы рассмотрим возможность перебазирования, как альтернативы слиянию.


Создание конфликта

Вернитесь в ветку master и внесите следующие изменения:

git checkout master

lib/hello.html:

<!-- Author: Orkhan Alyshov ([email protected]) -->
<html>
  <head>
    <!-- no style -->
  </head>
  <body>
    <h1>Hello, World! Life is great!</h1>
  </body>
</html>
git add lib/hello.html
git commit -m "Life is great!"

Просмотр веток:

$ git hist --all
* 454ec68 2011-03-09 | Life is great! (HEAD, master) [Orkhan Alyshov]
| * 5813a3f 2011-03-09 | Merge branch 'master' into style (style) [Orkhan Alyshov]
| |\  
| |/  
|/| 
* | 6c0f848 2011-03-09 | Added README [Orkhan Alyshov]
| * 07a2a46 2011-03-09 | Updated index.html [Orkhan Alyshov]
| * 649d26c 2011-03-09 | Hello uses style.css [Orkhan Alyshov]
| * 1f3cbd2 2011-03-09 | Added css stylesheet [Orkhan Alyshov]
|/  
* 8029c07 2011-03-09 | Added index.html. [Orkhan Alyshov]
* 567948a 2011-03-09 | Moved hello.html to lib [Orkhan Alyshov]
* 6a78635 2011-03-09 | Add an author/email comment [Orkhan Alyshov]
* fa3c141 2011-03-09 | Added HTML header (v1) [Orkhan Alyshov]
* 8c32287 2011-03-09 | Added standard HTML page tags (v1-beta) [Orkhan Alyshov]
* 43628f7 2011-03-09 | Added h1 tag [Orkhan Alyshov]
* 911e8c9 2011-03-09 | First Commit [Orkhan Alyshov]

После коммита "Added README" ветка master была объединена с веткой style, но в настоящее время в master есть дополнительный коммит, который не был слит с style.


Разрешение конфликтов

Теперь вернемся к ветке style и попытаемся объединить ее с новой веткой master:

$ git checkout style
Switched to branch 'style'
$ git merge master
Auto-merging lib/hello.html
CONFLICT (content): Merge conflict in lib/hello.html
Automatic merge failed; fix conflicts and then commit the result.

Если вы откроете lib/hello.html, вы увидите:

<!-- Author: Orkhan Alyshov ([email protected]) -->
<html>
  <head>
<<<<<<< HEAD
    <link type="text/css" rel="stylesheet" media="all" href="style.css" />
=======
    <!-- no style -->
>>>>>>> master
  </head>
  <body>
    <h1>Hello,World! Life is great!</h1>
  </body>
</html>

Первый раздел - версия во главе текущей ветки (style). Второй раздел - версия ветки master.

Вам необходимо вручную разрешить конфликт. Внесите изменения в lib/hello.html для достижения следующего результата:

<!-- Author: Orkhan Alyshov ([email protected]) -->
<html>
  <head>
    <link type="text/css" rel="stylesheet" media="all" href="style.css" />
  </head>
  <body>
    <h1>Hello, World! Life is great!</h1>
  </body>
</html>

Сделайте коммит решения конфликта:

$ git add lib/hello.html
$ git commit -m "Merged master fixed conflict."
Recorded resolution for 'lib/hello.html'.
[style 645c4e6] Merged master fixed conflict.

Перебазирование как альтернатива слиянию

Давайте рассмотрим различия между слиянием и перебазированием. Для того, чтобы это сделать, нам нужно вернуться в репозиторий в момент до первого слияния, а затем повторить те же действия, но с использованием перебазирования вместо слияния. Мы будем использовать команду reset для возврата веток к предыдущему состоянию.


Сброс ветки style

Давайте вернемся во времени на ветке style к точке перед тем, как мы слили ее с веткой master. Мы можем сбросить ветку к любому коммиту. По сути, это изменение указателя ветки на любую точку дерева коммитов. В этом случае мы хотим вернуться в ветке style в точку перед слиянием с master. Нам необходимо найти последний коммит перед слиянием:

$ git checkout style
Already on 'style'
$ git hist
*   645c4e6 2011-03-09 | Merged master fixed conflict. (HEAD, style) [Orkhan Alyshov]
|\  
| * 454ec68 2011-03-09 | Life is great! (master) [Orkhan Alyshov]
* |   5813a3f 2011-03-09 | Merge branch 'master' into style [Orkhan Alyshov]
|\ \  
| |/  
| * 6c0f848 2011-03-09 | Added README [Orkhan Alyshov]
* | 07a2a46 2011-03-09 | Updated index.html [Orkhan Alyshov]
* | 649d26c 2011-03-09 | Hello uses style.css [Orkhan Alyshov]
* | 1f3cbd2 2011-03-09 | Added css stylesheet [Orkhan Alyshov]
|/  
* 8029c07 2011-03-09 | Added index.html. [Orkhan Alyshov]
* 567948a 2011-03-09 | Moved hello.html to lib [Orkhan Alyshov]
* 6a78635 2011-03-09 | Add an author/email comment [Orkhan Alyshov]
* fa3c141 2011-03-09 | Added HTML header (v1) [Orkhan Alyshov]
* 8c32287 2011-03-09 | Added standard HTML page tags (v1-beta) [Orkhan Alyshov]
* 43628f7 2011-03-09 | Added h1 tag [Orkhan Alyshov]
* 911e8c9 2011-03-09 | First Commit [Orkhan Alyshov]

Это немного трудно читать, но, глядя на данные, мы видим, что коммит "Updated index.html" был последним на ветке style перед слиянием. Давайте сбросим ветку style к этому коммиту:

$ git reset --hard 07a2a46
HEAD is now at 07a2a46 Updated index.html

Поищите лог ветки style. У нас в истории больше нет коммитов слияний:

$ git hist --all
* 454ec68 2011-03-09 | Life is great! (master) [Orkhan Alyshov]
* 6c0f848 2011-03-09 | Added README [Orkhan Alyshov]
| * 07a2a46 2011-03-09 | Updated index.html (HEAD, style) [Orkhan Alyshov]
| * 649d26c 2011-03-09 | Hello uses style.css [Orkhan Alyshov]
| * 1f3cbd2 2011-03-09 | Added css stylesheet [Orkhan Alyshov]
|/  
* 8029c07 2011-03-09 | Added index.html. [Orkhan Alyshov]
* 567948a 2011-03-09 | Moved hello.html to lib [Orkhan Alyshov]
* 6a78635 2011-03-09 | Add an author/email comment [Orkhan Alyshov]
* fa3c141 2011-03-09 | Added HTML header (v1) [Orkhan Alyshov]
* 8c32287 2011-03-09 | Added standard HTML page tags (v1-beta) [Orkhan Alyshov]
* 43628f7 2011-03-09 | Added h1 tag [Orkhan Alyshov]
* 911e8c9 2011-03-09 | First Commit [Orkhan Alyshov]

Сброс ветки master

Добавив интерактивный режим в ветку master, мы внесли изменения, конфликтующие с изменениями в ветке style. Давайте вернемся в ветке master в точку перед внесением конфликтующих изменений. Это позволяет нам продемонстрировать работу команды rebase, не беспокоясь о конфликтах:

$ git checkout master
$ git hist
* 454ec68 2011-03-09 | Life is great! (HEAD, master) [Orkhan Alyshov]
* 6c0f848 2011-03-09 | Added README [Orkhan Alyshov]
* 8029c07 2011-03-09 | Added index.html. [Orkhan Alyshov]
* 567948a 2011-03-09 | Moved hello.html to lib [Orkhan Alyshov]
* 6a78635 2011-03-09 | Add an author/email comment [Orkhan Alyshov]
* fa3c141 2011-03-09 | Added HTML header (v1) [Orkhan Alyshov]
* 8c32287 2011-03-09 | Added standard HTML page tags (v1-beta) [Orkhan Alyshov]
* 43628f7 2011-03-09 | Added h1 tag [Orkhan Alyshov]
* 911e8c9 2011-03-09 | First Commit [Orkhan Alyshov]

Коммит "Added README" идет непосредственно перед коммитом конфликтующего интерактивного режима. Мы сбросим ветку master к коммиту "Added README".

$ git reset --hard 6c0f848
$ git hist --all
* 6c0f848 2011-03-09 | Added README (HEAD, master) [Orkhan Alyshov]
| * 07a2a46 2011-03-09 | Updated index.html (style) [Orkhan Alyshov]
| * 649d26c 2011-03-09 | Hello uses style.css [Orkhan Alyshov]
| * 1f3cbd2 2011-03-09 | Added css stylesheet [Orkhan Alyshov]
|/  
* 8029c07 2011-03-09 | Added index.html. [Orkhan Alyshov]
* 567948a 2011-03-09 | Moved hello.html to lib [Orkhan Alyshov]
* 6a78635 2011-03-09 | Add an author/email comment [Orkhan Alyshov]
* fa3c141 2011-03-09 | Added HTML header (v1) [Orkhan Alyshov]
* 8c32287 2011-03-09 | Added standard HTML page tags (v1-beta) [Orkhan Alyshov]
* 43628f7 2011-03-09 | Added h1 tag [Orkhan Alyshov]
* 911e8c9 2011-03-09 | First Commit [Orkhan Alyshov]

Перебазирование

Итак, мы вернулись в точку до первого слияния и хотим перенести изменения из ветки master в нашу ветку style. На этот раз для переноса изменений из ветки master мы будем использовать команду rebase вместо слияния.

$ git checkout style
Switched to branch 'style'
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: Added css stylesheet
Applying: Hello uses style.css
Applying: Updated index.html
$ git hist
* 6e6c76a 2011-03-09 | Updated index.html (HEAD, style) [Orkhan Alyshov]
* 1436f13 2011-03-09 | Hello uses style.css [Orkhan Alyshov]
* 59da9a7 2011-03-09 | Added css stylesheet [Orkhan Alyshov]
* 6c0f848 2011-03-09 | Added README (master) [Orkhan Alyshov]
* 8029c07 2011-03-09 | Added index.html. [Orkhan Alyshov]
* 567948a 2011-03-09 | Moved hello.html to lib [Orkhan Alyshov]
* 6a78635 2011-03-09 | Add an author/email comment [Orkhan Alyshov]
* fa3c141 2011-03-09 | Added HTML header (v1) [Orkhan Alyshov]
* 8c32287 2011-03-09 | Added standard HTML page tags (v1-beta) [Orkhan Alyshov]
* 43628f7 2011-03-09 | Added h1 tag [Orkhan Alyshov]
* 911e8c9 2011-03-09 | First Commit [Orkhan Alyshov]

Слияние VS перебазирование

Конечный результат перебазирования очень похож на результат слияния. Ветка style в настоящее время содержит все свои изменения, а также все изменения ветки master. Однако, дерево коммитов значительно отличается. Дерево коммитов ветки style было переписано таким образом, что ветка master является частью истории коммитов. Это делает цепь коммитов линейной и гораздо более читабельной.

Когда использовать перебазирование, а когда слияние?

Не используйте перебазирование:

  1. Если ветка является публичной и расшаренной. Переписывание общих веток будет мешать работе других членов команды;
  2. Когда важна точная история коммитов ветки (так как команда rebase переписывает историю коммитов);

Учитывая приведенные выше рекомендации, я предпочитаю использовать rebase для кратковременных, локальных веток, а слияние для веток в публичном репозитории.


Слияние в ветку master

$ git checkout master
Switched to branch 'master'
$ git merge style
Updating 6c0f848..6e6c76a
Fast-forward
 index.html       |    2 +-
 lib/style.css |    8 ++++++++
 lib/hello.html   |    6 ++++--
 3 files changed, 13 insertions(+), 3 deletions(-)
 create mode 100644 lib/style.css

Поскольку последний коммит ветки master прямо предшествует последнему коммиту ветки style, git может выполнить ускоренное слияние-перемотку. При быстрой перемотке вперед, git просто передвигает указатель вперед, таким образом указывая на тот же коммит, что и ветка style.

При быстрой перемотке конфликтов быть не может.


Несколько репозиториев

До сих пор мы работали с одним git репозиторием. Однако, git удается отлично работать с несколькими репозиториями. Эти дополнительные репозитории могут храниться локально, или доступ к ним может осуществляться через сетевое подключение. В следующем разделе мы создадим новый репозиторий с именем "cloned_hello". Мы покажем, как перемещать изменения из одного репозитория в другой и как разрешать конфликты, возникающие в результате работы с двумя репозиториями.

А пока что поработаем с локальными репозиториями (т.е. репозиториями, хранящимися на вашем локальном жестком диске), однако практически все, что вы узнаете в этом разделе, будет применяться к нескольким репозиториям, несмотря на то, хранятся ли они локально или являются публичными.

Примечание: Мы будем вносить изменения в обе копии наших репозиториев. Обращайте внимание на то, в каком репозитории вы находитесь на каждом шаге в следующих уроках.


Клонирование репозиториев

Если вы работаете в команде, последующие главы довольно важны в понимании, т.к. вы почти всегда будете работать с клонированными репозиториями.

Перейдите в рабочий каталог и сделайте клон вашего репозитория hello:

$ cd ..
$ pwd
/Users/Orkhan/Documents/Presentations/githowto/auto
$ ls
hello

В этот момент вы должны находиться в "рабочем" каталоге. Здесь должен быть единственный репозиторий под названием "hello".

Давайте создадим клон репозитория:

$ git clone hello cloned_hello
Cloning into cloned_hello...
done.
$ ls
cloned_hello
hello

В вашем рабочем каталоге теперь должно быть два репозитория: оригинальный репозиторий "hello" и клонированный репозиторий "cloned_hello".


Просмотр клонированного репозитория

Давайте взглянем на клонированный репозиторий:

$ cd cloned_hello
$ ls
README
index.html
lib

Просмотрите историю репозитория:

$ git hist --all
* 6e6c76a 2011-03-09 | Updated index.html (HEAD, origin/master, origin/style, origin/HEAD, master) [Orkhan Alyshov]
* 1436f13 2011-03-09 | Hello uses style.css [Orkhan Alyshov]
* 59da9a7 2011-03-09 | Added css stylesheet [Orkhan Alyshov]
* 6c0f848 2011-03-09 | Added README [Orkhan Alyshov]
* 8029c07 2011-03-09 | Added index.html. [Orkhan Alyshov]
* 567948a 2011-03-09 | Moved hello.html to lib [Orkhan Alyshov]
* 6a78635 2011-03-09 | Add an author/email comment [Orkhan Alyshov]
* fa3c141 2011-03-09 | Added HTML header (v1) [Orkhan Alyshov]
* 8c32287 2011-03-09 | Added standard HTML page tags (v1-beta) [Orkhan Alyshov]
* 43628f7 2011-03-09 | Added h1 tag [Orkhan Alyshov]
* 911e8c9 2011-03-09 | First Commit [Orkhan Alyshov]

Вы увидите список всех коммитов в новый репозиторий, и он должен (более или менее) совпадать с историей коммитов в оригинальном репозитории. Единственная разница должна быть в названиях веток.

Вы увидите ветку master (HEAD) в списке истории. Вы также увидите ветки со странными именами (origin/master, origin/style и origin/HEAD). Мы поговорим о них чуть позже.


Что такое origin?

$ git remote
origin

Мы видим, что клонированный репозиторий знает об имени по умолчанию удаленного репозитория. Давайте посмотрим, можем ли мы получить более подробную информацию об имени по умолчанию:

$ git remote show origin
* remote origin
  Fetch URL: /Users/Orkhan/Documents/Presentations/githowto/auto/hello
  Push  URL: /Users/Orkhan/Documents/Presentations/githowto/auto/hello
  HEAD branch (remote HEAD is ambiguous, may be one of the following):
    style
    master
  Remote branches:
    style  tracked
    master tracked
  Local branch configured for 'git pull':
    master merges with remote master
  Local ref configured for 'git push':
    master pushes to master (up to date)

Мы видим, что "имя по умолчанию"("origin") удаленного репозитория – оригинальное hello. Удаленные репозитории обычно размещаются на отдельной машине, возможно, централизованном сервере. Однако, как мы видим здесь, они могут с тем же успехом указывать на репозиторий на той же машине. Нет ничего особенного в имени "origin", однако существует традиция использовать "origin" в качестве имени первичного централизованного репозитория (если таковой имеется).


Удаленные ветки

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

$ git branch
* master

Как мы видим, в списке только ветка master. Где ветка style? Команда git branch выводит только список локальных веток по умолчанию.

Для того, чтобы увидеть все ветки, попробуйте следующую команду:

$ git branch -a
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/style
  remotes/origin/master

Git выводит все коммиты в оригинальный репозиторий, но ветки в удаленном репозитории не рассматриваются как локальные. Если мы хотим собственную ветку style, мы должны сами ее создать.


Изменение оригинального репозитория

Внесите изменения в оригинальный репозиторий hello. Сейчас мы находимся в репозитории hello.

Внесите следующие изменения в файл README:

This is the Hello World example from the git tutorial.
(changed in original)

Теперь добавьте это изменение и сделайте коммит:

git add README
git commit -m "Changed README in original repo"

Теперь в оригинальном репозитории есть более поздние изменения, которых нет в клонированной версии. Далее мы извлечем и сольем эти изменения в клонированный репозиторий.


Извлечение изменений

Сейчас мы находимся в репозитории cloned_hello.

$ git fetch
From /Users/Orkhan/Documents/Presentations/githowto/auto/hello
   6e6c76a..2faa4ea  master     -> origin/master
$ git hist --all
* 2faa4ea 2011-03-09 | Changed README in original repo (origin/master, origin/HEAD) [Orkhan Alyshov]
* 6e6c76a 2011-03-09 | Updated index.html (HEAD, origin/style, master) [Orkhan Alyshov]
* 1436f13 2011-03-09 | Hello uses style.css [Orkhan Alyshov]
* 59da9a7 2011-03-09 | Added css stylesheet [Orkhan Alyshov]
* 6c0f848 2011-03-09 | Added README [Orkhan Alyshov]
* 8029c07 2011-03-09 | Added index.html. [Orkhan Alyshov]
* 567948a 2011-03-09 | Moved hello.html to lib [Orkhan Alyshov]
* 6a78635 2011-03-09 | Add an author/email comment [Orkhan Alyshov]
* fa3c141 2011-03-09 | Added HTML header (v1) [Orkhan Alyshov]
* 8c32287 2011-03-09 | Added standard HTML page tags (v1-beta) [Orkhan Alyshov]
* 43628f7 2011-03-09 | Added h1 tag [Orkhan Alyshov]
* 911e8c9 2011-03-09 | First Commit [Orkhan Alyshov]

На данный момент в репозитории есть все коммиты из оригинального репозитория, но они не интегрированы в локальные ветки клонированного репозитория. В истории выше найдите коммит "Changed README in original repo". Обратите внимание, что коммит включает в себя коммиты "origin/master" и "origin/HEAD". Теперь давайте посмотрим на коммит "Updated index.html". Вы увидите, что локальная ветка master указывает на этот коммит, а не на новый коммит, который мы только что извлекли. Выводом является то, что команда "git fetch" будет извлекать новые коммиты из удаленного репозитория, но не будет сливать их с вашими наработками в локальных ветках.

Мы можем продемонстрировать, что клонированный файл README не изменился:

$ cat README
This is the Hello World example from the git tutorial.

Как видите, никаких изменений.


Слияние извлеченных изменений

Слейте извлеченные изменения в локальную ветку master:

$ git merge origin/master
Updating 6e6c76a..2faa4ea
Fast-forward
 README |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)

Еще раз проверьте файл README. Сейчас мы должны увидеть изменения:

$ cat README
This is the Hello World example from the git tutorial.
(changed in original)

Вот и изменения. Хотя команда "git fetch" не сливает изменения, мы можем вручную слить изменения из удаленного репозитория.


Извлечение и слияние изменений

Команда git pull эквивалентна комбинации git fetch и git merge.

Мы не собираемся опять проходить весь процесс создания нового изменения и его извлечения, но мы хотим, чтобы вы знали, что выполнение:

git pull

действительно эквивалентно двум следующим шагам:

git fetch
git merge origin/master

Чистые репозитории

Чистые репозитории (без рабочих каталогов) обычно используются для расшаривания.

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

Создайте чистый репозиторий:

$ git clone --bare hello hello.git
Cloning into bare repository hello.git...
done.
$ ls hello.git
HEAD
config
description
hooks
info
objects
packed-refs
refs
 

Как правило, репозитории, оканчивающиеся на ".git" являются чистыми репозиториями. Мы видим, что в репозитории hello.git нет рабочего каталога. По сути, это есть не что иное, как каталог .git нечистого репозитория.


Добавление удаленного репозитория

Давайте добавим репозиторий hello.git к нашему оригинальному репозиторию:

cd hello
git remote add shared ../hello.git

Отправка изменений

Так как чистые репозитории, как правило, расшариваются на каком-нибудь сетевом сервере, нам необходимо отправить наши изменения в другие репозитории. Начнем с создания изменения для отправки. Отредактируйте файл README и сделайте коммит:

This is the Hello World example from the git tutorial.
(Changed in the original and pushed to shared)
git checkout master
git add README
git commit -m "Added shared comment to readme"

Теперь отправьте изменения в общий репозиторий:

$ git push shared master
To ../hello.git
   2faa4ea..79f507c  master -> master

Извлечение общих изменений

Быстро переключитесь в клонированный репозиторий и извлеките изменения, только что отправленные в общий репозиторий:

cd ../cloned_hello

Сейчас мы находимся в репозитории cloned_hello.

git remote add shared ../hello.git
git branch --track shared master
git pull shared master
cat README