Как сделать pull request

В кругу моих знакомых имеются товарищи весьма и весьма далёкие от мира железок, интернета и open source (в общем, они пропустили всё самое интересное). Не далее как на прошлой неделе угораздило таки употребить при них непотребное словосочетание «pull request», после чего, ловя недоумённые взгляды, пришлось мяться и придумывать как сие получше перевести на великий и могучий. Увы, к взаимопониманию это не привело: попробую ответить здесь.

Pull Request — запрос на включение. На включение написанного вами кода в чужой репозиторий.

С чего начать?

А для начала этот самый репозиторий нужно форкнуть (fork — вилка, ответвление). Разберём это нехитрое действо на примере веб-сервиса для хостинга IT-проектов, название которому GitHub. Разумеется, кроме GitHub есть и другие: BitBucket, например. Выбирать по вкусу.

Примечание

Для успешного проведения нижеизложенных операций у вас (что естественно) должен быть установлен git

Заходим на страницу интересующего проекта, жмём кнопку Fork, ищем на своей странице URL для клонирования.

fork

В консоли в зависимости от входных данных набираем нечто подобное:

$ git clone git@github.com/username/django_documentation.git

Отлично. Уже можно вносить свои изменения в код проекта.

Тот репозиторий, что теперь лежит на вашем жёстком диске, независим от основного. В нём отслеживаются только ваши наработки. Но как следить за изменениями, происходящими в первоисточнике, откуда вы «стянули» репозиторий? Добавить удаленный репозиторий в отслеживаемые. Например, так:

$ git remote -v
origin  git@github.com:username/django_documentation.git (fetch)
origin  git@github.com:username/django_documentation.git (push)

$ git remote add upstream git@github.com:Alerion/django_documentation.git

После добавления upstream в отслеживаемые, наберите команду git remote -v ещё раз, вы должны увидеть произошедшие изменения.

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

$ git pull upstream master
From github.com:Alerion/django_documentation
* branch            master     -> FETCH_HEAD
Updating 66f032e..062b294
Fast-forward
djbook/commands/authors.py                    | 65 ++--
djbook/deps.txt                               |  6 ++--
djbook/templates/authors.html                 | 14 ++--
djbook/templates/base.html                    |  7 ++--
locale/ru/LC_MESSAGES/faq/install.po          | 94 +++-
locale/ru/LC_MESSAGES/faq/usage.po            | 49 +---
locale/ru/LC_MESSAGES/howto/outputting-csv.po | 61 ++--
locale/ru/LC_MESSAGES/topics/cache.po         | 77 ++--
locale/ru/LC_MESSAGES/topics/db/multi-db.po   | 80 +++-
9 files changed, 290 insertions(+), 163 deletions(-)

У нас есть основной репозиторий с веткой master и недавно добавленный нами — с upstream. Команда, данная выше, забирает всё новое из ветки upstream и сливает изменения в master. Так, мы всегда можем получить последние наработки.

Вы можете также воспользоваться другой командой — fetch вместо указанной pull. В таком случае git заберет изменения с удаленного репозитория, но не будет пытаться слить их с вашей текущей веткой автоматически.

$ git fetch upstream
$ git merge upstream/master

Допустим, мы написали, что хотели и сделали коммит. Забираем изменения (но не сливаем их, fetch) из upstream. Затем вручную сливаем изменения из удалённой ветки upstream/master к себе (merge), разрешаем конфликты (если есть) и снова делаем коммит.

Если репозиторий огромен, а забирать его весь не хочется, клонируем только нужную ветку:

# клонировать только ветку real_branch:
$ git clone -b real_branch --single-branch git@github.com/username/django_documentation.git

Что такое ветки?

Чаще всего ветки (branch — ответвление, ветвь, филиал) бывают тематическими. Например, при общей разработке, когда у всех участников есть право записи в репозиторий. В этом случае ветки используются для отделения изменений, сделанных одним из разработчиков, от общего репозитория. Ветки могут пригодиться и в случае с созданием pull-request'а.

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

