Skip to content

Instantly share code, notes, and snippets.

@1234ru
Last active April 16, 2024 17:17
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save 1234ru/d15722f9a76ab7f901afa0003bd0dfef to your computer and use it in GitHub Desktop.
Save 1234ru/d15722f9a76ab7f901afa0003bd0dfef to your computer and use it in GitHub Desktop.
Полезное при работе с git

Отмена изменений

Вернуть файл к состоянию последнего коммита (аналог svn revert) можно командой git checkout файл. Уточнение: файл будет возвращен к зафиксированному состоянию, если после коммита была явно дана команда git add файл. То есть, откатываются изменения, сделанные после git commit или git add. Если файл был удален, то нужно указать версию; например git checkout HEAD файл.

Так можно откатить и всю папку (в т.ч. рабочий каталог): git checkout ..

Для отмены изменений на уровне рабочей копии служит команда git reset. Она "отматывает" историю изменений назад до указанного момента (в том числе отменяет коммиты), не трогая физические файлы:

  • git reset файл

С ключом --hard переписываются и файлы.

  • git reset --hard HEAD^ - отменить последний коммит
  • git reset --hard HEAD~2 - отменить два последних коммита

Отменять коммиты можно, только если они не опубликованы в общий репозиторий (не было git push).

Отменить добавление нового файла в индекс можно командой git rm --cached файл.

git log

  • история изменений, по одной строке на каждый коммит - git log --oneline
  • вариант сжатого формата истории коммитов: --pretty=format:"%h %cd %s"
  • то же с выделением цветом: --pretty=format:"%C(auto)%h %C(green)%cd%C(reset) %s" (опция %C(auto) включает использование цветов по умолчанию для стандартных частей отформатированной строки, эти цвета недоступны через прямое указание)
  • метка: --pretty=format:"%(describe:tags=true)"
  • формат даты задается отдельным аргументом: --date=format:"%d.%m.%Y %H:%M"
  • всё вышеперечисленное:
    git log --pretty=format:" %C(yellow)%h %Cgreen%cd%Creset %s %C(red) %(describe:tags=true)%C(reset)" --date=format:"%d.%m.%Y %H:%M"
    

Полный список меток можно посмотреть командой git help log в секции pretty.

  • распечатать сообщение указанного коммита: git log (коммит) --format=%B -n 1
    в качестве идентификатора коммита достаточно первых пяти символов его хэш-суммы либо специального имени типа HEAD
  • дополнить последний коммит в одну команду с сохранением сообщения:
    git commit --amend --no-edit
  • фильтрация по строке в тексте коммита: git log --grep="шаблон"
    (можно указывать опцию несколько раз, результат будет объединением; при --all-match=1 - пересечением)
  • вывод истории только для локальной ветки, отпочковавшейся от master:
    git log master.. (обратить внимание на две точки в конце!)
    подробнее см. https://stackoverflow.com/a/4649377/

Получение истории изменений репозитория, который хранится где-то еще

