Начало работы с Git

29.11.2019

Теги: CLIGitWeb-разработкаКомандаСистемаКонтроляВерсий

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

Изменения сохраняются в виде коммитов, по-русски — фиксация. Сначала надо сделать начальный коммит, чтобы сохранить исходное состояние проекта, а затем для каждого изменения. Кроме того, Git позволяет отправлять данные на удаленный сервер. Отправляются не только готовая версия, но и все снимки, таким образом, любой человек из команды может посмотреть историю изменений.

Основные сведения

У Git’а есть три основных состояния, в которых могут находиться ваши файлы: зафиксированное (committed), изменённое (modified) и подготовленное (staged):

  • Зафиксированный значит, что файл уже сохранён в локальной базе.
  • К изменённым относятся файлы, которые поменялись, но ещё не были зафиксированы.
  • Подготовленные файлы — это изменённые файлы, отмеченные для включения в следующий commit.

Мы подошли к трём основным секциям проекта Git: Git-директория (git directory), рабочая директория (working directory) и область подготовленных файлов (staging area).

Git-директория — это то место, где Git хранит метаданные и базу объектов вашего проекта. Это самая важная часть Git, и это та часть, которая копируется при клонировании репозитория с другого компьютера.

Рабочая директория является снимком версии проекта. Файлы распаковываются из сжатой базы данных в Git-директории и располагаются на диске, для того чтобы их можно было изменять и использовать.

Область подготовленных файлов — это файл, обычно располагающийся в Git-директории, в нём содержится информация о том, какие изменения попадут в следующий commit. Эту область ещё называют «индекс», однако называть её stage-область также общепринято.

Базовый подход в работе с Git выглядит так:

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

Если определённая версия файла есть в Git-директории, эта версия считается зафиксированной. Если версия файла изменена и добавлена в индекс, значит, она подготовлена. И если файл был изменён с момента последнего распаковывания из репозитория, но не был добавлен в индекс, он считается изменённым.

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

Перейдем в директорию проекта и создадим в ней файл index.php, директорию app и файл app/readme.txt:

$ cd c:/work/localhost15/www
$ touch index.php
$ mkdir app
$ touch app/readme.txt

Инициализируем пустой репозиторий в директории проекта:

$ git init
Initialized empty Git repository in C:/work/localhost15/www/.git/

Добавление файлов в индекс

Добавляем файлы в индекс (stage-область), чтобы Git отслеживал изменения:

