Статьи

Git Explained: для начинающих

Я работаю с Git уже около двух лет, но только для своих личных проектов и проектов, которые у меня есть на GitHub. На работе мы все еще используем TFS и SVN (на данный момент). Недавно Паоло Перротта пришел в нашу компанию, чтобы провести курс по гибкому планированию, и, поскольку Git был довольно новым для большинства моих партнеров, он также быстро объяснил Git в контексте рефакторинга. Мне очень понравился его подход к объяснению, и поэтому я хотел бы повторить его объяснение здесь.

Как раз перед тем, как мы начнем ..

Чем Git отличается от других VCS (систем контроля версий)? Вероятно, наиболее очевидное различие заключается в том, что Git распространяется (в отличие, например, от SVN или TFS). Этот

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

Сам Git может быть представлен как нечто, что находится поверх вашей файловой системы и манипулирует файлами. Более того, вы можете представить Git в виде древовидной структуры, в которой каждый коммит создает новый узел в этом дереве. Практически все команды Git служат для навигации по этому дереву и для управления им соответствующим образом. Поэтому в этом уроке я хотел бы взглянуть на то, как работает Git, просмотрев репозиторий Git с точки зрения дерева, которое он создает. Для этого я иду через некоторые распространенные случаи использования, такие как

  • добавление / изменение нового файла
  • создание и объединение веток с конфликтами слияния и без них
  • Просмотр истории / журнала изменений
  • Выполнение отката до определенного коммита
  • Совместное использование / синхронизация вашего кода в удаленном / центральном хранилище

терминология

Вот терминология git:

  • master — основная ветка хранилища. В зависимости от рабочего процесса, это тот, над кем работают люди, или тот, где происходит интеграция.
  • клон — копирует существующий репозиторий git, обычно из какого-то удаленного места в вашу локальную среду.
  • commit — отправка файлов в хранилище (локальное); в других VCS это часто упоминается как «регистрация»
  • Получить или получить — это как «обновить» или «получить последнюю» в других VCS. Разница между извлечением и извлечением заключается в том, что извлечение сочетает в себе как получение самого последнего кода из удаленного репо, так и выполнение слияния.
  • push — используется для отправки кода в удаленный репозиторий
  • remote — это «удаленные» местоположения вашего хранилища, обычно на каком-то центральном сервере.
  • SHA — каждый коммит или узел в дереве Git идентифицируется уникальным ключом SHA. Вы можете использовать их в различных командах, чтобы управлять конкретным узлом.
  • head — это ссылка на узел, на который в данный момент указывает наше рабочее пространство хранилища.
  • ветвь — так же, как в других VCS, с той разницей, что ветвь в Git на самом деле не более особенная, чем конкретная метка на данном узле. Это не физическая копия файлов, как в других популярных VCS.

Настройка рабочей станции

Я не хочу вдаваться в детали настройки вашей рабочей станции, поскольку существует множество инструментов, которые частично различаются на разных платформах. Для этого поста я выполняю все операции в командной строке. Даже если вы не мошенник, вам стоит попробовать (это никогда не повредит). Для настройки командной строки Git access просто перейдите на git-scm.com/downloads, где вы найдете необходимые загрузки для вашей ОС. Более подробную информацию можно найти и здесь . После того, как все настроено и у вас есть «git» в переменной окружения PATH, первое, что вам нужно сделать, это настроить git с вашим именем и адресом электронной почты:

1
2
$ git config --global user.name "Juri Strumpflohner"
$ git config --global user.email "[email protected]"

Начнем: создайте новый Git-репозиторий

Прежде чем начать, давайте создадим новый каталог, в котором будет находиться репозиторий git, и cd в него:

1
2
$ mkdir mygitrepo
$ cd mygitrepo

Теперь мы готовы инициализировать новый git-репозиторий.

1
2
$ git init
Initialized empty Git repository in c:/projects/mystuff/temprepos/mygitrepo/.git/

Мы можем проверить текущее состояние git-репозитория, используя

1
2
3
4
5
6
$ git status
# On branch master
#
# Initial commit
#
nothing to commit (create/copy files and use "git add" to track)

Создать и зафиксировать новый файл

Следующим шагом является создание нового файла и добавление в него некоторого содержимого.

1
2
$ touch hallo.txt
$ echo Hello, world! > hallo.txt

Опять же, проверка статуса теперь показывает следующее

01
02
03
04
05
06
07
08
09
10
$ git status
# On branch master
#
# Initial commit
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#       hallo.txt
nothing added to commit but untracked files present (use "git add" to track)

Чтобы «зарегистрировать» файл для фиксации, нам нужно добавить его в git, используя

1
$ git add hallo.txt

Проверка состояния теперь указывает, что файл готов к фиксации:

01
02
03
04
05
06
07
08
09
10
$ git status
# On branch master
#
# Initial commit
#
# Changes to be committed:
#   (use "git rm --cached <file>..." to unstage)
#
#       new file:   hallo.txt
#

Теперь мы можем зафиксировать это в хранилище

1
2
3
$ git commit -m "Add my first file"
  1 file changed, 1 insertion(+)
  create mode 100644 hallo.txt

