Course from Stepik

2. Использование существующих репозиториев

При клонировании репозитория есть два варианта

Если клонировать через https, то и пушить гит будет через https, а при работе по https он будет спрашивать пароль, при работе через ssh он будет сразу пушить (такое поведение легко исправить через конфиг (в конфиге указывается метод взаимодействия))

Для работы по ssh нужно чтобы на сервере был добавлен Ваш ssh-ключ


При клонировании можно сразу указать новую директорию

git clone git@github.com:vandud/test.git ~/Documents/testgitrepo  

Но это сработает только с несуществующей директорией (уже существующая директория имеет в себе хардлинки (. и ..))


Конфиги есть трех видов и хранятся в трех местах

  1. Глобальные для всего сервера\компьютера - /etc/gitconfig - ключ --system
  2. Глобальные для пользователя - /home/$USER/.gitconfig или /home/$USER/config/git/config - ключ --global
  3. Конфиг для конкретного репозитория - repodir/.git/config

Конфиги применяются в описаном порядке, то есть третий конфиг перекрывает все предыдущие


После установки git сразу нужно указать имя и емайл пользователя (эта информация передается в коммитах)

git config --global user.name "Ivan Dudin"
git config --global user.email s@vandud.ru

Команда git config --list покажет текущие настройки

vandud@vandud-desktop:~/Documents/testgitrepo$ git config --list
user.name=Ivan Dudin
user.email=s@vandud.ru

Любой файл имеет два состояния

Чтобы проиндексровать файл, нужно выполнить команду

git add 

Чтобы запомнить состояние файла(ов)

git commit

Чтобы отправить информацию (коммиты) на сервер

git push

3. Обзор текущего состояния локального репозитория

Для обзора текущего состояния локального репозитория обычноиспользуют команду git status
К git status можно добавлять ключ -s, для краткого вывода

[vandud@thinkpad test]$ git status
On branch master
Your branch is up to date with 'origin/master'.

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	new file:   a
	new file:   b
	new file:   c

Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   a
	deleted:    b
	modified:   c

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	txt

[vandud@thinkpad test]$ git status -s
AM a
AD b
AM c
?? txt

git add

https://git-scm.com/docs/git-add

Команда git add FILE добавляет файл в индекс гита
Индекс содержит в себе снапшот контента рабочего дерева и из него будет браться контент для следующего коммита

Таким образом после изменений в рабочей директории перед коммитом нужно выполнить команду add чтобы добавить новые или модифицированные файлы в индекс

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

git status

https://git-scm.com/docs/git-status
Показывает состояние текущего расположения. Точнее показывает разницу между текущим состоянием и последним коммитом

При выводе краткого статуса возможно два варианта

XY PATH
XY ORIG_PATH -> PATH # когда файл был переименован  

Значения статусов:

git commit

Создает новый коммит содержащий в себе текущий контент индекса и переданное ему сообщение, которое описывает сделанные изменения

Коммитируемый контент может быть добавлен в коммит несколькими способами

Опция --dry-run позволит увидеть все добавленное в индекс для следующего коммита (т.е. все что будет включено в следующий коммит)

git log

Показывает историю коммитов (коммиты и некоторую инфу о них)
По умолчанию в обратно-хронологическом порядке (новее=выше)

Есть два понятия, коммитер и автор, они отличаются

git checkout

Переключение между ветвями или восстановление файлов в рабочей директории
Приводит файлы в рабочей директории к состоянию какой-либо версии из индекса

Переключение на ветвь это изменение файлов в соответствии с индексом и перевешивание указателя HEAD

HEAD – это указатель на коммит в вашем репозитории, который станет родителем следующего коммита

4. Коммит изменений

macbook:test vandud$ git rm govno 
rm 'govno'

macbook:test vandud$ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	deleted:    govno

macbook:test vandud$ git commit -m 'zhopa23'
[master fb6a8c6] zhopa23
 1 file changed, 1 deletion(-)
 delete mode 100644 govno
 
macbook:test vandud$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)
nothing to commit, working tree clean
macbook:test vandud$ 

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

.gitignore

macbook:test vandud$ cat .gitignore 
*.zhopa

macbook:test vandud$ vim zhopa.zhopa

macbook:test vandud$ git status -s
?? .gitignore

macbook:test vandud$ git add zhopa.zhopa 
The following paths are ignored by one of your .gitignore files:
zhopa.zhopa
Use -f if you really want to add them.

Файл .gitignore нужен для вывода антрэкед файлов из поля зрения гита. Уже затреченые файлы он продолжит видеть не смотря на гитигнор

По формату файла можно смотреть тут - https://git-scm.com/docs/gitignore