# отобразить все ветки
$ git branch -a
* master
remotes/origin/1.4
remotes/origin/HEAD -> origin/master
remotes/origin/master
remotes/upstream/1.4
remotes/upstream/master

# создать новую ветку из master, переключиться на неё
$ git checkout -b new_branch

Новые ветки создаются не только из master, берите любую!

# создать новую ветку из удалённой origin/1.4
$ git checkout -b new_branch origin/1.4

# если ветки нет в отслеживаемых, а отслеживать надо, поможет флаг t (track)
$ git checkout -t origin/1.4

Находясь в только что созданной ветке, вы можете приступить к работе. Вносите в код свои изменения, а когда закончите просто переключитесь обратно к своей основной ветке. Вы можете отправить pull request, выбрав ветку new_branch или же прежде слить изменения из неё в основную ветку разработки. Рассмотрим это подробнее:

$ git checkout master  # вернуться к основной ветке
$ git merge new_branch # слить изменения из new_branch

Если нужно отправить в свой удалённый репозиторий вновь созданную ветку (не сливать её с master), делаем следующее:

$ git push master new_branch

Не торопитесь сливать изменения. Если что-то не заладилось, созданную ветку можно удалить:

$ git branch -d new_branch            # удалить локально
$ git push master :new_branch         # в удалённом репозитории

# ещё один способ удалить ветку в удалённом репозитории
$ git push origin --delete new_branch

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

$ git branch --merged | egrep -v "(master|develop)" | xargs git branch -d

Отправляем изменения

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

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

Перед тем как сделать запрос вы имеете возможность добавить комментарий, просмотреть то, какие файлы будут изменены, какие коммиты добавлены. В верхнем углу окна добавления запроса обратите внимание откуда куда и что вы сливаете. Если необходимо слить основные ветки выбор падёт на репозиторий username:master, если отдельную ветку (вспоминаем branch) — так и указывайте её.

request

А дальше... ждать. Пока придёт владелец оригинального репозитория и примет/отклонит ваши изменения.

Ну вот, мы его достигли. Просветления то есть :)

Как отменить изменения

Если что-то пошло совсем не так как хотелось, изменения можно «откатить». Когда изменённый файл ещё не проиндексирован, сделать это просто:

$ git checkout -- myfile

Когда нужно вернуть более старое состояние уже проиндексированных файлов и забыть о них совсем (помните, что упомянутая здесь операция отменит всю вашу работу до определённого коммита!):

$ git log
$ git reset --hard HEAD~1

Cмотрим на какой коммит откатиться. В примере откатываемся назад на 1 коммит. Для изменения состояния в этой же ветке удалённого репозитория тоже придётся использовать грубую силу — флаг force:

$ git push origin new_branch:new_branch --force

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

$ git checkout 4b9df4bbd -- files

Дословно можно понимать эту команду как «из коммита 4b9df4bbd вернуть files». Затем останется только зафиксировать изменения (сделать коммит).

Кстати, git log очень полезная команда, её изучению определённо стоит уделить время. Например, полезно знать, что при помощи флага -S мы можем получить список всех коммитов, в которых менялась строка, а соответственно и имя автора коммита.

$ git log -S "mystring"

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

$ git log --follow -p путь_к_файлу

Последний пример покажет как стереть историю коммитов (фактически удалить .git и запушить с флагом --force):

$ git clone https://github.com/user/user-repo.git
$ cd user-repo
$ rm -rf .git/
$ git init
$ git add .
$ git commit -m 'первый коммит'

$ git remote add origin https://github.com/user/user-repo.git
$ git push --force origin master

А подробнее?

Ну, что ещё? Применительно к github'у можно отметить факт наличия у них Issues. Это такая «примочка», благодаря которой можно ставить задачи для проекта, давать им описание и метку, определяющую к какому разряду данная задача относится (закрытие бага или написание фичи), а ещё назначать ответственного за выполнение определённого issue: всё как у больших.

Итогов подводить не стану. Для заинтересованных лиц ссылочка на неофициальную документацию: The Git Community Book