Обычной практикой является использование «присутствия» в сообщениях коммитов. Поэтому вместо того, чтобы писать «добавил мой первый файл», мы пишем «добавить мой первый файл». Так что, если мы сейчас отступим на секунду и посмотрим на дерево, у нас будет следующее.

Состояние дерева репо после 1-го коммита

Состояние дерева репо после 1-го коммита

Есть один узел, на который указывает мастер «label».

Добавить другой файл

Добавим еще один файл:

1
2
3
4
5
$ echo "Hi, I'm another file" > anotherfile.txt
$ git add .
$ git commit -m "add another file with some other content"
  1 file changed, 1 insertion(+)
  create mode 100644 anotherfile.txt

Кстати, обратите внимание, что на этот раз я использовал git add . который добавляет все файлы в текущем каталоге ( . ). С точки зрения дерева у нас теперь есть другой узел, и мастер перешел к этому.

gitrepo_tree2

Создать (особенность) ветку

Ветвление и слияние — это то, что делает Git настолько мощным и для чего он был оптимизирован, будучи распределенной системой контроля версий (VCS). Действительно, ветки функций довольно популярны для использования с Git. Ветви компонентов создаются для каждого нового вида функций, которые вы собираетесь добавить в свою систему, и они обычно удаляются после того, как функция возвращается обратно в основную ветку интеграции (обычно это основная ветка). Преимущество состоит в том, что вы можете экспериментировать с новыми функциями в отдельной изолированной «игровой площадке» и быстро переключаться назад и вперед к исходной «основной» ветке, когда это необходимо. Более того, его можно легко снова удалить (если он не нужен), просто отбросив ветку функций. Но давайте начнем. Прежде всего я создаю новую функциональную ветку:

1
$ git branch my-feature-branch

проведение

1
2
3
$ git branch
* master
  my-feature-branch

мы получаем список филиалов. Знак * перед master указывает, что мы в данный момент находимся в этой ветви. Вместо этого перейдем к my-feature-branch :

1
2
$ git checkout my-feature-branch
Switched to branch 'my-feature-branch'

Еще раз

1
2
3
$ git branch
  master
* my-feature-branch

Обратите внимание, что вы можете напрямую использовать команду git checkout -b my-feature-branch для создания и извлечения новой ветки за один шаг. Отличие от других VCS в том, что существует только один рабочий каталог . Все ваши ветки живут в одном и том же, и нет отдельной папки для каждой создаваемой вами ветки. Вместо этого, когда вы переключаетесь между ветвями, Git заменит содержимое вашего рабочего каталога так, чтобы оно совпадало с тем, в котором вы переключаетесь. Давайте изменим один из наших существующих файлов

1
2
3
4
$ echo "Hi" >> hallo.txt
$ cat hallo.txt
Hello, world!
Hi

… а затем передайте его в наш новый филиал

1
2
3
$ git commit -a -m "modify file adding hi"
2fa266a] modify file adding hi
1 file changed, 1 insertion(+)

Обратите внимание , что на этот раз я использовал git commit -a -m чтобы добавить и зафиксировать модификацию за один шаг. Это работает только для файлов, которые уже были добавлены в Git Repo ранее. Новые файлы не будут добавлены таким образом и требуют явного git add как показано ранее. Как насчет нашего дерева?

gitrepo_tree3

Пока что все кажется довольно нормальным, и у нас все еще есть прямая линия в дереве, но обратите внимание, что теперь master остался там, где он был, и мы двинулись вперед с my-feature-branch . Вернемся к мастеру и изменим там же файл.

1
2
$ git checkout master
Switched to branch 'master'

Как и ожидалось, hallo.txt не hallo.txt :

1
2
$ cat hallo.txt
Hello, world!

Давайте изменим и передадим его на master (это позже вызовет хороший конфликт ).

1
2
3
4
$ echo "Hi I was changed in master" >> hallo.txt
$ git commit -a -m "add line on hallo.txt"
c8616db] add line on hallo.txt
1 file changed, 1 insertion(+)

Наше дерево теперь визуализирует ветку:

gitrepo_tree4

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

Следующим шагом будет объединение нашей ветки функций обратно в master . Это делается с помощью команды слияния

1
2
3
4
$ git merge my-feature-branch
Auto-merging hallo.txt
CONFLICT (content): Merge conflict in hallo.txt
Automatic merge failed; fix conflicts and then commit the result.

Как и ожидалось, у нас есть конфликт слияния в hallo.txt .

1
2
3
4
5
6
Hello, world!
<<<<<<< HEAD
Hi I was changed in master
=======
Hi
>>>>>>> my-feature-branch

Позволяет решить это:

1
2
3
Hello, world!
Hi I was changed in master
Hi

..и затем совершить это

1
2
$ git commit -a -m "resolve merge conflicts"
[master 6834fb2] resolve merge conflicts

Дерево отражает наше слияние.

Перейти к определенному коммиту

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
$ git log
commit 6834fb2b38d4ed12f5486ebcb6c1699fe9039e8e
Merge: c8616db 2fa266a
Author: = <juri.strumpflohner@gmail.com>
Date:   Mon Apr 22 23:19:32 2013 +0200
 
    resolve merge conflicts
 
