Начало работы с Git
29.11.2019
Теги: CLI • Git • Web-разработка • Команда • СистемаКонтроляВерсий
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
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 • Система контроля версий