macbook:test vandud$ git add -f zhopa.zhopa 
macbook:test vandud$ git status -s
A  zhopa.zhopa
?? .gitignore

git-add

Добавляет контент файлов в индекс
Можно перечислять файлы поименно, можно использовать ключ -A/--all, можно использовать шаблоны как в .gitignore

macbook:test vandud$ git add --dry-run au.zhopa 
The following paths are ignored by one of your .gitignore files:
au.zhopa
Use -f if you really want to add them.

macbook:test vandud$ git add --dry-run aou.aeou 
add 'aou.aeou'

macbook:test vandud$ git status -s # видим что ничего не добавилось
A  zhopa.zhopa
?? .gitignore
?? aou.aeou

Чтобы обновить инфу об удаленных файлах в индексе, нужно добавить их командой add

macbook:test vandud$ touch test
macbook:test vandud$ git status -s
?? test
macbook:test vandud$ git add test
macbook:test vandud$ git status -s
A  test
macbook:test vandud$ rm test
macbook:test vandud$ git status -s
AD test
macbook:test vandud$ git add test
macbook:test vandud$ git status -s
macbook:test vandud$ 

git-rm

Удаляет файл(ы) и обновляет индекс, принимает имена файлов или шаблоны

Не даст удалить проиндексированный но не закоммиченный файл
Чтобы все равно удалить, нужно использовать ключ -f/--force

macbook:test vandud$ git add aou.aeou 
macbook:test vandud$ git rm aou.aeou 
error: the following file has changes staged in the index:
    aou.aeou
(use --cached to keep the file, or -f to force removal)

macbook:test vandud$ git commit -m 'test rm'
[master cc5a978] test rm
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 aou.aeou

macbook:test vandud$ git rm aou.aeou 
rm 'aou.aeou'
macbook:test vandud$ git status -s
D  aou.aeou
?? .gitignore

А вот так с ключом -f

macbook:test vandud$ touch test
macbook:test vandud$ git status -s
D  aou.aeou
?? .gitignore
?? test

macbook:test vandud$ git add test 
macbook:test vandud$ git status -s
R  aou.aeou -> test
?? .gitignore

macbook:test vandud$ git rm test 
error: the following file has changes staged in the index:
    test
(use --cached to keep the file, or -f to force removal)

macbook:test vandud$ git rm -f test
rm 'test'
macbook:test vandud$ git status -s
D  aou.aeou
?? .gitignore
macbook:test vandud$ git status -s
A  zhopa.zhopa
?? .gitignore
?? aou.aeou

macbook:test vandud$ cat zhopa.zhopa 
santoehu

macbook:test vandud$ git rm --cached zhopa.zhopa 
rm 'zhopa.zhopa'

macbook:test vandud$ git status -s
?? .gitignore
?? aou.aeou

macbook:test vandud$ cat zhopa.zhopa 
santoehu

git-commit

"Слепок индекса"

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

macbook:test vandud$ git log --oneline
cc5a978 (HEAD -> master) test rm
fb6a8c6 zhopa23
5876123 (origin/master) govno
3993fdf rm
6b356d6 double zhop commit
6cdf909 zhopnyi commit
macbook:test vandud$ git status -s
D  aou.aeou
?? .gitignore
macbook:test vandud$ git add .gitignore 
macbook:test vandud$ git status -s
A  .gitignore
D  aou.aeou
macbook:test vandud$ git commit -m '--amend test' --amend
[master 176e908] --amend test
 Date: Sun Feb 7 05:09:54 2021 +0300
 1 file changed, 1 insertion(+)
 create mode 100644 .gitignore
macbook:test vandud$ git status -s
macbook:test vandud$ git log --oneline
176e908 (HEAD -> master) --amend test
fb6a8c6 zhopa23
5876123 (origin/master) govno
3993fdf rm
6b356d6 double zhop commit
6cdf909 zhopnyi commit

5. Создание веток

aeouaeouaoeua.png

git branch - создает новую ветвь
git checkout - переключает на ветвь
git checkout -b - объединяет две пред. команды
git merge - мерджит указанную ветвь в текущую


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

git checkout [options] [<branch>]/<commit>/<tag>
git checkout [options] <file_name_to_restore>

-f --force - Позволяет переключиться между ветками, даже если состояние файлов отличается от состояния, обозначенного как HEAD. Может использоваться для отмены локальных изменений
-b <new_branch> - Создаёт новую ветку с именем <new_branch>; для получения дополнительной информации см. git-branch