commit c8616db8097e926c64bfcac4a09306839b008dc6
Author: Juri <juri.strumpflohner@gmail.com>
Date:   Mon Apr 22 09:39:57 2013 +0200
 
    add line on hallo.txt
 
commit 2fa266aaaa61c51bd77334516139597a727d4af1
Author: Juri <juri.strumpflohner@gmail.com>
Date:   Mon Apr 22 09:24:00 2013 +0200
 
    modify file adding hi
 
commit 03883808a04a268309b9b9f5c7ace651fc4f3f4b
Author: Juri <juri.strumpflohner@gmail.com>
Date:   Mon Apr 22 09:13:49 2013 +0200
 
    add another file with some other content
 
commit aad15dea687e46e9104db55103919d21e9be8916
Author: Juri <juri.strumpflohner@gmail.com>
Date:   Mon Apr 22 08:58:51 2013 +0200
 
    Add my first file

Возьмите один из идентификаторов (также, если он не весь, это не имеет значения) и перейдите на этот узел с помощью команды checkout

01
02
03
04
05
06
07
08
09
10
11
12
13
$ git checkout c8616db
Note: checking out 'c8616db'.
 
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 c8616db... add line on hallo.txt

Обратите внимание на комментарий, который выводит git. Что это значит? Отделенная голова означает, что «голова» больше не указывает на «метку» ветви, а на конкретный коммит в дереве. Вы можете думать о ГОЛОВКЕ как о «текущей ветви». Когда вы переключаете ветки с помощью git checkout , ревизия HEAD изменяется, чтобы указывать на вершину новой ветки. … HEAD может ссылаться на конкретную ревизию, которая не связана с именем ветви. Эта ситуация называется отделенной ГОЛОВОЙ . Stackoverflow Post Обычно, когда я изменяю hallo.txt и фиксирую изменения, дерево выглядит следующим образом:

Отдельное состояние головы

Отдельное состояние головы

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

01
02
03
04
05
06
07
08
09
10
11
12
$ git checkout master
Warning: you are leaving 1 commit behind, not connected to
any of your branches:
 
  576bcb8 change file undoing previous changes
 
If you want to keep them by creating a new branch, this may be a good time
to do so with:
 
 git branch new_branch_name 576bcb8239e0ef49d3a6d5a227ff2d1eb73eee55
 
Switched to branch 'master'

И на самом деле, Git так любезно, чтобы напомнить нам об этом факте. Теперь дерево выглядит снова, как на рисунке 6.

отмена

Переход назад — это хорошо, но что, если мы хотим отменить все обратно в состояние до слияния ветви функции? Это так же просто, как

1
2
$ git reset --hard c8616db
HEAD is now at c8616db add line on hallo.txt
Дерево после сброса

Дерево после сброса

Общий синтаксис здесь: git reset --hard <tag/branch/commit id> .

Совместное использование / синхронизация вашего репозитория

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

1
$ git remote add origin git@github.com:juristr/intro.js.git

Чтобы увидеть, удалось ли мне, просто введите:

1
$ git remote -v

который перечисляет все добавленные пульты. Теперь нам нужно опубликовать нашу локальную ветку master в удаленном хранилище. Это сделано как

1
$ git push -u origin master

И мы сделали. По-настоящему мощным является то, что вы можете добавить несколько разных пультов. Это часто используется в сочетании с решениями облачного хостинга для развертывания вашего кода на вашем сервере. Например, вы можете добавить удаленный сервер с именем «deploy», который указывает на некоторый репозиторий сервера облачного хостинга, например

1
$ git remote add deploy git@somecloudserver.com:juristr/myproject

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

1
$ git push deploy

клонирование

Точно так же это работает, если вы хотите начать с существующего удаленного хранилища. Первый шаг, который необходимо сделать, это «извлечь» исходный код, который называется клонированием в терминологии Git. Так что мы бы сделали что-то вроде

1
2
3
4
5
6
7
$ git clone git@github.com:juristr/intro.js.git
Cloning into 'intro.js'...
remote: Counting objects: 430, done.
remote: Compressing objects: 100% (293/293), done.
remote: Total 430 (delta 184), reused 363 (delta 128)
Receiving objects: 100% (430/430), 419.70 KiB | 102 KiB/s, done.
Resolving deltas: 100% (184/184), done.

Это создаст папку (в данном случае) с именем «intro.js», и если мы введем ее

1
$ cd intro.js/

и проверим для удаленных мы видим, что соответствующая информация отслеживания удаленного хранилища уже настроена

1
2
3
$ git remote -v
origin  git@github.com:juristr/intro.js.git (fetch)
origin  git@github.com:juristr/intro.js.git (push)

Теперь мы можем запустить цикл commit / branch / push как обычно.

Ресурсы и ссылки

Сценарии, приведенные выше, были простыми, но в то же время, вероятно, и наиболее используемыми. Но есть намного больше, на что способен Git. Чтобы получить более подробную информацию, вы можете обратиться по ссылкам ниже.