git clone: копия репозитория
Чтобы скачать проект с GitHub, используют команду git clone <адрес>: она создаёт папку, кладёт в неё полный репозиторий со всей историей и сама записывает адрес как origin. Сейчас вы сделаете вторую копию своей визитки и научитесь синхронизировать две копии через GitHub.
Зачем вторая копия? Вы можете работать то на ноутбуке, то на настольном компьютере, а в команде у каждого свой клон, и всем нужно получать чужие изменения. Мы смоделируем это сами: вторая папка portfolio-colleague будет «компьютером коллеги», который работает с тем же репозиторием на GitHub.
Клон (clone) — это полная копия репозитория: все файлы, все коммиты и вся история. От простого скачивания архива он отличается тем, что остаётся связанным с оригиналом. Выполните, находясь в папке ~/projects (рядом с portfolio, а не внутри него):
$ cd ~/projects
$ git clone https://github.com/alex-ivanov/portfolio.git portfolio-colleague
Cloning into 'portfolio-colleague'...
...
$ cd portfolio-colleague
$ git status
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
$ git remote -v
origin https://github.com/alex-ivanov/portfolio.git (fetch)
origin https://github.com/alex-ivanov/portfolio.git (push)
$ ls -A
.git .gitignore README.md index.html projects.html style.css
Разбор по строкам
git clone <адрес> portfolio-colleague— скачать репозиторий в новую папку с указанным именем. Без имени Git взял бы имя из адреса (portfolio), и папка совпала бы с вашей рабочей.Cloning into 'portfolio-colleague'...— идёт скачивание. Вместо...вы увидите строки прогресса (Receiving objects,Resolving deltas), они зависят от размера проекта.Your branch is up to date with 'origin/main'.— клон сразу настроен: веткаmainсвязана сorigin/main, и флаг-uне нужен.git remote -v—originуже записан,git remote addделать не пришлось.ls -A— вся история лежит в скрытой папке.git. Папкиnotesнет: она в.gitignore, в Git не попадала и поэтому на GitHub не уехала.
Чтобы коммиты «коллеги» отличались от ваших, задайте для этой папки другое имя автора (без --global, настройка действует только в этом клоне):
$ git config user.name "Коллега"
$ git config user.email colleague@example.com
Изменения с другой стороны: fetch
Пусть «коллега» что-то поменял и отправил на GitHub. Допишите в конец README.md в папке portfolio-colleague пустую строку и строку «Автор: Алекс Иванов.», затем:
$ git commit -am "Добавить автора в README"
$ git push
To https://github.com/alex-ivanov/portfolio.git
a3bf107..71155ae main -> main
Теперь вернитесь в основную папку и спросите у Git, как дела:
$ cd ../portfolio
$ git status
On branch main
Your branch is up to date with 'origin/main'.
Git утверждает, что всё актуально, хотя на GitHub уже лежит новый коммит. Это не баг. Помните, что origin/main — ваша локальная пометка о том, где была ветка на GitHub при последнем обмене. git status смотрит только на неё и в интернет не ходит. Чтобы пометка обновилась, нужно сходить на сервер:
$ git fetch
From https://github.com/alex-ivanov/portfolio
a3bf107..71155ae main -> origin/main
$ git status
On branch main
Your branch is behind 'origin/main' by 1 commit, and can be fast-forwarded.
(use "git pull" to update your local branch)
nothing to commit, working tree clean
$ git log --oneline main..origin/main
71155ae Добавить автора в README
Разбор по строкам
git fetch— «сходи на GitHub, скачай новые коммиты и обнови пометкиorigin/*». Рабочие файлы и ваша веткаmainне меняются.From https://github.com/...— откуда скачали.a3bf107..71155ae main -> origin/main— пометкаorigin/mainпередвинулась с коммитаa3bf107на71155ae.behind 'origin/main' by 1 commit, and can be fast-forwarded— вашаmainотстаёт на один коммит, и её можно просто «перемотать» вперёд (fast-forward) без слияния.git log --oneline main..origin/main— коммиты, которые есть вorigin/main, но ещё нет вmain. ЗаписьA..Bчитается «всё, что в B, но не в A».
Частая путаница: fetch и pull
fetch только узнаёт о новом и скачивает коммиты, ваши файлы он не трогает, поэтому безопасен всегда. pull делает то же самое и сразу применяет новое к текущей ветке. Если хотите сначала посмотреть, что придёт, используйте fetch.
git pull = fetch + merge
**git pull** — это два действия подряд: git fetch (скачать) и затем слияние (merge) скачанного в вашу текущую ветку. Слияние вы уже знаете по модулю про ветки. Применим новый коммит:
$ git pull
Updating a3bf107..71155ae
Fast-forward
README.md | 2 ++
1 file changed, 2 insertions(+)
Fast-forward — перемотка: ваша ветка просто двинулась вперёд на чужой коммит, а новых коммитов слияния не появилось. Теперь git status снова скажет up to date, и README.md содержит строку про автора.
Когда ветки разошлись
Сложнее, если новые коммиты есть и там, и там. Продолжим сценарий. «Коллега» дописывает под строкой про автора фразу «Технологии: HTML, CSS, Git и GitHub.» и отправляет (commit, push). А вы, не зная об этом, в основной папке добавляете в конец style.css пустую строку и правило a { color: #3b82f6; }, делаете коммит Покрасить ссылки в синий и пытаетесь отправить:
$ git push
To https://github.com/alex-ivanov/portfolio.git
! [rejected] main -> main (fetch first)
error: failed to push some refs to 'https://github.com/alex-ivanov/portfolio.git'
hint: Updates were rejected because the remote contains work that you do not
hint: have locally. This is usually caused by another repository pushing to
hint: the same ref. If you want to integrate the remote changes, use
hint: 'git pull' before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
Разбор по строкам
! [rejected] main -> main (fetch first)— GitHub отказал: «на сервере есть коммиты, которых у вас нет, сначала скачайте». Если вы уже делалиfetch, вместо(fetch first)будет(non-fast-forward)и пояснение, что ваша ветка позади.failed to push some refs— отправить не получилось. Это защита: Git не даёт молча затереть чужую работу.hint: ... use 'git pull' before pushing again— подсказка и есть лечение: сначалаgit pull.
Посмотрим на ситуацию через git fetch и git status:
$ git fetch
$ git status
On branch main
Your branch and 'origin/main' have diverged,
and have 1 and 1 different commits each, respectively.
(use "git pull" if you want to integrate the remote branch with yours)
diverged значит «разошлись»: у вас один свой коммит, на GitHub один чужой, и общее у вас только то, что было раньше. На схеме:
D main (у вас)
/
A---B---C
\
E origin/main (на GitHub)
Что на схеме: общая история (A, B, C) дальше расходится на два коммита, ваш D и чужой E. Их нужно объединить слиянием. Это делает git pull:
$ git pull
Merge made by the 'ort' strategy.
README.md | 1 +
1 file changed, 1 insertion(+)
Перед этим откроется редактор с готовым сообщением коммита слияния Merge branch 'main' of https://github.com/alex-ivanov/portfolio. Сообщение менять не надо: сохраните файл и закройте вкладку. Git создаст коммит слияния, и история будет такой:
$ git log --oneline --graph -5
* 71e4ceb Merge branch 'main' of https://github.com/alex-ivanov/portfolio
|\
| * 404e947 Добавить технологии в README
* | 55c1edd Покрасить ссылки в синий
|/
* 71155ae Добавить автора в README
* a3bf107 Дополнить README
Теперь можно отправлять: git push пройдёт, потому что ваша ветка включает всё, что есть на GitHub. Если бы оба коммита правили одну и ту же строку, при pull вы получили бы конфликт: он решается так же, как конфликт слияния веток из прошлого модуля (правка, git add, git commit).
Почему pull.rebase false и что будет без него
Когда ветки разошлись, Git может объединить их двумя способами: слиянием (merge) или перебазированием (rebase). Какой выбрать, решаете вы, а до решения Git git pull не выполнит. Именно поэтому в начале курса вы выполнили git config --global pull.rebase false: эта настройка говорит «объединять слиянием». Если её нет, вы увидите такое:
hint: You have divergent branches and need to specify how to reconcile them.
hint: You can do so by running one of the following commands sometime before
hint: your next pull:
hint:
hint: git config pull.rebase false # merge
hint: git config pull.rebase true # rebase
hint: git config pull.ff only # fast-forward only
hint:
hint: You can replace "git config" with "git config --global" to set a default
hint: preference for all repositories. You can also pass --rebase, --no-rebase,
hint: or --ff-only on the command line to override the configured default per
hint: invocation.
fatal: Need to specify how to reconcile divergent branches.
Перевод главной строки: «нужно указать, как согласовать разошедшиеся ветки». Исправление: git config --global pull.rebase false и повторите git pull. Вариант rebase разберём в модуле про чистую историю, а пока пользуйтесь слиянием.
Практика у себя
- В папке
~/projectsсклонируйте репозиторий:git clone https://github.com/ваш-логин/portfolio.git portfolio-colleague. Войдите в папку и проверьтеgit status: должно бытьup to date with 'origin/main'. - Задайте в клоне другого автора:
git config user.name "Коллега"иgit config user.email colleague@example.com(без--global). - В клоне допишите в
README.mdпустую строку и «Автор: Алекс Иванов.», выполнитеgit commit -am "Добавить автора в README"иgit push. - Перейдите в
~/projects/portfolio. Выполнитеgit status(тамup to date), затемgit fetchи сноваgit status: теперьbehind ... by 1 commit. - Посмотрите, что придёт:
git log --oneline main..origin/main. Примените:git pull. Должно появитьсяFast-forward. - Создайте расхождение. В клоне допишите под строкой про автора «Технологии: HTML, CSS, Git и GitHub.», закоммитьте как
Добавить технологии в READMEи отправьте. В основной папке допишите вstyle.cssпустую строку иa { color: #3b82f6; }, закоммитьте какПокрасить ссылки в синий. - В основной папке выполните
git push— получитеrejected. Затемgit fetch,git status(diverged),git pull(сохраните и закройте окно сообщения коммита) иgit push. - Посмотрите
git log --oneline --graph -5: коммит слияния и два параллельных коммита.
Проверьте себя
- В
~/projectsдве папки:portfolioиportfolio-colleague, обе сremote originна ваш репозиторий. - В
git log --oneline --graphвиден коммитMerge branch 'main' of https://github.com/.... - После последнего
git pushкомандаgit statusв основной папке сообщаетup to date with 'origin/main'. - Вы можете объяснить разницу между
fetchиpullи то, почему пометкаorigin/mainне обновляется сама.
Частые ошибки
Начали работать с утра, не сделав pull
Что видно: вы закоммитили, а git push отвечает rejected. Причина: пока вы работали, на GitHub что-то изменилось (в команде — чужие коммиты, у вас — работа с другого компьютера). Привычка, которая этого избегает: начинать день с git pull, а перед сложной правкой делать git fetch и смотреть git status.
git push --force, «чтобы прошло»
Не используйте --force в общей ветке
git push --force отправляет вашу версию, стирая коммиты, которых у вас нет. На общей ветке main это безвозвратно удаляет чужую работу. Если Git отказал, решение одно: git pull, решить конфликты, git push. Принудительная отправка нужна в редких случаях после перебазирования, и о ней мы поговорим позже, с безопасным вариантом.
Клон внутри другого репозитория
Если выполнить git clone из папки portfolio, получится репозиторий внутри репозитория: внешний git status покажет неотслеживаемую папку portfolio-colleague/, а команды будут путаться между двумя .git. Исправление: удалите вложенную папку и клонируйте заново из ~/projects. Перед клонированием проверяйте pwd.
Конфликт при pull
Если после git pull Git пишет CONFLICT (content): Merge conflict in ..., ничего не сломалось: одну строку поменяли в двух местах. Решайте так же, как в модуле про ветки: правка файла, git add, git commit; передумали — git merge --abort.
Что важно запомнить
git clone <адрес> <папка>скачивает репозиторий с историей и сам настраиваетorigin.origin/main— локальная пометка о том, гдеmainбыла на GitHub при последнем обмене.git fetchобновляет её,git statusв интернет не ходит.git pull=git fetch+ слияние. Если ваша ветка просто отстала, будетFast-forward; если разошлась, — коммит слияния.rejected ... (fetch first)значит: на GitHub есть чужие коммиты. Лечение:git pull, при необходимости решить конфликт,git push.- Не используйте
git push --forceна общей ветке. Начинайте работу сgit pull.
Теперь у вас есть всё для синхронизации двух копий проекта через GitHub. Осталось доделать главное, ради чего мы затеяли этот модуль: показать визитку миру. В последнем уроке модуля вы включите GitHub Pages и превратите репозиторий в настоящий сайт.