Переход к другой ветке - git checkout master
Переход к коммиту с тегом HEAD - git checkout HEAD
Переход к коммиту, предшествующему HEAD на 4 - git checkout HEAD~4
Найти хеш коммита с использованием git log и перейти к этому коммиту:

git log --grep="Commit message fragment"`  
git checkout b20eb8bdb8daf5fcec3ceb191c6b9b25507376ca

Отменить незакоммиченные изменения в файле file.txt - git checkout -- file.txt


box@47f08793a3e6: ~/REPO $ git branch --list *5555                              
  15Qy1C1hC8BdGenCuGQthMZhaOk4l7vJ_5555

macbook:test vandud$ git branch # local
  aoeuaoeu
  master
  new_test_branch
* vandud

macbook:test vandud$ git branch --list # local
  aoeuaoeu
  master
  new_test_branch
* vandud

macbook:test vandud$ git branch --all # all
  aoeuaoeu
  master
  new_test_branch
* vandud
  remotes/origin/master
  remotes/origin/vandud
  
macbook:test vandud$ git branch -r #remote
  origin/master
  origin/vandud

Удалять ветки можно с помощью git branch -d/-D <branchname>
С сервера ветки можно удалять так git push <reponame> --delete <branchname>

macbook:test vandud$ git branch -a
* master
  remotes/origin/master
  remotes/origin/testus
  remotes/origin/vandud
  
macbook:test vandud$ git push
Everything up-to-date

macbook:test vandud$ git push origin --delete testus
remote: . Processing 1 references
remote: Processed 1 references in total
To https://gitea.vandud.ru/vandud/test.git
 - [deleted]         testus
 
macbook:test vandud$ git push origin --delete vandud
remote: . Processing 1 references
remote: Processed 1 references in total
To https://gitea.vandud.ru/vandud/test.git
 - [deleted]         vandud
 
macbook:test vandud$ git branch -a
* master
  remotes/origin/master
macbook:test vandud$ 

Интересный факт

macbook:test vandud$ git log --all --graph --oneline
* 0ba27fa (origin/master) clear working tree
* 27b74a8 (HEAD -> master) dick
*   2132219 Merge branch 'vandud'
|\  
| * 59b8d9f aoue
| * 80ba1f4 govno
...
macbook:test vandud$ git branch 59b8d9f
macbook:test vandud$ git checkout 59b8d9f
warning: refname '59b8d9f' is ambiguous. # <-- "ambiguous" - говорит о том что имя неоднозначное
D	govno
D	mazafaka
D	new.tt
Switched to branch '59b8d9f' # <-- и в итоге свитчится на бранч

6. Переход между коммитами

git log

git log - позволяет просматривать историю коммитов, производить поиск по тексту сообщений в коммитах, по измененным файлам, по датам, по авторам, по веткам и прочим параметрам

По умолчанию история коммитов выводится в обратном хронологическом порядке (выше - новее)

Также можно управлять выводом деталей в git log при помощи готовых пресетов и параметра --pretty. Доступные пресеты: oneline, short, full и fuller

Пример использования:

git log --pretty=oneline

111111 commit1
222222 commit2
333333 commit3

Кроме того, git log позволяет полностью настроить формат вывода при помощи параметра format

Пример использования format:

git log --pretty=format:"%h - %an, %ar : %s"
1111111 - Name Surname, 9 months ago : commit1
2222222 - Name Surname, 10 months ago : commit2
3333333 - Name Surname, 11 months ago : commit3

Где %h - хэш коммита, %an - имя автора, %ar - относительная дата, %s - сообщение коммита

git checkout подходит не только для переключения между ветками, но и между коммитами и тегами:

git checkout [options] [<branch>]/<commit>/<tag>
git checkout [options] <file_name_to_restore>


git log --all --grep='commit message' # позволит отыскать коммит мессадж

Пример решения задачки со степика

box@b888ffedb096: ~/REPO $ git log --all --grep='Git rules!' --pretty=format:%H 
> /home/box/answer 

git stash

https://www.git-scm.com/docs/git-stash

Эта команда нужна для сохранения изменений которые по каким-то причинам не нужно включать в коммит. Например при переключении между ветками

stash сохраняет текущее состояние репозитория без создания коммита

Сохраняются только измененные отслеживаемые файлы
Неотслеживаемые файлы в stash не включаются

Сохранить незакоммиченную информацию перед переключением ветки можно было бы с помощью squash'a, но stash подходит лучше (squash позволяет объединять коммиты в один)

git stash - эквивалентна git stash push, которая сохраняет текущее состояние в стэш

Посмотреть записи в стэше можно с помощью git stash list

Стэш существует назависимо от бранчей

WIP - work in progress


git stash pop/apply - позволяют извлечь состояние из стэша
После команды pop/apply указывается индекс записи в стэше, если не указать то будет взята последняя

Для удаления записей из стэша - git stash drop

git reset

Указатель HEAD указывает на указатель вершины ветки (например master)

macbook:test vandud$ git log --all --oneline
0ba27fa (HEAD -> master, origin/master) clear working tree
27b74a8 dick
2132219 Merge branch 'vandud'
...
macbook:test vandud$ git reset --soft HEAD~
macbook:test vandud$ git log --all --oneline
0ba27fa (origin/master) clear working tree
27b74a8 (HEAD -> master) dick
2132219 Merge branch 'vandud'
59b8d9f aoue
...

У git reset есть три важных ключа

То есть checkout переключает нас на какой-то коммит, а reset откатывает

7. git pull & git push

git fetch

Позволяет скачивать объекты и ссылки с удаленного репозитория


git fetch origin - обновить состояние всех веток в соответствии с репозиторием origin
git fetch -t - скачать все метки


Если необходимо скачать только новые файлы с удалённого репозитория, не обновляя существующие на локальном, то подходит команда git fetch

git pull

Скачать и синхронизироваться с репозиторием

git pull [opts] [<repo> [<refspec> ..]]

Слить удаленную ветку next в текущую

git pull origin next

Пример
Локально закоммитили и запушили
После этого на сервере кто-то закоммитил
Делаем изменения у себя локально без коммита
Пытаемся спуллить
Возникнет конфликт

git push


Загрузить изменения в конкретную ветку на удалённом репозитории

git push origin branch_name

Разрешение конфликтов

Конфликт - это ситуация, когда, в ходе слияния (pull/push/merge) система не может решить какие изменения нужно сохранить
Чаще всего такое бывает когда из одного коммита создано несколько веток и в один и тот же файл внесены изменения без синхронизации между ветками

Пример
Terminal_2021-02-14-22-50-30.png
Из мастера ответвились дважды и наделали независимых изменений
Далее смерджили первую ветку в мастер (все окей)
А когда попытаемся смерджить вторую ветку в мастер - будет конфликт

Внутри конфликтного файла будет такое

<<<<<<< HEAD
Changes from branch2
=======
Changes from branch1
>>>>>>> master

Упрощенное разрешение конфликтов

git checkout --ours README.md # оставляем изменения из текущей ветки (отвергаем изменения из ветки которую мерджим в текущую)  
git checkout --theirs README.md # наоборот  

(логика простая, либо оставляем НАШЕ, либо оставляем ИХ)

Далее git add, git commit и все становится хорошо


Ручное разрешение конфликтов

Ручное разрешение конфликтов подходит в ситуациях, когда необходимы изменения, добавленные во всех конфликтующих ветках. Например, если в branch1 были добавлены стили, а в branch2 исправлены ошибки текста. Для ручного разрешения конфликта необходимо проделать следующее во всех конфликтных файлах (причем конфликт может возникать в нескольких местах одного файла):

Для всех файлов с исправленным конфликтом необходимо сделать git add, после чего закоммитить и повторить операцию слияния

В данном примере, конфликт можно было спровоцировать, проделав git pull branch2 из ветки branch1 или наоборот. Алгоритм разрешения конфликтов в подобной ситуации аналогичен

8. git diff

git diff - позволяет увидеть изменения в отслеживаемых файлах, но которые еще не были внесены в индекс

Команда может быть выполнена для отдельного файла или для всего репозитория


git diff - сравнение рабочей директории и последнего коммита
git diff --cached - разница между индексом и последним коммитом
git diff HEAD - изменения в рабочем дереве с момента последнего коммита; т.е. то, что будет закоммичено, если вы запустите «git commit -a»


Сравнение с произвольными коммитами:
git diff test - Сравнение с веткой «test»
git diff HEAD -- ./file.txt - Сравнение с текущей веткой, но diff ограничен только изменениями в файле "file.txt"
git diff HEAD^ HEAD - Сравнение последнего и предпоследнего коммита

Сравнение веток:
git diff topic master - Разница веток topic и master
git diff topic...master - Изменения, произошедшие в ветке master с тех пор, как ветка topic была создана

Ограничение вывода команды git diff:
git diff --diff-filter=MRC - Показывать только изменения, переименование и копирование (modification, rename, copy), но не добавление или удаление
git diff --name-status - Показывать только названия и характер изменений, но не фактический вывод git diff git diff arch/i386 include/asm-i386 - Ограничить вывод git diff для указанных поддеревьев


Можно также делать git diff COMMIT1 COMMIT2