Scott Chacon, Ben Straub.
https://git-scm.com/book/ru/v2
Licence. Это произведение распространяется по свободной лицензии Creative Commons Attribution- NonCommercial-ShareAlike 3.0. Ознакомиться с текстом лицензии вы можете на сайте https://creativecommons.org/licenses/by-nc-sa/3.0/deed.ru
- Глава №1. Все команды Git, краткое описание
- Глава №2. Введение
- Глава №3. Основы Git
- Глава №4. Ветвление в Git
- Глава №5. Git на сервере
- Глава №6. Распределенный Git
- Глава №7. GitHub
- Глава №8. Инструменты Git
- Глава №9. Настройка Git
- Глава №10. Git в других окружениях
Можно использовать аббревиатуры для опций. Например, можно использовать команду git commit --a
вместо git commit --amend
. Сокращение применимо только для тех опций, первая буква в имени которых не является начальной для других. При написании скриптов рекомендуется использовать полное название.
Две довольно распространённые команды, используемые как сразу после установки Git, так и в повседневной практике для настройки и получения помощи — это config
и help
.
Сотни вещей в Git работают без всякой конфигурации, используя параметры по умолчанию. Для большинства из них вы можете задать иные умолчания, либо вовсе использовать собственные значения. Это включает в себя целый ряд настроек, начиная от вашего имени и заканчивая цветами в терминале и вашим любимым редактором. Команда config
хранит и читает настройки в нескольких файлах, так что вы можете задавать значения глобально или для конкретных репозиториев.
Согласно инструкциям, большинство редакторов может быть установлено следующим образом:
Emacs -> git config --global core.editor emacs
Vim -> git config --global core.editor "vim --nofork"
Команда git help
служит для отображения встроенной документации Git о других командах. И хотя мы приводим описания самых популярных команд в этой главе, полный список параметров и флагов каждой команды доступен через git help <command>
.
Существует два способа создать Git репозиторий. Первый — клонировать его из существующего репозитория (например, по сети); второй — создать репозиторий в существующем каталоге.
Чтобы превратить обычный каталог в Git репозиторий и начать версионировать файлы в нём, просто запустите git init
.
На самом деле git clone
работает как обёртка над некоторыми другими командами. Она создаёт новый каталог, переходит внутрь и выполняет git init
для создания пустого репозитория, затем она добавляет новый удалённый репозиторий (git remote add
) для указанного URL
(по умолчанию он получит имя origin
), выполняет git fetch
для этого репозитория и, наконец, извлекает последний коммит в ваш рабочий каталог, используя git checkout
.
--bare
, чтобы создать копию Git репозитория без рабочей копии.
--recursive
чтобы упростить клонирование репозитория с подмодулями.
Всего несколько команд нужно для базового варианта использования Git для ведения истории изменений.
Команда git add
добавляет содержимое рабочего каталога в индекс (staging area
) для последующего коммита. По умолчанию git commit
использует лишь этот индекс, так что вы можете использовать git add
для сборки слепка вашего следующего коммита.
Команда git status
показывает состояния файлов в рабочем каталоге и индексе: какие файлы изменены, но не добавлены в индекс; какие ожидают коммита в индексе. Вдобавок к этому выводятся подсказки о том, как изменить состояние файлов.
Команда git diff
используется для вычисления разницы между любыми двумя Git деревьями. Это может быть разница между вашей рабочей копией и индексом (собственно git diff
), разница между индексом и последним коммитом (git diff --staged
), или между любыми двумя коммитами (git diff master branchB
).
--check
для проверки на проблемы с пробелами с помощью аргумента.
git diff A...B
как эффективно сравнивать ветки используя синтаксис.
-w
для скрытия различий в пробельных символах, как сравнивать конфликтующие изменения с опциями --theirs
, --ours
и --base
.
--submodule
для сравнения изменений в подмодулях.
Команда git difftool
просто запускает внешнюю утилиту сравнения для показа различий в двух деревьях, на случай если вы хотите использовать что-либо отличное от встроенного просмотрщика git diff
.
Команда git commit
берёт все данные, добавленные в индекс с помощью git add
, и сохраняет их слепок во внутренней базе данных (точка сохранения данных проекта), а затем сдвигает указатель текущей ветки на этот слепок.
Есть полезные опции: -a
для добавления всех изменений в индекс без использования git add
, что может быть удобным в повседневном использовании
-m
для передачи сообщения коммита без запуска полноценного редактора.
--amend
для изменения последнего совершённого коммита.
-S
как подписывать ваши коммиты
Команда git reset
, используется в основном для отмены изменений. Она изменяет указатель HEAD
и, опционально, состояние индекса. Также эта команда может изменить файлы в рабочем каталоге при использовании параметра --hard
, что может привести к потере наработок при неправильном использовании, так что убедитесь в серьёзности своих намерений прежде чем использовать его.
git reset --hard
чтобы отменить слияние в Прерывании слияния, использование команды git merge --abort
для этих целей, которая работает как обёртка над git reset
.
Команда git rm
используется в Git для удаления файлов из индекса и рабочей копии. Она похожа на git add
с тем лишь исключением, что она удаляет, а не добавляет файлы для следующего коммита.
--cached
удалить файлы из индекса
--ignore-unmatch
при выполнении git filter-branch
, которая подавляет ошибки удаления несуществующих файлов (может быть полезно для автоматически выполняемых скриптов).
Команда git mv
— это удобный способ переместить файл, а затем выполнить git add
для нового файла и git rm
для старого.
Команда git clean
используется для удаления мусора из рабочего каталога. Это могут быть результаты сборки проекта или файлы конфликтов слияний.
За создание новых веток и слияние их воедино отвечает несколько Git команд.
Команда git branch
— это своего рода "менеджер веток". Она умеет перечислять ваши ветки, создавать новые, удалять и переименовывать их.
git branch -u
для отслеживания веток.
Команда git checkout
используется для переключения веток и выгрузки их содержимого в рабочий каталог.
--track
для отслеживания веток.
--conflict=diff3
для разрешения конфликтов заново, в случае если предыдущее решение не подходило по некоторым причинам.
Команда git merge
используется для слияния одной или нескольких веток в текущую. Затем она устанавливает указатель текущей ветки на результирующий коммит.
Имеет вид git merge <branch>
с указанием единственной ветки для слияния.
-Xignore-all-whitespace
и --abort
, используемый для отмены слияния в случае возникновения проблем.
Команда git mergetool
просто вызывает внешнюю программу слияний, в случае если у вас возникли проблемы слияния.
Команда git log
используется для просмотра истории коммитов, начиная с самого свежего и уходя к истокам проекта. По умолчанию, она показывает лишь историю текущей ветки, но может быть настроена на вывод истории других, даже нескольких сразу, веток. Также её можно использовать для просмотра различий между ветками на уровне коммитов.
Практически во всех главах книги эта команда используется для демонстрации истории проекта.
-p
и --stat
для получения представления об изменениях в каждом коммите.
--pretty
и --oneline
для настройки формата вывода этой команды — более полным и подробным или кратким.
--decorate
чтобы отобразить указатели веток на истории коммитов.
--graph
чтобы просматривать историю в виде дерева.
Синтаксис branchA..branchB
, позволяющем команде git log
показывать только коммиты, присутствующие в одной ветке, но отсутствующие в другой.
Синтаксис branchA... branchB
и опцию --left-right
позволяющие увидеть, что находится в одной или в другой ветке, но не в них обеих сразу.
--merge
, которая может быть полезной при разрешении конфликтов.
--cc
для просмотра конфликтов слияния в истории проекта.
-S
и -L
для поиска событий в истории проекта, например, истории развития какой-либо фичи.
--show-signature
для отображения строки валидации подписи для каждого коммита в git log
.
Команда git stash
используется для временного сохранения всех незафиксированных изменений с целью очистки рабочего каталога без необходимости фиксировать незавершённую работу в текущей ветке.
Команда git tag
используется для задания постоянной метки на какой-либо момент в истории проекта. Обычно она используется для релизов.
Не так уж много команд в Git требуют сетевого подключения для своей работы, практически все команды оперируют с локальной копией проекта. Когда вы готовы поделиться своими наработками, всего несколько команд помогут вам работать с удалёнными репозиториями.
Команда git fetch
связывается с удалённым репозиторием и забирает из него все изменения, которых у вас пока нет и сохраняет их локально.
Команда git pull
работает как комбинация команд git fetch
и git merge
, т. е. Git вначале забирает изменения из указанного удалённого репозитория, а затем пытается слить их с текущей веткой.
Команда git push
используется для установления связи с удалённым репозиторием, вычисления локальных изменений отсутствующих в нём, и собственно их передачи в вышеупомянутый репозиторий. Этой команде нужно право на запись в репозиторий, поэтому она использует аутентификацию.
--delete
для удаления веток на сервере, используя git push
.
--recurse -submodules
чтобы удостовериться, что все подмодули будут опубликованы перед отправкой на проекта на сервер, что может быть реально полезным при работе с репозиториями, содержащими подмодули.
Команда git remote
служит для управления списком удалённых репозиториев. Она позволяет сохранять длинные URL
репозиториев в виде понятных коротких строк, например origin
, так что вам не придётся набирать её каждый раз для связи с сервером. Вы можете использовать несколько удалённых репозиториев для работы и git remote
поможет добавлять, изменять и удалять их.
Вид: git remote add <имя> <URL>
Команда git archive
используется для упаковки в архив указанных коммитов или всего репозитория.
Мы использовали git archive
для создания тарбола (tar.gz файла) всего проекта для передачи по сети.
Команда git submodule
используется для управления вложенными репозиториями. Например, это могут быть библиотеки или другие, используемые не только в этом проекте ресурсы. У команды submodule
есть несколько под-команд — add
, update
, sync
и др. — для управления такими репозиториями.
Команда git show
отображает объект в простом и человекопонятном виде. Обычно она используется для просмотра информации о метке или коммите.
Ещё одна интересная вещь, с помощью git show в режиме ручного слияние файлов — это извлечение содержимого файлов на различных стадиях во время конфликта слияния.
Команда git shortlog
служит для подведения итогов команды git log
. Она принимает практически те же параметры, что и git log
, но вместо простого листинга всех коммитов, они будут сгруппированы по автору.
Команда git describe
принимает на вход что угодно, что можно трактовать как коммит (ветку, тег) и выводит более-менее человекочитаемую строку, которая не изменится в будущем для данного коммита. Это может быть использовано как более удобная, но по-прежнему уникальная, замена SHA-1
.
Можно использовать git describe
в генерации номера сборки и подготовки релиза, чтобы сгенерировать название для архивного файла с релизом.
В Git есть несколько команд, используемых для нахождения проблем в коде. Это команды для поиска места в истории, где проблема впервые проявилась и собственно виновника этой проблемы.
Команда git bisect
— это полезная утилита для поиска коммита в котором впервые проявился баг или проблема с помощью автоматического бинарного поиска.
Команда git blame
выводит перед каждой строкой файла SHA-1
коммита, последний раз менявшего эту строку и автора этого коммита. Это помогает в поисках человека, которому нужно задавать вопросы о проблемном куске кода.
Команда git grep
используется для поиска любой строки или регулярного выражения в любом из файлов вашего проекта, даже в более ранних его версиях.
Некоторые команды в Git основываются на подходе к рассмотрению коммитов в терминах внесённых ими изменений, т. е. рассматривают историю коммитов как цепочку патчей.
Команда git cherry-pick
берёт изменения, вносимые одним коммитом, и пытается повторно применить их в виде нового коммита в текущей ветке. Эта возможность полезна в ситуации, когда нужно забрать парочку коммитов из другой ветки, а не сливать ветку целиком со всеми внесенными в нее изменениями.
Команда git rebase
— это «автоматизированный» cherry-pick
. Он выполняет ту же работу, но для цепочки коммитов, тем самым как бы перенося ветку на новое место.
Команда git revert
— полная противоположность git cherry-pick
. Она создаёт новый коммит, который вносит изменения, противоположные указанному коммиту, по существу отменяя его.
Можно использовать её, чтобы отменить коммит слияния (merge commit).
Множество проектов, использующих Git (включая сам Git), активно используют списки рассылок для координирования процесса разработки. В Git есть несколько команд, помогающих в этом, начиная от генерации патчей, готовых к пересылке по электронной почте, заканчивая применением таких патчей прямиком из почты.
Команда git apply
применяет патч, сформированный с помощью команды git diff
или GNU diff
. Она делает практически то же самое, что и команда patch
.
Команда git am
используется для применения патчей из входящих сообщений электронной почты, в частности, тех что используют формат mbox
. Это используется для простого получения изменений через email и применения их к проекту.
Команда git format-patch
используется для создания набора патчей в формате mbox
которые можно использовать для отправки в список рассылки.
Команда git send-email
используется для отсылки патчей, сформированных с использованием git format-patch
, по электронной почте.
Команда git request-pull
используется для генерации примерного текста сообщения для отсылки кому-либо. Если у вас есть ветка, хранящаяся на публичном сервере, и вы хотите чтобы кто-либо забрал эти изменения без трудностей с отсылкой патчей по электронной почте, вы можете выполнить эту команду и послать её вывод тому человеку.
В Git есть несколько стандартных команд для работы с другими системами контроля версий.
Команда git svn
используется для работы с сервером Subversion. Это означает, что вы можете использовать Git в качестве SVN клиента, забирать изменения и отправлять свои собственные на сервер Subversion.
Для других систем контроля версий, либо для импорта произвольно форматированных данных, вы можете использовать git fast-import
, которая умеет преобразовывать данные в формат, понятный Git.
Если вы администрируете Git репозиторий или вам нужно исправить что-либо, Git предоставляет несколько административных команд вам в помощь.
Команда git gc
запускает сборщик мусора в вашем репозитории, который удаляет ненужные файлы из хранилища объектов и эффективно упаковывает оставшиеся файлы.
Обычно, эта команда выполняется автоматически без вашего участия, но, если пожелаете, можете вызвать её вручную.
Команда git fsck
используется для проверки внутренней базы данных на предмет наличия ошибок и несоответствий.
Команда git reflog
просматривает историю изменения голов веток на протяжении вашей работы для поиска коммитов, которые вы могли внезапно потерять, переписывая историю.
Команда git filter-branch
используется для переписывания содержимого коммитов по заданному алгоритму, например, для полного удаления файла из истории или для вычленения истории лишь части файлов в проекте для вынесения в отдельный репозиторий.
Система контроля версий — это система, записывающая изменения в файл или набор файлов в течение времени и позволяющая вернуться позже к определённой версии.
Локальные системы контроля версий страдают от проблемы: когда вся история проекта хранится в одном месте, вы рискуете потерять всё.
Распределённые системы контроля версий. Здесь в игру вступают распределённые системы контроля версий (РСКВ). В РСКВ (таких как Git, Mercurial, Bazaar или Darcs) клиенты не просто скачивают снимок всех файлов (состояние файлов на определённый момент времени) — они полностью копируют репозиторий. В этом случае, если один из серверов, через который разработчики обменивались данными, умрёт, любой клиентский репозиторий может быть скопирован на другой сервер для продолжения работы. Каждая копия репозитория является полным бэкапом всех данных.
История Git. В 2005 году сообщество разработчиков ядра Linux (а в частности Линус Торвальдс — создатель Linux) разработали свою собственную утилиту.
СVCS (Centralized Version Control System) — централизованные системы контроля версий, работа которых основана на том, что на сервере имеется одна центральная копия проекта, а программисты совершают свои изменения в этой центральной копии.
В настоящее время используют децентрализованные (распределённые) системы контроля версий DVCS (Distributed Version Control System). Они имеют иную структуру и ряд отличий. Например, у каждого разработчика есть свой репозиторий, который полностью копирует центральный, причём центральный сервер выступает в большей степени в качестве хаба для обмена. Пример — Git.
Подход Git к хранению данных больше похож на набор снимков миниатюрной файловой системы. Каждый раз, когда вы делаете коммит, то есть сохраняете состояние своего проекта в Git, система запоминает, как выглядит каждый файл в этот момент, и сохраняет ссылку на этот снимок. Для увеличения эффективности, если файлы не были изменены, Git не запоминает эти файлы вновь, а только создаёт ссылку на предыдущую версию идентичного файла, который уже сохранён. Git представляет свои данные как, поток снимков.
Если вы в самолёте или в поезде и хотите немного поработать, вы сможете создавать коммиты без каких-либо проблем (в вашу локальную копию): когда будет возможность подключиться к сети, все изменения можно будет синхронизировать.
В Git для всего вычисляется хеш-сумма, и только потом происходит сохранение. В дальнейшем обращение к сохранённым объектам происходит по этой хеш-сумме. Это значит, что невозможно изменить содержимое файла или каталога так, чтобы Git не узнал об этом. Git сохраняет все объекты в свою базу данных не по имени, а по хеш-сумме содержимого объекта.
У Git есть три основных состояния, в которых могут находиться ваши файлы:
-Изменён (modified) относятся файлы, которые поменялись, но ещё не были зафиксированы.
-Индексирован (staged) изменённый файл в его текущей версии, отмеченный для включения в следующий коммит.
-Зафиксирован (committed) файл уже сохранён в вашей локальной базе.
Мы подошли к трём основным секциям проекта Git: рабочая копия working tree
, область индексирования staging area
и каталог Git .git directory
.
Рабочая копия является снимком одной версии проекта. Эти файлы извлекаются из сжатой базы данных в каталоге Git и помещаются на диск, для того чтобы их можно было использовать или редактировать.
Область индексирования — это файл, обычно находящийся в каталоге Git, в нём содержится информация о том, что попадёт в следующий коммит. Её техническое название на языке Git — «индекс», но фраза «область индексирования» также работает.
Каталог Git — это то место, где Git хранит метаданные и базу объектов вашего проекта.
По умолчанию уже стоит в системе, но есть и установка на Mac OS, Windows, Linux, установка напрямую свежей версии из исходников https://git-scm.com/download/
` git --version `
В состав Git входит утилита git config, которая позволяет просматривать и настраивать параметры, контролирующие все аспекты работы Git, а также его внешний вид. Эти параметры могут быть сохранены в трёх местах:
- Файл
[path]/etc/gitconfig
содержит значения, общие для всех пользователей системы и для всех их репозиториев. Если при запуске git config указать параметр--system
, то параметры будут читаться и сохраняться именно в этот файл. Так как этот файл является системным, то вам потребуются права суперпользователя для внесения изменений в него. - Файл
~/.gitconfig или ~/.config/git/config
хранит настройки конкретного пользователя. Этот файл используется при указании параметра--global
и применяется ко всем репозиториям, с которыми вы работаете в текущей системе. - Файл config в каталоге Git (т. е.
.git/config
) репозитория, который вы используете в данный момент, хранит настройки конкретного репозитория. Вы можете заставить Git читать и писать в этот файл с помощью параметра--local
, но на самом деле это значение по умолчанию. Неудивительно, что вам нужно находиться где-то в репозитории Git, чтобы эта опция работала правильно.
Настройки на каждом следующем уровне подменяют настройки из предыдущих уровней, то есть значения в.git/config
перекрывают соответствующие значения в[path]/etc/gitconfig
Чтобы посмотреть все установленные настройки и узнать где именно они заданы, используйте команду:
git config --list --show-origin
Можно указать ваше имя и адрес электронной почты:
git config --global user.name "Name"
git config --global user.email name@example.com
Eсли указана опция --global
, то эти настройки достаточно сделать только один раз, поскольку в этом случае Git будет использовать эти данные для всего, что вы делаете в этой системе. Если для каких-то отдельных проектов вы хотите указать другое имя или электронную почту, можно выполнить эту же команду без параметра --global
в каталоге с нужным проектом.
Можно выбрать текстовый редактор, который будет использоваться, если будет нужно набрать сообщение в Git. По умолчанию Git использует стандартный редактор вашей системы, обычно Vim. Если вы хотите использовать другой текстовый редактор, например, Emacs, можно проделать следующее:
git config --global core.editor emacs
Когда вы инициализируете репозиторий командой git init
, Git создаёт ветку с именем master
по умолчанию. Вы можете задать другое имя для создания ветки по умолчанию.
Например, чтобы установить имя main
для вашей ветки по умолчанию, выполните следующую команду:
git config --global init.defaultBranch main
Если вы хотите проверить используемую конфигурацию, можете использовать команду git config --list
, чтобы показать все настройки, которые Git найдёт:
flyboroda@MacBook-Air-Artem ~ % git config --list
credential.helper=osxkeychain
init.defaultbranch=main
user.name=artemiosdev
user.email=name@gmail.com
user.password=********
core.excludesfile=~/.gitignore_global
credential.helper=osxkeychain
credential.username=artemiosdev
alias.last=log -1 HEAD
Сброс пароля, при новом выполнении git push
система спросит пароль/токен и можно ввести новый
git config --global --unset user.password
or
git config --unset user.password
Если вам нужна помощь при использовании Git, есть три способа открыть страницу руководства по любой команде Git:
git help <команда>
git <команда> --help
man git-<команда>
Команда создаёт в текущем каталоге новый подкаталог с именем .git, содержащий все необходимые файлы репозитория — структуру Git репозитория
git init
Если вы хотите добавить под версионный контроль существующие файлы (в отличие от пустого каталога), вам стоит добавить их в индекс и осуществить первый коммит изменений. Добиться этого вы сможете запустив команду git add
(имеет различные вариации использования). Указав индексируемые файлы, затем выполним git commit
чтобы зафиксировать:
git add *.md
git add .
git add <file name>
git commit -m 'Message about commit'
Git индексирует файл в точности в том состоянии, в котором он находился, когда вы выполнили команду git add
. Команда git add
принимает параметром путь к файлу или каталогу, если это каталог, команда рекурсивно добавляет все файлы из указанного каталога в индекс, это многофункциональная команда, она используется для добавления под версионный контроль новых файлов, для индексации изменений, для указания файлов с исправленным конфликтом слияния, «добавить этот контент в следующий коммит».
git clone 'https://github.com/artemiosdev/ProGit-MyNotes'
В Git реализовано несколько транспортных протоколов, которые вы можете использовать. В предыдущем примере использовался протокол https://
, вы также можете встретить git://
или user@server:path/to/repo.git
, использующий протокол передачи SSH.
Основной инструмент, используемый для определения, какие файлы в каком состоянии находятся — это команда
git status
Есть сайт который формирует и генерирует нужный файл .gitignore
под нужный инструментарий и язык
https://www.toptal.com/developers/gitignore/
Зачастую, у вас имеется группа файлов, которые вы не только не хотите автоматически добавлять в репозиторий, но и видеть в списках неотслеживаемых. К таким файлам обычно относятся автоматически генерируемые файлы (различные логи, результаты сборки программ и т. п.). В таком случае, вы можете создать файл .gitignore
. с перечислением шаблонов соответствующих таким файлам. И при коммите указанные файлы будут проигнорированы.
Хорошая практика заключается в настройке файла .gitignore
до того, как начать серьёзно работать, это защитит вас от случайного добавления в репозиторий файлов, которых вы там видеть не хотите.
К шаблонам в файле .gitignore
применяются следующие правила:
- Пустые строки, а также строки, начинающиеся с
#
, игнорируются. - Стандартные шаблоны являются глобальными и применяются рекурсивно для всего дерева каталогов.
- Чтобы избежать рекурсии используйте символ слеш
(/)
в начале шаблона. - Чтобы исключить каталог добавьте слеш
(/)
в конец шаблона. - Можно инвертировать шаблон, использовав восклицательный знак
(!)
в качестве первого символа.
Glob-шаблоны представляют собой упрощённые регулярные выражения, используемые командными интерпретаторами. Символ (*)
соответствует 0 или более символам; последовательность [abc]
— любому символу из указанных в скобках (в данном примере a, b или c); знак вопроса (?)
соответствует одному символу; и квадратные скобки, в которые заключены символы, разделённые дефисом ([0-9])
, соответствуют любому символу из интервала (в данном случае от 0 до 9). Вы также можете использовать две звёздочки, чтобы указать на вложенные каталоги: a/**/z
соответствует a/z, a/b/z, a/b/c/z,
и так далее.
Пример файла .gitignore
:
# Исключить все файлы с расширение .a
*.a
# Но отслеживать файл lib.a даже если он подпадает под исключение выше
!lib.a
# Исключить файл TODO в корневом каталоге, но не файл в subdir/TODO
/TODO
# Игнорировать все файлы в каталоге build/
build/
# Игнорировать файл doc/notes.txt, но не файл doc/server/arch.txt
doc/*.txt
# Игнорировать все .txt файлы в каталоге doc/
doc/**/*.txt
GitHub поддерживает довольно полный список примеров .gitignore
файлов для множества проектов и языков https://github.com/github/gitignore
Чтобы увидеть, что же вы изменили, но пока не проиндексировали, наберите git diff
без аргументов
Эта команда сравнивает содержимое вашего рабочего каталога с содержимым индекса. Результат показывает ещё не проиндексированные изменения.
Если вы хотите посмотреть, что вы проиндексировали и что войдёт в следующий коммит, вы можете выполнить git diff --staged
(или git diff --cached
для просмотра проиндексированных изменений (--staged
и --cached
синонимы)). Эта команда сравнивает ваши проиндексированные изменения с последним коммитом:
git diff --staged
or
git diff --cached
git diff
сама по себе не показывает все изменения сделанные с последнего коммита — только те, что ещё не проиндексированы. Такое поведение может сбивать с толку, так как если вы проиндексируете все свои изменения, то git diff ничего не вернёт.
Простейший способ зафиксировать изменения — это набрать git commit
Для ещё более подробного напоминания, что же именно вы поменяли, можете передать аргумент -v
в команду git commit
. Это приведёт к тому, что в комментарий будет также помещена дельта/diff изменений, таким образом вы сможете точно увидеть все изменения которые вы совершили.
Вы можете набрать свой комментарий к коммиту в командной строке вместе с командой commit
указав его после параметра -m
git commit -m "Message about commit"
Запомните, что коммит сохраняет снимок состояния вашего индекса. Всё, что вы не проиндексировали, так и висит в рабочем каталоге как изменённое; вы можете сделать ещё один коммит, чтобы добавить эти изменения в репозиторий. Каждый раз, когда вы делаете коммит, вы сохраняете снимок состояния вашего проекта, который позже вы можете восстановить или с которым можно сравнить текущее состояние.
Если у вас есть желание пропустить этап индексирования (т.е добавления файлов командой git add
), Git предоставляет простой способ. Добавление параметра -a
в команду git commit
заставляет Git автоматически индексировать каждый уже отслеживаемый на момент коммита файл, позволяя вам обойтись без git add
:
git commit -a -m 'Message about commit'
В данном случае перед коммитом вам не нужно выполнять git add
для файла, потому что флаг -a
включает все файлы. Это удобно, но будьте осторожны: флаг -a
может включить в коммит нежелательные изменения.
Для того чтобы удалить файл из Git, вам необходимо удалить его из отслеживаемых файлов (точнее, удалить его из вашего индекса) а затем выполнить коммит. Это позволяет сделать команда git rm
, которая также удаляет файл из вашего рабочего каталога, так что в следующий раз вы не увидите его как «неотслеживаемый».
Если вы изменили файл и уже проиндексировали его, вы должны использовать принудительное удаление с помощью параметра -f
. Это сделано для повышения безопасности, чтобы предотвратить ошибочное удаление данных, которые ещё не были записаны в снимок состояния и которые нельзя восстановить из Git.
Другая полезная штука, которую вы можете захотеть сделать — это удалить файл из индекса, оставив его при этом в рабочем каталоге. Другими словами, вы можете захотеть оставить файл на жёстком диске, но перестать отслеживать изменения в нём. Это особенно полезно, если вы забыли добавить что-то в файл .gitignore
и по ошибке проиндексировали, например, большой файл с логами, или кучу промежуточных файлов компиляции. Чтобы сделать это, используйте опцию --cached
:
git rm --cached <file name>
В команду git rm
можно передавать файлы, каталоги или шаблоны.
git rm log/\*.log
Обратите внимание на обратный слеш \
перед *
. Он необходим из-за того, что Git использует свой собственный обработчик имён файлов вдобавок к обработчику вашего командного интерпретатора. Эта команда удаляет все файлы, имеющие расширение .log
и находящиеся в каталоге log/
.
Или же вы можете сделать вот так:
git rm \*~
Эта команда удаляет все файлы, имена которых заканчиваются на ~
.
Команда mv
eсли вам хочется переименовать файл:
git mv file_from file_to
Однако, это эквивалентно выполнению следующих команд:
mv README MyFile
git rm README
git add MyFile
Команда git log
имеет очень большое количество опций для поиска коммитов по разным критериям. Одним из самых полезных аргументов является -p
или --patch
, который показывает разницу (выводит патч), внесенную в каждый коммит. Так же вы можете ограничить количество записей в выводе команды; используйте параметр -2
для вывода только двух записей:
git log -p -2
Если вы хотите увидеть сокращенную статистику для каждого коммита, вы можете использовать опцию --stat
:
git log --stat
Опция --stat
печатает под каждым из коммитов список и количество измененных файлов, а также сколько строк в каждом из файлов было добавлено и удалено. В конце можно увидеть суммарную таблицу изменений.
Следующей опцией является --pretty
. Эта опция меняет формат вывода. Существует несколько встроенных вариантов отображения.
Наиболее интересной опцией является format
, которая позволяет указать формат для вывода информации. Особенно это может быть полезным когда вы хотите сгенерировать вывод для автоматического анализа — так как вы указываете формат явно, он не будет изменен даже после обновления Git:
git log --pretty=format:"%h - %an, %ar : %s"
ca82a6d - Artem Androsenko, 1 years ago : Change version number
`%H` Хеш коммита.
`%h` Сокращенный хеш коммита.
`%T` Хеш дерева.
`%t` Сокращенный хеш дерева.
`%P` Хеш родителей.
`%p` Сокращенный хеш родителей.
`%an` Имя автора.
`%ae` Электронная почта автора.
`%ad` Дата автора (формат даты можно задать опцией ` --date=option `).
`%ar` Относительная дата автора.
`%cn` Имя коммитера.
`%ce` Электронная почта коммитера.
`%cd` Дата коммитера.
`%cr` Относительная дата коммитера.
`%s` Содержание.
Опция --graph
команды log
. С этой опцией вы сможете увидеть небольшой граф в формате ASCII, который показывает текущую ветку и историю слияний:
git log --pretty=format:"%h %s" --graph
* 2d3acf9 Ignore errors from SIGCHLD on trap
* 5e3ee11 Merge branch 'master' of git://github.com/artemiosdev/grit
|\
| * 420eac9 Add method for getting the current branch
* | 30e367c Timeout code and tests
* | e1193f8 Support for heads with slashes in them
|/
* d6016bc Require time for xmlschema
* 11d191e Merge branch 'defunkt' into local
` -p ` Показывает патч для каждого коммита.
` --stat ` Показывает статистику измененных файлов для каждого коммита.
` --shortstat ` Отображает только строку с количеством изменений/вставок/удалений для команды ` --stat `.
` --name-only ` Показывает список измененных файлов после информации о коммите.
` --name-status ` Показывает список файлов, которые добавлены/изменены/удалены.
` --abbrev-commit ` Показывает только несколько символов SHA-1 чек-суммы вместо всех 40.
` --relative-date ` Отображает дату в относительном формате (например, «2 weeks ago») вместо стандартного формата даты.
` --graph ` Отображает ASCII граф с ветвлениями и историей слияний.
` --pretty ` Показывает коммиты в альтернативном формате. Возможные варианты опций: `oneline`, `short`, `full`, `fuller` и `format` (с помощью последней можно указать свой формат).
` --oneline ` Сокращение для одновременного использования опций ` --pretty=oneline --abbrev-commit `
В дополнение к опциям форматирования вывода, команда git log
принимает несколько опций для ограничения вывода — опций, с помощью которых можно увидеть определенное подмножество коммитов.
Вы можете использовать -<n>
, где n
— это любое натуральное число и представляет собой n
последних коммитов.
Опции для ограничения вывода по времени, такие как --since
и --until
, являются очень удобными. Например, следующая команда покажет список коммитов, сделанных за последние две недели:
git log --since=2.weeks
Это команда работает с большим количеством форматов — вы можете указать определенную дату вида 2008-01-15 или же относительную дату, например 2 years 1 day 3 minutes ago.
Допускается указывать несколько параметров --author
и --grep
для поиска, которые позволят найти коммиты, соответствующие любому указанному --author
и любому указанному --grep
шаблону; однако, применение опции --all-match
заставит искать коммиты соответствующие всем указанным --grep
шаблонам.
Опция -S
, которая принимает аргумент в виде строки и показывает только те коммиты, в которых изменение в коде повлекло за собой добавление или удаление этой строки. Например, если вы хотите найти последний коммит, который добавил или удалил вызов определенной функции, вы можете запустить команду:
git log -S function_name
Опция путь. Если вы укажете каталог или имя файла, вы ограничите вывод только теми коммитами, в которых были изменения этих файлов. Эта опция всегда указывается последней после двойного тире (--)
, чтобы отделить пути от опций:
git log -- path/to/file
` -(n) ` Показывает только последние n коммитов.
` --since `, ` --after ` Показывает только те коммиты, которые были сделаны после указанной даты.
` --until `, ` --before ` Показывает только те коммиты, которые были сделаны до указанной даты.
` --author ` Показывает только те коммиты, в которых запись author совпадает с указанной строкой.
` --committer ` Показывает только те коммиты, в которых запись committer совпадает с указанной строкой.
` --grep ` Показывает только коммиты, сообщение которых содержит указанную строку.
` -S ` Показывает только коммиты, в которых изменение в коде повлекло за собой добавление или удаление указанной строки.
Например, если вы хотите увидеть, в каких коммитах произошли изменения в тестовых файлах в исходном коде Git в октябре 2008 года, автором которых был Junio Hamano и которые не были коммитами слияния, вы можете запустить следующую команду:
git log --pretty="%h - %s" --author='Junio C Hamano' --since="2008-10-01" \ --before="2008-11-01" --no-merges -- t/
Из почти 40 000 коммитов в истории исходного кода Git, эта команда показывает только 6, которые соответствуют этим критериям.
В зависимости от используемого порядка работы, история коммитов в вашем репозитории может содержать большое количество коммитов слияния, которые сами по себе не очень информативны. Чтобы исключить их из вывода команды git log
используйте опцию --no-merges
Основные способы отмены сделанных изменений. Будьте осторожны, не все операции отмены в свою очередь можно отменить! Это одна из редких областей Git, где неверными действиями можно необратимо удалить результаты своей работы.
Отмена может потребоваться, если вы сделали коммит слишком рано, например, забыв добавить какие-то файлы или комментарий к коммиту. Если вы хотите переделать коммит — внесите необходимые изменения, добавьте их в индекс и сделайте коммит ещё раз, указав параметр --amend
:
git commit --amend
Например, если вы сделали коммит и поняли, что забыли проиндексировать изменения в файле, который хотели добавить в коммит, то можно сделать следующее:
git commit -m 'Initial commit'
git add forgotten_file
git commit --amend
Можно изменить/заменить сообщение последнего коммита
git commit --amend -m "New text"
В итоге получится единый коммит — второй коммит заменит результаты первого.
Очень важно понимать, что когда вы вносите правки в последний коммит, вы не столько исправляете его, сколько заменяете новым, который полностью его перезаписывает. В результате всё выглядит так, будто первоначальный коммит никогда не существовал, а так же он больше не появится в истории вашего репозитория.
Например, вы изменили два файла и хотите добавить их в разные коммиты, но случайно выполнили команду git add *
и добавили в индекс оба. Как исключить из индекса один из них? Команда git status
напомнит вам:
git add *
git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
Используйте git reset HEAD <file>
для исключения из индекса.
Команда git status
напомнит вам:
Changes not staged for commit:
(use "git checkout -- <file>..." to discard changes in working directory)
git checkout -- <file>
Важно понимать, что git checkout -- <file>
— опасная команда. Все локальные изменения в файле пропадут — Git просто заменит его версией из последнего коммита. Ни в коем случае не используйте эту команду, если вы не уверены, что изменения в файле вам не нужны.
Помните, все что попало в коммит почти всегда Git может восстановить. Можно восстановить даже коммиты из веток, которые были удалены, или коммиты, перезаписанные параметром --amend
(см. Восстановление данных). Но всё, что не было включено в коммит и потеряно — скорее всего, потеряно навсегда.
Предположим, что вы изменили два файла и хотите зафиксировать их как два отдельных изменения, но случайно набираете git add *
и индексируете их оба. Как вы можете убрать из индекса один из двух? Команда git status
напоминает вам:
git add *
git status
On branch master
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
Использовать git restore --staged <file>
для отмены индексации файла.
Что, если вы поймете, что не хотите сохранять изменения в файле ? Как легко его откатить — вернуть обратно к тому, как он выглядел при последнем коммите (или изначально клонирован, или каким-либо образом помещён в рабочий каталог).
К счастью, git status
тоже говорит, как это сделать. В выводе последнего примера, неиндексированная область выглядит следующим образом:
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory
git restore <file>
Важно понимать, что git restore <file>
— опасная команда. Любые локальные изменения, внесенные в этот файл, исчезнут — Git просто заменит файл последней зафиксированной версией. Никогда не используйте эту команду, если точно не знаете, нужны ли вам эти несохраненные локальные изменения.
Для того, чтобы просмотреть список настроенных удалённых репозиториев, вы можете запустить команду git remote
. Она выведет названия доступных удалённых репозиториев. Если вы клонировали репозиторий, то увидите как минимум origin
— имя по умолчанию, которое Git даёт серверу, с которого производилось клонирование:
-v
, чтобы просмотреть адреса для чтения и записи, привязанные к репозиторию:
git remote -v
origin https://github.com/schacon/ticgit (fetch)
origin https://github.com/schacon/ticgit (push)
Для того, чтобы добавить удалённый репозиторий и присвоить ему имя (shortname)
, просто выполните команду
git remote add <shortname> <url>
Например, если вы хотите получить изменения, которые есть у кого-то, но нету у вас, вы можете выполнить команду git fetch <shortname>
Как вы только что узнали, для получения данных из удалённых проектов, следует выполнить:
git fetch [remote-name]
Данная команда связывается с указанным удалённым проектом и забирает все те данные проекта, которых у вас ещё нет. После того как вы выполнили команду, у вас должны появиться ссылки на все ветки из этого удалённого проекта, которые вы можете просмотреть или слить в любой момент.
Когда вы клонируете репозиторий, команда clone автоматически добавляет этот удалённый репозиторий под именем origin
. Таким образом, git fetch origin
извлекает все наработки, отправленные на этот сервер после того, как вы его клонировали (или получили изменения с помощью fetch
). Важно отметить, что команда git fetch
забирает данные в ваш локальный репозиторий, но не сливает их с какими-либо вашими наработками и не модифицирует то, над чем вы работаете в данный момент. Вам необходимо вручную слить эти данные с вашими, когда вы будете готовы.
Когда вы хотите поделиться своими наработками, вам необходимо отправить их в удалённый репозиторий. Команда для этого действия простая: git push <remote-name> <branch-name>
. Чтобы отправить вашу ветку main на сервер origin (повторимся, что клонирование обычно настраивает оба этих имени автоматически), вы можете выполнить следующую команду для отправки ваших коммитов:
git push origin master
Эта команда срабатывает только в случае, если вы клонировали с сервера, на котором у вас есть права на запись, и если никто другой с тех пор не выполнял команду push
. Если вы и кто-то ещё одновременно клонируете, затем он выполняет команду push
, а после него выполнить команду push попытаетесь вы, то ваш push точно будет отклонён. Вам придётся сначала получить изменения и объединить их с вашими и только после этого вам будет позволено выполнить push
.
Если хотите получить побольше информации об одном из удалённых репозиториев, вы можете использовать команду git remote show <remote>
. Выполнив эту команду с некоторым именем, например, origin
, вы получите следующий результат:
git remote show origin
Она выдаёт URL удалённого репозитория, а также информацию об отслеживаемых ветках. Эта команда любезно сообщает вам, что если вы, находясь на ветке main
, выполните git pull
, ветка main
с удалённого сервера будет автоматически влита в вашу сразу после получения всех необходимых данных. Она также выдаёт список всех полученных ею ссылок.
Для переименования удалённого репозитория можно выполнить git remote rename
. Например, если вы хотите переименовать oldname в newname, вы можете это сделать при помощи git remote rename:
git remote rename oldname newname
Если вы хотите удалить удаленный репозиторий — вы сменили сервер или больше не используете определённое зеркало, или кто-то перестал вносить изменения — вы можете использовать git remote rm
git remote remove repositoryname
При удалении ссылки на удалённый репозиторий все отслеживаемые ветки и настройки, связанные с этим репозиторием, так же будут удалены.
Git имеет возможность помечать определённые моменты в истории как важные. Как правило, эта функциональность используется для отметки моментов выпуска версий (v1.0, и т. п.). Такие пометки в Git называются тегами.
Просмотреть список имеющихся тегов в Git можно очень просто. Достаточно набрать команду git tag
(параметры -l
и --list
опциональны)
Поиск тега по шаблону. Например, репозиторий Git содержит много тегов. Если вы хотите посмотреть теги выпусков 1.8.5, то выполните следующую команду:
git tag -l "v1.8.5*"
v1.8.5
v1.8.5.1
v1.8.5.2
v1.8.5.3
Если вы хотите отфильтровать список тегов согласно шаблону, использование параметров -l
или --list
становится обязательным.
Git использует два основных типа тегов: легковесные и аннотированные.
Легковесный тег — это что-то очень похожее на ветку, которая не изменяется — просто указатель на определённый коммит.
Аннотированные теги хранятся в базе данных Git как полноценные объекты. Они имеют контрольную сумму, содержат имя автора, его e-mail и дату создания, имеют комментарий и могут быть подписаны и проверены с помощью GNU Privacy Guard (GPG). Обычно рекомендуется создавать аннотированные теги, чтобы иметь всю перечисленную информацию; но если вы хотите сделать временную метку или по какой-то причине не хотите сохранять остальную информацию, то для этого годятся и легковесные.
Создание аннотированного тега — это указать -a
при выполнении команды tag
:
git tag -a v1.4 -m "my version 1.4"
git tag
v1.4
Опция -m
задаёт сообщение, которое будет храниться вместе с тегом. Если не указать сообщение, то Git запустит редактор VIM, чтобы его ввести.
С помощью команды git show
вы можете посмотреть данные тега вместе с коммитом:
git show v1.4
Легковесный тег — это ещё один способ пометить коммит. По сути, это только контрольная сумма коммита, сохранённая в файл — больше никакой информации не хранится. Для создания легковесного тега не передавайте опций -a
, -s
и -m
, укажите только название:
git tag v1.4-lw
Возможно помечать уже пройденные коммиты. Предположим, история коммитов такова:
git log --pretty=oneline
4682c3261057305bdd616e23b64b0857d832627b Add todo file
166ae0c4d3f420721acbb115cc33848dfcc2121a Create write support
9fceb02d0ae598e95dc970b74767f19372d61af8 Update rakefile
Теперь предположим, что вы забыли отметить версию проекта v1.2, которая была там, где находится коммит «Update rakefile». Вы можете добавить тег и позже. Для отметки коммита укажите его контрольную сумму (или её часть) как параметр команды:
git tag -a v1.2 9fceb02
По умолчанию, команда git push не отправляет теги на удалённые сервера. После создания теги нужно отправлять явно на удалённый сервер. Процесс аналогичен отправке веток — достаточно выполнить команду git push origin <tagname>
git push origin v1.5
Если у вас много тегов, и вам хотелось бы отправить все за один раз, то можно использовать опцию --tags
для команды git push
git push origin --tags
Теперь, если кто-то клонирует (clone
) или выполнит git pull
из вашего репозитория, то он получит вдобавок к остальному и ваши метки.
Команда git push
отправляет оба типа тегов.
Отправка тегов командой git push <remote> --tags
не различает аннотированные и легковесные теги.
Для удаления тега в локальном репозитории достаточно выполнить команду git tag -d <tagname>
. Например, удалить созданный ранее легковесный тег можно следующим образом:
git tag -d v1.4-lw
Deleted tag 'v1.4-lw' (was e7d5add)
Обратите внимание, что при удалении тега не происходит его удаления с внешних серверов. Существует два способа изъятия тега из внешнего репозитория.
Первый способ — это выполнить команду git push <remote> :refs/tags/<tagname>
git push origin :refs/tags/v1.4-lw
To /git@github.com:schacon/simplegit.git
- [deleted] v1.4-lw
Это следует понимать как обновление внешнего тега пустым значением, что приводит к его удалению.
Второй способ убрать тег из внешнего репозитория более интуитивный:
git push origin --delete <tagname>
Если вы хотите получить версии файлов, на которые указывает тег, то вы можете сделать git checkout
для тега. Однако, это переведёт репозиторий в состояние «detached HEAD
», которое имеет ряд неприятных побочных эффектов.
git checkout v2.0.0
Note: switching to 'v2.0.0'.
Если в состоянии «detached HEAD
» внести изменения и сделать коммит, то тег не изменится, при этом новый коммит не будет относиться ни к какой из веток, а доступ к нему можно будет получить только по его хешу. Поэтому, если вам нужно внести изменения — исправить ошибку в одной из старых версий — скорее всего вам следует создать ветку:
git checkout -b version2 v2.0.0
Switched to a new branch 'version2'
Если сделать коммит в ветке version2
, то она сдвинется вперед и будет отличаться от тега v2.0.0
, так что будьте с этим осторожны.
Git не будет пытаться сделать вывод о том, какую команду вы хотели ввести, если вы ввели её не полностью. Если вы не хотите печатать каждую команду для Git целиком, вы легко можете настроить псевдонимы (alias) для любой команды с помощью git config
. Вот несколько примеров псевдонимов:
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit
git config --global alias.st status
Это означает, что, например, вместо ввода git commit
, вам достаточно набрать только git ci
.
Обычно, добавляют команду last
следующим образом:
git config --global alias.last 'log -1 HEAD'
Таким образом, можно легко просмотреть последний коммит:
git last
Git просто заменяет эти команды на созданные вами псевдонимы (alias
). Однако, возможно, вы захотите выполнить внешнюю команду, а не подкоманду Git. В этом случае, следует начать команду с символа !
. Это полезно, если вы пишете свои утилиты для работы с Git-репозиторием.
Принято использовать названия: Master - Hotfix - Release - Development - Feature
Используя ветвление, вы отклоняетесь от основной линии разработки и продолжаете работу независимо от неё, не вмешиваясь в основную линию.
git add README test.rb LICENSE
git commit -m 'Initial commit'
Когда вы создаёте коммит командой git commit
, Git вычисляет контрольные суммы каждого подкаталога (в нашем случае, только основной каталог проекта) и сохраняет его в репозитории как объект дерева каталогов. Затем Git создаёт объект коммита с метаданными и указателем на основное дерево проекта для возможности воссоздать этот снимок в случае необходимости.
Ваш репозиторий Git теперь хранит пять объектов: три блоб объекта (по одному на каждый файл), объект дерева каталогов, содержащий список файлов и соответствующих им блобов, а так же объект коммита, содержащий метаданные и указатель на объект дерева каталогов.
Если вы сделаете изменения и создадите ещё один коммит, то он будет содержать указатель на предыдущий коммит.
Ветка в Git — это простой перемещаемый указатель на один из таких коммитов. По умолчанию, имя основной ветки в Git — master or main
. Как только вы начнёте создавать коммиты, ветка master
будет всегда указывать на последний коммит. Каждый раз при создании коммита указатель ветки master
будет передвигаться на следующий коммит автоматически.
Ветка master
в Git — это не какая-то особенная ветка. Она точно такая же, как и все остальные ветки. Она существует почти во всех репозиториях только лишь потому, что её создаёт команда git init
, а большинство людей не меняют её название.
Что же на самом деле происходит при создании ветки? Всего лишь создаётся новый указатель для дальнейшего перемещения. Допустим вы хотите создать новую ветку с именем testing
. Вы можете это сделать командой git branch
:
git branch testing
Как Git определяет, в какой ветке вы находитесь? Он хранит специальный указатель HEAD
. В Git — это указатель на текущую локальную ветку. В нашем случае мы все еще находимся в ветке master
. Команда git branch
только создаёт новую ветку, но не переключает на неё.
Вы можете легко это увидеть при помощи простой команды git log
, которая покажет вам куда указывают указатели веток. Эта опция называется —decorate
.
git log --oneline --decorate
f30ab (HEAD -> master, testing)
Здесь можно увидеть указывающие на коммит f30ab ветки: master
и testing
.
Для переключения на существующую ветку выполните команду git checkout
. Давайте переключимся на ветку testing
:
git checkout testing
В результате указатель HEAD
переместится на ветку testing
.
Какой в этом смысл? Давайте сделаем ещё один коммит:
vim test.rb
git commit -a -m 'made a change'
Интересная ситуация: указатель на ветку testing
переместился вперёд, а master
указывает на тот же коммит, где вы были до переключения веток командой git checkout
. Давайте переключимся назад на ветку master:
git checkout master
git log
не показывает все ветки по умолчанию
Если выполнить команду git log
прямо сейчас, то в её выводе только что созданная ветка testing
фигурировать не будет.
Ветка никуда не исчезла; просто Git не знает, что именно она вас интересует, и выводит наиболее полезную по его мнению информацию. Другими словами, по умолчанию git log
отобразит историю коммитов только для текущей ветки.
Для просмотра истории коммитов другой ветки необходимо явно указать её имя: git log testing
Чтобы посмотреть историю по всем веткам — выполните команду с дополнительным флагом: git log --all
Эта команда сделала две вещи: переместила указатель HEAD назад на ветку master
и вернула файлы в рабочем каталоге в то состояние, на снимок которого указывает master
. Это также означает, что все вносимые с этого момента изменения будут относиться к старой версии проекта. Другими словами, вы откатили все изменения ветки testing
и можете продолжать в другом направлении.
Переключение веток меняет файлы в рабочем каталоге Важно запомнить, что при переключении веток в Git происходит изменение файлов в рабочем каталоге. Если вы переключаетесь на старую ветку, то рабочий каталог будет выглядеть так же, как выглядел на момент последнего коммита в ту ветку. Если Git по каким-то причинам не может этого сделать — он не позволит вам переключиться вообще.
Давайте сделаем еще несколько изменений и создадим очередной коммит:
vim test.rb
git commit -a -m 'made other changes'
Теперь история вашего проекта разошлась (см Разветвлённая история). Вы создали ветку и переключились на нее, поработали, а затем вернулись в основную ветку и поработали в ней. Эти изменения изолированы друг от друга: вы можете свободно переключаться туда и обратно, а когда понадобится — объединить их. И все это делается простыми командами: branch
, checkout
и commit
.
Все описанные действия можно визуализировать с помощью команды git log
. Для отображения истории коммитов, текущего положения указателей веток и истории ветвления выполните команду git log --oneline --decorate --graph --all
git log --oneline --decorate --graph --all
* c2b9e (HEAD, master) Made other changes
| * 87ab2 (testing) Made a change
|/
* f30ab Add feature #32 - ability to add new formats to the central interface
* 34ac2 Fix bug #1328 - stack overflow under certain conditions
* 98ca9 initial commit of my project
Ветка в Git — это простой файл, содержащий 40 символов контрольной суммы SHA-1 коммита, на который она указывает; поэтому операции с ветками являются дешёвыми с точки зрения потребления ресурсов или времени.
Как правило, при создании новой ветки вы хотите сразу на неё переключиться — это можно сделать используя команду git checkout -b <newbranchname>
Начиная с Git версии 2.23, вы можете использовать git switch
вместо git checkout
, чтобы:
-Переключиться на существующую ветку: git switch testing-branch
.
-Создать новую ветку и переключиться на нее: git switch -c new-branch
. Флаг -c
означает создание, но также можно использовать полный формат: --create
.
-Вернуться к предыдущей извлечённой ветке: git switch -
Вы решаете, что теперь вы будете заниматься проблемой #53
из вашей системы отслеживания ошибок. Чтобы создать ветку и сразу переключиться на нее, можно выполнить команду git checkout
с параметром -b
:
git checkout -b iss53
Switched to a new branch "iss53"
Это то же самое что и:
git branch iss53
git checkout iss53
Вы работаете над своим сайтом и делаете коммиты. Это приводит к тому, что ветка iss53
движется вперед, так как вы переключились на нее ранее (HEAD
указывает на нее).
vim index.html
git commit -a -m 'Create new footer [issue 53]'
Имейте в виду, что если рабочий каталог либо индекс содержат незафиксированные изменения, конфликтующие с веткой, на которую вы хотите переключиться, то Git не позволит переключить ветки. Лучше всего переключаться из чистого рабочего состояния проекта. Есть способы обойти это (припрятать изменения stash
или добавить их в последний коммит amend
), но об этом мы поговорим позже в разделе Припрятывание и очистка главы 7
Давайте создадим новую ветку для исправления, в которой будем работать, пока не закончим исправление
git checkout -b hotfix
Switched to a new branch 'hotfix'
vim index.html
git commit -a -m 'Fix broken email address'
[hotfix 1fb7853] Fix broken email address
Чтобы выполнить слияние ветки hotfix
с веткой master
для включения изменений в продукт. Это делается командой git merge
:
git checkout master
git merge hotfix
Updating f42c576..3a0874c
Fast-forward
index.html | 2 ++
1 file changed, 2 insertions(+)
Заметили фразу fast-forward
в этом слиянии? Git просто переместил указатель ветки вперед, потому что коммит C4, на который указывает слитая ветка hotfix
, был прямым потомком коммита C2, на котором вы находились до этого. Другими словами, если коммит сливается с тем, до которого можно добраться двигаясь по истории прямо, Git упрощает слияние просто перенося указатель ветки вперед, так как нет расхождений в изменениях. Это называется fast-forward
.
Теперь ваши изменения включены в коммит, на который указывает ветка master
, и исправление можно внедрять.
После внедрения исправления из ранее созданной ветки, нужно удалить ветку hotfix
, потому что она больше не нужна — ветка master
указывает на то же самое место. Для удаления ветки выполните команду git branch
с параметром -d
:
git branch -d hotfix
Deleted branch hotfix (3a0874c)
Теперь вы можете переключиться обратно на ветку iss53
и продолжить работу над проблемой #53
:
git checkout iss53
Switched to branch "iss53"
vim index.html
git commit -a -m 'Finish the new footer [issue 53]'
[iss53 ad82d7a] Finish the new footer [issue 53]
1 file changed, 1 insertion(+)
Стоит обратить внимание на то, что все изменения из ветки hotfix
не включены в вашу ветку iss53
. Если их нужно включить, вы можете влить ветку master в вашу ветку iss53
командой git merge master
, или же вы можете отложить слияние этих изменений до завершения работы, и затем влить ветку iss53
в master
.
Предположим, вы решили, что работа по проблеме #53
закончена и её можно влить в ветку master
. Для этого нужно выполнить слияние ветки iss53
точно так же, как вы делали это с веткой hotfix
ранее. Все, что нужно сделать — переключиться на ветку, в которую вы хотите включить изменения, и выполнить команду git merge
:
git checkout master
Switched to branch 'master'
git merge iss53
Результат этой операции отличается от результата слияния ветки hotfix
. В данном случае процесс разработки ответвился в более ранней точке. Так как коммит, на котором мы находимся, не является прямым родителем ветки, с которой мы выполняем слияние, Git придётся немного потрудиться. В этом случае Git выполняет простое трёхстороннее слияние, используя последние коммиты объединяемых веток и общего для них родительского коммита.
Вместо того, чтобы просто передвинуть указатель ветки вперёд, Git создаёт новый результирующий снимок трёхстороннего слияния, а затем автоматически делает коммит. Этот особый коммит называют коммитом слияния, так как у него более одного предка.
Теперь, когда изменения слиты, ветка iss53
больше не нужна. Вы можете закрыть задачу в
системе отслеживания ошибок и удалить ветку:
git branch -d iss53
Иногда процесс не проходит гладко. Если вы изменили одну и ту же часть одного и того же файла по-разному в двух объединяемых ветках, Git не сможет их чисто объединить.
git merge iss53
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.
Git не создал коммит слияния автоматически. Он остановил процесс до тех пор, пока вы не разрешите конфликт. Чтобы в любой момент после появления конфликта увидеть, какие файлы не объединены, вы можете запустить git status
:
git status
On branch master
You have unmerged paths.
(fix conflicts and run "git commit")
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: index.html
no changes added to commit (use "git add" and/or "git commit -a")
Всё, где есть неразрешённые конфликты слияния, перечисляется как неслитое. В конфликтующие файлы Git добавляет специальные маркеры конфликтов, чтобы вы могли исправить их вручную. В вашем файле появился раздел, выглядящий примерно так:
<<<<<<< HEAD:index.html
<div id="footer">contact : email.support@github.com</div>
=======
<div id="footer">
please contact us at support@github.com
</div>
>>>>>>> iss53:index.html
Это означает, что версия из HEAD
(вашей ветки master
, поскольку именно её вы извлекли перед запуском команды слияния) — это верхняя часть блока (всё, что над =======
), а версия из вашей другой ветки iss53
представлена в нижней части. Чтобы разрешить конфликт, придётся выбрать один из вариантов, либо объединить содержимое по-своему. Например, вы можете разрешить конфликт, заменив весь блок следующим:
<div id="footer">
please contact us at email.support@github.com
</div>
В этом разрешении есть немного от каждой части, а строки <<<<<<<
, =======
и >>>>>>>
полностью удалены. Разрешив каждый конфликт во всех файлах, запустите git add
для каждого файла, чтобы отметить конфликт как решённый. Добавление файла в индекс означает для Git
, что все конфликты в нём исправлены.
Если вы хотите использовать графический инструмент (есть и в Xcode тоже) для разрешения конфликтов, можно запустить git mergetool
, который проведет вас по всем конфликтам:
git mergetool
This message is displayed because 'merge.tool' is not configured.
See 'git mergetool --tool-help' or 'git help config' for more details.
'git mergetool' will now attempt to use one of the following tools:
opendiff kdiff3 tkdiff xxdiff meld tortoisemerge gvimdiff diffuse diffmerge ecmerge
p4merge araxis bc3 codecompare vimdiff emerge
Merging:
index.html
Normal merge conflict for 'index.html':
{local}: modified file
{remote}: modified file
Hit return to start merge resolution tool (opendiff):
Cписок всех поддерживаемых инструментов представлен вверху после фразы one of the following tools
. Просто введите название инструмента, который хотите использовать.
Если вы считаете, что коммит слияния требует дополнительных пояснений — опишите как были разрешены конфликты и почему были применены именно такие изменения, если это не очевидно.
Команда git branch
делает несколько больше, чем просто создаёт и удаляет ветки. При запуске без параметров, вы получите простой список имеющихся у вас веток:
git branch
iss53
* master
testing
Обратите внимание на символ *
, стоящий перед веткой master
: он указывает на ветку, на которой вы находитесь в настоящий момент (т. е. ветку, на которую указывает HEAD
). Это означает, что если вы сейчас сделаете коммит, ветка master
переместится вперёд в соответствии с вашими последними изменениями. Чтобы посмотреть последний коммит на каждой из веток, выполните команду git branch -v
:
git branch -v
iss53 93b412c Fix javascript issue
* master 7a98805 Merge branch 'iss53'
testing 782fd34 Add scott to the author list in the readme
Опции --merged
и --no-merged
могут отфильтровать этот список для вывода только тех веток, которые слиты или ещё не слиты в текущую ветку. Чтобы посмотреть те ветки, которые вы уже слили с текущей, можете выполнить команду git branch --merged
:
git branch --merged
iss53
* master
Ветка iss53 присутствует в этом списке потому что вы ранее слили её в master
. Те ветки из этого списка, перед которыми нет символа *
, можно смело удалять командой git branch -d
; наработки из этих веток уже включены в другую ветку, так что ничего не потеряется.
Чтобы увидеть все ветки, содержащие наработки, которые вы пока ещё не слили в текущую ветку, выполните команду git branch --no-merged
:
git branch --no-merged
testing
Вы увидите оставшуюся ветку. Так как она содержит ещё не слитые наработки, попытка удалить её командой git branch -d
приведёт к ошибке:
git branch -d testing
error: The branch 'testing' is not fully merged.
If you are sure you want to delete it, run 'git branch -D testing'.
Если вы действительно хотите удалить ветку вместе со всеми наработками, используйте опцию -D
, как указано в подсказке.
Если в качестве аргумента не указан коммит или ветка, то опции --merged
и --no-merged
покажут что уже слито или не слито с вашей текущей веткой соответственно.
Вы всегда можете указать дополнительный аргумент для вывода той же информации, но относительно указанной ветки предварительно не извлекая и не переходя на неё.
git checkout testing
git branch --no-merged master
topicA
featureB
Не переименовывайте ветки, которые всё ещё используются другими участниками.
Предположим, у вас есть ветка с именем bad-branch-name
, и вы хотите изменить её на corrected-branch-name
, сохранив при этом всю историю. Вместе с этим, вы также хотите изменить имя ветки на удалённом сервере (GitHub, GitLab или др сервер). Как это сделать?
Переименуйте ветку локально с помощью команды git branch --move
:
git branch --move bad-branch-name corrected-branch-name
Ветка bad-branch-name
будет переименована в corrected-branch-name
, но это изменение пока только локальное. Чтобы все остальные увидели исправленную ветку в удалённом репозитории, отправьте её туда:
git push --set-upstream origin corrected-branch-name
Теперь проверим, где мы сейчас находимся:
git branch --all
* corrected-branch-name
main
remotes/origin/bad-branch-name
remotes/origin/corrected-branch-name
remotes/origin/main
Обратите внимание, что текущая ветка corrected-branch-name
, которая также присутствует и на удалённом сервере. Однако, старая ветка всё ещё по-прежнему там, но её можно удалить с помощью команды:
git push origin --delete bad-branch-name
Переименуйте локальную ветку master
в main
с помощью следующей команды:
git branch --move master main
Чтобы все остальные могли видеть новую ветку main
, вам нужно отправить её в общий репозиторий. Это делает переименованную ветку доступной в удалённом репозитории.
git push --set-upstream origin main
В итоге, состояние репозитория становится следующим:
git branch --all
* main
remotes/origin/HEAD -> origin/master
remotes/origin/main
remotes/origin/master
Ваша локальная ветка master
исчезла, так как она заменена веткой main
. Ветка main
доступна в удалённом репозитории. Старая ветка master
всё ещё присутствует в удалённом репозитории. Остальные участники будут продолжать использовать ветку master
в качестве основы для своей работы, пока вы не совершите ряд дополнительных действий.
Теперь, для завершения перехода на новую ветку перед вами стоят следующие задачи:
• Все проекты, которые зависят от текущего, должны будут обновить свой код и/или конфигурацию.
• Обновите конфигурацию всех запускаемых тестов.
• Исправьте скрипты сборки и публикации артефактов.
• Поправьте настройки репозитория на сервере: задайте новую ветку по умолчанию, обновите правила слияния, а также прочие настройки, которые зависят от имени веток.
• Обновите документацию, исправив ссылки, указывающие на старую ветку.
• Слейте или отмените запросы на слияние изменений, нацеленные на старую ветку.
После того, как вы выполнили все эти задачи и уверены, что ветка main
работает так же, как ветка master
, вы можете удалить ветку master
:
git push origin --delete master
Идея состоит в том, что каждая ветка представляет собой определённый уровень стабильности; как только он повышается, содержимое сливается в ветку уровнем выше.
Так называется временная ветка, создаваемая и используемая для работы над конкретной функциональной возможностью или решения сопутствующих задач.
origin
— это не специальное название.
Подобно названию ветки master
, origin
не имеет какого-либо специального значения в Git. В то время как master
— это название по умолчанию для ветки при выполнении git init
только потому, что часто используется, origin
— это название по умолчанию для удалённого сервера, когда вы запускаете git clone
. Если вы выполните git clone -o newname
, то по умолчанию ветка слежения будет иметь вид newname/master
.
Если вы сделаете что-то в своей локальной ветке master, а тем временем кто-то отправит изменения на сервер git.ourcompany.com и обновит там ветку master, то ваши истории продолжатся по-разному. Пока вы не свяжетесь с сервером origin ваш указатель origin/master останется на месте.
Для синхронизации ваших изменений с удаленным сервером выполните команду git fetch <remote>
(в нашем случае git fetch origin
). Эта команда определяет какому серверу соответствует origin
(в нашем случае это git.ourcompany.com), извлекает оттуда данные, которых у вас ещё нет, и обновляет локальную базу данных, сдвигая указатель origin/master
на новую позицию.
Чтобы продемонстрировать, как будут выглядеть удалённые ветки в ситуации с несколькими удалёнными серверами, предположим, что у вас есть ещё один внутренний Git-сервер, который используется для разработки только одной из ваших команд разработчиков. Этот сервер находится на git.team1.ourcompany.com
. Вы можете добавить его в качестве новой удалённой ссылки для текущего проекта с помощью команды git remote add
, как было описано в главе Основы Git. Назовите этот удалённый сервер teamone
— это имя будет сокращением вместо полного URL.
Теперь вы можете выполнить команду git fetch teamone
для получения всех изменений с сервера teamone, которых у вас нет локально. Так как в данный момент на этом сервере есть только те данные, что содержит сервер origin
, Git ничего не получит, но создаст ветку слежения с именем teamone/master
, которая будет указывать на тот же коммит, что и ветка master
на сервере teamone
.
Когда вы хотите поделиться веткой, вам необходимо отправить её на удалённый сервер, где у вас есть права на запись. Ваши локальные ветки автоматически не синхронизируются с удалёнными при отправке — вам нужно явно указать те ветки, которые вы хотите отправить. Таким образом, вы можете использовать свои личные ветки для работы, которую не хотите показывать, а отправлять только те тематические ветки, над которыми вы хотите работать с кем-то совместно.
Если у вас есть ветка serverfix
, над которой вы хотите работать с кем-то ещё, вы можете отправить её точно так же, как вы отправляли вашу первую ветку. Выполните команду git push <remote> <branch>
:
git push origin serverfix
Получение локальной ветки из удалённой ветки автоматически создаёт то, что называется веткой слежения
(а ветка, за которой следит локальная называется upstream branch
). Ветки слежения — это локальные ветки, которые напрямую связаны с удалённой веткой. Если, находясь на ветке слежения, выполнить git pull
, то Git уже будет знать с какого сервера получать данные и какую ветку использовать для слияния.
При клонировании репозитория, как правило, автоматически создаётся ветка master
, которая следит за origin/master
. Однако, при желании вы можете настроить отслеживание и других веток — следить за ветками на других серверах или отключить слежение за веткой master
. Вы только что видели простейший пример, что сделать это можно с помощью команды git checkout -b <branch> <remote>/<branch>
. Это часто используемая команда, поэтому Git предоставляет сокращённую форму записи в виде флага --track
:
git checkout --track origin/serverfix
Branch serverfix set up to track remote branch serverfix from origin.
Switched to a new branch 'serverfix'
В действительности, это настолько распространённая команда, что существует сокращение для этого сокращения. Если вы пытаетесь извлечь ветку, которая не существует, но существует только одна удалённая ветка с точно таким же именем, то Git автоматически создаст ветку слежения:
git checkout serverfix
Branch serverfix set up to track remote branch serverfix from origin.
Switched to a new branch 'serverfix'
Чтобы создать локальную ветку с именем, отличным от имени удалённой ветки, просто укажите другое имя:
git checkout -b newname origin/serverfix
Branch sf set up to track remote branch serverfix from origin.
Switched to a new branch 'newname'
Теперь ваша локальная ветка newname
будет автоматически получать изменения из origin/serverfix
.
Если у вас уже есть локальная ветка и вы хотите настроить ее на слежение за удалённой веткой, которую вы только что получили, или хотите изменить используемую upstream-ветку, то воспользуйтесь параметрами -u
или --set-upstream-to
для команды git branch
, чтобы явно установить новое значение.
git branch -u origin/serverfix
Branch serverfix set up to track remote branch serverfix from origin.
Если у вас настроена отслеживаемая ветка, вы можете ссылаться на нее с помощью сокращений @{upstream}
или @{u}
. Итак, если вы находитесь на ветке master и она следит за origin/master
, при желании вы можете использовать git merge @{u}
вместо git merge origin/master
.
Если вы хотите посмотреть как у вас настроены ветки слежения, воспользуйтесь опцией -vv
для команды git branch
. Это выведет список локальных веток и дополнительную информацию о том, какая из веток отслеживается, отстаёт, опережает или всё сразу относительно отслеживаемой.
git branch -vv
iss53 7e424c3 [origin/iss53: ahead 2] Add forgotten brackets
master 1ae2a45 [origin/master] Deploy index fix
* serverfix f8674d9 [teamone/server-fix-good: ahead 3, behind 1] This should do it
testing 5ea463a Try something new
Итак, здесь мы видим, что наша ветка iss53
следит за origin/iss53
и «опережает» её на два изменения — это значит, что у нас есть два локальных коммита, которые не отправлены на сервер. Мы также видим, что наша ветка master
отслеживает ветку origin/master
и находится в актуальном состоянии. Далее мы можем видеть, что локальная ветка serverfix
следит за веткой server-fix-good
на сервере teamone
, опережает её на три коммита и отстает на один — это значит, что на сервере есть один коммит, который мы ещё не слили, и три локальных коммита, которые ещё не отправлены на сервер. В конце мы видим, что наша ветка testing
не отслеживает удаленную ветку.
Важно отметить, что эти цифры описывают состояние на момент последнего получения данных с каждого из серверов. Эта команда не обращается к серверам, а лишь говорит вам о том, какая информация с этих серверов сохранена в локальном кэше. Если вы хотите иметь актуальную информацию об этих числах, вам необходимо получить данные со всех ваших удалённых серверов перед запуском команды. Сделать это можно вот так:
git fetch --all; git branch -vv
Команда git fetch
получает с сервера все изменения, которых у вас ещё нет, но не будет изменять состояние вашей рабочей копии. Эта команда просто получает данные и позволяет вам самостоятельно сделать слияние. Тем не менее, существует команда git pull
, которая в большинстве случаев является командой git fetch
, за которой непосредственно следует команда git merge
. Если у вас настроена ветка слежения как показано в предыдущем разделе, или она явно установлена, или она была создана автоматически командами clone
или checkout
, git pull
определит сервер и ветку, за которыми следит ваша текущая ветка, получит данные с этого сервера и затем попытается слить удалённую ветку.
Обычно, лучше явно использовать команды fetch
и merge
, поскольку магия git pull
может часто сбивать с толку.
Скажем, вы и ваши соавторы закончили с нововведением и слили его в ветку master
на удалённом сервере (или в какую-то другую ветку, где хранится стабильный код). Вы можете удалить ветку на удалённом сервере используя параметр --delete
для команды git push
. Для удаления ветки serverfix
на сервере, выполните следующую команду:
git push origin --delete serverfix
To https://github.com/schacon/simplegit
- [deleted] serverfix
Всё, что делает эта строка — удаляет указатель на сервере. Как правило, Git сервер хранит данные пока не запустится сборщик мусора, поэтому если ветка была удалена случайно, чаще всего её легко восстановить.
В Git есть два способа внести изменения из одной ветки в другую: слияние и перебазирование.
Как мы выяснили ранее, простейший способ выполнить слияние двух веток — это команда merge
. Она осуществляет трёхстороннее слияние между двумя последними снимками сливаемых веток (C3 и C4) и самого недавнего общего для этих веток родительского снимка (C2), создавая новый снимок (и коммит)
Тем не менее есть и другой способ: вы можете взять те изменения, что были представлены в C4, и применить их поверх C3. В Git это называется перебазированием.
С помощью команды rebase
вы можете взять все коммиты из одной ветки и в том же порядке применить их к другой ветке поверх.
В данном примере переключимся на ветку experiment
и перебазируем её относительно ветки master
следующим образом:
git checkout experiment
git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command
Это работает следующим образом: берётся общий родительский снимок двух веток (текущей, и той, поверх которой вы выполняете перебазирование), определяется дельта каждого коммита текущей ветки и сохраняется во временный файл, текущая ветка устанавливается на последний коммит ветки, поверх которой вы выполняете перебазирование, а затем по очереди применяются дельты из временных файлов.
После этого вы можете переключиться обратно на ветку master и выполнить слияние перемоткой.
git checkout master
git merge experiment
Теперь снимок, на который указывает C4'
абсолютно такой же, как тот, на который указывал C5
в примере с трёхсторонним слиянием. Нет абсолютно никакой разницы в конечном результате между двумя показанными примерами, но перебазирование делает историю коммитов чище.
Если вы взглянете на историю перебазированной ветки, то увидите, что она выглядит абсолютно линейной: будто все операции были выполнены последовательно, даже если изначально они совершались параллельно в разных ветках.
Часто вы будете делать так для уверенности, что ваши коммиты могут быть бесконфликтно слиты в удалённую ветку — возможно, в проекте, куда вы пытаетесь внести вклад, но владельцем которого вы не являетесь. В этом случае вам следует работать в своей ветке и затем перебазировать вашу работу поверх origin/master, когда вы будете готовы отправить свои изменения в основной проект. Тогда владельцу проекта не придётся делать никакой лишней работы — всё решится простой перемоткой или бесконфликтным слиянием.
Учтите, что снимок, на который ссылается ваш последний коммит — является ли он последним коммитом после перебазирования или коммитом слияния после слияния — в обоих случаях это один и тот же снимок, отличаются только истории коммитов. Перебазирование повторяет изменения из одной ветки поверх другой в том порядке, в котором эти изменения были сделаны, в то время как слияние берет две конечные точки и сливает их вместе.
Но даже перебазирование, при всех своих достоинствах, не лишено недостатков, которые можно выразить одной строчкой:
Не перемещайте коммиты, уже отправленные в публичный репозиторий
Если вы рассматриваете перебазирование как способ наведения порядка и работаете с коммитами локально до их отправки или ваши коммиты никогда не будут доступны публично — у вас всё будет хорошо. Однако, если вы перемещаете коммиты, отправленные в публичный репозиторий, и есть вероятность, что работа некоторых людей основывается на этих коммитах, то ваши действия могут вызвать существенные проблемы, а вы — вызвать презрение вашей команды.
История коммитов в вашем репозитории — это запись того, что на самом деле произошло. Это исторический документ, ценный сам по себе, и его нельзя подделывать. С этой точки зрения изменение истории коммитов практически кощунственно; вы лжёте о том, что на самом деле произошло.
Противоположная точка зрения заключается в том, что история коммитов — это история того, как был сделан ваш проект. Вы не публикуете первый черновик книги или инструкции по поддержке вашего программного обеспечения, так как это нуждается в тщательном редактировании. Сторонники этого лагеря считают использование инструментов rebase
и filter-branch
способом рассказать историю проекта наилучшим образом для будущих читателей.
Git умеет работать с четырьмя сетевыми протоколами для передачи данных: локальный, HTTP, Secure Shell (SSH) и Git.
Если у вас смонтирована общая файловая система, вы можете клонировать, отправлять и получать изменения из локального репозитория. Чтобы клонировать такой репозиторий или добавить его в качестве удалённого в существующий проект, используйте путь к репозиторию в качестве URL. Например, для клонирования локального репозитория вы можете выполнить что-то вроде этого:
git clone /srv/git/project.git
Или этого:
git clone file:///srv/git/project.git
Чтобы добавить локальный репозиторий в существующий проект, вы можете воспользоваться командой:
git remote add local_proj /srv/git/project.git
Недостаток этого метода в том, что общий доступ обычно сложнее настроить и получить из разных мест, чем простой сетевой доступ.
Новая версия часто называется Умным (Smart) HTTP, а старая Тупым (Dumb) HTTP. Сначала мы рассмотрим Умный протокол.
Умный протокол HTTP поверх стандартных HTTP/S портов и может использовать различные механизмы аутентификации HTTP, это часто проще для пользователя, чем что-то вроде SSH, так как можно использовать аутентификацию по логину/паролю вместо установки SSH-ключей. Наверное, сейчас он стал наиболее популярным способом использования Git, так как может использоваться и для анонимного доступа как протокол git://, и для отправки изменений с аутентификацией и шифрованием как протокол SSH. Вместо использования разных адресов URL для этих целей, можно использовать один URL адрес для всего. Если вы пытаетесь отослать изменения и репозиторий требует аутентификации (обычно так и есть), сервер может спросить логин и пароль. То же касается и доступа на чтение. На самом деле для сервисов вроде GitHub, адрес URL, который вы используете для просмотра репозитория в браузере (например, https://github.com/schacon/simplegit), можно использовать для клонирования или, если у вас есть доступ, для отправки изменений.
Если сервер не отвечает на умный запрос Git по HTTP, клиент Git попытается откатиться на более простой Тупой HTTP-протокол. Тупой протокол ожидает, что голый репозиторий Git будет обслуживаться веб-сервером как набор файлов
Прелесть тупого протокола HTTP — в простоте настройки. По сути, всё, что необходимо сделать — поместить голый репозиторий
в корневой каталог HTTP и установить обработчик post-update
(смотри Хуки в Git). Теперь каждый может клонировать репозиторий, если имеет доступ к веб-серверу, на котором он был размещен. Таким образом, чтобы открыть доступ на чтение к вашему репозиторию посредством HTTP, нужно сделать что-то наподобие этого:
cd /var/www/htdocs/
git clone --bare /path/to/git_project gitproject.git
cd gitproject.git
mv hooks/post-update.sample hooks/post-update
chmod a+x hooks/post-update
Вот и всё. Обработчик post-update
, входящий в состав Git по умолчанию, выполняет необходимую команду (git update-server-info
), чтобы получение изменений и клонирование по HTTP работали правильно. Эта команда выполняется, когда вы отправляете изменения в репозиторий (возможно посредством SSH); затем остальные могут клонировать его командой
git clone https://example.com/gitproject.git
Часто используемый транспортный протокол для самостоятельного хостинга Git — это SSH
. SSH — протокол с аутентификацией.
Чтобы клонировать Git-репозиторий по SSH, вы можете указать префикс ssh://
в URL, например:
git clone ssh://[user@]server/project.git
Или можно использовать для протокола SSH краткий синтаксис наподобие scp:
git clone [user@]server:project.git
Также вы можете не указывать имя пользователя, Git будет использовать то, под которым вы вошли в систему.
Следующий протокол — Git-протокол. Вместе с Git поставляется специальный демон, который слушает отдельный порт 9418
и предоставляет сервис, схожий с протоколом SSH
, но абсолютно без аутентификации. Чтобы использовать Git-протокол для репозитория, вы должны создать файл git-export-daemon-ok
, иначе демон не будет работать с этим репозиторием, но следует помнить, что в протоколе отсутствуют средства безопасности. Соответственно, любой репозиторий в Git может быть либо доступен для клонирования всем, либо нет. Как следствие, обычно отправлять изменения по этому протоколу нельзя. Вы можете открыть доступ на запись, но из-за отсутствия аутентификации в этом случае кто угодно, зная URL вашего проекта, сможет его изменить. В общем, это редко используемая возможность.
Достоинства
Git-протокол ― часто самый быстрый из доступных протоколов. Если у вас проект с публичным доступом и большой трафик, или у вас очень большой проект, для которого не требуется аутентификация пользователей для чтения, вам стоит настроить демон Git для вашего проекта. Он использует тот же механизм передачи данных, что и протокол SSH, но без дополнительных затрат на шифрование и аутентификацию.
Недостатки
Недостатком Git-протокола является отсутствие аутентификации
Центральный хаб или репозиторий может принимать код, а все остальные синхронизируют свою работу с ним. Все разработчики являются узлами (пользователями хаба) и синхронизируются только с ним.
Это означает, что если два разработчика клонируют репозиторий и каждый внесёт изменения, то первый из них сможет отправить свои изменения в репозиторий без проблем. Второй разработчик должен слить изменения, сделанные первым разработчиком, чтобы избежать их перезаписи во время отправки на сервер.
Достаточно создать один репозиторий и предоставить каждому члену команды push-доступ
; Git не позволит перезаписать изменения, сделанные другими.
Предположим, Джон и Джессика начинают работать над проектом одновременно. Джон вносит изменения и отправляет их на сервер. Затем Джессика пытается отправить свои изменения, но сервер их отклоняет. Ей говорят, что она пытается отправить изменения, для которых невозможно выполнить быструю перемотку и она не сможет это сделать пока не получит все новые для неё изменения и не сольёт их. Такой рабочий процесс привлекает большинство людей, так как реализует парадигму, с которой они уже знакомы. Такой подход применим не только к небольшим командам. Используя модель ветвления Git, сотни разработчиков могут одновременно работать над одним проектом, используя при этом десятки веток.
Так как Git допускает использование нескольких удалённых репозиториев, то становится возможным организация рабочего процесса, где каждый разработчик имеет доступ на запись в свой публичный репозиторий и доступ на чтение ко всем остальным. При таком сценарии обычно существует канонический репозиторий, который представляет собой официальный
проект. Для отправки своих наработок в этот проект следует создать его клон и отправить изменения в него. Затем вы отправляете запрос на слияние ваших изменений сопровождающему основного проекта. В свою очередь он может добавить ваш репозиторий как удаленный, протестировать ваши изменения локально, слить их в соответствующую ветку и отправить в основной репозиторий.
Процесс работает в следующей последовательности:
1. Сопровождающий проекта отправляет изменения в свой публичный репозиторий.
2. Участник клонирует этот репозиторий и вносит изменения.
3. Участник отправляет свои изменения в свой публичный репозиторий.
4. Участник отправляет письмо сопровождающему с запросом на слияние изменений.
5. Сопровождающий добавляет репозиторий участника как удалённый и сливает изменения локально.
6. Сопровождающий отправляет слитые изменения в основной репозиторий.
Это вариант организации рабочего процесса с использованием нескольких репозиториев. В основном такой подход используется на огромных проектах, насчитывающих сотни участников; самый известный пример — ядро Linux. Помощники (lieutenants) — это интеграционные менеджеры, которые отвечают за отдельные части репозитория. Над ними главенствует один диспетчер интеграции, которого называют великодушным диктатором. Репозиторий диктатора выступает как эталонный (blessed), откуда все участники процесса должны получать изменения. Процесс работает следующим образом:
1. Обычные разработчики работают в своих тематических ветках и перебазируют свою работу относительно ветки `master`. Ветка `master` — это ветка эталонного репозитория в которую имеет доступ только диктатор.
2. Помощники сливают тематические ветки разработчиков в свои ветки `master`
3. Диктатор сливает ветки `master` помощников в свою ветку `master`.
4. Наконец, диктатор отправляет свою ветку `master` в эталонный репозиторий, чтобы все остальные могли перебазировать свою работу на основании неё.
Для начала, вам не следует отправлять ненужные пробелы. Git предоставляет простой способ проверки — перед коммитом выполните команду git diff --check
, которая выведет список ненужных пробелов.
Пишите сообщение коммита в императиве: «Fix bug» а не «Fixed bug» или «Fixes bug».
Вот шаблон хорошего сообщения коммита:
Краткое (не более 50 символов) резюме с заглавной буквы
Более детальный, поясняющий текст, если он требуется.
Старайтесь не превышать длину строки в 72 символа.
В некоторых случаях первая строка подразумевается как тема письма, а всё остальное -- как тело письма.
Пустая строка, отделяющая сводку от тела, имеет решающее
значение (за исключением случаев, когда детального описания
нет); в противном случае такие инструменты, как rebase, могут
вас запутать.
Сообщения коммитов следует писать используя неопределенную форму глагола совершенного вида
повелительного наклонения: «Fix bug» (Исправить баг).
Это соглашение соответствует сообщениям коммитов,
генерируемых такими командами, как `git merge` и `git revert`.
Последующие абзацы идут после пустых строк.
- Допускаются обозначения пунктов списка
- Обычно, элементы списка обозначаются с помощью тире или звёздочки, с одним пробелом перед ними, а
разделяются пустой строкой, но соглашения могут отличаться
- Допускается обратный абзацный отступ.
Давайте посмотрим что происходит, когда два разработчика начинают работать вместе и используют общий репозиторий. Первый разработчик Джон клонирует репозиторий, вносит изменения и делает коммит локально. (В последующих примерах сообщения протокола заменены на ... с целью их немного сократить.)
# Компьютер Джона
git clone john@githost:simplegit.git
Cloning into 'simplegit'...
...
cd simplegit/
vim lib/simplegit.rb
git commit -am 'Remove invalid default value' [master 738ee87] Remove invalid default value
1 files changed, 1 insertions(+), 1 deletions(-)
Второй разработчик Джессика делает то же самое — клонирует репозиторий и делает коммит:
# Компьютер Джессики
git clone jessica@githost:simplegit.git Cloning into 'simplegit'...
...
cd simplegit/
vim TODO
git commit -am 'Add reset task'
[master fbff5bc] Add reset task
1 files changed, 1 insertions(+), 0 deletions(-)
Затем Джессика отправляет изменения на сервер:
# Компьютер Джессики
git push origin master
...
To jessica@githost:simplegit.git
1edee6b..fbff5bc master -> master
Джон вносит некоторые изменения, делает коммит и пытается отправить его на тот же сервер:
# Компьютер Джона
git push origin master
To john@githost:simplegit.git
! [rejected] master -> master (non-fast forward)
error: failed to push some refs to 'john@githost:simplegit.git'
В данном случае изменения Джона отклонены, так как Джессика уже отправила свои. В Git вы должны сначала слить изменения локально. Джон должен получить изменения Джессики и слить их локально, прежде чем сможет отправить свои. Для начала, Джон получает изменения Джессики (слияния изменений пока что не происходит):
git fetch origin
...
From john@githost:simplegit
+ 049d078...fbff5bc master -> origin/master
В этот момент локальный репозиторий Джона выглядит примерно так:
Теперь Джон может слить полученные изменения Джессики со своей локальной веткой:
git merge origin/master
Merge made by the 'recursive' strategy.
TODO | 1 +
1 files changed, 1 insertions(+), 0 deletions(-)
Процесс слияния проходит гладко — история коммитов у Джона выглядит примерно так:
Теперь Джон может протестировать новый код, чтобы убедиться в корректной работе объединённых изменений, после чего он может отправить объединённые изменения на сервер:
git push origin master
...
To john@githost:simplegit.git
fbff5bc..72bbc59 master -> master
В результате история коммитов у Джона выглядит так:
Тем временем Джессика создала тематическую ветку с названием issue54
и сделала в ней три коммита.
Тем временем Джессика создала тематическую ветку с названием issue54
и сделала в ней три коммита. Она ещё не получила изменения Джона, поэтому история коммитов у неё выглядит следующим образом:
Внезапно Джессика узнаёт, что Джон отправил какие-то изменения на сервер и теперь она хочет на них взглянуть; для этого ей следует получить с сервера все новые изменения:
# Компьютер Джессики
$ git fetch origin
...
From jessica@githost:simplegit
fbff5bc..72bbc59 master -> origin/master
Это приводит к получению изменений, отправленных Джоном в репозиторий. Теперь, история коммитов у Джессики выглядит так:
Джессика считает, что её тематическая ветка готова, но так же хочет знать какие изменения следует слить со своей работой перед отправкой на сервер. Для прояснения ситуации он выполняет команду git log
:
git log --no-merges issue54..origin/master
commit 738ee872852dfaa9d6634e0dea7a324040193016
Author: John Smith <jsmith@example.com>
Date: Fri May 29 16:01:27 2009 -0700
Remove invalid default value
issue54..origin/master
— это синтаксис фильтра, который указывает Git отображать только список коммитов, которые существуют в последней ветке (в данном случае origin/master
), но отсутствуют в первой (в данном случае issue54
). Более детально этот синтаксис рассматривается в разделе Диапазоны коммитов главы 7.
В данном случае, в выводе команды мы видим только один коммит, сделанный Джоном и ещё не слитый Джессикой. Если она сольёт origin/master
, то это будет единственный коммит, который изменит локальное состояние.
Теперь, Джессика может слить изменения тематической ветки и изменения Джона (origin/master
) в свою локальную ветку master
, а затем отправить её на сервер.
Для начала (при условии отсутствия изменений в тематической ветке, не включённых в коммит), Джессика переключается на свою ветку master
:
git checkout master
Switched to branch 'master'
Your branch is behind 'origin/master' by 2 commits, and can be fast-forwarded.
Обе ветки origin/master
и issue54
являются отслеживаемыми, поэтому порядок слияния не важен. Конечный результат будет идентичным вне зависимости от порядка слияния, однако история коммитов будет немного отличаться. Джессика решает слить ветку issue54
первой:
git merge issue54
Updating fbff5bc..4af4298
Fast forward
README | 1+
lib/simplegit.rb | 6 +++++-
2 files changed, 6 insertions(+), 1 deletions(-)
Проблем не возникает; как можно заметить, это простое перемещение вперед. Теперь Джессика заканчивает процесс локального слияния объединяя полученные ранее изменения Джона, находящиеся в ветке origin/master
:
git merge origin/master
Auto-merging lib/simplegit.rb
Merge made by the 'recursive' strategy.
lib/simplegit.rb | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
Слияние прошло чисто и теперь история коммитов у Джессики выглядит следующим образом:
Теперь Джессика может отправить свою ветку master
в origin/master
, при условии что Джон больше не отправлял изменений:
git push origin master
...
To jessica@githost:simplegit.git
72bbc59..8059c15 master -> master
Каждый разработчик сделал коммиты несколько раз и успешно слил изменения другого.
В течение некоторого времени вы работаете в тематической ветке, а затем сливаете изменения в ветку master
когда всё готово. Чтобы поделиться проделанной работой, вы сливаете её в вашу ветку master
, затем получаете и сливаете изменения из ветки origin/master
если таковые имеются, и наконец, отправляете все изменения в ветку master
на сервере.
В этом сценарии мы рассмотрим роли участников в более крупной частной команде. Вы узнаете как работать в окружении, где мелкие группы совместно работают над улучшениями, а затем их вклад интегрируется третьей стороной.
Предположим, что Джон и Джессика вместе работают над одной функцией (назовём её featureA
), при этом Джессика и Джози работают над другой (featureB
). В этом случае компания использует тип рабочего процесса с менеджером по интеграции, при котором работа отдельных групп интегрируется определёнными инженерами, а ветка master
основного репозитория может быть обновлена только этими инженерами. При таком сценарии вся работа ведётся в отдельных ветках для каждой команды, а затем объединяется интегратором.
Давайте рассмотрим рабочий процесс Джессики, так как она работает над двумя функциями, параллельно сотрудничая с разными разработчиками. Предположим, что репозиторий уже клонирован и она решает работать сначала над функцией featureA
. Джессика создаёт новую ветку для этой функции и некоторое время работает над ней:
# Компьютер Джессики
git checkout -b featureA
Switched to a new branch 'featureA'
vim lib/simplegit.rb
git commit -am 'Add limit to log function' [featureA 3300904] Add limit to log function
1 files changed, 1 insertions(+), 1 deletions(-)
В данный момент ей необходимо поделиться проделанной работой с Джоном, поэтому Джессика отправляет ветку featureA
на сервер. У Джессики нет доступа на запись в ветку master
(он есть только у интеграторов), поэтому для совместной работы с Джоном она отправляет изменения в другую ветку:
git push -u origin featureA
...
To jessica@githost:simplegit.git
* [new branch] featureA -> featureA
Джессика отправляет письмо Джону с уведомлением, что внесённые ей изменения уже доступны в ветке featureA
. Пока Джессика ждёт ответа от Джона, она решает поработать над другой функцией featureB
вместе с Джози. Для начала, Джесика создаёт новую тематическую ветку, базируясь на состоянии ветки master
на сервере:
# Компьютер Джессики
git fetch origin
git checkout -b featureB origin/master Switched to a new branch 'featureB'
После этого, Джессика делает несколько коммитов в ветке featureB
:
vim lib/simplegit.rb
git commit -am 'Make ls-tree function recursive'
[featureB e5b0fdc] Make ls-tree function recursive
1 files changed, 1 insertions(+), 1 deletions(-)
vim lib/simplegit.rb
git commit -am 'Add ls-files'
[featureB 8512791] Add ls-files
1 files changed, 5 insertions(+), 0 deletions(-)
Джессика готова отправить свою работу, но получает письмо Джози, что начальная работа уже отправлена на сервер в ветку featureBee
. Теперь Джессике нужно слить эти изменения со своими перед отправкой на сервер. Изменения Джози она получает командой git fetch
:
$ git fetch origin
...
From jessica@githost:simplegit
* [new branch] featureBee -> origin/featureBee
Полагая что Джессика находится в ветке featureB
, она может слить полученные изменения Джози со своими при помощи команды git merge
:
git merge origin/featureBee
Auto-merging lib/simplegit.rb
Merge made by the 'recursive' strategy.
lib/simplegit.rb | 4 ++++
1 files changed, 4 insertions(+), 0 deletions(-)
Одна небольшая проблема — ей нужно отправить слитые изменения из локальной ветки featureB
в ветку featureBee
на сервере. Для этого в команде git push
Джессика указывает названия локальной и удалённой веток, разделенных двоеточием:
git push -u origin featureB:featureBee
...
To jessica@githost:simplegit.git
fba9af8..cd685d1 featureB -> featureBee
Это называется спецификация ссылок. В разделе Спецификации ссылок главы 10 приведено более детальное описание спецификаций ссылок Git и различные способы их использования. Так же обратите внимание на флаг -u
; это сокращение для --set-upstream
, который настраивает ветки для упрощения отправки и получения изменений в дальнейшем.
После этого, Джессика получает письмо от Джона, в котором он сообщает, что отправил некоторые изменения в ветку featureA
и просит их проверить. Джесика выполняет команду git fetch
для получения всех новых изменений, включая изменения Джона:
git fetch origin
...
From jessica@githost:simplegit
3300904..aad881d featureA -> origin/featureA
Теперь, она может посмотреть что именно было изменено путём сравнения полученной ветки featureA
со своей локальной веткой:
git log featureA..origin/featureA
commit aad881d154acdaeb2b6b18ea0e827ed8a6d671e6
Author: John Smith <jsmith@example.com>
Date: Fri May 29 19:57:33 2009 -0700
Increase log output to 30 from 25
Если Джессику всё устраивает, то она сливает изменения Джона в свою ветку featureA
:
git checkout featureA
Switched to branch 'featureA'
git merge origin/featureA
Updating 3300904..aad881d
Fast forward
lib/simplegit.rb | 10 +++++++++-
1 files changed, 9 insertions(+), 1 deletions(-)
Джессика решает немного подправить, делает коммит в локальной ветке featureA
и отправляет конечный результат на сервер:
git commit -am 'Add small tweak to merged content'
[featureA 774b3ed] Add small tweak to merged content
1 files changed, 1 insertions(+), 1 deletions(-)
git push
...
To jessica@githost:simplegit.git
3300904..774b3ed featureA -> featureA
В результате история коммитов у Джессики выглядит так:
Джессика, Джози и Джон информируют интеграторов, что ветки featureA и featureBee на сервере готовы к слиянию в основную. После того как интеграторы сольют эти ветки в основную, полученные изменения будут содержать коммит слияния, а история коммитов будет иметь вид:
Возможность совместной работы небольших подгрупп команды в удалённых ветках без необходимости вовлекать или мешать всей команде — огромное преимущество Git.
Участие в публичном проекте сильно отличается. Так как у вас нет доступа обновлять ветки проекта напрямую, то передавать проделанную работу следует другим способом. В первом примере рассматривается участие в публичном проекте посредством форка на Git платформах, где возможно его простое создание.
Для начала, вам следует клонировать основной репозиторий, создать тематическую ветку для одного или нескольких патчей и работать в ней.
git clone <url>
cd project
git checkout -b featureA
... work ...
git commit
... work ...
git commit
Когда работа в тематической ветке завершена и вы готовы передать изменения исходному проекту, перейдите на страницу исходного проекта и нажмите кнопку Fork
, тем самым создавая доступный для записи форк проекта. Затем нужно добавить URL на созданный проект как второй удалённый репозиторий, в нашем случае с именем myfork
:
git remote add myfork <url>
После этого следует отправить проделанную работу в него. Проще отправить вашу тематическую ветку, в которой велась работа, чем сливать изменения в вашу ветку master
и отправлять её. Если ваши изменения будут отклонены или какой-то из коммитов будет применен выборочно (команда cherry-pick
более детально рассматривается в разделе Схема с перебазированием и отбором главы 5), то вы не сможете вернуть состояние вашей ветки master
. Если менеджер проекта сольёт, перебазирует или выборочно применит ваши изменения, то вы сможете их получить из оригинального репозитория.
Отправить свои изменения вы можете командой:
git push -u myfork featureA
Когда ваши изменения отправлены в ваш форк, следует уведомить сопровождающих исходного проекта о том, что у вас есть изменения для интеграции. Обычно, это называется запросом слияния, который вы можете создать используя как веб сайт — GitHub использует собственный механизм запросов слияния, который будет рассмотрен в главе GitHub — так и команду git request-pull
, отправив её вывод по почте.
Команда git request-pull
принимает в качестве аргументов название базовой ветки, в которую следует влить изменения из вашей тематической ветки, и ссылку на Git репозиторий, из которого следует получать изменения, а результатом будет список всех изменений, которые вы предлагаете внести. Например, если Джессика хочет отправить Джону запрос слияния и она отправила два коммита в тематическую ветку, то ей следует выполнить команду:
git request-pull origin/master myfork
The following changes since commit 1edee6b1d61823a2de3b09c160d7080b8d1b3a40:
Jessica Smith (1):
Create new function
are available in the git repository at:
git://githost/simplegit.git featureA
Jessica Smith (2):
Add limit to log function
Increase log output to 30 from 25
lib/simplegit.rb | 10 +++++++++-
1 files changed, 9 insertions(+), 1 deletions(-)
Вывод команды можно отправить сопровождающему проекта — в нём говорится с какого момента велась работа, приводится сводка коммитов и указывается откуда можно получить эти изменения.
В проектах, где вы не являетесь сопровождающим, проще держать ветку master
в соответствии с origin/master
, а работать в тематических ветках — так вам будет проще отменить изменения, если они будут отклонены. Разделение направлений разработки по изолированным веткам облегчит их перебазирование, когда состояние основного репозитория изменится, а ваши коммиты уже не смогут быть чисто применены. Например, если вы собираетесь отправить исправления на другую тему, не продолжайте работать в той же тематической ветке — создайте новую, базируясь на ветке master
основного репозитория:
git checkout -b featureB origin/master
... work ...
git commit
git push myfork featureB
git request-pull origin/master myfork
... email generated request pull to maintainer ...
git fetch origin
Теперь, каждая из ваших тематик разработки изолирована — аналогично очереди патчей — каждую из которых можно переписать, перебазировать или исправить без влияния на другие ветки.
Предположим, что сопровождающий проекта слил некоторый набор других патчей, а затем пытается применить вашу первую ветку, но она уже не может быть слита без конфликтов. В этом случае вы можете попытаться перебазировать свою ветку относительно origin/master
, разрешить конфликты и заново отправить свои изменения:
git checkout featureA
git rebase origin/master
git push -f myfork featureA
Эти действия перепишут историю ваших коммитов, которая станет похожа на История коммитов после работы над featureA
.
Так как вы перебазировали ветку, то должны указать флаг -f
во время отправки на сервер, чтобы переписать историю ветки featureA
коммитами, не являющимися её потомками. Альтернативным решением может быть отправка этих исправлений в ветку с другим названием (например, featureAv2
).
Давайте рассмотрим ещё один возможный сценарий: сопровождающий посмотрел вашу вторую ветку и ему понравилась идея, но он хочет попросить вас изменить некоторые детали. Возможно, вы так же захотите перебазировать эту ветку относительно текущего состояния ветки master
. Вы создаёте новую ветку базируясь на текущей origin/master
, сбрасываете все изменения в неё, разрешаете возможные конфликты, делаете изменения в реализации и отправляете её как новую ветку:
git checkout -b featureBv2 origin/master
git merge --squash featureB
... change implementation ...
git commit
git push myfork featureBv2
Опция --squash
берет все изменения из указанной ветки, объединяет их и создаёт новый коммит в текущей ветке без создания коммита слияния. Это значит, что новый коммит будет иметь только одного родителя и будет включать все изменения из другой ветки, а так же позволяет внести дополнительные изменения до фактического создания коммита. Опция --no-commit
указывает Git не создавать новый коммит автоматически.
Теперь можно отправить сопровождающему сообщение, что вы сделали запрошенные изменения и они находятся в вашей ветке featureBv2
.
Существует несколько больших старых проектов, которые принимают патчи посредством почтовых рассылок.
Рабочий процесс похож на предыдущий — вы создаёте тематическую ветку для каждого набора патчей, над которыми собираетесь работать. Основное отличие в способе их передачи проекту. Вместо того, чтобы форкнуть проект и отправить в него свои изменения, вы генерируете почтовую версию для каждого набора коммитов с целью отправки её в список рассылки разработчиков:
git checkout -b topicA
... work ...
git commit
... work ...
git commit
Сейчас у вас два коммита, которые вы хотите отправить в почтовую рассылку. Используйте команду git format-patch
для генерации файлов в формате mbox
, которые можно отправить по почте — это обернёт каждый коммит в сообщение e-mail, где первая строка из сообщения коммита будет темой письма, а остальные строки плюс сам патч будут телом письма. Применение патча в формате e-mail, сгенерированного с помощью команды format-patch
, сохраняет всю информацию о коммите должным образом.
git format-patch -M origin/master
0001-add-limit-to-log-function.patch
0002-increase-log-output-to-30-from-25.patch
Команда format-patch
выводит список имён файлов патчей, которые она создаёт. Флаг -M
указывает Git искать переименования. В итоге файлы выглядят вот так:
cat 0001-add-limit-to-log-function.patch
From 330090432754092d704da8e76ca5c05c198e71a8 Mon Sep 17 00:00:00 2001
From: Jessica Smith <jessica@example.com>
Date: Sun, 6 Apr 2008 10:17:23 -0700
Subject: [PATCH 1/2] Add limit to log function
Limit log functionality to the first 20
---
lib/simplegit.rb | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/lib/simplegit.rb b/lib/simplegit.rb
index 76f47bc..f9815f1 100644
--- a/lib/simplegit.rb
+++ b/lib/simplegit.rb
@@ -14,7 +14,7 @@ class SimpleGit
end
def log(treeish = 'master')
command("git log #{treeish}")
command("git log -n 20 #{treeish}")
end
def ls_tree(treeish = 'master')
--
2.1.0
Вы можете редактировать эти файлы, добавляя информацию для списка рассылки, но которую вы не хотите видеть в сообщении к коммиту. Если добавить текст между строкой ---
и началом патча (строка diff --git
), то разработчики увидят его, но применяться он не будет.
Для отправки в список рассылки можно либо вставить файлы в почтовую программу, либо отправить их из командной строки.
Git предоставляет утилиту, которая умеет отправлять корректно отформатированные патчи по протоколу IMAP
.
Для начала, следует настроить раздел imap
в файле ~/.gitconfig
. Каждое отдельное значение можно установить вызовом команды git config
, а можно указать вручную сразу в файле, но в итоге файл конфигурации должен выглядеть следующим образом:
[imap]
folder = "[Gmail]/Drafts"
host = imaps://imap.gmail.com
user = user@gmail.com
pass = YX]8g76G_2^sFbd
port = 993
sslverify = false
Если ваш сервер IMAP
не использует SSL
, то последние две строки не обязательны, а значение host
должно быть imap://
вместо imaps://
. Как только все сделано, воспользуйтесь командой git imap-send
для помещения ваших патчей в папку Drafts на указанном IMAP сервере:
cat *.patch |git imap-send
Resolving imap.gmail.com... ok
Connecting to [74.125.142.109]:993... ok
Logging in...
sending 2 messages
100% (2/2) done
Теперь вы можете перейти в папку Drafts, изменить поле To, указав адрес почтовой рассылки, при необходимости заполнить поле СС, указав адрес сопровождающего или ответственного, и отправить письмо.
Так же вы можете отправить свои патчи используя SMTP
сервер. Как и в предыдущем случае, вы можете использовать набор команд git config
или создать секцию sendemail
в файле ~/.gitconfig
:
[sendemail]
smtpencryption = tls
smtpserver = smtp.gmail.com
smtpuser = user@gmail.com
smtpserverport = 587
Отправить патчи можно командой git send-email
:
git send-email *.patch
0001-add-limit-to-log-function.patch
0002-increase-log-output-to-30-from-25.patch
Who should the emails appear to be from? [Jessica Smith <jessica@example.com>]
Emails will be sent from: Jessica Smith <jessica@example.com>
Who should the emails be sent to? jessica@example.com
Message-ID to be used as In-Reply-To for the first email? y
Помощь по конфигурации, дополнительные советы и рекомендации, а так же тестовое окружение для отправки патчей по email доступны здесь git-send-email.io
Сопровождение может включать в себя принятие и применение патчей, сгенерированных с помощью format-patch
и отправленных вам по почте, или интеграцию изменений в ветках удалённых репозиториев.
Перед интеграцией новых изменений желательно проверить их в тематической ветке — временной ветке, специально созданной для проверки работоспособности новых изменений. Таким образом, можно применять патчи по одному и пропускать неработающие, пока не найдётся время к ним вернуться.
Если вы создадите ветку с коротким и понятным названием, основанным на тематике изменений, например, ruby_client
или что-то похожее, то без труда можно будет вернуться к ней, если пришлось на какое-то время отказаться от работы с ней. Сопровождающему Git проекта свойственно использовать пространство имен для веток, например, sc/ruby_client
, где sc
— это сокращение от имени того, кто проделал работу. Как известно, ветки можно создавать на основании базовой ветки, например:
git branch sc/ruby_client master
Если вы хотите сразу переключиться на создаваемую ветку, то используйте опцию checkout -b
:
git checkout -b sc/ruby_client master
Теперь вы можете добавить новые изменения в созданную тематическую ветку и определить хотите ли слить эти изменения в ваши долгосрочные ветки.
Если вы получили патч по почте и его нужно интегрировать в проект, то следует проанализировать его, применив сначала в тематической ветке. Существует два варианта применения полученного по почте патча: git apply
или git am
.
Если полученный по почте патч был создан командой git diff
или Unix командой diff
(что не рекомендуется делать), то применить его можно командой git apply
. Предположим, патч сохранен здесь /tmp/patch-ruby-client.patch
, тогда применить его можно вот так:
git apply /tmp/patch-ruby-client.patch
Это действие модифицирует файлы в вашем рабочем каталоге. Выполнение команды практически эквивалентно выполнению команды patch -p1
, однако, является более параноидальным и принимает меньше неточных совпадений, чем patch
. При этом обрабатываются добавления, удаления и переименования файлов, указанные в формате git diff
, тогда как patch
этого не делает.
Наконец, git apply
использует модель «применить всё или отменить всё», где изменения либо применяются полностью, либо не применяются вообще, тогда как patch
может частично применить патч файлы, приведя ваш рабочий каталог в непонятное состояние. В целом, git apply
более консервативен, чем patch
. После выполнения команды новый коммит не создаётся и его нужно делать вручную.
Командой git apply
можно проверить корректность применения патча до его фактического применения, используя git apply --check
:
git apply --check 0001-see-if-this-helps-the-gem.patch
error: patch failed: ticgit.gemspec:1
error: ticgit.gemspec: patch does not apply
Если ошибок не выведено, то патч может быть применён без проблем. Так же, в случае ошибки эта команда возвращает отличное от 0 значение, что позволяет использовать её в скриптах.
Если участник проекта пользователь Git и умеет пользоваться командой format-patch
для генерации патчей, то вам будет легче, так как в патч включается информация об авторе и сообщение коммита. Если возможно, требуйте от ваших участников использовать команду format-patch
вместо diff
для генерации патчей. Вам останется использовать git apply
только для устаревших патчей и подобного им.
Для применения патча, созданного с помощью format-patch
, используйте git am
(команда названа am потому что применяет «apply» набор патчей в формате «mailbox»). С технической точки зрения она просто читает mbox-файл
, в котором в виде обычного текста хранится одно или несколько электронных писем. Этот файл имеет следующий вид:
From 330090432754092d704da8e76ca5c05c198e71a8 Mon Sep 17 00:00:00 2001
From: Jessica Smith <jessica@example.com>
Date: Sun, 6 Apr 2008 10:17:23 -0700
Subject: [PATCH 1/2] Add limit to log function
Limit log functionality to the first 20
Это начало вывода команды format-patch
, которая рассматривалась в предыдущем разделе; это так же представляет собой валидный формат mbox
. Если кто-то отправил патч, корректно сформированный командой git send-email
, и вы сохранили его в формате mbox
, то можно указать передать этот файл в качестве аргумента команде git am
, которая начнёт применять все найденные в файле патчи. Если вы используете почтовый клиент, который умеет сохранять несколько писем в формате mbox
, то можно сохранить сразу серию патчей в один файл, а затем применить их за раз, используя git am
.
Так или иначе, если кто-нибудь загрузит созданный с помощью format-patch
патч файл в систему управления задачами, то вы сможете сохранить его себе и применить локально с помощью git am
:
git am 0001-limit-log-function.patch
Applying: Add limit to log function
Если участник проекта создал свой Git репозиторий, отправил в него свои изменения, а затем прислал вам ссылку и название ветки, куда были отправлены изменения, то вы можете добавить этот репозиторий как удалённый и провести слияние локально.
К примеру, Джессика отправила вам письмо, в котором сказано, у неё есть новый функционал в ветке ruby-client
её репозитория. Добавив удалённый репозиторий и получив изменения из этой ветки, вы можете протестировать изменения извлекая их локально:
git remote add jessica git://github.com/jessica/myproject.git
git fetch jessica
git checkout -b rubyclient jessica/ruby-client
Если она снова пришлёт вам письмо с указанием на новый функционал уже в другой ветке, то для его получения достаточно fetch
и checkout
, так как удалённый репозиторий уже подключён.
Если вы с кем-то не работаете постоянно, но всё равно хотите использовать удалённый репозиторий, то можно указать ссылку на него в команде git pull
. Это приведёт к однократному выполнению, а ссылка на репозиторий сохранена не будет.
git pull https://github.com/onetimeguy/project
From https://github.com/onetimeguy/project
* branch HEAD -> FETCH_HEAD
Merge made by the 'recursive' strategy.
На текущий момент у вас есть тематическая ветка, содержащая предоставленные изменения. Сейчас вы можете определиться что с ними делать. В этом разделе рассматривается набор команд, которые помогут вам увидеть что именно будет интегрировано, если вы решите слить изменения в основную ветку.
Обычно, полезно просмотреть все коммиты текущей ветки, которые ещё не включены в основную. Вы можете исключить коммиты, которые уже есть в вашей основной ветке добавив опцию --not
перед её названием. Это аналогично указанию использовавшегося ранее формата master..contrib
. Например, если участник проекта отправил вам два патча, а вы создали ветку с названием contrib
и применили их, то можно выполнить следующую команду:
git log contrib --not master
commit 5b6235bd297351589efc4d73316f0a68d484f118
Author: Scott Chacon <schacon@gmail.com>
Date: Fri Oct 24 09:53:59 2008 -0700
See if this helps the gem
commit 7482e0d16d04bea79d0dba8988cc78df655f16a0
Author: Scott Chacon <schacon@gmail.com>
Date: Mon Oct 22 19:38:36 2008 -0700
Update gemspec to hopefully work better
Для просмотра изменений, представленных в каждом коммите, можно использовать опцию -p
команды git log
, которая выведет разницу по каждому коммиту.
Для просмотра полной разницы того, что произойдёт если вы сольёте изменения в другую ветку, вам понадобится использовать возможно странный способ для получения корректных результатов:
git diff master
Эта команда может вводить в заблуждение, но точно покажет разницу. Если ваша master
ветка продвинулась вперед с тех пор как вы создали тематическую ветку, то вы получите на первый взгляд странные результаты. Это происходит потому, что Git непосредственно сравнивает снимки последних коммитов текущей и master
веток. Например, если вы добавили строку в файл в ветке master
, то прямое сравнение снимков будет выглядеть как будто тематическая ветка собирается удалить эту строку.
Это не проблема, если ветка master
является непосредственным родителем вашей тематической ветки, но если история обоих веток изменилась, то разница будет выглядеть как добавление всех изменений из тематической ветки и удаление всего нового из master
ветки.
Что действительно нужно видеть, так это изменения тематической ветки, которые предстоит слить в master
ветку. Это можно сделать, сказав Git сравнивать последний коммит тематической ветки с первым общим родителем для обоих веток.
Технически это делается за счёт явного указания общего коммита и применения разницы к нему:
git merge-base contrib master
36c7dba2c95e6bbb78dfa822519ecfec6e1ca649
git diff 36c7db
или более кратко:
git diff $(git merge-base contrib master)
Однако это не удобно, поэтому Git предоставляет более короткий способ: синтаксис троеточия ...
. При выполнении команды diff
, следует поставить три точки после имени ветки для получения разницы между ней и текущей веткой, относительно общего родителя с другой веткой:
git diff master...contrib
Данная команда отобразит проделанную работу только из тематической ветки, относительно общего родителя с веткой master
. Полезно запомнить указанный синтаксис.
Когда все изменения в текущей тематической ветке готовы к интеграции с основной веткой, возникает вопрос как это сделать.
В простом рабочем процессе проделанная работа просто сливается в ветку master
. При таком сценарии у вас есть ветка master
, которая содержит стабильный код. Когда работа в тематической ветке завершена или вы проверили чью-то работу, вы сливаете её в ветку master
и удаляете, затем процесс повторяется.
Если в репозитории присутствуют две ветки ruby_client
и php_client
с проделанной работой, как показано на рисунке История с несколькими тематическими ветками, и вы сначала сливаете ветку ruby_client
, а затем php_client
, то состояние вашего репозитория будет выглядеть как показано на рисунке Слияние тематической ветки.
Это, пожалуй, простейший рабочий процесс и его использование проблематично в больших или более стабильных проектах, где вы должны быть более осторожны с предоставленными изменениями.
Если у вас очень важный проект, то возможно вам стоит использовать двухступенчатый цикл слияния. При таком сценарии у вас имеются две долгоживущие ветки master
и develop
, где в master
сливаются только очень стабильные изменения, а все новые доработки интегрируются в ветку develop
. Обе ветки регулярно отправляются в публичный репозиторий. Каждый раз, когда новая тематическая ветка готова к слиянию (Перед слиянием тематической ветки), вы сливаете её в develop
(После слияния тематической ветки); затем, когда вы выпускаете релиз, ветка master
смещается на стабильное состояние ветки develop
(После релиза проекта).
Таким образом, люди могут клонировать репозиторий вашего проекта и использовать ветку master
для сборки последнего стабильного состояния и получения актуальных изменений или использовать ветку develop
, которая содержит самые последние изменения. Вы также можете продолжить эту концепцию, имея интеграционную ветку integrate
, в которой объединяется вся работа. После того, как кодовая база указанной ветки стабильна и пройдены все тесты, она сливается в ветку develop
, а после того, как стабильность слитых изменений доказана, вы перемещаете состояние ветки master
на стабильное.
В проекте Git присутствуют четыре долгоживущие ветки: master
, next
, seen
(ранее pu
— предложенные обновления) для новой работы и maint
для поддержки обратной совместимости.
Предложенные участниками проекта наработки накапливаются в тематических ветках основного репозитория по ранее описанному принципу. На этом этапе производится оценка содержимого тематических веток, чтобы определить, работают ли предложенные фрагменты так, как положено, или им требуется доработка. Если все в порядке, тематические ветки сливаются в ветку next
, которая отправляется на сервер, чтобы у каждого была возможность опробовать результат интеграции.
Если содержимое тематических веток требует доработки, оно сливается в ветку seen
. Когда выясняется, что предложенный код полностью стабилен, он сливается в ветку master
. Затем ветки next
и seen
перестраиваются на основании master
. Это означает, что master
практически всегда двигается только вперед, next
время от времени перебазируется, а seen
перебазируется ещё чаще.
После того, как тематическая ветка окончательно слита в master
, она удаляется из репозитория. Репозиторий также содержит ветку maint
, которая ответвляется от последнего релиза для предоставления патчей, если требуется поддержка обратной совместимости.
Таким образом, после клонирования проекта у вас будет четыре ветки, дающие возможность перейти на разные стадии его разработки, в зависимости от того, на сколько передовым вы хотите быть или как вы собираетесь участвовать в проекте; вместе с этим, рабочий процесс структурирован, что помогает сопровождающему проекта проверять поступающий код. Рабочий процесс проекта Git специфицирован. Для полного понимания процесса обратитесь к Git Maintainer’s guide
Некоторые сопровождающие предпочитают перебазировать или выборочно применять cherry-pick
изменения относительно ветки master
вместо слияния, что позволяет поддерживать историю проекта в линейном виде. Когда проделанная работа из тематической ветки готова к интеграции, вы переходите на эту ветку и перебазируете её относительно ветки master
(или develop
и т. д.). Если конфликты отсутствуют, то вы можете просто сдвинуть состояние ветки master, что обеспечивает линейность истории проекта.
Другим способом переместить предлагаемые изменений из одной ветки в другую является их отбор коммитов cherry-pick
. Отбор в Git похож на перебазирование для одного коммита. В таком случае формируется патч для выбранного коммита и применяется к текущей ветке. Это полезно, когда в тематической ветке присутствует несколько коммитов, а вы хотите взять только один из них, или в тематической ветке только один коммит и вы предпочитаете использовать отбор вместо перебазирования.
Для применения коммита e43a6 к ветке master
выполните команду:
git cherry-pick e43a6
Finished one cherry-pick.
[master]: created a0a41a9: "More friendly message when locking the index fails."
3 files changed, 17 insertions(+), 3 deletions(-)
Это действие применит изменения, содержащиеся в коммите e43a6, но будет сформирован новый коммит с другим значением SHA-1.
Теперь тематическую ветку можно удалить, отбросив коммиты, которые вы не собираетесь включать в проект.
Если вы часто производите перебазирование и слияние или поддерживаете долгоживущие тематические ветки, то в Git есть специальная возможность под названием rerere
, призванная вам помочь.
Rerere означает reuse recorded resolution
(повторно использовать сохранённое решение) — это способ сокращения количества операций ручного разрешения конфликтов. Когда эта опция включена, Git будет сохранять набор образов до и после успешного слияния, а также разрешать конфликты самостоятельно, если аналогичные конфликты уже были разрешены ранее.
Эта возможность реализована как команда и как параметр конфигурации. Параметр конфигурации называется rerere.enabled
, который можно включить глобально следующим образом:
git config --global rerere.enabled true
После этого любое разрешение конфликта слияния будет записано на случай повторного использования.
Если нужно, вы можете обращаться к кэшу rerere
напрямую, используя команду git rerere
. Когда команда вызвана без параметров, Git проверяет базу данных и пытается найти решение для разрешения текущего конфликта слияния (точно так же как и при установленной настройке rerere.enabled
в значение true
).
Существует множество дополнительных команд для просмотра, что именно будет записано, удаления отдельных записей из кэша, а так же его полной очистки. Более детально rerere
будет рассмотрено в разделе Rerere главы 7.
После выпуска релиза, возможно, вы захотите пометить текущее состояние так, чтобы можно было вернуться к нему в любой момент. Для этого можно добавить тег, как было описано в главе Основы Git. Кроме этого, вы можете добавить цифровую подпись для тега, выглядеть это будет вот так:
git tag -s v1.5 -m 'my signed 1.5 tag'
You need a passphrase to unlock the secret key for
user: "Scott Chacon <schacon@gmail.com>"
1024-bit DSA key, ID F721C45A, created 2009-02-09
Если вы используете цифровую подпись при расстановке тегов, то возникает проблема распространения публичной части PGP ключа, использованного при создании подписи. Сопровождающий Git проекта может решить эту проблему добавив в репозиторий свой публичный ключ как бинарный объект и установив ссылающийся на него тег. Чтобы это сделать, выберите нужный ключ из списка доступных, который можно получить с помощью команды gpg --list-keys
:
gpg --list-keys
/Users/schacon/.gnupg/pubring.gpg
---------------------------------
pub uid sub
1024D/F721C45A 2009-02-09 [expires: 2010-02-09]
Scott Chacon <schacon@gmail.com>
2048g/45D02282 2009-02-09 [expires: 2010-02-09]
Затем экспортируйте выбранный ключ и поместите его непосредственно в базу данных Git при помощи команды git hash-object
, которая создаст новый объект с содержимым ключа и вернёт SHA-1 этого объекта:
gpg -a --export F721C45A | git hash-object -w --stdin
659ef797d181633c87ec71ac3f9ba29fe5775b92
Теперь, когда ваш публичный ключ находится в репозитории, можно поставить указывающий на него тег, используя полученное ранее значение SHA-1:
git tag -a maintainer-pgp-pub 659ef797d181633c87ec71ac3f9ba29fe5775b92
Выполнив команду git push --tags
, maintainer-pgp-pub
тег станет общедоступным. Теперь все, кто захочет проверить вашу подпись, могут импортировать ваш публичный ключ, предварительно получив его из репозитория:
git show maintainer-pgp-pub | gpg --import
После этого можно проверять цифровую подпись ваших тегов. Кроме этого, вы можете включить дополнительные инструкции по проверке вашей подписи в сообщение тега, которое будет отображаться каждый раз при вызове команды git show <tag>
Git не использует монотонно возрастающие идентификаторы для коммитов, поэтому если вы хотите получить читаемые имена коммитов, то воспользуйтесь командой git describe
для нужного коммита. Git вернёт имя ближайшего тега, количество коммитов после него и частичное значение SHA-1 для указанного коммита (с префиксом в виде буквы g
— означает Git):
git describe master
v1.6.2-rc1-20-g8c5b85c
Таким образом, вы можете сделать снимок или собрать сборку и дать ей понятное для человека название. К слову, если вы клонируете репозиторий Git и соберете его из исходного кода, то вывод команды git --version
будет примерно таким же. Если попытаться получить имя коммита, которому назначен тег, то результатом будет название самого тега.
По умолчанию, команда git describe
поддерживает только аннотированные теги (созданные с использованием опций -a
или -s
); если вы хотите использовать легковесные (не аннотированные) метки, то укажите команде параметр --tags
. Также это название можно использовать при выполнении команд git checkout
и git show
, но в будущем они могут перестать работать из-за сокращенного значения SHA-1.
К примеру, ядро Linux недавно перешло к использованию 10 символов в SHA-1 вместо 8 чтобы обеспечить уникальность каждого объекта, таким образом предыдущие результаты git describe
стали недействительными.
Время делать релиз сборки. Возможно, вы захотите сделать архив последнего состояния вашего кода для тех, кто не использует Git. Для создания архива выполните команду git archive
:
git archive master --prefix='project/' | gzip > `git describe master`.tar.gz
$ ls *.tar.gz
v1.6.2-rc1-20-g8c5b85c.tar.gz
Открывший этот tarball-архив
пользователь получит последнее состояние кода проекта в каталоге project
. Точно таким же способом можно создать zip-архив, просто добавив опцию --format=zip
для команды git archive
:
git archive master --prefix='project/' --format=zip > `git describe master`.zip
В итоге получим tarball-
и zip-архивы с релизом проекта, которые можно загрузить на сайт или отправить по почте.
Сейчас самое время оповестить людей из списка рассылки, которые хотят знать что происходит с вашим проектом. С помощью команды git shortlog
можно быстро получить список изменений, внесённых в проект с момента последнего релиза или предыдущей рассылки. Она собирает все коммиты в заданном интервале; например, следующая команда выведет список коммитов с момента последнего релиза с названием v1.0.1:
git shortlog --no-merges master --not v1.0.1
Chris Wanstrath (6):
Add support for annotated tags to Grit::Tag
Add packed-refs annotated tag support.
Add Grit::Commit#to_patch
Update version and History.txt
Remove stray `puts`
Make ls_tree ignore nils
Tom Preston-Werner (4):
fix dates in history
dynamic version method
Version bump to 1.0.2
Regenerated gemspec for version 1.0.2
На данный момент вы можете подключаться к репозиториям Git используя протокол https://
авторизуясь при помощи только что созданного логина и пароля.
Если же вы хотите использовать SSH
доступ, в таком случае вам понадобится добавить публичный SSH
ключ. (Если же у вас нет публичного SSH
ключа, вы можете его сгенерировать)
Если вы хотите вносить свой вклад в уже существующие проекты, в которых у нас нет прав на внесения изменений путём отправки push
изменений, вы можете создать своё собственное ответвление fork
проекта. Это означает, что GitHub создаст вашу собственную копию проекта, данная копия будет находиться в вашем пространстве имён и вы сможете легко делать изменения путём отправки push
изменений.
Таким образом, проекты не обеспокоены тем, чтобы пользователи, которые хотели бы выступать в роли соавторов, имели право на внесение изменений путём их отправки push
. Люди просто могут создавать свои собственные ветвления fork
, вносить туда изменения, а затем отправлять свои внесённые изменения в оригинальный репозиторий проекта путём создания запроса на принятие изменений Pull Request
, сами же запросы на принятие изменений Pull Request
будут описаны далее.
Запрос на принятие изменений Pull Request
откроет новую ветвь с обсуждением отправляемого кода, и автор оригинального проекта, а так же другие его участники, могут принимать участие в обсуждении предлагаемых изменений до тех пор, пока автор проекта не будет ими доволен, после чего автор проекта может добавить предлагаемые изменения в проект.
Для того, чтобы создать ответвление проекта, зайдите на страницу проекта и нажмите кнопку «Создать ответвление» Fork
Вы будете перенаправлены на собственную новую проектную страницу, содержащую вашу копию, в которой у вас есть права на запись.
Рабочий процесс GitHub основан на тематических ветках
Вот как это обычно работает:
1. Создайте форк проекта.
2. Создайте тематическую ветку на основании ветки `master`.
3. Создайте один или несколько коммитов с изменениями, улучшающих проект.
4. Отправьте эту ветку в ваш проект на GitHub.
5. Откройте запрос на слияние на GitHub.
6. Обсуждайте его, вносите изменения, если нужно.
7. Владелец проекта принимает решение о принятии изменений, либо об их отклонении.
8. Получите обновлённую ветку `master` и отправьте её в свой форк.
Тони ищет, чего бы запустить на своём новеньком Arduino. Кажется, он нашёл классный пример на https://github.com/schacon/blink.
Для начала, нажмите кнопку Fork
, как было сказано выше, чтобы заполучить собственную копию проекта. Мы зарегистрированы на GitHub под именем tonychacon
, так что наша копия окажется по адресу https://github.com/tonychacon/blink, где мы сможем редактировать её. Мы клонируем его, создадим тематическую ветку, внесём необходимые изменения и, наконец, отправим их на GitHub.
$ git clone https://github.com/tonychacon/blink (1) Cloning into 'blink'...
$ cd blink
$ git checkout -b slow-blink (2) Switched to a new branch 'slow-blink'
$ sed -i '' 's/1000/3000/' blink.ino (macOS) (3) # If you're on a Linux system, do this instead: # $ sed -i 's/1000/3000/' blink.ino (3)
$ git diff --word-diff (4)
diff --git a/blink.ino b/blink.ino
index 15b9911..a6cc5a5 100644
--- a/blink.ino
+++ b/blink.ino
@@ -18,7 +18,7 @@ void setup() {
// the loop routine runs over and over again forever: void loop() {
digitalWrite(led, HIGH); // turn the LED on (HIGH is the voltage level)
[-delay(1000);-]{+delay(3000);+} // wait for a second
digitalWrite(led, LOW); // turn the LED off by making the voltage LOW
[-delay(1000);-]{+delay(3000);+} // wait for a second
}
$ git commit -a -m 'Change delay to 3 seconds' (5) [slow-blink 5ca509d] Change delay to 3 seconds
1 file changed, 2 insertions(+), 2 deletions(-)
$ git push origin slow-blink (6)
Username for 'https://github.com': tonychacon
Password for 'https://tonychacon@github.com':
Counting objects: 5, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 340 bytes | 0 bytes/s, done. Total 3 (delta 1), reused 0 (delta 0)
To https://github.com/tonychacon/blink
* [new branch] slow-blink -> slow-blink
- Клонируем нашу копию
- Создаём тематическую ветку
- Вносим свои изменения
- Проверяем изменения
- Фиксируем изменения в тематической ветку
- Отправляем новую ветку в нашу копию на GitHub
Теперь, если мы зайдём на страничку нашей копии на GitHub, мы увидим, что GitHub заметил наши изменения и предлагает открыть запрос на слияние с помощью большой зелёной кнопки.
Также можно зайти на страницу Branches
, по адресу https://github.com/<user>/<project>/branches
, найти интересующую ветку и открыть запрос оттуда.
Мы увидим список коммитов в нашей тематической ветке, «опередивших» ветку master
(в данном случае всего один коммит) и предпросмотр всех изменений, вносимых этими коммитами.
После создания запроса на слияние (путём нажатия кнопки Create pull request
на этой странице) владелец форкнутого проекта получит уведомление о предложенных изменениях со ссылкой на страницу с информацией о запросе.
На этом этапе, владелец проекта может просмотреть предложенные изменения, принять, отклонить или прокомментировать их.
Владелец проекта может просмотреть суммарные изменения, вносимые запросом, и прокомментировать любую отдельно взятую строку.
Как только владелец прокомментирует изменения, автор запроса на слияние (а также все подписавшиеся на этот репозиторий) получат уведомления.
GitHub так же проверяет может ли запрос на слияние быть применён без конфликтов и предоставляет кнопку для осуществления слияния на сервере. Эта кнопка отображается только если у вас есть права на запись в репозиторий и возможно простейшее слияние. По нажатию на неё GitHub произведёт non-fast-forward
слияние, что значит даже если слияние может быть осуществлено перемоткой вперед, всё равно будет создан коммит слияния.
Большинство проектов на GitHub понимают ветки запросов на слияние как беседу относительно предлагаемого изменения, завершающуюся слиянием унифицированных изменений.
Это важное различие, так как изменение предлагается до того, как код станет считаться идеальным, что гораздо реже происходит с распространяемыми наборами патчей через списки рассылок. Обсуждение происходит на более раннем этапе и выработка правильного решения происходит за счёт усилий сообщества. Когда код предлагается через запрос на слияние и сопровождающий проекта или сообщество предлагает изменения, то не применяется набор патчей, а отправляются результирующие изменения как новый коммит в ветку, двигая обсуждение вперёд и сохраняя уже проделанную работу нетронутой.
Если ваш запрос на слияние устарел или не может быть слит без конфликтов, то вам нужно изменить его, чтобы сопровождающий мог просто его слить. GitHub проверит это за вас и под каждым из запросов на слияние отобразит уведомление, можно ли его слить без конфликтов или нет.
Если вы видите что-то вроде Запрос имеет конфликты слияния
, то вам следует изменить
свою ветку так, чтобы исключить конфликты и сопровождающий не делал лишнюю работу.
Существует два основных варианта это сделать. Вы можете либо перебазировать свою ветку относительно целевой ветки (обычно, относительно master
ветки исходного репозитория), либо слить целевую ветку в свою.
Большинство разработчиков на GitHub выбирают последний вариант по тем же причинам, что и мы в предыдущем разделе. Важна история и окончательное слияние, а перебазирование не принесёт вам ничего, кроме немного более чистой истории, при этом оно гораздо сложнее и может стать источником ошибок.
Если вы хотите сделать запрос на слияние применяемым, то следует добавить исходный репозиторий как новый удалённый, слить изменения из его основной ветки в вашу тематическую, если имеются исправить все проблемы и, наконец, отправить все изменения в ту ветку, на основании которой был открыт запрос на слияние.
Предположим, что в примере tonychacon
, который мы использовали ранее, основной автор сделал изменения, которые конфликтуют с запросом на слияние. Рассмотрим это пошагово.
$ git remote add upstream https://github.com/schacon/blink (1)
$ git fetch upstream (2)
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (3/3), done. Unpacking objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0) From https://github.com/schacon/blink
* [new branch] master -> upstream/master
$ git merge upstream/master (3)
Auto-merging blink.ino
CONFLICT (content): Merge conflict in blink.ino
Automatic merge failed; fix conflicts and then commit the result.
$ vim blink.ino (4)
$ git add blink.ino
$ git commit
[slow-blink 3c8d735] Merge remote-tracking branch 'upstream/master' \
into slower-blink
$ git push origin slow-blink (5)
Counting objects: 6, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (6/6), 682 bytes | 0 bytes/s, done. Total 6 (delta 2), reused 0 (delta 0)
To https://github.com/tonychacon/blink
ef4725c..3c8d735 slower-blink -> slow-blink
- Добавляем исходный репозиторий как удалённый с именем
upstream
. - Получаем последние изменения из него.
- Сливаем основную ветку в нашу тематическую.
- Исправляем указанный конфликт.
- Отправляем изменения в ту же тематическую ветку.
Как только это будет сделано, запрос на слияние будет автоматически обновлён и перепроверен на возможность слияния.
Одна из замечательных особенностей Git - это то, что вы можете делать это постоянно. Если у вас очень длительный проект, вы можете легко сливать изменения из целевой ветки снова и снова и иметь дело только с конфликтами, возникшими с момента вашего последнего слияния, что делает процесс очень управляемым.
Если вы очень хотите перебазировать ветку, чтобы её почистить, то, конечно, вы можете это сделать, но настоятельно не рекомендуется переписывать ветку, к которой уже открыт запрос на слияние. Если другие люди уже стянули её и проделали много работы, то вы столкнётесь со всеми проблемами, описанными в разделе Опасности перемещения главы 3
. Вместо этого, отправьте перебазированную ветку в новую на GiHub и откройте новый запрос на слияние, который указывает на предыдущий, затем закройте исходный.
Возможно, ваш следующий вопрос будет: «Как мне сослаться на предыдущий запрос слияния?»
Оказывается, существует много способов ссылаться на другие вещи практически везде, где у вас есть права записи на GitHub.
Давайте начнём с перекрёстных ссылок для запросов слияния или проблем. Всем запросам слияния и проблемам присваиваются уникальные номера в пределах проекта. Например, у вас не может быть запроса на слияние с номером #3
и проблемы с номером #3
. Если вы хотите сослаться на любой запрос слияния или проблему из другого места, просто добавьте #<num>
в комментарий или описание.
Так же можно указывать более конкретно, если проблема или запрос слияния находятся где-то ещё; пишите username#<num>
если ссылаетесь на проблему или запрос слияния, находящиеся в ответвлённом репозитории, или username/repo#<num>
если ссылаетесь на другой репозиторий. Ссылки будут подсвечиваться.
Кроме идентификационных номеров, можно ссылаться на конкретный коммит используя SHA-1
. Следует указывать полный 40 символьный хеш SHA-1
, но если GitHub увидит его в комментарии, то автоматически подставит ссылку на коммит. Как было сказано выше, вы можете ссылаться на коммиты как в других, так и в ответвлённых репозиториях точно так же, как делали это с Проблемами.
Ссылки на другие Проблемы — это лишь часть интереснейших вещей, которые вы можете делать в текстовых полях на GitHub. Для проблемы
или запроса слияния
в полях описания, комментария, комментария кода и других вы можете использовать так называемую GitHub-версию разметки Markdown
.
Список задач — это первая действительно важная возможность специфической разметки GitHub, особенно для запросов слияния. Список задач представляет собой список флажков для задач, которые вы хотите выполнить. Размещение его в описании Проблемы или запроса на слияние обычно указывает на то, что должно быть сделано до того, как проблема будет считаться решённой.
Список задач можно добавить следующим образом:
- [X] Write the code
- [ ] Write all the tests
- [ ] Document the code
В комментарии так же можно вставлять отрывки кода.
Для добавления отрывка кода следует обрамить его обратными кавычками.
for(int i=0 ; i < 5 ; i++)
{
System.out.println("i is : " + i);
}
Если вы укажете название языка, как показано на примере, GitHub попробует применить к нему подсветку синтаксиса.
Если вы отвечаете только на часть большого комментария, то можно цитировать только выбранную часть, предваряя её символом >
. Это настолько часто используется, что даже существует комбинация клавиш для этого. Если в комментарии выделить текст, на который вы собираетесь ответить, и нажать клавишу r
, то выделенный текст будет включён как цитата в ваш комментарий.
> Whether 'tis Nobler in the mind to suffer
> The Slings and Arrows of outrageous Fortune,
How big are these slings and in particular, these arrows?
Технически, картинки не относятся к разметке GitHub, но их использование очень полезно. В дополнение к ссылкам на картинки в комментариях, GitHub позволяет встраивать картинки в комментарии. При добавлении картинки в комментарий она автоматически загружается на облако git и будет видна и доступна по отдельной ссылке.
После создания форка, ваш репозиторий будет существовать независимо от оригинального репозитория. В частности, при появлении в оригинальном репозитории новых коммитов GitHub информирует вас следующим сообщением:
This branch is 5 commits behind progit:master.
При этом GitHub никогда не обновляет ваш репозиторий — это вы должны делать сами. К счастью, это очень просто сделать.
Первый способ не требует конфигурации. Например, если вы сделали форк репозитория https://github.com/progit/progit2.git, то актуализировать ветку master
можно следующим образом:
$ git checkout master (1)
$ git pull https://github.com/progit/progit2.git (2)
$ git push origin master (3)
- Если вы находитесь на другой ветке — перейти на ветку
master
. - Получить изменения из репозитория https://github.com/progit/progit2.git и слить их с
веткой
master
. - Отправить локальную ветку
master
в ваш форкorigin
.
Каждый раз писать URL репозитория для получения изменений достаточно утомительно. Этот процесс можно автоматизировать слегка изменив настройки:
$ git remote add progit https://github.com/progit/progit2.git (1)
$ git fetch progit (2)
$ git branch --set-upstream-to=progit/master master (3)
$ git config --local remote.pushDefault origin 4
- Добавить исходный репозиторий как удалённый и назвать его
progit
. - Получить ветки репозитория
progit
, в частности веткуmaster
. - Настроить локальную ветку
master
на получение изменений из репозиторияprogit
. - Установить
origin
как репозиторий по умолчанию для отправки.
После этого, процесс обновления становится гораздо проще:
$ git checkout master 1 $ git pull 2
$ git push 3
- Если вы находитесь на другой ветке — перейти на ветку
master
. - Получить изменения из репозитория
progit
и слить их с веткойmaster
. - Отправить локальную ветку
master
в ваш форкorigin
.
Данный подход не лишён недостатков. Git будет молча выполнять указанные действия и не предупредит вас в случае, когда вы добавили коммит в master
, получили изменения из progit
и отправили всё вместе в origin
— все эти операции абсолютно корректны. Поэтому вам стоит исключить прямое добавление коммитов в ветку master
, поскольку эта ветка фактически принадлежит другому репозиторию.
Все проекты на GitHub доступны как по HTTP https://github.com/<user>/<project_name>
, так по SSH git@github.com:<user>/<project_name>
Обычно, для общедоступного проекта предпочтительнее использовать HTTPS
ссылки, так как это не требует наличия GitHub аккаунта для клонирования репозитория. При этом, для использования SSH
ссылки у пользователя должен быть GitHub аккаунт и его SSH
ключ должен быть добавлен в ваш проект. Так же HTTPS
ссылка полностью совпадает с URL
адресом, который пользователи могут вставить в браузер для просмотра вашего репозитория.
Если вы работаете с другими людьми, которым вы хотите предоставить доступ для отправки коммитов, то вам следует добавить их как «участников».
Это предоставит им push
доступ; это означает, что они будут иметь права доступа как на чтение, так и на запись в проект и Git репозиторий.
Перейдите по ссылке Settings
Затем выберите Collaborators
в меню слева. Напишите имя пользователя в поле для ввода и нажмите кнопку Add collaborator
.
Сейчас у вас есть проект с некоторым кодом и, возможно, несколько участников с push
доступом, давайте рассмотрим ситуацию, когда вы получаете запрос на слияние.
Запрос на слияние может быть как из ветки вашего репозитория, так и из ветки форка вашего проекта. Отличаются они тем, что вы не можете отправлять изменения в ветки ответвлённого проекта, а его владельцы не могут отправлять в ваши, при этом для внутренних запросов на слияние характерно наличие доступа к ветке у обоих пользователей.
Для последующих примеров предположим, что вы tonychacon
и создали новый проект для Arduino с названием fade
.
Email уведомления Кто-то вносит изменения в ваш код и отправляет вам запрос на слияние. Вы должны получить письмо с уведомлением о новом запросе слияния, которое выглядит как на Email уведомление о новом запросе слияния.
Если вы видите строку с текстом git pull <url> patch-1
, то это самый простой способ слить удалённую ветку без добавления удалённого репозитория. Это кратко описывалось в Извлечение удалённых веток. Если хотите, то можно сначала переключиться в тематическую ветку и только потом выполнить эту команду для изменений запроса слияния.
Когда вы готовы слить код, вы можете стянуть его себе и слить локально, слить используя команду git pull <url> <branch>
, которую мы видели ранее, или добавив ответвлённый репозиторий как удалённый получить и слить изменения.
Если слияние тривиально, то можно просто нажать кнопку Merge
на сайте GitHub. Это всегда приводит с созданию коммита слияния, даже если доступно слияние перемоткой вперёд. Это значит, что в любом случае создаётся коммит слияния, как только вы нажимаете кнопку Merge
.
Если вы решаете не сливать запрос, то вы можете просто закрыть запрос на слияние, а открывший его участник будет уведомлен.
Если у вас много запросов слияния и вы не хотите добавлять пачку удалённых репозиториев или постоянно делать однократный pull
, то у GitHub есть хитрый трюк, позволяющий это делать. Этот трюк очень сложный, но полезный и мы рассмотрим его немного позже в Спецификации ссылок.
Фактически, GitHub представляет ветки запросов слияния как псевдо ветки на сервере. По умолчанию, они не копируются при клонировании, а существуют в замаскированном виде и вы можете легко получить доступ к ним.
В качестве примера мы используем низкоуровневую команду ls-remote
(часто упоминается как plumbing
команда). Обычно, эта команда не используется в повседневных Git операциях, но сейчас поможет нам увидеть какие ссылки присутствуют на сервере.
Если выполнить её относительно использованного ранее репозитория blink
, мы получим список всех веток, тегов и прочих ссылок в репозитории.
$ git ls-remote https://github.com/schacon/blink
10d539600d86723087810ec636870a504f4fee4d HEAD
10d539600d86723087810ec636870a504f4fee4d refs/heads/master
6a83107c62950be9453aac297bb0193fd743cd6e refs/pull/1/head
afe83c2d1a70674c9505cc1d8b7d380d5e076ed3 refs/pull/1/merge
3c8d735ee16296c242be7a9742ebfbc2665adec1 refs/pull/2/head
Аналогично, если вы, находясь в своём репозитории, выполните команду git ls-remote origin
или укажете любой другой удалённый репозиторий, то результат будет схожим.
Если репозиторий находится на GitHub и существуют открытые запросы слияния, то эти ссылки будут отображены с префиксами refs/pull/
. По сути это ветки, но так как они находятся не в refs/heads/
, то они не копируются при клонировании или получении изменений с сервера — процесс получения изменений игнорирует их по умолчанию.
Для каждого запроса слияния существует две ссылки, одна из которых записана в /head
и указывает на последний коммит в ветке запроса на слияние. Таким образом, если кто-то открывает запрос на слияние в наш репозиторий из своей ветки bug-fix
, которая указывает на коммит a5a775
, то в нашем репозитории не будет ветки bug-fix
(так как она находится в форке), при этом у нас появится pull/<pr#>/head
, которая указывает на a5a775
. Это означает, что мы можем стянуть все ветки запросов слияния одной командой не добавляя набор удалённых репозиториев.
Теперь можно получить ссылки напрямую.
$ git fetch origin refs/pull/958/head
From https://github.com/libgit2/libgit2
* branch refs/pull/958/head -> FETCH_HEAD
Эта команда указывает Git: «Подключись к origin
репозиторию и скачай ссылку refs/pull/958/head
». Git с радостью слушается и выкачивает всё необходимое для построения указанной ссылки, а так же устанавливает указатель на коммит в .git/FETCH_HEAD
.
Далее, вы можете слить изменения в нужную ветку при помощи команды git merge FETCH_HEAD
, однако сообщение коммита слияния будет выглядеть немного странно. Так же это становится утомительным, если вы просматриваете много запросов на слияние.
Существует способ получать все запросы слияния и поддерживать их в актуальном состоянии при подключении к удалённому репозиторию. Откройте файл .git/config
в текстовом редакторе и обратите внимание на секцию удалённого репозитория origin
. Она должна выглядеть как-то так:
[remote "origin"]
url = https://github.com/libgit2/libgit2
fetch = +refs/heads/*:refs/remotes/origin/*
Строка, начинающаяся с fetch =
, является спецификацией ссылок refspec
. Это способ сопоставить названия в удалённом репозитории и названиями в локальном каталоге .git
. Конкретно эта строка говорит Git: «все объекты удалённого репозитория из refs/heads
должны сохраняться локально в refs/remotes/origin
. Вы можете изменить это поведение добавив ещё одну строку спецификации:
[remote "origin"]
url = https://github.com/libgit2/libgit2.git
fetch = +refs/heads/*:refs/remotes/origin/*
fetch = +refs/pull/*/head:refs/remotes/origin/pr/*
Последняя строка говорит Git: «Все ссылки, похожие на refs/pull/123/head
, должны быть сохранены локально как refs/remotes/origin/pr/123
». Теперь, если сохранить файл и выполнить команду git fetch
, вы получите:
$ git fetch #...
* [new ref] refs/pull/1/head -> origin/pr/1
* [new ref] refs/pull/2/head -> origin/pr/2
* [new ref] refs/pull/4/head -> origin/pr/4
#...
Все запросы слияния из удалённого репозитория представлены в локальном репозитории как ветки слежения; они только для чтения и обновляются каждый раз при выполнении git fetch
. Таким образом, локальное тестирование кода запроса слияния становится очень простым:
$ git checkout pr/2
Checking out files: 100% (3769/3769), done.
Branch pr/2 set up to track remote branch pr/2 from origin.
Switched to a new branch 'pr/2'
Вы можете открыть запрос слияния не только в ветку master, запросы слияния могут указывать на любую ветку любого репозитория в сети. По сути, вы можете даже открыть запрос слияния, указывающий на другой запрос слияния.
Если вы видите толковый запрос слияния и у вас есть идея как его улучшить или вы не уверены, что это хорошая идея, или у вас просто нет прав записи в целевую ветку, то в таком случае вы можете открыть запрос слияния, указывающий на данный запрос.
При открытии запроса на слияние вверху страницы вы увидите меню для выбора целевой и исходной веток. Если нажать кнопку Edit справа, то станет доступным выбор не только исходной ветки, а ещё и форка.
Здесь можно указать вашу новую ветку для слияния с другим запросом слияния или другим форком проекта.
В любом комментарии можно написать символ @
, что автоматически вызовет список автодополнения с именами пользователей, которые включены в проект или просто участвуют в нём.
Так же можно упомянуть пользователя, не указанного в выпадающем списке, но с помощью автодополнения это можно сделать быстрее. Как только вы оставите комментарий с упоминанием пользователя, ему будет отправлено уведомление.
Если GitHub увидит такой файл в вашем исходном коде, то отобразит его на заглавной странице проекта.
Большинство команд используют его для поддержания актуальной информации о проекте для новичков. Как правило, он включает следующее: • Для чего предназначен проект • Инструкции по конфигурации и установке • Примеры использования • Используемую лицензию
• Правила участия в проекте В этот файл можно встраивать изображения или ссылки для простоты восприятия информации.
Следующий файл - это CONTRIBUTING
. Если в вашем репозитории будет файл CONTRIBUTING
с любым расширением, то GitHub будет показывать ссылку на него при создании любого запроса на слияние.
Идея состоит в том, что вы можете указать конкретные вещи, которые вы хотите или не хотите видеть в новых запросах на слияние. Таким образом люди могут ознакомится с руководством, перед тем как создавать новый запрос на слияние.
Если вы используете в качестве основной другую ветку, отличную от master
, и хотите, чтобы пользователи открывали запросы на слияние к ней, то это можно изменить в настройках репозитория на закладке Options
.
Просто выберите нужную ветку из выпадающего меню и она станет основной для большинства операций, включая извлечение кода при клонировании репозитория.
Если вы хотите передать проект другому пользователю или организации на GitHub, то это можно сделать нажатием кнопки Transfer ownership
в настройках репозитория на закладке Options
.
Эта опция полезна, когда вы хотите отказаться от проекта, а кто-то другой хочет им заниматься, или когда ваш проект растёт и вы хотите передать его какой-нибудь организации.
Это действие приведёт не только к передаче репозитория со всеми его подписчиками и звёздами, но и добавит перенаправление с вашего URL на новый. Кроме этого, изменятся ссылки для клонирования и получения изменений из Git, а не только для веб запросов.
Создать новую организацию очень легко; просто нажмите на иконку +
в правом верхнем углу страницы GitHub и выберите пункт New organization
из меню.
Организации связаны с отдельными людьми посредством команд, которые представляют собой сгруппированные аккаунты индивидуальных пользователей, репозиториев внутри организации и того, какой доступ эти люди имеют в этих репозиториях.
Например, у вашей компании есть три репозитория: frontend
, backend
и deployscripts
. Вы бы хотели, чтобы ваши разработчики HTML/CSS/JavaScript имели доступ к frontend
и возможно к backend
, а ваши администраторы имели доступ к backend
и deployscripts
. С помощью команд это легко реализовать не настраивая доступ к каждому репозиторию для каждого участника.
Для управления командами нужно перейти на закладку Teams
справа вверху на странице Страница организации. Это приведёт вас на страницу где можно добавлять пользователей в команду, добавлять команде репозитории или управлять настройками и правами доступа.
Каждая команда может иметь только следующие уровни доступа к репозиториям: «только чтение», «чтение/запись» или «администратор». Уровень доступа может быть изменен нажатием кнопки Settings
на странице Страница команды.
Упоминания команд @mentions
, такие как @acmecorp/frontend
, работают точно так же как и упоминания отдельных пользователей, за исключением того, что уведомляются все члены команды.
Организации так же предоставляют владельцам информацию о всех происходящих событиях внутри них. Перейдя на закладку Audit Log
вы можете увидеть произошедшие события на уровне организации, кто участвовал в них.
Они при повседневной работе вам, наверное, не потребуются, но в какой-то момент могут оказаться полезными.
Выбор ревизии Git позволяет различными способами указать коммиты или их диапазоны. Эти способы не всегда очевидны, но их полезно знать.
Одиночные ревизии
Конечно, вы можете ссылаться на коммит по его SHA-1
хешу
Git достаточно умен, чтобы понять какой коммит имеется ввиду по нескольким первым символам его хеша, если указанная часть SHA-1
имеет в длину по крайней мере четыре символа и однозначна — то есть в текущем репозитории существует только один объект с таким частичным SHA-1
.
Например, предположим, чтобы найти некоторый коммит, вы выполнили команду git log
и нашли коммит, в которой добавили определённую функциональность:
$ git log
commit 1c002dd4b536e7479fe34593e72e6c6c1819e53b
Author: Scott Chacon <schacon@gmail.com>
Date: Thu Dec 11 14:58:32 2008 -0800
Add some blame and merge stuff
Предположим, что в нашем примере это коммит 1c002dd.....
Если вы хотите выполнить для него git show
, то следующие команды эквиваленты (предполагается, что сокращения однозначны):
$ git show 1c002dd4b536e7479fe34593e72e6c6c1819e53b
$ git show 1c002dd4b536e7479f
$ git show 1c002d
Git может вычислить уникальные сокращения для ваших значений SHA-1
. Если вы передадите опцию --abbrev-commit
команде git log
, в выводе будут использоваться сокращённые значения, сохраняющие уникальность; по умолчанию используется семь символов, но для сохранения уникальности SHA-1
могут использоваться более длинные значения.
$ git log --abbrev-commit --pretty=oneline
ca82a6d Change the version number
085bb3b Remove unnecessary test code
a11bef0 Initial commit
Теперь вы умеете указывать отдельные коммиты, давайте посмотрим как указывать диапазоны коммитов. Это в частности полезно для управления вашими ветками — если у вас есть множество веток, вы можете использовать указание диапазонов коммитов для ответа на вопрос «Что было сделано в этой ветке, что я ещё не слил в основную ветку?»
Наиболее часто для указания диапазона коммитов используется синтаксис с двумя точками. Таким образом, вы, по сути, просите Git включить в диапазон коммитов только те, которые достижимы из одной, но не достижимы из другой.
Вы хотите посмотреть что находится в вашей экспериментальной ветке, которая ещё не была слита в основную. Вы можете попросить Git отобразить в логе только такие коммиты, используя запись master..experiment
— она означает «все коммиты, которые доступны из ветки experiment
, но не доступны из ветки master
». Для краткости и наглядности в этих примерах вместо настоящего вывода лога мы будем использовать для коммитов их буквенные обозначения из диаграммы, располагая их в должном порядке:
$ git log master..experiment
D
C
С другой стороны, если вы хотите наоборот увидеть все коммиты ветки master
, которых нет в ветке experiment
, вы можете поменять имена веток в команде. При использовании записи experiment..master
будут отображены все коммиты ветки master
, недоступные из ветки experiment
:
$ git log experiment..master
F
E
Это полезно если вы хотите сохранить ветку experiment
в актуальном состоянии и просмотреть, какие изменения нужно в нее слить. Другое частое использование такого синтаксиса — просмотр того, что будет отправлено в удалённый репозиторий.
$ git log origin/master..HEAD
Такая команда покажет вам все коммиты вашей текущей ветки, которые отсутствуют в ветке master
удалённого репозитория origin
. Если вы выполните git push
, находясь на ветке, отслеживающей origin/master
, то коммиты, отображённые командой git log origin/master..HEAD
, будут теми коммитами, которые отправятся на сервер.
Запись с двумя точками полезна как сокращение, но, возможно, вы захотите использовать более двух веток для указания нужной ревизии, например, для того, чтобы узнать какие коммиты присутствуют в любой из нескольких веток, но отсутствуют в ветке, в которой вы сейчас находитесь. Git позволяет сделать это, используя символ ^
или опцию --not
, перед любой ссылкой, доступные коммиты из которой вы не хотите видеть. Таким образом, следующие три команды эквивалентны:
$ git log refA..refB
$ git log ^refA refB
$ git log refB --not refA
Этот синтаксис удобен, так как позволяет указывать в запросе более двух ссылок, чего не позволяет сделать синтаксис с двумя точками. Например, если вы хотите увидеть все коммиты, доступные из refA
и refB
, но не доступные из refC
, вы можете использовать одну из следующих команд:
$ git log refA refB ^refC
$ git log refA refB --not refC
Последний основной способ выбора ревизий — это синтаксис с тремя точками, который обозначает все коммиты, доступные хотя бы из одной ссылки, но не из обеих сразу. Вспомните пример истории коммитов в Пример истории для выбора диапазонов коммитов. Если вы хотите узнать какие коммиты есть либо в ветке master
, либо в experiment
, но не в обеих сразу, вы можете выполнить:
$ git log master...experiment
F
E
D
C
Эта команда снова выводит обычный журнал коммитов, но в нем содержится информация только об этих четырёх коммитах, традиционно отсортированная по дате коммитов.
В таких случаях с командой log
часто используют опцию --left-right
, которая отображает сторону диапазона, с которой был сделан каждый из коммитов. Это делает данную информацию более полезной:
$ git log --left-right master...experiment
<F
<E
>D
>C
Часто пока вы работаете над одной частью вашего проекта и всё находится в беспорядке, у вас возникает желание сменить ветку и поработать над чем-то ещё. Сложность при этом заключается в том, что вы не хотите фиксировать наполовину сделанную работу только для того, чтобы иметь возможность вернуться к ней позже. Справиться с ней помогает команда git stash
.
Операция stash
берет изменённое состояние вашего рабочего каталога, то есть изменённые отслеживаемые файлы и проиндексированные изменения, и сохраняет их в хранилище незавершённых изменений, которые вы можете в любое время применить обратно.
В конце октября 2017 года в списке рассылки Git проходило обширное обсуждение, по итогам которого команда git stash save
признана устаревшей в пользу существующей альтернативы git stash push
. Основная причина этого заключается в том, что в git stash push
есть возможность сохранить выбранные спецификации пути, что не поддерживает git stash save
.
Команда git stash save
не исчезнет в ближайшее время, поэтому не беспокойтесь о её внезапной пропаже. Но вы можете начать переход на push
для использования новой функциональности.
Для примера, предположим, что вы перешли в свой проект, начали работать над несколькими файлами и, возможно, добавили в индекс изменения одного из них. Если вы выполните git status
, то увидите ваше изменённое состояние:
$ git status
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: index.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: lib/simplegit.rb
Теперь вы хотите сменить ветку, но пока не хотите фиксировать ваши текущие наработки; поэтому вы припрячете эти изменения. Для того, чтобы припрятать изменение в выделенное для этого специальное хранилище, выполните git stash
или git stash push
:
$ git stash
Saved working directory and index state \
"WIP on master: 049d078 Create index file"
HEAD is now at 049d078 Create index file
(To restore them type "git stash apply")
Теперь вы можете увидеть, что рабочая копия не содержит изменений:
$ git status
# On branch master
nothing to commit, working directory clean
В данный момент вы можете легко переключать ветки и работать в любой; ваши изменения сохранены. Чтобы посмотреть список припрятанных изменений, вы можете использовать git stash list
:
$ git stash list
stash@{0}: WIP on master: 049d078 Create index file
stash@{1}: WIP on master: c264051 Revert "Add file_size"
stash@{2}: WIP on master: 21d80a5 Add number to log
В данном примере, предварительно были припрятаны два изменения, поэтому теперь вам доступны три различных отложенных наработки. Вы можете применить только что припрятанные изменения, используя команду, указанную в выводе исходной команды: git stash apply
.
Если вы хотите применить одно из предыдущих припрятанных изменений, вы можете сделать это, используя его имя, вот так: git stash apply stash@{2}
. Если вы не укажете имя, то Git попытается восстановить самое последнее припрятанное изменение:
$ git stash apply
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: index.html
modified: lib/simplegit.rb
no changes added to commit (use "git add" and/or "git commit -a")
Как видите, Git восстановил в файлах изменения, которые вы отменили ранее, когда прятали свои наработки. В данном случае при применении отложенных наработок ваш рабочий каталог был без изменений, а вы пытались применить их в той же ветке, в которой вы их и сохранили; но отсутствие изменений в рабочем каталоге и применение их в той же ветке не являются необходимыми условиями для успешного восстановления припрятанных наработок.
Вы можете припрятать изменения, находясь в одной ветке, а затем переключиться на другую и попробовать восстановить эти изменения. Также при восстановлении припрятанных наработок в вашем рабочем каталоге могут присутствовать изменённые и незафиксированные файлы — Git выдаст конфликты слияния, если не сможет восстановить какие-то наработки.
Спрятанные изменения будут применены к вашим файлам, но файлы, которые вы ранее добавляли в индекс, не будут добавлены туда снова. Для того, чтобы это было сделано, вы должны запустить git stash apply
с опцией --index
, при которой команда попытается восстановить изменения в индексе. Если вы выполните команду таким образом, то полностью восстановите ваше исходное состояние:
$ git stash apply --index
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: index.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: lib/simplegit.rb
Команда apply
только пытается восстановить припрятанные наработки — при этом они останутся в хранилище. Для того, чтобы удалить их, вы можете выполнить git stash drop
, указав имя удаляемых изменений:
$ git stash list
stash@{0}: WIP on master: 049d078 Create index file
stash@{1}: WIP on master: c264051 Revert "Add file_size"
stash@{2}: WIP on master: 21d80a5 Add number to log
$ git stash drop stash@{0}
Dropped stash@{0} (364e91f3f268f0900bc3ee613f9f733e82aaed43)
Вы также можете выполнить git stash pop
, чтобы применить припрятанные изменения и тут же удалить их из хранилища.
У припрятанных изменений есть несколько дополнительных вариантов использования, которые также могут быть полезны. Первый — это использование довольно популярной опции --keep-index
с командой git stash
. Она просит Git не только припрятать то, что вы уже добавили в индекс, но одновременно оставить это в индексе.
$ git status -s
M index.html
M lib/simplegit.rb
$ git stash --keep-index
Saved working directory and index state WIP on master: 1b65b17 added the index file
HEAD is now at 1b65b17 added the index file
$ git status -s
M index.html
Другой распространённый вариант, который вы, возможно, захотите использовать — это припрятать помимо отслеживаемых файлов также и неотслеживаемые. По умолчанию git stash
будет сохранять только изменённые и проиндексированные отслеживаемые файлы. Если вы укажете опцию --include-untracked
или -u
, Git также припрячет все неотслеживаемые файлы, которые вы создали. Однако включение этой опции по-прежнему не будет прятать файлы с явным игнорированием; чтобы дополнительно припрятать игнорируемые файлы, используйте --all
(или просто -a
).
$ git status -s
M index.html
M lib/simplegit.rb
?? new-file.txt
$ git stash -u
Saved working directory and index state WIP on master: 1b65b17 added the index file
HEAD is now at 1b65b17 added the index file
$ git status -s
Если вы спрятали некоторые изменения, оставили их на время, а сами продолжили работать в той же ветке, у вас могут возникнуть проблемы с восстановлением наработок. Если восстановление будет затрагивать файл, который уже был изменён с момента сохранения наработок, то вы получите конфликт слияния и должны будете попытаться разрешить его.
Если вам нужен более простой способ снова протестировать припрятанные изменения, вы можете выполнить команду git stash branch
, которая создаст для вас новую ветку, перейдёт на коммит, на котором вы были, когда прятали свои наработки, применит на нём эти наработки и затем, если они применились успешно, удалит эти припрятанные изменения:
$ git stash branch testchanges
M index.html
M lib/simplegit.rb
Switched to a new branch 'testchanges'
On branch testchanges
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: index.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: lib/simplegit.rb
Dropped refs/stash@{0} (29d385a81d163dfd45a452a2ce816487a6b8b014)
Это удобное сокращение для того, чтобы легко восстановить припрятанные изменения и поработать над ними в новой ветке.
Наконец, у вас может возникнуть желание не прятать некоторые из изменений или файлов в вашем рабочем каталоге, а просто избавиться от них. Команда git clean
сделает это для вас.
Одной из распространённых причин для этого может быть удаление мусора, который был сгенерирован при слиянии или внешними утилитами, или удаление артефактов сборки в процессе её очистки. Вам нужно быть очень аккуратными с этой командой, так как она предназначена для удаления неотслеживаемых файлов из вашего рабочего каталога.
Даже если вы передумаете, очень часто нельзя восстановить содержимое таких файлов. Более безопасным вариантом является использование команды git stash --all
для удаления всего, но с сохранением этого в виде припрятанных изменений.
Предположим, вы хотите удалить мусор и очистить ваш рабочий каталог; вы можете сделать это с помощью git clean
. Для удаления всех неотслеживаемых файлов в вашем рабочем каталоге, вы можете выполнить команду git clean -f -d
, которая удалит все файлы и также все каталоги, которые в результате станут пустыми.
Параметр -f
(сокращение от слова force
— заставить) означает принудительное удаление, подчеркивая, что вы действительно хотите это сделать, и требуется, если переменная конфигурации Git clean.requireForce
явным образом не установлена в false
.
Если вы хотите только посмотреть, что будет сделано, вы можете запустить команду с опцией -n
, которая означает имитируй работу команды и скажи мне, что ты будешь удалять.
$ git clean -d -n
Would remove test.o
Would remove tmp/
По умолчанию команда git clean
будет удалять только неотслеживаемые файлы, которые не добавлены в список игнорируемых.
Если вы не знаете, что сделает при запуске команда git clean
, всегда сначала выполняйте её с опцией -n
, чтобы проверить дважды, перед заменой -n
на -f
и выполнением настоящей очистки. Другой способ, который позволяет вам более тщательно контролировать сам процесс — это выполнение команды с опцией -i
(в «интерактивном» режиме).
Ниже выполнена команда очистки в интерактивном режиме.
$ git clean -x -i
Would remove the following items:
build.TMP test.o
*** Commands ***
1: clean 2: filter by pattern 3: select by numbers
4: ask each 5: quit 6: help
What now>
Вне зависимости от размера кодовой базы, часто возникает необходимость поиска места вызова/определения функции или получения истории изменения метода. Git предоставляет несколько полезных утилит, с помощью которых легко и просто осуществлять поиск по коду и коммитам. Мы обсудим некоторые из них.
Git поставляется с командой grep
, которая позволяет легко искать в истории коммитов или в рабочем каталоге по строке или регулярному выражению. В следующих примерах, мы обратимся к исходному коду самого Git.
По умолчанию эта команда ищет по файлам в рабочем каталоге. В качестве первого варианта вы можете использовать любой из параметров -n
или --line-number
, чтобы распечатать номера строк, в которых Git нашел совпадения:
$ git grep -n gmtime_r
compat/gmtime.c:3:#undef gmtime_r
compat/gmtime.c:8: return git_gmtime_r(timep, &result);
compat/gmtime.c:11:struct tm *git_gmtime_r(const time_t *timep, struct tm *result)
compat/gmtime.c:16: ret = gmtime_r(timep, result);
Например, вместо того, чтобы печатать все совпадения, вы можете попросить git grep
обобщить выводимые командой данные, показав только те файлы, в которых обнаружены совпадения, вместе с количеством этих совпадений в каждом файле. Для этого потребуется параметр -c
или --count
:
$ git grep --count gmtime_r
compat/gmtime.c:4
compat/mingw.c:1
compat/mingw.h:1
date.c:3
git-compat-util.h:2
Если вас интересует контекст строки поиска, можно показать метод или функцию, в котором присутствует совпадение с помощью параметра -p
или --show-function
:
$ git grep -p gmtime_r *.c
date.c=static int match_multi_number(timestamp_t num, char c, const char *date,
date.c: if (gmtime_r(&now, &now_tm))
date.c=static int match_digit(const char *date, struct tm *tm, int *offset, int
*tm_gmt)
date.c: if (gmtime_r(&time, tm)) {
date.c=int parse_date_basic(const char *date, timestamp_t *timestamp, int *offset)
date.c: /* gmtime_r() in match_digit() may have clobbered it */
Вы также можете искать сложные комбинации строк, используя опцию --and
, которая гарантирует, что будут отображены только строки, имеющие сразу несколько совпадений.
Например, давайте поищем любые строки, которые определяют константу, имя которой содержит любую из подстрок LINK
или BUF_MAX
, особенно в более старой версии кодовой базы Git, представленной тегом v1.8.0 (мы добавим параметры --break
и --heading
, которые помогут вывести результаты в более читаемом виде):
$ git grep --break --heading \
-n -e '#define' --and \( -e LINK -e BUF_MAX \) v1.8.0
v1.8.0:builtin/index-pack.c
62:#define FLAG_LINK (1u<<20)
v1.8.0:cache.h
73:#define S_IFGITLINK 0160000
74:#define S_ISGITLINK(m)
(((m) & S_IFMT) == S_IFGITLINK)
Возможно, вы ищете не где присутствует некоторое выражение, а когда оно существовало или было добавлено. Команда git log
обладает некоторыми мощными инструментами для поиска определённых коммитов по содержимому их сообщений или содержимому сделанных в них изменений.
Например, если вы хотите найти, когда была добавлена константа ZLIB_BUF_MAX
, то вы можете с помощью опции -S
попросить Git показывать только те коммиты, в которых была добавлена или удалена эта строка.
$ git log -S ZLIB_BUF_MAX --oneline
e01503b zlib: allow feeding more than 4GB in one go
ef49a7a zlib: zlib can only process 4GB at a time
Если мы посмотрим на изменения, сделанные в этих коммитах, то увидим, что в ef49a7a константа была добавлена, а в e01503b — изменена. Если вам нужно найти что-то более сложное, вы можете с помощью опции -G
передать регулярное выражение.
Другой, довольно продвинутый, поиск по истории, который бывает чрезвычайно полезным — поиск по истории изменений строки. Просто запустите git log
с параметром -L
, и он покажет вам историю изменения функции или строки кода в вашей кодовой базе.
Например, если мы хотим увидеть все изменения, произошедшие с функцией git_deflate_bound
в файле zlib.c
, мы можем выполнить git log -L :git_deflate_bound:zlib.c
. Эта команда постарается определить границы функции, выполнит поиск по истории и покажет все изменения, которые были сделаны с функцией, в виде набора патчей в обратном порядке до момента создания функции.
Если для вашего языка программирования Git не умеет правильно определять функции и методы, вы можете передать ему регулярное выражение. Например, следующая команда выполнит такой же поиск как и предыдущая git log -L '/unsigned long git_deflate_bound/',/^}/:zlib.c
. Также вы можете передать интервал строк или номер определённой строки и в этом случае вы получите похожий результат.
В частности, можно изменить порядок коммитов, сообщения или изменённые в коммитах файлы, объединить вместе или разбить на части, полностью удалить коммит — но только до того, как вы поделитесь своими наработками с другими.
Изменение вашего последнего коммита, наверное, наиболее частое исправление истории, которое вы будете выполнять. Наиболее часто с вашим последним коммитом вам будет нужно сделать две основные операции: изменить сообщение коммита или изменить только что сделанный снимок, добавив, изменив или удалив файлы.
Если вы хотите изменить только сообщение вашего последнего коммита, это очень просто:
$ git commit --amend
Эта команда откроет в вашем текстовом редакторе сообщение вашего последнего коммита, для того, чтобы вы могли его исправить. Когда вы сохраните его и закроете редактор, будет создан новый коммит, содержащий это сообщение, который теперь и будет вашим последним коммитом.
Если вы создали коммит и затем хотите изменить зафиксированный снимок, добавив или изменив файлы (возможно, вы забыли добавить вновь созданный файл, когда совершали изначальный коммит), то процесс выглядит в основном так же. Вы добавляете в индекс необходимые изменения, редактируя файл и выполняя для него git add
или git rm
для отслеживаемого файла, а последующая команда git commit --amend
берет вашу текущую область подготовленных изменений и делает её снимок для нового коммита.
Вы должны быть осторожными, используя этот приём, так как при этом изменяется SHA-1
коммита. Поэтому, как и с операцией rebase
— не изменяйте ваш последний коммит, если вы уже отправили его в общий репозиторий.
С другой стороны, если изменения незначительны (исправление опечаток, добавление в коммит забытого файла), то текущее сообщение вполне можно оставить; чтобы лишний раз не вызывать редактор, просто добавьте измененные файлы в индекс и выполните команду
$ git commit --amend --no-edit
Если вы хотите избавиться от какого-либо коммита, то удалить его можно во время интерактивного перебазирования rebase -i
. Напишите слово drop
перед коммитом, который хотите удалить, или просто удалите его из списка:
pick 461cb2a This commit is OK
drop 5aecc10 This commit is broken
Из-за того, как Git создаёт объекты коммитов, удаление или изменение коммита влечёт за собой перезапись всех последующих коммитов. Чем дальше вы вернётесь в историю ваших коммитов, тем больше коммитов потребуется переделать. Это может вызвать множество конфликтов слияния, особенно если у вас много последующих коммитов, которые зависят от удалённого.
Если во время подобного перебазирования вы поняли, что это была не очень хорошая идея, то всегда можно остановиться. Просто выполните команду git rebase --abort
и ваш репозиторий вернётся в то состояние, в котором он был до начала перебазирования.
Если вы завершили перебазирование, а затем решили, что полученный результат это не то, что вам нужно — воспользуйтесь командой git reflog
, чтобы восстановить предыдущую версию вашей ветки. Дополнительную информацию по команде reflog
можно найти в разделе Восстановление данных главы 10.
Дрю Дево создал практическое руководство с упражнениями по использованию git rebase
. Найти его можно здесь: https://git-rebase.io/
Перед тем, как перейти к более специализированными утилитам, давайте поговорим о reset
и checkout
. Эти команды кажутся самыми непонятными из всех, которые есть в Git, когда вы в первый раз сталкиваетесь с ними. Они делают так много, что попытки по-настоящему их понять и правильно использовать кажутся безнадёжными.
Разобраться с командами reset
и checkout
будет проще, если считать, что Git управляет содержимым трёх различных деревьев. Здесь под «деревом» мы понимаем «набор файлов», а не специальную структуру данных. (В некоторых случаях индекс ведет себя не совсем так, как дерево, но для наших текущих целей его проще представлять именно таким.)
В своих обычных операциях Git управляет тремя деревьями
Дерево Назначение
HEAD Снимок последнего коммита, родитель следующего
Индекс Снимок следующего намеченного коммита
Рабочий Каталог Песочница
HEAD
— это указатель на текущую ветку, которая, в свою очередь, является указателем на последний коммит, сделанный в этой ветке. Это значит, что HEAD будет родителем следующего созданного коммита. Как правило, самое простое считать HEAD снимком вашего последнего коммита.
Индекс — это ваш следующий намеченный коммит. Мы также упоминали это понятие как «область подготовленных изменений» Git — то, что Git просматривает, когда вы выполняете git commit
.
Git заполняет индекс списком изначального содержимого всех файлов, выгруженных в последний раз в ваш рабочий каталог. Затем вы заменяете некоторые из таких файлов их новыми версиями и команда git commit
преобразует изменения в дерево для нового коммита.
Технически, индекс не является древовидной структурой, на самом деле, он реализован как сжатый список flattened manifest
— но для наших целей такого представления будет достаточно.
Наконец, у вас есть рабочий каталог. Два других дерева сохраняют свое содержимое эффективным, но неудобным способом внутри каталога .git
. Рабочий Каталог распаковывает их в настоящие файлы, что упрощает для вас их редактирование. Считайте Рабочий Каталог песочницей, где вы можете опробовать изменения перед их коммитом в индекс (область подготовленных изменений) и затем в историю.
$ tree
.
├── README
├── Rakefile
└── lib
└── simplegit.rb
1 directory, 3 files
Основное предназначение Git — это сохранение снимков последовательно улучшающихся состояний вашего проекта, путём управления этими тремя деревьями.
Давайте рассмотрим этот процесс: пусть вы перешли в новый каталог, содержащий один файл. Данную версию этого файла будем называть v1
и изображать голубым цветом. Выполним команду git init
, которая создаст Git-репозиторий, у которого ссылка HEAD будет указывать на ещё несуществующую ветку (master
пока не существует).
На данном этапе только дерево Рабочего Каталога содержит данные.
Теперь мы хотим закоммитить этот файл, поэтому мы используем git add
для копирования содержимого Рабочего Каталога в Индекс.
Затем, мы выполняем команду git commit
, которая сохраняет содержимое Индекса как неизменяемый снимок, создает объект коммита, который указывает на этот снимок, и обновляет master так, чтобы он тоже указывал на этот коммит.
Если сейчас выполнить git status
, то мы не увидим никаких изменений, так как все три дерева одинаковые.
Теперь мы хотим внести изменения в файл и закоммитить его. Мы пройдём через всё ту же процедуру; сначала мы отредактируем файл в нашем рабочем каталоге. Давайте называть эту версию файла v2
и обозначать красным цветом.
Если сейчас мы выполним git status
, то увидим, что файл выделен красным в разделе «Изменения, не подготовленные к коммиту», так как его представления в Индексе и Рабочем Каталоге различны. Затем мы выполним git add
для этого файла, чтобы поместить его в Индекс.
Если сейчас мы выполним git status
, то увидим, что этот файл выделен зелёным цветом в разделе «Изменения, которые будут закоммичены», так как Индекс и HEAD
различны — то есть, наш следующий намеченный коммит сейчас отличается от нашего последнего коммита. Наконец, мы выполним git commit
, чтобы окончательно совершить коммит.
Сейчас команда git status
не показывает ничего, так как снова все три дерева одинаковые.
Переключение веток и клонирование проходят через похожий процесс. Когда вы переключаетесь checkout
на ветку, HEAD
начинает также указывать на новую ветку, ваш Индекс замещается снимком коммита этой ветки, и затем содержимое Индекса копируется в ваш Рабочий Каталог.
Команда reset
становится более понятной, если рассмотреть её с учётом вышеизложенного.
В следующих примерах предположим, что мы снова изменили файл file.txt
и закоммитили его в третий раз.
Давайте теперь внимательно проследим, что именно происходит при вызове reset
. Эта команда простым и предсказуемым способом управляет тремя деревьями, существующими в Git. Она выполняет три основных операции.
Шаг 1: Перемещение указателя HEAD
Первое, что сделает reset
— переместит то, на что указывает HEAD
. Обратите внимание, изменяется не сам HEAD
(что происходит при выполнении команды checkout
); reset
перемещает ветку, на которую указывает HEAD
. Таким образом, если HEAD
указывает на ветку master
(то есть вы сейчас работаете с веткой master
), выполнение команды git reset 9e5e6a4
сделает так, что master
будет указывать на 9e5e6a4
.
Не важно с какими опциями вы вызвали команду reset
с указанием коммита (reset
также можно вызывать с указанием пути), она всегда будет пытаться сперва сделать данный шаг. При вызове reset --soft
на этом выполнение команды и остановится.
Теперь взгляните на диаграмму и постарайтесь разобраться, что случилось: фактически была отменена последняя команда git commit
. Когда вы выполняете git commit
, Git создает новый коммит и перемещает на него ветку, на которую указывает HEAD. Если вы выполняете reset
на HEAD~
(родителя HEAD
), то вы перемещаете ветку туда, где она была раньше, не изменяя при этом ни Индекс, ни Рабочий Каталог. Вы можете обновить Индекс и снова выполнить git commit
, таким образом добиваясь того же, что делает команда git commit --amend
(смотрите Изменение последнего коммита).
Шаг 2: Обновление Индекса --mixed
Заметьте, если сейчас вы выполните git status
, то увидите отмеченные зелёным цветом изменения между Индексом и новым HEAD
.
Следующим, что сделает reset
, будет обновление Индекса содержимым того снимка, на который указывает HEAD
.
Если вы указали опцию --mixed
, выполнение reset
остановится на этом шаге. Такое поведение также используется по умолчанию, поэтому если вы не указали совсем никаких опций (в нашем случае git reset HEAD~
), выполнение команды также остановится на этом шаге.
Снова взгляните на диаграмму и постарайтесь разобраться, что произошло: отменен не только ваш последний commit
, но также и добавление в индекс всех файлов. Вы откатились назад до момента выполнения команд git add
и git commit
.
Шаг 3: Обновление Рабочего Каталога --hard
Третье, что сделает reset
— это приведение вашего Рабочего Каталога к тому же виду, что и Индекс. Если вы используете опцию --hard
, то выполнение команды будет продолжено до этого шага.
Давайте разберемся, что сейчас случилось. Вы отменили ваш последний коммит, результаты выполнения команд git add
и git commit
, а также все изменения, которые вы сделали в рабочем каталоге.
Важно отметить, что только указание этого флага --hard
делает команду reset опасной, это один из немногих случаев, когда Git действительно удаляет данные. Все остальные вызовы reset
легко отменить, но при указании опции --hard
команда принудительно перезаписывает файлы в Рабочем Каталоге. В данном конкретном случае, версия v3
нашего файла всё ещё остаётся в коммите внутри базы данных Git и мы можем вернуть её, просматривая наш reflog
, но если вы не коммитили эту версию, Git перезапишет файл и её уже нельзя будет восстановить.
Команда reset
в заранее определённом порядке перезаписывает три дерева Git, останавливаясь тогда, когда вы ей скажете:
- Перемещает ветку, на которую указывает HEAD (останавливается на этом, если указана опция
--soft
) - Делает Индекс таким же как и
HEAD
(останавливается на этом, если не указана опция--hard
) - Делает Рабочий Каталог таким же как и Индекс.
Основной форме команды reset
(без опций --soft
и --hard
) вы также можете передавать путь, с которым она будет оперировать. В этом случае, reset пропустит первый шаг, а на остальных будет работать только с указанным файлом или набором файлов. Первый шаг пропускается, так как HEAD
является указателем и не может ссылаться частично на один коммит, а частично на другой. Но Индекс и Рабочий Каталог могут быть изменены частично, поэтому reset
выполняет шаги 2 и 3.
Итак, предположим вы выполнили команду git reset file.txt
. Эта форма записи (так как вы не указали ни SHA-1
коммита, ни ветку, ни опций --soft
или --hard
) является сокращением для git reset --mixed HEAD file.txt
, которая:
- Перемещает ветку, на которую указывает
HEAD
(будет пропущено) - Делает Индекс таким же как и
HEAD
(остановится здесь) То есть, фактически, она копирует файлfile.txt
изHEAD
в Индекс.
Это создает эффект отмены индексации файла. Если вы посмотрите на диаграммы этой команды и команды git add
, то увидите, что их действия прямо противоположные.
Именно поэтому в выводе git status
предлагается использовать такую команду для отмены индексации файла. (Смотрите подробности в Отмена индексации файла.)
Мы легко можем заставить Git «брать данные не из HEAD», указав коммит, из которого нужно взять версию этого файла. Для этого мы должны выполнить следующее git reset eb43bf file.txt
.
Можно считать, что, фактически, мы в Рабочем Каталоге вернули содержимое файла к версии v1, выполнили для него git add
, а затем вернули содержимое обратно к версии v3 (в действительности все эти шаги не выполняются). Если сейчас мы выполним git commit
, то будут сохранены изменения, которые возвращают файл к версии v1, но при этом файл в Рабочем Каталоге никогда не возвращался к такой версии.
Заметим, что как и команде git add
, reset
можно указывать опцию --patch
для отмены индексации части содержимого. Таким способом вы можете избирательно отменять индексацию или откатывать изменения.
Давайте посмотрим, как, используя вышеизложенное, сделать кое-что интересное — слияние коммитов.
Допустим, у вас есть последовательность коммитов с сообщениями вида «упс.», «В работе» и «позабыл этот файл». Вы можете использовать reset
для того, чтобы просто и быстро слить их в один. (В разделе Объединение коммитов главы 7 представлен другой способ сделать то же самое, но в данном примере проще воспользоваться reset
.)
Предположим, у вас есть проект, в котором первый коммит содержит один файл, второй коммит добавляет новый файл и изменяет первый, а третий коммит снова изменяет первый файл. Второй коммит был сделан в процессе работы и вы хотите слить его со следующим.
Вы можете выполнить git reset --soft HEAD~2
, чтобы вернуть ветку HEAD
на какой-то из предыдущих коммитов (на первый коммит, который вы хотите оставить):
Затем просто снова выполните git commit
:
Теперь вы можете видеть, что ваша «достижимая» история (история, которую вы впоследствии отправите на сервер), сейчас выглядит так — у вас есть первый коммит с файлом file-a.txt
версии v1, и второй, который изменяет файл file-a.txt
до версии v3 и добавляет file-b.txt
. Коммита, который содержал файл версии v2 не осталось в истории.
Наконец, вы можете задаться вопросом, в чем же состоит отличие между checkout
и reset
. Как и reset
, команда checkout
управляет тремя деревьями Git, и также её поведение зависит от того указали ли вы путь до файла или нет.
Без указания пути
Команда git checkout [branch]
очень похожа на git reset --hard [branch]
, в процессе их выполнения все три дерева изменяются так, чтобы выглядеть как [branch]
. Но между этими командами есть два важных отличия.
Во-первых, в отличие от reset --hard
, команда checkout бережно относится к рабочему каталогу, и проверяет, что она не трогает файлы, в которых есть изменения. В действительности, эта команда поступает немного умнее — она пытается выполнить в Рабочем Каталоге простые слияния так, чтобы все файлы, которые вы не изменяли, были обновлены. С другой стороны, команда reset --hard
просто заменяет всё целиком, не выполняя проверок.
Второе важное отличие заключается в том, как эти команды обновляют HEAD
. В то время как reset
перемещает ветку, на которую указывает HEAD
, команда checkout
перемещает сам HEAD
так, чтобы он указывал на другую ветку.
Например, пусть у нас есть ветки master
и develop
, которые указывают на разные коммиты и мы сейчас находимся на ветке develop
(то есть HEAD указывает на неё). Если мы выполним git reset master
, сама ветка develop
станет ссылаться на тот же коммит, что и master
. Если мы выполним git checkout master
, то develop
не изменится, но изменится HEAD
. Он станет указывать на master
.
Итак, в обоих случаях мы перемещаем HEAD
на коммит A, но важное отличие состоит в том, как мы это делаем. Команда reset
переместит также и ветку, на которую указывает HEAD, а checkout
перемещает только сам HEAD
.
Другой способ выполнить checkout
состоит в том, чтобы указать путь до файла. В этом случае, как и для команды reset
, HEAD
не перемещается. Эта команда как и git reset [branch] file
обновляет файл в индексе версией из коммита, но дополнительно она обновляет и файл в рабочем каталоге. То же самое сделала бы команда git reset --hard [branch] file
(если бы reset
можно было бы так запускать) — это небезопасно для рабочего каталога и не перемещает HEAD
.
Также как git reset
и git add
, команда checkout
принимает опцию --patch
для того, чтобы позволить вам избирательно откатить изменения содержимого файла по частям.
Заключение
Ниже приведена памятка того, как эти команды воздействуют на каждое из деревьев. В столбце HEAD
указывается REF
если эта команда перемещает ссылку (ветку), на которую HEAD
указывает, и HEAD
если перемещается только сам HEAD
. Обратите особое внимание на столбец «Сохранность РК» — если в нем указано NO, то хорошенько подумайте прежде чем выполнить эту команду
Философия Git заключается в том, чтобы быть умным, когда слияние разрешается однозначно, но если возникает конфликт, он не пытается сумничать и разрешить его автоматически.
Во-первых, если есть возможность, перед слиянием, в котором может возникнуть конфликт, позаботьтесь о том, чтобы ваша рабочая копия была без локальных изменений. Если у вас есть несохранённые наработки, либо припрячьте их, либо сохраните их во временной ветке. Таким образом, вы сможете легко отменить любые изменения, которые сделаете в рабочем каталоге. Если при выполнении слияния вы не сохраните сделанные изменения, то некоторые из описанных ниже приёмов могут привести к утрате этих наработок.
В данный момент у нас есть несколько вариантов дальнейших действий. Во-первых, давайте рассмотрим как выйти из этой ситуации. Если вы, возможно, не были готовы к конфликтам и на самом деле не хотите связываться с ними, вы можете просто отменить попытку слияния, используя команду git merge --abort
$ git status -sb
## master
UU hello.rb
$ git merge --abort
$ git status -sb
## master
Эта команда пытается откатить ваше состояние до того, что было до запуска слияния. Завершиться неудачно она может только в случаях, если перед запуском слияния у вас были не припрятанные или не зафиксированные изменения в рабочем каталоге, во всех остальных случаях всё будет хорошо.
Если по каким-то причинам вы обнаружили себя в ужасном состоянии и хотите просто начать всё сначала, вы можете также выполнить git reset --hard HEAD
(либо вместо HEAD
указав то, куда вы хотите откатиться). Но помните, что это откатит все изменения в рабочем каталоге, поэтому удостоверьтесь, что никакие из них вам не нужны.
В данном конкретном случае конфликты связаны с пробельными символами. Мы знаем это, так как это простой пример, но в реальных ситуациях это также легко определить при изучении конфликта, так как каждая строка в нем будет удалена и добавлена снова. По умолчанию Git считает все эти строки изменёнными и поэтому не может слить файлы.
Стратегии слияния, используемой по умолчанию, можно передать аргументы, и некоторые из них предназначены для соответствующей настройки игнорирования изменений пробельных символов. Если вы видите, что множество конфликтов слияния вызваны пробельными символами, то вы можете прервать слияние и запустить его снова, но на этот раз с опцией -Xignore-all-space
или -Xignore-space-change
. Первая опция игнорирует изменения в любом количестве существующих пробельных символов, вторая игнорирует вообще все изменения пробельных символов.
$ git merge -Xignore-space-change whitespace
Auto-merging hello.rb
Merge made by the 'recursive' strategy.
hello.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
Поскольку в этом примере реальные изменения файлов не конфликтуют, то при игнорировании изменений пробельных символов всё сольётся хорошо. Это значительно облегчает жизнь, если кто-то в вашей команде любит временами заменять все пробелы на табуляции или наоборот.
Другой полезный инструмент при разрешении конфликтов слияния — это команда git log
. Она поможет вам получить информацию о том, что могло привести к возникновению конфликтов. Временами может быть очень полезным просмотреть историю, чтобы понять почему в двух ветках разработки изменялась одна и та же область кода.
Для получения полного списка всех уникальных коммитов, которые были сделаны в любой из сливаемых веток, мы можем использовать синтаксис «трёх точек», который мы изучили в Три точки.
$ git log --oneline --left-right HEAD...MERGE_HEAD
< f1270f7 Update README
< 9af9d3b Create README
< 694971d Update phrase to 'hola world'
> e3eb223 Add more tests
> 7cff591 Create initial testing script
> c3ffff1 Change text to 'hello mundo'
Это список всех шести коммитов, включённых в слияние, с указанием также ветки разработки, в которой находится каждый из коммитов.
Мы также можем сократить его, попросив предоставить нам более специализированную информацию. Если мы добавим опцию --merge
к команде git log
, то она покажет нам только те коммиты, в которых изменялся конфликтующий в данный момент файл.
$ git log --oneline --left-right --merge
< 694971d Update phrase to 'hola world'
> c3ffff1 Change text to 'hello mundo'
Если вы выполните эту команду с опцией -p
, то получите только список изменений файла, на котором возник конфликт. Это может быть действительно полезным для быстрого получения информации, которая необходима, чтобы понять почему что-либо конфликтует и как наиболее правильно это разрешить.
Так как Git добавляет в индекс все успешные результаты слияния, то при вызове git diff
в состоянии конфликта слияния будет отображено только то, что сейчас конфликтует. Это может быть полезно, так как вы сможете увидеть какие ещё конфликты нужно разрешить.
Если вы выполните git diff
сразу после конфликта слияния, то получите информацию в довольно своеобразном формате.
$ git diff
diff --cc hello.rb
index 0399cd5,59727f0..0000000
--- a/hello.rb
+++ b/hello.rb
@@@ -1,7 -1,7 +1,11 @@@
#! /usr/bin/env ruby
def hello
++<<<<<<< HEAD
+ puts 'hola world'
++=======
+ puts 'hello mundo'
++>>>>>>> mundo
end hello()
Такой формат называется «комбинированным» (Combined Diff
), для каждого различия в нем содержится два раздела с информацией. В первом разделе отображены различия строки (добавлена она или удалена) между «вашей» веткой и содержимым вашего рабочего каталога, а во втором разделе содержится то же самое, но между «их» веткой и рабочим каталогом.
Таким образом, в данном примере вы можете увидеть строки <<<<<<<
и >>>>>>>
в файле в вашем рабочем каталоге, хотя они отсутствовали в сливаемых ветках. Это вполне оправдано, потому что, добавляя их, инструмент слияния предоставляет вам дополнительную информацию, но предполагается, что мы удалим их.
Если мы разрешим конфликт и снова выполним команду git diff
, то получим ту же информацию, но в немного более полезном представлении.
$ vim hello.rb
$ git diff
diff --cc hello.rb
index 0399cd5,59727f0..0000000
--- a/hello.rb
+++ b/hello.rb
@@@ -1,7 -1,7 +1,7 @@@
#! /usr/bin/env ruby
def hello
- puts 'hola world'
- puts 'hello mundo'
++ puts 'hola mundo'
end hello()
В этом выводе указано, что строка hola world
при слиянии присутствовала в «нашей» ветке, но отсутствовала в рабочей копии, строка hello mundo
была в «их» ветке, но не в рабочей копии, и, наконец, hola mundo
не была ни в одной из сливаемых веток, но сейчас присутствует в рабочей копии. Это бывает полезно просмотреть перед коммитом разрешения конфликта.
Такую же информацию вы можете получить и после выполнения слияния с помощью команды git log
, узнав таким образом как был разрешён конфликт. Git выводит информацию в таком формате, если вы выполните git show
для коммита слияния или вызовете команду git log -p
с опцией --cc
(без неё данная команда не показывает изменения для коммитов слияния).
$ git log --cc -p -1
commit 14f41939956d80b9e17bb8721354c33f8d5b5a79
Merge: f1270f7 e3eb223
Author: Scott Chacon <schacon@gmail.com>
Date: Fri Sep 19 18:14:49 2014 +0200
Merge branch 'mundo'
Conflicts:
hello.rb
diff --cc hello.rb
index 0399cd5,59727f0..e1d0799
--- a/hello.rb
+++ b/hello.rb
@@@ -1,7 -1,7 +1,7 @@@
#! /usr/bin/env ruby
def hello
- puts 'hola world'
- puts 'hello mundo'
++ puts 'hola mundo'
end hello()
Коммит слияния не исключение. Допустим, вы начали работать в тематической ветке, случайно слили ее в master
, и теперь ваша история коммитов выглядит следующим образом:
Есть два подхода к решению этой проблемы, в зависимости от того, какой результат вы хотите получить.
Если нежелаемый коммит слияния существует только в вашем локальном репозитории, то простейшее и лучшее решение состоит в перемещении веток так, чтобы они указывали туда куда вам нужно. В большинстве случаев, если вы после случайного git merge выполните команду git reset --hard HEAD~
, то указатели веток восстановятся так, что будут выглядеть следующим образом:
Здесь небольшое напоминание: reset --hard
обычно выполняет три шага:
- Перемещает ветку, на которую указывает
HEAD
. В данном случае мы хотим переместитьmaster
туда, где она была до коммита слияния (C6). - Приводит индекс к такому же виду что и
HEAD
. - Приводит рабочий каталог к такому же виду, что и индекс.
Недостаток этого подхода состоит в изменении истории, что может привести к проблемам в случае совместно используемого репозитория. Загляните в Опасности перемещения, чтобы узнать что именно может произойти; кратко говоря, если у других людей уже есть какие-то из изменяемых вами коммитов, вы должны отказаться от использования reset
. Этот подход также не будет работать, если после слияния уже был сделан хотя бы один коммит; перемещение ссылки фактически приведёт к потере этих изменений.
Если перемещение указателей ветки вам не подходит, Git предоставляет возможность сделать новый коммит, который откатывает все изменения, сделанные в другом. Git называет эту операцию «восстановлением» («revert»), в данном примере вы можете вызвать её следующим образом:
$ git revert -m 1 HEAD
[master b1d8379] Revert "Merge branch 'topic'"
Опция -m 1
указывает какой родитель является «основной веткой» и должен быть сохранен. Когда вы выполняете слияние в HEAD
(git merge topic
), новый коммит будет иметь двух родителей: первый из них HEAD
(C6), а второй — вершина ветки, которую сливают с текущей (C4). В данном случае, мы хотим отменить все изменения, внесённые слиянием родителя #2 (C4), и сохранить при этом всё содержимое из родителя #1 (C6).
История с коммитом восстановления (отменой коммита слияния) выглядит следующим образом:
Новый коммит ^M
имеет точно такое же содержимое как C6, таким образом, начиная с нее всё выглядит так, как будто слияние никогда не выполнялось, за тем лишь исключением, что «теперь уже не слитые» коммиты всё также присутствуют в истории HEAD. Git придет в замешательство, если вы вновь попытаетесь слить topic
в ветку master
:
$ git merge topic
Already up-to-date.
В ветке topic
нет ничего, что ещё недоступно из ветки master
. Плохо, что в случае добавления новых наработок в topic
, при повторении слияния Git добавит только те изменения, которые были сделаны после отмены слияния:
Лучшим решением данной проблемы является откат коммита отмены слияния, так как теперь вы хотите внести изменения, которые были отменены, а затем создание нового коммита слияния:
$ git revert ^M
[master 09f0126] Revert "Revert "Merge branch 'topic'""
$ git merge topic
В этом примере, M и ^M отменены. В коммите ^^M
, фактически, сливаются изменения из C3 и C4, а в C8 — изменения из C7, таким образом, ветка topic
полностью слита.
Функциональность git rerere
— частично скрытый компонент Git. Её имя является сокращением для «reuse recorded resolution» («повторно использовать сохранённое решение»). Как следует из названия, эта функциональность позволяет попросить Git запомнить то, как вы разрешили некоторую часть конфликта, так что в случае возникновения такого же конфликта, Git сможет его разрешить автоматически.
Помимо рассмотренных ранее основных способов передачи данных Git по сети (HTTP, SSH и т.п.), существует ещё один способ, который обычно не используется, но в некоторых случаях может быть весьма полезным.
Git умеет «упаковывать» свои данные в один файл. Это может быть полезным в разных ситуациях. Может быть, ваша сеть не работает, а вы хотите отправить изменения своим коллегам. Возможно, вы работаете откуда-то извне офиса и не имеете доступа к локальной сети по соображениям безопасности. Может быть, ваша карта беспроводной/проводной связи просто сломалась. Возможно, у вас в данный момент нет доступа к общему серверу, а вы хотите отправить кому-нибудь по электронной почте обновления, но передавать 40 коммитов с помощью format-patch
не хотите.
В этих случаях вам может помочь команда git bundle
. Она упакует всё, что в обычной ситуации было бы отправлено по сети командой git push
, в бинарный файл, который вы можете передать кому-нибудь по электронной почте или поместить на флешку и затем распаковать в другом репозитории.
Рассмотрим простой пример. Допустим, у вас есть репозиторий с двумя коммитами:
$ git log
commit 9a466c572fe88b195efd356c3f2bbeccdb504102
Author: Scott Chacon <schacon@gmail.com>
Date: Wed Mar 10 07:34:10 2010 -0800
Second commit
commit b1ec3248f39900d2a406049d762aa68e9641be25
Author: Scott Chacon <schacon@gmail.com>
Date: Wed Mar 10 07:34:01 2010 -0800
First commit
Если вы хотите отправить кому-нибудь этот репозиторий, но не имеете доступа на запись к общей копии репозитория или просто не хотите его настраивать, то вы можете упаковать его командой git bundle create
.
$ git bundle create repo.bundle HEAD master
Counting objects: 6, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (6/6), 441 bytes, done.
Total 6 (delta 0), reused 0 (delta 0)
В результате вы получили файл repo.bundle
, в котором содержатся все данные, необходимые для воссоздания ветки master
репозитория. Команде bundle
необходимо передать список или диапазон коммитов, которые вы хотите добавить в пакет. Если вы намереваетесь использовать пакет для того, чтобы клонировать репозиторий где-нибудь ещё, вы должны добавить в этот список HEAD
, как это сделали мы.
Вы можете отправить файл repo.bundle
кому-нибудь по электронной почте или скопировать его на USB-диск, тем самым легко решив исходную проблему.
С другой стороны, допустим, вы получили файл repo.bundle
и хотите поработать над этим проектом. Вы можете клонировать репозиторий из бинарного файла в каталог, почти также как вы делаете это при использовании URL
.
$ git clone repo.bundle repo
Cloning into 'repo'...
...
$ cd repo
$ git log --oneline
9a466c5 Second commit
b1ec324 First commit
Если при создании пакета вы не указали в списке ссылок HEAD
, то при распаковке вам потребуется указать -b master
или какую-либо другую ветку, включённую в пакет, иначе Git не будет знать, на какую ветку ему следует переключиться.
Теперь предположим, что вы сделали три коммита и хотите отправить их обратно в виде пакета на USB-флешке или по электронной почте.
$ git log --oneline
71b84da Last commit - second repo
c99cf5b Fourth commit - second repo
7011d3d Third commit - second repo
9a466c5 Second commit
b1ec324 First commit
Во-первых, нам нужно определить диапазон коммитов, которые мы хотим включить в пакет. В отличие от сетевых протоколов, которые сами выясняют минимальный набор данных, который нужно передать по сети, в данном случае мы должны сделать это сами вручную. В данном примере вы можете сделать, как раньше и упаковать полностью весь репозиторий, но будет лучше упаковать только изменения — три коммита, сделанные локально.
Таким образом, команда git bundle
может быть, действительно, полезной для организации совместной работы или для выполнения сетевых операций, когда у вас нет доступа к соответствующей сети или общему репозиторию.
Если для подключения к удалённым серверам вы используете протокол SSH
, то вы можете использовать ключ вместо пароля, что позволит вам безопасно передавать данные без ввода логина и пароля. Однако, это невозможно при использовании HTTP
-протоколов — каждое подключение требует пары логин, пароль. Всё ещё сложнее для систем с двухфакторной аутентификацией, когда выражение, которое вы используете в качестве пароля, генерируется случайно и его сложно воспроизвести.
К счастью, в Git есть система управления учётными данными, которая может помочь в этом. В Git «из коробки» есть несколько опций:
• По умолчанию Git не кеширует учётные данные совсем. Каждое подключение будет запрашивать у вас логин и пароль.
• В режиме cache
учётные данные сохраняются в памяти в течение определённого периода времени. Ни один из паролей никогда не сохраняется на диск и все они удаляются из кеша через 15 минут.
• В режиме store
учётные данные сохраняются на неограниченное время в открытом виде в файле на диске. Это значит что, до тех пор пока вы не измените пароль к Git-серверу, вам не потребуется больше вводить ваши учётные данные. Недостатком такого подхода является то, что ваш пароль хранится в открытом виде в файле в вашем домашнем каталоге.
• На случай если вы используете Mac, в Git есть режим osxkeychain
, при использовании которого учётные данные хранятся в защищённом хранилище, привязанному к вашему системному аккаунту. В этом режиме учётные данные сохраняются на диск на неограниченное время, но они шифруются с использованием той же системы, с помощью которой сохраняются HTTPS-сертификаты и автозаполнения для Safari.
Вы можете выбрать один из этих методов, изменив настройки Git:
$ git config --global credential.helper cache
В главе Введение кратко упоминалось, что вы можете настроить Git, используя команду git config
. Первое, что вы делали, это установили своё имя и e-mail адрес:
$ git config --global user.name "John Doe"
$ git config --global user.email johndoe@example.com
Сейчас вы познакомитесь с несколькими наиболее интересными опциями, которые можно установить для настройки поведения Git.
Кратко: Git использует набор конфигурационных файлов для изменения стандартного поведения, если это необходимо. Вначале, Git ищет настройки в файле /etc/gitconfig
, который содержит настройки для всех пользователей в системе и всех репозиториев. Если передать опцию --system
команде git config
, то операции чтения и записи будут производиться именно с этим файлом.
Следующее место, куда смотрит Git — это файл ~/.gitconfig
(или ~/.config/git/config
), который хранит настройки конкретного пользователя. Вы можете указать Git читать и писать в него, используя опцию --global
.
Наконец, Git ищет параметры конфигурации в файле настроек в каталоге Git (.git/config
) текущего репозитория. Эти значения относятся только к текущему репозиторию и доступны при передаче параметра --local
команде git config
. (Если уровень настроек не указан явно, то подразумевается локальный.)
Каждый из этих уровней (системный, глобальный, локальный) переопределяет значения предыдущего уровня, например, значения из .git/config
важнее значений из /etc/gitconfig
.
Конфигурация Git это обычные текстовые файлы, поэтому можно вручную установить необходимые значения используя соответствующий синтаксис. Как правило, это проще чем вызывать команду git config
для каждого параметра.
Конфигурационные параметры Git разделяются на две категории: настройки клиента и настройки сервера. Большая часть — клиентские, для настройки ваших личных предпочтений в работе.
Для просмотра полного списка настроек, поддерживаемых вашей версией Git, выполните команду:
$ man git-config
Эта команда выведет список доступных настроек с довольно подробным описанием. Так же, соответствующую документацию можно найти здесь https://git-scm.com/docs/git-config.html.
core.editor
По умолчанию, Git использует ваш редактор по умолчанию ($VISUAL
или $EDITOR
), если значение не задано — переходит к использованию редактора vi
при создании и редактировании сообщений коммитов или тегов. Чтобы изменить редактор по умолчанию, воспользуйтесь настройкой core.editor
:
$ git config --global core.editor emacs
Теперь, вне зависимости от того, какой редактор является основным для вашего окружения, Git будет вызывать Emacs для редактирования сообщений.
commit.template
Если указать путь к существующему файлу, то он будет использован как сообщение по умолчанию при создании коммита. Смысл создания шаблона сообщения коммита в том, чтобы лишний раз напомнить себе (или другим) о требованиях к формату или стилю оформления сообщения коммита.
Например, предположим что вы создали файл ~/.gitmessage.txt
, который выглядит так:
Subject line (try to keep under 50 characters)
Multi-line description of commit,
feel free to be detailed.
[Ticket: X]
Обратите внимание, что шаблон напоминает коммитеру о том, чтобы строка заголовка сообщения была короткой (для поддержки однострочного вывода команды git log -- oneline
), что дополнительную информацию в сообщении следует располагать ниже, а так же о том, что было бы неплохо при наличии добавить ссылку на номер задачи или сообщения в системе отслеживания ошибок.
Чтобы заставить Git отображать содержимое этого файла в редакторе каждый раз при выполнении команды git commit
, следует установить значение параметра commit.template
:
$ git config --global commit.template ~/.gitmessage.txt
$ git commit
Теперь, при создании коммита, в вашем редакторе будет отображаться сообщение изменённого вида:
Subject line (try to keep under 50 characters)
Multi-line description of commit,
feel free to be detailed.
[Ticket: X]
# 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: lib/test.rb
#
~
~
".git/COMMIT_EDITMSG" 14L, 297C
Если ваша команда придерживается требований к сообщениям коммитов, то создание шаблона такого сообщения и настройка Git на его использование увеличит вероятность соответствия заданным требованиям.
core.excludesfile
В разделе Игнорирование файлов главы 2 сказано, что вы можете указывать шаблоны исключений в файле .gitignore
вашего проекта, чтобы Git не отслеживал их и не добавлял в индекс при выполнении команды git add
.
Однако, иногда вам нужно игнорировать определенные файлы во всех ваших репозиториях. Если на вашем компьютере работает Mac OS X, вероятно вы знакомы с файлами .DS_Store
. Если вы используете Emacs или Vim, то вы знаете про файлы, имена которых заканчиваются на ~
или .swp
.
Данная настройка позволяет вам определить что-то вроде глобального файла .gitignore
. Если вы создадите файл ~/.gitignore_global
с содержанием:
*~
.*.swp
.DS_Store
... и выполните команду git config --global core.excludesfile ~/.gitignore_global
, то Git больше не потревожит вас на счёт этих файлов.
Атрибуты Git так же позволяют вам делать некоторые интересные вещи при экспорте вашего проекта.
export-ignore
Вы можете указать Git игнорировать определённые файлы и каталоги при создании архива. Если в вашем проекте есть файл или каталог, которые вам нужны, но вы не хотите включать их в архив при экспорте, то можно присвоить им атрибут export-ignore
.
Например, у вас есть несколько файлов в каталоге test/ и совершенно нет смысла включать их в архив вашего проекта. В этом случае достаточно добавить следующую строку в файл .gitattributes
:
test/ export-ignore
Теперь, при создании архива проекта командой git archive
, каталог test/
не будет включен в архив.
Как и многие другие системы контроля версий, Git предоставляет возможность запуска пользовательских скриптов в случае возникновения определённых событий. Такие действия называются хуками и разделяются на две группы: серверные и клиентские. Если хуки на стороне клиента запускаются такими операциями как слияние или создание коммита, то на стороне сервера они инициируются сетевыми операциями, такими как получение отправленного коммита. Хуки часто используются для широкого круга задач.
Хуки хранятся в подкаталоге hooks
относительно основного каталога Git. Для большинства проектов это .git/hooks
. Когда вы инициализируете новый репозиторий командой git init
, Git наполняет каталог hooks
примерами скриптов, большинство из которых готовы к использованию, при этом каждый из них содержит документацию по используемым входным данным. Все примеры представлены в виде шелл скриптов, содержащими код на Perl, но вы можете использовать любой язык для написания скриптов — главное правильно именовать исполняемые файлы. Если вы решите использовать какой-либо из предустановленных скриптов, то достаточно его просто переименовать, убрав суффикс .sample
.
Для подключения собственного скрипта достаточно задать ему соответствующее имя, поместить в подкаталог hooks
основного каталога Git и сделать его исполняемым. Далее, мы рассмотрим наиболее часто используемые хуки.
Для клиента существует множество различных хуков. В этой главе они разделены на хуки уровня коммита, уровня e-mail и прочие. Необходимо отметить, что клиентские хуки НЕ копируются при клонировании репозитория. Если вы намерены использовать такие скрипты для обеспечения соблюдения политики, то вам следует использовать серверные хуки.
В дополнение к хукам на стороне клиента, как системный администратор вы можете использовать несколько важных хуков на сервере для вашего проекта, тем самым обеспечив выполнение практически любой политики. Эти скрипты выполняются до и после отправки на сервер. Pre-хуки могут возвращать ненулевой код в любой момент, что отменит передачу и отправит сообщение об ошибке клиенту; таким образом вы можете реализовать сколь угодно сложную политику.
Стоит помнить, что всё, что можно сделать с помощью графического интерфейса, может быть выполнено и из консоли; командная строка по прежнему является местом, где у вас больше всего возможностей и контроля над вашими репозиториями.
Установив Git, вы также получаете два графических инструмента: gitk
и git-gui
.
gitk
— это графический просмотрщик истории. Что-то типа улучшенных git log
и git grep
. Это тот инструмент, который вы будете использовать для поиска событий или визуализации истории.
Проще всего вызвать Gitk
из командной строки: Просто перейдите в каталог с репозиторием и наберите:
$ gitk [git log options]
Gitk
принимает много различных опций, большинство из которых транслируются в используемый git log
. Возможно, наиболее полезная опция — --all
, которая указывает Gitk
выводить коммиты, доступные из любой ссылки, а не только HEAD
Интерфейс на картинке похож на вывод git log --graph
; каждая точка соответствует коммиту, линии отражают родство коммитов, а ссылки изображены цветными прямоугольниками. Жёлтая точка обозначает HEAD, а красная — изменения, которые попадут в следующий коммит. В нижней части расположены элементы интерфейса для просмотра выделенного коммита: слева показаны изменения и комментарий, а справа — общая информация по изменённым файлам. В центре расположены элементы для поиска по истории.
git-gui
, в отличие от gitk
— это инструмент редактирования отдельных коммитов. Его тоже очень просто вызвать из консоли:
$ git gui
Существует огромное множество других графических инструментов для работы с Git, начиная от специализированных, выполняющих одну задачу, заканчивая «комбайнами» покрывающими всю функциональность Git. На официальном сайте Git поддерживается в актуальном состоянии список наиболее популярных оболочек: https://git-scm.com/downloads/guis. Более подробный список доступен на Git вики: https://git.wiki.kernel.org/index.php/Interfaces,_frontends,_and_tools#Graphical_Interfaces.
Если вы используете Bash, то можете задействовать некоторые из его фишек для облегчения работы с Git. К слову, Git поставляется с плагинами для нескольких командных оболочек, но они выключены по умолчанию.
Для начала, скачайте файл contrib/completion/git-completion.bash
из репозитория с исходным кодом Git. Поместите его в укромное место — например, в ваш домашний каталог — и добавьте следующие строки в .bashrc
:
. ~/git-completion.bash
Как только закончите с этим, перейдите в каталог с Git репозиторием и наберите:
$ git chec<tab>
...и Bash дополнит строку до git checkout
. Эта магия работает для всех Git команд, их параметров, удалённых репозиториев и имён ссылок там, где это возможно.
Возможно, вам также пригодится отображение информации о репозитории, расположенном в текущем каталоге. Вы можете выводить сколь угодно сложную информацию, но обычно достаточно названия текущей ветки и статуса рабочего каталога. Чтобы снабдить строку приветствия этой информацией, скачайте файл contrib/completion/git-prompt.sh
из репозитория с исходным кодом Git и добавьте примерно такие строки в .bashrc
:
. ~/git-prompt.sh
export GIT_PS1_SHOWDIRTYSTATE=1
export PS1='\w$(__git_ps1 " (%s)")\$ '
Часть \w
означает текущий рабочий каталог, \$
— индикатор суперпользователя (обычно $
или #
), а __git_ps1 " (%s)"
вызывает функцию, объявленную в git-prompt.sh
, с аргументом (%s)
— строкой форматирования. Теперь ваша строка приветствия будет похожа на эту, когда вы перейдёте в каталог с Git репозиторием:
Оба вышеперечисленных скрипта снабжены полезной документацией, загляните внутрь
git-completion.bash
и git-prompt.sh
чтобы узнать больше.
Git поставляется с поддержкой автодополнения для Zsh. Чтобы начать им пользоваться, просто добавьте строку autoload -Uz compinit && compinit
в ваш .zshrc
файл. Интерфейс Zsh более функциональный чем в Bash:
$ git che<tab>
check-attr -- display gitattributes information
check-ref-format -- ensure that a reference name is well formed
checkout -- checkout branch or paths to working tree
checkout-index -- copy files from index to working directory
cherry -- find commits not merged upstream
cherry-pick -- apply changes introduced by some existing commits
Возможные варианты автодополнения не просто перечислены; они снабжены полезными описаниями и вы можете выбрать нужный вариант из предложенных, перемещаясь по ним нажатием клавиши Tab. Это работает не только для команд Git, но и для их аргументов, названий объектов внутри репозитория (например, ссылки и удалённые репозитории), а также для имён файлов и прочего.
В состав Zsh входит фреймворк vcs_info
, предназначенный для извлечения информации из систем контроля версий. Чтобы отобразить имя текущей ветки в правой строке приветствия, добавьте следующие строки в файл ~/.zshrc
:
autoload -Uz vcs_info
precmd_vcs_info() { vcs_info }
precmd_functions+=( precmd_vcs_info )
setopt prompt_subst
RPROMPT=\$vcs_info_msg_0_
# PROMPT=\$vcs_info_msg_0_'%# '
zstyle ':vcs_info:git:*' formats '%b'
В результате вы будете видеть имя текущей ветки в правой части окна терминала каждый раз, как перейдёте внутрь Git репозитория. (Для отображения названия ветки слева используйте PROMPT
вместо RPROMPT
)
Дополнительную информацию о vcs_info
можно найти в документации zshcontrib(1)
или онлайн http://zsh.sourceforge.net/Doc/Release/User-Contributions.html#Version-Control-Information.
Возможно, вы предпочтёте использовать поставляемый вместе с Git скрипт настройки git-prompt.sh
; детали использования приведены в комментариях к файлу https://github.com/git/git/blob/master/contrib/completion/git-prompt.sh.
Скрипт git-prompt.sh
совместим с обеими оболочками Bash
и Zsh
.
Zsh
настолько конфигурируем, что существуют целые фреймворки, посвящённые его улучшению. Пример такого проекта, называемый oh-my-zsh
, расположен на https://github.com/robbyrussell/oh-my-zsh. Система плагинов этого проекта включает в себя мощнейший набор правил автодополнения для Git, а многие «темы» (служащие для настройки строк приветствия) отображают информацию из различных систем контроля
версий. Пример темы oh-my-zsh
— лишь один из многих вариантов применения.