$ git add index.php  # добавить один файл
$ git add --all  # добавить все файлы
$ git add .  # добавить текущую директорию и все поддиректории
$ git add app/*  # добавить все файлы из директории app

Фиксация изменений (commit)

Фиксация изменений выполняется с помощью команды commit. Хорошая практика выполнять фиксацию перед каждым серьезным изменением.

Чтобы создать первый commit:

$ git commit -m "Initial сommit"
[master (root-commit) 9dfb500] Initial commit
 2 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 app/readme.txt
 create mode 100644 index.php

Команда status позволяет отслеживать, какие файлы в каком состоянии находятся:

$ git status
On branch master
nothing to commit, working tree clean

Внесем изменение в файл app/readme.txt:

$ echo 'Lorem ipsum' > app/readme.txt

Посмотрим состояние:

$ 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:   app/readme.txt
no changes added to commit (use "git add" and/or "git commit -a")

Файл app/readme.txt изменён, так что надо снова использовать команду add. Это неудобно, если измененных файлов много, поэтому делают так:

$ git commit -a -m "Change app/readme.txt"
[master c7a298b] Change app/readme.txt
 1 file changed, 1 insertion(+)

Добавление параметра -a в команду commit заставляет Git зафиксировать изменения в каждом уже отслеживаемом файле, позволяя обойтись без команды add.

Часто используемые команды

Что в индексе?

Команда git diff сравнивает содержимое рабочего каталога с содержимым индекса и показывает ещё не проиндексированные изменения.

$ git diff

Команда git diff --staged сравнивает индексированные изменения с последним commit:

$ git diff --staged  # --staged и --cached синонимы

Отмена добавления в индекс

Если файл somefile.txt был добавлен в индекс по ошибке, можно отменить индексирование:

$ git add somefile.txt  # файл добавлен в индекс по ошибке
$ git reset somefile.txt  # отменяем добавление файла в индекс
Команда git reset file.txt является сокращением для git reset --mixed HEAD file.txt.

Отмена изменений в файле

Допустим, мы внесли изменения в файл somefile.txt, эти изменения нас по каким-то причинам не устраивают. В этом случае можно вернуть его к тому виду, который был в последнем commit:

$ git checkout -- somefile.txt

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

Удаление файла

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

$ git rm app/readme.txt  # удаляем файл из отслеживаемых
rm 'app/readme.txt'
$ git commit -m "Delete app/readme.txt"  # фиксируем новое состояние
[master 40b0ab6] Delete app/readme.txt
 1 file changed, 1 deletion(-)
 delete mode 100644 app/readme.txt
$ git status  # все, файл удален из git
On branch master
nothing to commit, working tree clean

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

$ rm somefile.txt
$ git add .

Чтобы удалить файл из-под наблюдения Git, оставив его при этом в рабочем каталоге:

$ git rm --cached app/readme.txt

Чтобы удалить директорию из-под наблюдения Git, но оставить ее в рабочем каталоге:

$ git rm -r --cached app/temp/

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

Давайте создадим новую ветку style:

$ git checkout -b style
Switched to a new branch 'style'
$ git checkout -b branch-name
является сокращением для
$ git branch branch-name  # создать новую ветку branch-name
$ git checkout branch-name  # переключиться на ветку branch-name

Команда git status сообщает о том, что мы находимся в ветке style:

$ git status
On branch style
nothing to commit, working tree clean

Посмотреть список всех веток и узнать, с какой из них мы сейчас работаем, можно с помощью команды:

$ git branch
  master
* style

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

$ mkdir view
$ touch view/style.css

Добавим файл view/style.css под наблюдение Git:

$ git add view/style.css
$ git commit -m "Added view/style.css"
[style 33c26e0] Added view/style.css
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 view/style.css

Посмотрим, какие директории и файлы теперь в ветке style:

$ ls -lR
.:
total 0
-rw-r--r-- 1 Evgeniy 197121 0 ноя 23 12:26 index.php
drwxr-xr-x 1 Evgeniy 197121 0 ноя 23 12:31 view/

./view:
total 0
-rw-r--r-- 1 Evgeniy 197121 0 ноя 23 12:31 style.css

Переключимся на ветку master:

$ git checkout master
Switched to branch 'master'

Здесь нет директории view и файла style.css в ней:

$ ls -lR
.:
total 0
-rw-r--r-- 1 Evgeniy 197121 0 ноя 23 12:26 index.php
Когда мы переключаемся между ветками, Git изменяет содержимое директории проекта. Поэтому, находясь в ветке style мы видим директорию view и файл style.css в ней, а находясь в ветке master — нет.

Слияние веток

Давайте добавим в ветку master файл app/database.php:

$ mkdir app
$ touch app/database.php
$ git add app/database.php
$ git commit -m "Added app/database.php"
[master f863305] Added app/database.php
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 app/database.php

И посмотрим историю ветки master:

$ git log --pretty=oneline
f8633051af7ee984a36b52e9bdb073b996c45f4b (HEAD -> master) Added app/database.php
bef5cd8cb83b15b4bc6694e45df337fec5211306 Delete app/readme.txt
7e687a1323cce07e0bff6959cbc76dfb168b609d Change app/readme.txt
3b4820575ef1b82694de01a7f76503017c0867fc Initail commit

Переключимся на ветку style:

$ git checkout style

И посмотрим историю ветки style:

$ git log --pretty=oneline
f7750a4ac2aff93e15b69760d1c9d4f9925653a7 (HEAD -> style) Added view/style.css
bef5cd8cb83b15b4bc6694e45df337fec5211306 Delete app/readme.txt
7e687a1323cce07e0bff6959cbc76dfb168b609d Change app/readme.txt
3b4820575ef1b82694de01a7f76503017c0867fc Initail commit

Вливание ветки master в ветку style (merge branch «master» into «style»):

$ git merge master
Merge made by the 'recursive' strategy.
 app/database.php | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 app/database.php
В этот момент мы попадаем в редактор vi. Здесь можно добавить свой комментарий вдобавок к тому, что уже создан Git. При запуске редактора vi мы находимся в командном режиме (command mode). Чтобы добавить комментарий, надо перейти в режим ввода (insert mode) с помощью команды i (от слова insert). Переход из режима ввода в командный режим осуществляется клавишей Esc. Чтобы просто сохранить файл и выйти — ESC :wq Enter.

Как теперь выглядит история ветки style:

$ git log --pretty=oneline
188ca5cb64ac1a255850af769d68ba63f4008a41 (HEAD -> style) Merge branch 'master' into style
f8633051af7ee984a36b52e9bdb073b996c45f4b (master) Added app/database.php
f7750a4ac2aff93e15b69760d1c9d4f9925653a7 Added view/style.css
bef5cd8cb83b15b4bc6694e45df337fec5211306 Delete app/readme.txt
7e687a1323cce07e0bff6959cbc76dfb168b609d Change app/readme.txt
3b4820575ef1b82694de01a7f76503017c0867fc Initail commit

Состояние проекта в ветке style:

$ ls -lR
.:
total 0
drwxr-xr-x 1 Evgeniy 197121 0 ноя 23 13:08 app/
-rw-r--r-- 1 Evgeniy 197121 0 ноя 23 12:26 index.php
drwxr-xr-x 1 Evgeniy 197121 0 ноя 23 13:28 view/

./app:
total 0
-rw-r--r-- 1 Evgeniy 197121 0 ноя 23 13:08 database.php

./view:
total 0
-rw-r--r-- 1 Evgeniy 197121 0 ноя 23 13:28 style.css

Вливание ветки style в ветку master (merge branch «style» into «master»):

$ git checkout master  # переключаемся на ветку master
$ git merge style  # вливаем ветку style в ветку master

Конфликты при слиянии веток

Сейчас обе ветки у нас одинаковые. Давайте для ветки style добавим в файл view/style.css следующий код:

$ git checkout style
Already on 'style'
$ echo 'body {margin: 0; padding: 0}' > view/style.css
$ echo 'a {text-decoration: none}' >> view/style.css
$ git commit -a -m "Change view/style.css"
[style 226177d] Change view/style.css
 1 file changed, 1 insertion(+)

Файл view/style.css в ветке style теперь выглядит так:

$ cat view/style.css
body {margin: 0; padding: 0}
a {text-decoration: none}

Переключимся на ветку master и опять внесем изменения в файл view/style.css:

$ git checkout master
Switched to branch 'master'
$ echo 'body {margin: 10px; padding: 10px}' > view/style.css
$ echo 'a {text-decoration: none}' >> view/style.css
$ git commit -a -m "Change view/style.css"
[master f6bf2d2] Change view/style.css
 1 file changed, 1 insertion(+)

Файл view/style.css в ветке master теперь выглядит так:

$ cat view/style.css
body {margin: 10px; padding: 10px}
a {text-decoration: none}

Попытаемся слить ветку master с веткой style:

$ git merge style
Auto-merging view/style.css
CONFLICT (content): Merge conflict in view/style.css
Automatic merge failed; fix conflicts and then commit the result.

Файл view/style.css теперь выглядит так:

$ cat view/style.css
<<<<<<< HEAD
body {margin: 10px; padding: 10px}  # ветка master
=======
body {margin: 0; padding: 0}  # ветка style
>>>>>>> style
a {text-decoration: none}

Устраним конфликт:

$ echo 'body {margin: 0; padding: 0}' > view/style.css
$ echo 'a {text-decoration: none}' >> view/style.css
$ git commit -a -m "Merge conflict fixed"
[master c6f1cd1] Merge conflict fixed

Новое имя ветки

Чтобы переименовать текущую локальную ветку:

$ git branch -m new-branch-name

Чтобы переименовать другую локальную ветку:

$ git branch -m old-branch-name new-branch-name
Иногда можно встретить ключ -M вместо -m, оба ключа равнозначны, можно использовать любой.

Напрямую (одной командой) переименовать удаленную ветку нельзя. Сначала нужно переименовать локальную ветку, затем удалить удаленную ветку. И наконец, выгрузить локальную ветку, которую переименовали.

$ git branch -m old-branch-name new-branch-name
$ git push remote-repo-name :old-branch-name
$ git push remote-repo-name -u new-branch-name
Если сделать git push ветки без ключа -u, Git не свяжет локальную ветку с веткой удалённого репозитория. Последующая операция git pull в этой ветке будет неудачной, так как Git не будет знать, из какой удалённой ветки подтягивать изменения. Смысл использовать ключ -u есть только при push новых веток, для существующих (связанных с удалёнными) веток каждый раз перезаписывать связку необязательно.

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

Удаление локальной ветки:

$ git branch -d branch-name

Удаляемая ветка не должна быть текущей, иначе будет ошибка cannot delete branch. Сначала нужно переключиться на какую-либо другую ветку, а только потом выполнить удаление. Если ветка содержит ещё не слитые наработки, попытка удаления приведет к ошибке branch is not fully merged. В этом случае ветку можно удалить так:

$ git branch -D branch-name

Чтобы увидеть все ветки, содержащие наработки, которые ещё не были слиты в текущую ветку:

$ git branch --no-merged
  fixbug

Чтобы увидеть все ветки, которые уже были слиты в текущую ветку (и которые можно удалить):

$ git branch --merged
  issue53
* master

Те ветки из этого списка, перед которыми нет символа *, можно смело удалять.

Удаление удалённой ветки:

$ git push remote-repo-name :branch-name
$ git push remote-repo-name --delete branch-name

Удаленный репозиторой

Чтобы обменяться информацией с другими разработчиками и отправить данные в удаленный репозиторий, надо добавить себе ссылку (pointer) на этот репозиторий:

$ git remote add remote-repo-name https://github.com/pupkin/test.git

Теперь у нас есть ссылка на удаленный репозиторий под именем remote-repo-name. Посмотреть все удаленные репозитории можно с помощью команды remote:

$ git remote
remote-repo-name

Можно также указать ключ -v, чтобы просмотреть адреса для чтения и записи, привязанные к репозиторию:

$ git remote -v
remote-repo-name   https://github.com/pupkin/test.git (fetch)
remote-repo-name   https://github.com/pupkin/test.git (push)

Для получения данных из удалённого репозитория:

$ git fetch remote-repo-name
Команда get fetch забирает данные из удаленного репозитория, но не сливает их с локальным. Это необходимо делать вручную с помощью команды git merge.

Команда git pull — это сокращение для последовательности двух команд: git fetch (получение изменений с сервера) и git merge (слияние текущей ветки с такой же веткой на сервере).

Для отправки данных в удалённый репозиторий:

$ git push remote-repo-name local-branch:remote-branch

Это означает для Git «возьми мою локальную ветку local-branch и обнови ей удалённую ветку remote-branch». Если такой удаленной ветки еще нет — она будет создана. Если команда имеет вид:

$ git push remote-repo-name branch-name

То это означает для Git «возьми мою локальную ветку branch-name и обнови ей удалённую ветку branch-name». Если команда имеет вид:

$ git push remote-repo-name :remote-branch

То это означает для Git — удалить ветку remote-branch в удаленном репозитории remote-repo-name.

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

$ git remote rename old-remote-repo-name new-remote-repo-name

Чтобы удалить ссылку на удаленный репозиторий:

$ git remote rm remote-repo-name

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

Для получения локальной копии удаленного репозитория используется команда clone.

$ git clone https://github.com/pupkin/somelib

Эта команда создаёт директорию somelib, инициализирует в ней поддиректорию .git и скачивает все данные для этого репозитория. Для того, чтобы клонировать репозиторий в директорию с другим именем:

$ git clone https://github.com/pupkin/somelib mylib
При клонировании имя удаленного репозитория будет origin. При этом автоматически создаётся ветка master, которая отслеживает origin/master.

Получить изменения или отправить изменения в удаленный репозиторий:

$ git pull  # получить изменения из удаленного репозитория
$ git push  # отправить изменения в удаленный репозиторий

Отслеживание веток

При создании локальной ветки из удалённой автоматически получается отслеживаемая ветка (upstream branch). Это локальная ветка, напрямую связанная с удалённой. Если, находясь на такой ветке, выполнить команду git push, Git автоматически поймет, на какой сервер и в какую ветку отправить данные. А команда git pull получает все данные и автоматически выполняет слияние.

При клонировании имя удаленного репозитория будет origin. При этом автоматически создаётся ветка master, которая отслеживает origin/master. Однако, можно настроить отслеживание и других веток:

$ git checkout -b ЛокальнаяВетка УдаленныйРепозиторий/УдаленнаяВетка
$ git checkout -b some-fix origin/somefix
Branch some-fix set up to track remote branch somefix from origin.
Switched to a new branch 'some-fix'

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

$ git checkout --track origin/somefix
Branch somefix set up to track remote branch somefix from origin.
Switched to a new branch 'somefix'

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

$ git checkout some-fix
$ git branch -u origin/somefix
Branch some-fix set up to track remote branch somefix from origin.

Получить список отслеживаемых веток:

$ git branch -vv
  issue53   7e424c3 [origin/issue53: ahead 2] forgot the brackets
  master    1ae2a45 [origin/master] deploying index fix
* some-fix  f8674d9 [testing/somefix: ahead 3, behind 1] this should do it
  feature   5ea463a testing new feature

Здесь можно видеть:

  • issue53-ветка отслеживает origin/issue53 и она опережает на два изменения (есть два локальных коммита, которые не отправлены на сервер)
  • master-ветка отслеживает origin/master и она в актуальном состоянии
  • some-fix-ветка отслеживает somefix-ветку на сервере testing и опережает на три коммита и отстает на один (есть один коммит на сервере, который еще получен и три локальных коммита, которые еще не отправлены)
  • feature-ветка не отслеживает удаленную ветку

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

Если при выполнении команды git pull была получена ошибка:

$ git pull
There is no tracking information for the current branch.
Please specify which branch you want to merge with.

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

Команда git reset

Команда git reset отменяет последнюю команду git commit. Возьмем для примера ветку:

                                ┌---------┐
                                |  HEAD   |
                                └---------┘
                                     ↓
┌---------┐     ┌---------┐     ┌---------┐
|  11111  |  ←  |  22222  |  ←  |  33333  |
└---------┘     └---------┘     └---------┘

После выполнения команды git reset 22222 указатель HEAD будет указывать на 22222:

                ┌---------┐
                |  HEAD   |
                └---------┘
                     ↓
┌---------┐     ┌---------┐     ┌---------┐
|  11111  |  ←  |  22222  |  ←  |  33333  |
└---------┘     └---------┘     └---------┘

Команда git reset --soft

После выполнения

$ git reset --soft 22222

Мы откатимся до момента выполнения команды git commit. При этом изменения из коммита 33333 будут в индексе, как будто мы их добавили командой git add. Если сейчас выполнить git commit, то мы получим коммит, полностью идентичный 33333.

Команда git reset --mixed (по умолчанию)

После выполнения

$ git reset --mixed 22222

Будет отменен не только последний коммит, но также и добавление в индекс всех файлов. Мы откатились назад до момента выполнения команд git add и git commit. Если сейчас выполнить git add и git commit, то мы получим коммит, полностью идентичный 33333.

Команда git reset --hard

После выполнения

$ git reset --hard 22222

Будет отменен как последний коммит, так и добавление в индекс всех файлов, но главное — пропадут все изменения, которые были сделаны в рабочем каталоге. Мы вернемся к состоянию сразу после коммита 22222. Флаг hard делает команду reset опасной, это один из немногих случаев, когда Git действительно удаляет данные.

Дополнительно

Поиск: CLI • Git • GitHub • Web-разработка • Команда • commit • pull • push • fetch • remote • clone • checkout • branch • merge • Система контроля версий

Каталог оборудования
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Производители
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Функциональные группы
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.