Без клонирования репозитория к себе не обойтись, но это можно сделать в сокращенном виде (см. https://stackoverflow.com/a/60952814/589600):

git clone --filter=blob:none --no-checkout --single-branch --branch master git://some.repo.git .
git log

Поиск когда-то удаленного файла

git log --all --full-history -- путь/файл

или, если не известен путь:

git log --all --full-history -- "**/файл"

  • --all - предписывает команде охватить все коммиты всех веток
  • --full-history - (выясняется)
  • -- - разделитель аргументов

Источник: stackoverflow.com.

Посмотреть последнюю версию файла можно с помощью команды

git show HEAD^:путь-к-файлу

Текст commit-сообщения

Первую строку нужно отделять от остального текста двойным переводом строки. Тогда в короткий формат git log войдет только она. В противном случае туда попадет сообщение полностью, что снизит удобочитаемость списка.

Чтобы редактор vi открывался сразу в режиме ввода, файл ~/.gitconfig должен содержать секцию [core]:

[core]
  editor = 'vim' -c 'startinsert'

git alias - сокращения для команд

Добавить сокращение: git config alias.<имя> '<команда>'. Лушче давать команду с ключом --global, чтобы иметь к нему доступ из-под своего пользователя ОС в любом месте.

Например:

git config --global alias.st 'status -s'
git config --global alias.lg 'log --pretty=format:" %C(auto)%h %Cgreen%cd%Creset %s" --date=format:"%d.%m.%Y %H:%M"'
git config --global alias.ci 'commit --no-status'

Посмотреть список сокращений: git config --get-regexp alias. Без уточнения области видимости типа --system или --global будут показаны сокращения всех уровней. Область видимости нужно указывать до alias.

Удалить сокращение можно командой git config --unset alias.имя.

Другие полезные команды

  • откатить рабочий каталог к ревизии № rev. - git checkout (rev.) (допустим четырехзначный формат); git checkout (rev.) -- [файл] - откатить конкретный файл
  • посмотреть старую версию файла (в т.ч. удалённого): git show коммит:path/to/file
  • убрать файл из-под контроля git, оставив его при этом на диске: git rm --cached файл; очень важна при этом опция --cached - без неё команда удалит файл и с диска
  • git commit --no-status - начать с пустого сообщения для коммита в редакторе, без git diff
  • git ls-files -o - просмотр списка неотслеживаемых файлов; --exclude-standard - исключить из списка игнорируемые, --directory - не разворачивать содержимое неотслеживаемых каталогов (в список тогда попадут также пустые каталоги - они никогда не отслеживаются)
  • git status --ignored - показывать полный список игнорируемых файлов

Разное

  • клонировать репозиторий в непустой каталог (git clone ...) нельзя; инициализировать в непустом каталоге (git init) — можно
  • конфликты при content merge без прямого пересечения бывают, если изменения в разных ветках сделаны в соседних строках; если между зонами изменений есть хотя бы одна строка, слияние проходит автоматически
  • безусловно принять свою версию при конфликте можно, дав команду git checkout --ours файл, а затем - git add файл
  • при назначении вышестоящего репозитория (куда будет отправляться git pull) не важно, был ли он создан раньше дочернего; если дочерний был создан как отдельно стоящий (не клонированием), то зависимость устанавливается двумя командами:
    • git remote add origin (путь к файлу .git)
    • при первом git push нужно явно указать название репозитория (origin), ветку (master), и аргумент -u (--set-upstream):
    git push -u origin master
    
    • чтобы назначить вышестоящий репозиторий, необязательно делать push; специально для этого можно дать команду branch с тем же аргументом: git branch -u origin master, однако для этого вышестоящий репозиторий уже должен иметь коммиты, с пустым такое не получится
  • поменять upstream-ветку можно с помощью git remote set-url (ветка) (url), например:
    git remote set-url origin ssh://...
  • выявить причину неполадок при соединении по SSH поможет просмотр опции git config core.sshCommand и установка её в "ssh -v" (как было, например, в в этом случае)
  • включить цветной вывод (если уже не включен): git config --global color.ui true
  • отправить локальную ветку в вышестоящий репозиторий: git push origin ветка
  • получить id самого первого коммита: git rev-list --max-parents=0 HEAD (см. stackoverflow № 1007545); потом можно, например, просмотреть его сообщение - git log <id>
  • чтобы переключиться на ветку из вышестоящего репозитория, которой еще нет в локальном, нужно сначала выполнить git fetch origin ветка и уже потом на переключиться на неё - git checkout ветка

Почему не стоит вручную копировать рабочие копии на другие машины

Потому что возникают ошибки из-за разных идентификаторов пользователя ОС, которые на каждой машине уникальны, даже если имена пользователей совпадают (видимо, идентификатор присваивается самим git):

fatal: detected dubious ownership in repository at 'D:/s/portliss/1/vendor/one234ru/html-dynamic'
'D:/s/portliss/1/vendor/one234ru/html-dynamic' is owned by:
        'S-1-5-21-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxx'
but the current user is:
        'S-1-5-21-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxx'
To add an exception for this directory, call:

        git config --global --add safe.directory D:/s/portliss/1/vendor/one234ru/html-dynamic

От этих ошибок можно избавиться, выполнив команду из сообщения.

Если используется Composer, и были подключены пакеты в виде git-репозиториев, то эти ошибки возникнут для каждого из них.

Избавиться от ошибок во всех репозиториях сразу можно с помощью варианта команды для всех каталогов: --add safe.directory "*". Но лучше вообще избегать этой ситуации.

Слияние веток без истории изменений (в один коммит)

git checkout master
git merge --squash dev
git checkout --theirs .
git commit -a

Полезно, когда при разработке было сделано много промежуточных коммитов, которые по отдельности интереса не представляют, и все изменения просто нужно перенести в основную ветку в виде единственного коммита, чтобы не засорять историю изменений. При этом dev-ветка должна быть впереди master!

Для этого используется опция --squash команды git merge.

Если полученные изменения относятся к существующим файлам, они будут помечены как конфликт. Нужно скомандовать git безусловно принять вновь пришедшие изменения - git checkout . с опцией --theirs.

Частный случай: пустая ветка master (точнее, ее отсутствие). Такое бывает в начале разработки. Когда пора делать первый коммит в основную ветку, нужно дать команду git checkout --orphan master. Она добавит все файлы из рабочего каталога текущей ветки в новую, останется только выполнить git commit.

Получение изменений из master-ветки без переключения на неё

git pull origin master

Это намного удобней, чем давать последовательность команд

git checkout master
git pull
git checkout branch_name
git merge master

В том числе потому, что некоторые редакторы (например, PhpStorm) незамедлительно реагируют на физическое наличие на диске открытых файлов, и если файл удаляется с диска (что может происходить при переключении на master-ветку, если файл добавлен в ветке branch_name), то вкладки с ними автоматически закрываются и их приходится потом открывать заново.

git diff

  • Получить суммарные изменения между двумя коммитами:

    git diff коммит_1 коммит_2 каталог
    
  • Получить только имена изменённых файлов позволяет опция --name-only. Вот так, например, выглядит команда для получения такого списка относительно предпоследнего коммита:

    git diff --name-only HEAD~1
    

    При желании можно, например, добавить маски файлов: git diff {...} *.tpl *.css *.js.

    Вывести не только имена и статусы - команда --name-status.

  • Получить изменения по дате:

 git diff HEAD 'HEAD@{2020-10-01 12:00:00}' -- файл

Вместо точного времени можно указывать интервал относительно текущего момента - 'HEAD@{3 weeks ago}', а также указывать дату без временной части. Источник - stackoverflow.com (a/9658178 и a/41303758)

  • Вывести список файлов, в которых есть конфликты слияния:

    git diff --diff-filter=U
    

    U - unmerged.

    Файлы остаются помеченными как имеющие конфликт, даже если если метки конфликтов из них удалены (вручную или с помощью git checkout --theirs). Пометка исчезает, только когда файл добавляется в индекс вручную. Впрочем, для выполнения коммита это необязательно.

  • Исключить файлы из вывода - '!:маска', например:

    git diff --name-only HEAD~1 ':!*.scss'

Настройки

Посмотреть список всех локальных настроек: git config --list

Посмотреть список всех возможных настроек: git help config, секция Variables

Есть три уровня действия настроек: системный, текущего пользователя и конкретного репозитория (подробнее см. здесь).

Файлы .gitconfig имеют структуру:

[раздел]
        переменная = значение

которая соответствует команде

git config раздел.переменная = значение

Выполнение команды git config и редактирование файла .gitconfig вручную имеют одинаковый эффект.

Настройка git в bash

Для более удобной работы в оболочке bash (в т.ч. под Windows) существуют специальные shell-скрипты. Они размещены в публичном репозитории git (см. каталог contrib/completion).

Чтобы использовать заложенные в них возможности, нужно скачать их из репозитория куда-нибудь на рабочий компьютер (например, в свой же домашный каталог) и подключить в файле .bashrc.

Например, автодополнение команд по нажатию Tab (позволяет не печатать команды полностью; действует как на подкоманды, так и на их опции):

curl --output-dir ~ -O https://raw.githubusercontent.com/git/git/master/contrib/completion/git-completion.bash
# -O - использовать имя файла из ссылки
. ~/git-completion.bash

Для включения в системное приглашение информации о репозитории git нужно подключать файл git-prompt.sh и вызывать функцию __git_ps1:

export PROMPT_COMMAND='PS1="$(__git_ps1)"'
# даст приглашение типа 
# (название ветки)

То, что возвращает __git_ps1, определяется значениями переменных типа GIT_PS1_* (например, GIT_PS1_SHOWDIRTYSTATE - показывать ли звёздочку в случае, если есть неотслеживаемые файлы), полный список и описание которых можно найти в самом sh-файле.

export PROMPT_COMMAND='PS1="$(__git_ps1)"'
GIT_PS1_SHOWDIRTYSTATE=1

Также перечислены в статье.

Если git-часть приглашения требуется окружить пробелами или другими символами, нужно передать функции аргумент с меткой %s:

git_command='__git_ps1 "%s "'
# даст приглашение типа 
# (название ветки) >

Проверить работу функции можно непосредственно из командной строки, находясь в каталоге репозитория:

echo __git_ps1

Хуки

Хуки - это скрипты, которые могут выполняться при наступлении различных событий: при создании коммита, при получении коммита (на сервере) и пр. Подробнее - тут (en).

Пример - хуки для Composer.

Работа с подмодулями

Клонирование репозитория с подмодулями

При обычном клонировании связанные репозитории запрошены не будут. Запросить их можно, указав команде ключ --recurse-submodules:

git clone --recurse-submodules (repository) (local folder)

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

Если требуется внести в подчиненный репозиторий изменения, рекомендуется проверить, что указатель HEAD установлен на ветку master (бывает, что репозиторий имеет отсоединенный HEAD), просто посмотрев на результат команды git branch.

Подмодули также нужно инициализировать в upstream-репозитории, если туда была отправлена локальная ветка (git push origin ветка):

Выделение части кода проекта в подмодуль

Работа над подмодулем должна быть локализована в каком-то подкаталоге. Перед тем, как инициализировать подмодуль в проекте, его нужно опубликовать на github или каком-то другом хранилище, после чего выполнить команду

git submodule add https://...

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

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

git submodule init (путь к каталогу подмодуля)
git submodule update (путь к каталогу подмодуля)

Команда вида git submodule init (локальный каталог) не сработает, т.к. она ожидает пути к централизованному хранилищу, откуда потом можно будет забирать изменения.
Можно, однако, сначала перейти в каталог подмодуля, а потом последовательно выполнить команды git submodule init и git submodule update без указания каталога, как это сделано в примере из документации.

git submodule update --recursive --init подмодуль

При внесении изменений в подмодуль в вышестоящем репозитории git pull из него не обновляет подмодуль, вместо этого его каталог помечается как измененный. Чтобы изменения вступили в силу, нужно выполнить команду git submodule update подмодуль.

В конце работы рекомендуется просмотреть содержимое файла .gitmodules и исправить имена модулей с автоматически сгенерированных из путей на более удобные и понятные.

Трудности с обновлением подмодулей при работе по схеме push-to-deploy

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

Если вышестоящий репозиторий при этом является production-копией, как это имеет место в схеме push-to-deploy, обновление модулей должно следовать сразу же за командой push, т.к. сразу после push система может оказаться в нерабочем состоянии: код проекта обновился, а подчиненные модули - нет.

Немного о работе с github.com

Соединение с помощью SSH-ключа

Если планируется не только чтение, но и внесение изменений, нужно соединяться не по https, а по ssh, причем *обязательно от имени пользователя git: git@github.com.

Чтобы это сработало, нужно добавить свой публичный SSH-ключ через настройки учетной записи. Подробная инструкция здесь.

Проверить работу ключа можно, выполнив команду ssh -vT git@github.com (см. docs.github.com).

Поменять URL репозитория можно так:

git remote set-url origin git@github.com:<user>/<repository>.git

При первой отправке изменений в пустой, только что созданный, репозиторий нужно явно установить (обычно - продублировать) название будущей ветки. Делается это с помощью ключа -u/--set-upstream:

git push --set-upstream origin master

или, в общем случае

git push --set-upstream origin ветка

Такая процедура связана с тем, что в пустом репозитории на сервере github веток ещё нет. Подробнее см. https://stackoverflow.com/q/17096311/

Делать это можно только при наличии коммитов в локальной ветке. При пустой ветке команда не сработает.

Также нельзя обойтись одной только командой git push без создания репозитория через веб-интерфейс Github.

Если прямое соединение с помощью ssh проходит успешно, а команды git не проходят, нужно посмотреть вывод git config core.sshCommand - не указано ли там имя пользователя явно (как было в этом случае).

Работа с gists как репозиториями

Технически любой gist - это просто репозиторий на Github, процедура работы с которым упрощена:

  • названием является хэш типа f3cef177af2a86fbf0a8deb46c515b3b
  • при внесении правок через веб-интерфейс не требуется заполнять сообщения commit (что видно в git log)

Метки (tags)

Часто применяются для указания номеров версий, которые потом используются в т.ч. Composer'ом.

git tag -l - просмотреть список меток

Создать метку без подписи:

git tag название
# например, git tag v1.0

Создать метку с подписью (аннотированную):

git tag -a v1.0 -m "Подпись"

Метка будет приписана к последнему коммиту.

Можно назначать метку задним числом для прошлых коммитов. Для этого контрольную сумму коммита или ее часть нужно указать в конце команды:

git tag -a v1.0 abcde01

Метки сами не отправляются в вышестоящий репозиторий. Нужно либо отправлять каждую поименнно:

git push origin имя_метки

либо отправить их все сразу:

git push origin --tags

Подключение пакетов в Composer по адресу хранилища git

Ключевой момент: открываем composer.json и вручную добавляем туда запись в раздел repositories:

"repositories": [
  {
    "type": "git",
    "url": "ssh://user@host:port/path"
  }
]

Также обязательно нужен раздел autoload типа

{
    "autoload": {
        "psr-4": {"Namespace\\": "./"}
    }
}

После этого нужно дать команду composer require. При этом надо иметь в виду, что если проекту не назначаются версии в виде меток git (tags), команде нужно явно указать ветку в виде названия с префиксом dev-. Например, для ветки master команда будет выглядить так:

composer require vendor/package:dev-master

В противном случае обычная команда выдаст ошибку:

Could not find a version of package vendor/package matching your minimum-stability (stable). Require it with an explicit version constraint allowing its desired stability.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment