Показаны сообщения с ярлыком git. Показать все сообщения
Показаны сообщения с ярлыком git. Показать все сообщения

воскресенье, 12 мая 2024 г.

Git cложное слияние

http://uneex.org/LecturesCMC/PythonDevelopment2023/04_MergetoolCommandline

https://youtu.be/uVh3BEL1iyU

Вообще merge commit опасная штука, их плодить не надо т.к. могут быть конфликты, когда редактируется одно и то же место, которое правится вручную при merge commit

Сложное слияние

При merge и rebase могут возникать конфликты: в двух историях изменён один и тот же контекст: 

  • Создадим заведомо конфликтующий коммиты на двух ветках 
    $ git init
    Initialized empty Git repository in /home/george/example/.git/
    $ git add .
    $ git commit -a -m "Initial commit"
    [master (root-commit) 8ab1be9] Initial commit
     1 file changed, 63 insertions(+)
     create mode 100644 keyword.py
    $ git branch second
    $ git branch
    * master
      second
    $ grep -Ev "except|False" /usr/lib64/python3.10/keyword.py > keyword.py
    $ git diff
    diff --git a/keyword.py b/keyword.py
    index cc2b46b..9f30ffb 100644
    --- a/keyword.py
    +++ b/keyword.py
    @@ -16,7 +16,6 @@ Alternatively, you can run 'make regen-keyword'.
     __all__ = ["iskeyword", "issoftkeyword", "kwlist", "softkwlist"]
    
     kwlist = [
    -    'False',
         'None',
         'True',
         'and',
    @@ -31,7 +30,6 @@ kwlist = [
         'del',
         'elif',
         'else',
    -    'except',
         'finally',
         'for',
         'from',
    $ git commit -a -m "False+except"
    [master f1fbdeb] False+except
     1 file changed, 2 deletions(-)
    $ git checkout second
    Switched to branch 'second'
    $ grep -Ev "finally|yield" /usr/lib64/python3.10/keyword.py > keyword.py
    $ git diff
    diff --git a/keyword.py b/keyword.py
    index cc2b46b..251bd3a 100644
    --- a/keyword.py
    +++ b/keyword.py
    @@ -32,7 +32,6 @@ kwlist = [
         'elif',
         'else',
         'except',
    -    'finally',
         'for',
         'from',
         'global',
    @@ -50,7 +49,6 @@ kwlist = [
         'try',
         'while',
         'with',
    -    'yield'
     ]
    
     softkwlist = [
    $ git commit -a -m "finally+yield"
    [second 0804e39] finally+yield
     1 file changed, 2 deletions(-)
    $ git log --graph --pretty=oneline --abbrev-commit --all
    * 0804e39 (HEAD -> second) finally+yield
    | * f1fbdeb (master) False+except
    |/
    * 8ab1be9 Initial commit
    
    
  • Итак, у нас есть три состояния файла keyword.py

    1. 8ab1be9 (общий предок) 

    2. f1fbdeb (на ветке master) — без False и except

    3. 0804e39 (на ветке second) — без finally и yield

  • Контекст изменений для except и finally пересекается 

    • ⇒ при слиянии будут конфликты 
  • Попробуем объединить: 
       1 $ git branch
       2   master
       3 * second
       4 $ git merge master
       5 Auto-merging keyword.py
       6 CONFLICT (content): Merge conflict in keyword.py
       7 Automatic merge failed; fix conflicts and then commit the result.
       8 $ grep -EC3 "<<<<|====|>>>>" keyword.py
       9     'del',
      10     'elif',
      11     'else',
      12 <<<<<<< HEAD
      13     'except',
      14 =======
      15     'finally',
      16 >>>>>>> master
      17     'for',
      18     'from',
      19     'global',
      20 
    
  • Часть изменений применены (про False и про yield), потому что контексты не пересекались, часть (про except и finally) — нет. 

  • Файл содержит вставки вида: 
    <<<<<<< HEAD
    =======
    >>>>>>> master
  • Это т. н. 3-way diff по схеме «общий предок + конфликтующие изменения» 
    • Все неконфликтующие изменения из обеих веток применены 

    • HEAD — это содержимое текущей ветка, master — с чем мержим 

      • {1} было бы неплохо ещё знать, что раньше-то было, но тут не показывается 

    • Все "<<<<<<<""=======" и ">>>>>>>" надо убрать (и ненужные изменения тоже) 

    • Получится merge commit с изменением, неравным тому, что делалось на ветках 
  • Если вас удовлетворяют изменения, проделанные на ветке master, можно просто git checkout master keyword.py<!> но тогда пропадут все изменения, включая уже применённые

  • Когда всё готово, делаем git commit -a

   1 $ vim keyword.py
   2 
   3 $ git commit -a
   4 [second 6568682] Merge branch 'master' into second
   5 $ git log --graph --pretty=oneline --abbrev-commit --all
   6 *   6568682 (HEAD -> second) Merge branch 'master' into second
   7 |\
   8 | * f1fbdeb (master) False+except
   9 * | 0804e39 finally+yield
  10 |/
  11 * 8ab1be9 Initial commit
  12 
  • Если в историях больше одного коммита, merge надо продолжить с помощью git merge --continue

  • Если вы окончательно запутались (особенно в многокоммитных мержах), всё можно откатить назад с помощью git merge --abort



    Mergetool

    Инструмент, в котором есть {1} , имеет общее название «merge tool». 

    • Список: git mergetool --tool-help

    • Запускается вместо ручного исправления конфликтов 
    • *vimdiff показывает четыре окна: 

      • «Эта» ветка (LOCAL) 

        Общий предок (BASE) 

        «Та» ветка (REMOTE)

        Файл с конфликтами (его и надо исправлять)

      • Могут остаться backup-файлы, их надо удалить git clean -f

    • Другие утилиты позволяют «накликивать» изменения) 

    Пример на том же репозитории: 

    • просто удалим merge-коммит (git reset --hard HEAD~

    • вызовем git merge

    • вызовем git merge-tool --tool=gvimdiff (или meld)

      • :diffget RE  " get from REMOTE
        :diffget BA  " get from BASE
        :diffget LO  " get from LOCAL

    Для постоянного вызова правильного mergetool: 

    • $ git config --global merge.tool ваш_mergetool


Git метки (тэги)


Метки (теги)

  • Указатели на коммиты, лежат в .git/refs/tags/
  • Выступают в роли commit-ish (как commit ID, ветки и ссылки относительно HEAD)
  • Можно запушить с ключом --tags (но по умолчанию локальны)
  • Аннотированный тег сопровождается специальным объектом-тегом в .git/objects/**/
    • git tag [commitish] -a тег -m Аннотация
    • Можно подписывать электронной подписью 
Две роли тегов: информационная и управляющая (особенно подписанных)

find . > /tmp/files_before
git tag NewDate [commit id]
пометили тегом коммит

git tag

покажет существующие метки, чтобы вывести в консоль можно убрать пейджер git config core.pager ""

git log --graph --pretty=oneline --abbrev-commit --all
 метки можно посмотреть на графе

find . > /tmp/files_after

diff /tmp/files_before /tmp/files_after

покажет нам новый файл, который появился в связи с созданием метки .git/refs/tags/NewDate в этом файле просто commit id

git checkout -b old NewDate

переключаемся на ветку old с одновременным ее созданием на коммите с меткой

Аннотированные теги:

 теги сопровождающиеся специальным объектом, этот объект можно подписать электронной подписью

git tag Anno -a -m "tag message here"

-a добавляет аннотированный тег, -m добавляет message

git tag -d Anno

удаляет тег


Аннотированный тег создает специальный объект в .git/objects/  вида tag (четвертый тип в дополнение к blob, tree, commit), в котором хранится  commit id, название тэга, автор и commit message.

 

 

 

 

суббота, 11 мая 2024 г.

Git ветки

Ветка (branch) возможность иметь несколько путей разработки, потом ветки можно объединять.
Ветки создаются под разработку в которой код временно не рабочий.

Здесь разберем случае когда изменения не будут конфликтовать друг с другом.

git branch new
создали новую ветку new

git branch

покажет какие ветки есть в наличие, звездочкой помечена активная ветка

допустим мы накоммитили в ветку master, ветка new при этом находится в той точке где она и была заведена

git checkout new

сменили активную ветку, теперь в git branch звездочкой будет помечена ветка new

и теперь накоммитим сюда. только создадим другие файлы чтобы изменения не конфликтовали. На этом этапе у нас есть две ветки разработки у которых где-то в прошлом есть общий коммит. Но на текущий момент это две параллельные ветки. Когда мы говорим git branch, то в каталоге .git/refs/heads заводится еще один файл с названием как у ветки, в котором содержится commit id на вершину нового фронта разработки

Когда используют ветки:

  • совместная разработка
  • схема devel - testing - production
  • разработка временно ухудшающая качество
git checkout master
git merge new (можно указать commit id указав некоторую точку в дереве с которой хотим померджиться)

операция merge сопровождается специальным commit'ом, который называется merge commit



git reflog

показывает все коммиты в том числе и потерянные пока мы не сделаем repack 

вместо команды merge можно было сделать:
git checkout new
git rebase master
это rebase new поверх master


git checkout master

git merge new

здесь merge commit уже не создается потому что история после rebase стала линейная нам нечего сливать, мы просто подвигаем фронт под названием master к тому месту где был фронт под названием new

 

git rebase --interactive HEAD~5

интерактивный rebase последних 5 коммитов. rebase это переписывание истории его лучше делать на том что мы еще не запушили, если мы rebase' нули то что уже запушили нам потом придется  делать force push и те люди которые с вами синхронизируются пострадают.

 

Git удаленный репозиторий

mkdir repo.bare

cd repo.bare

git init --bare

инициализирует репозиторий прямо в текущем каталоге без подкаталога .git Это репозиторий, у которого нету рабочей копии.

cd ..

git clone repo.bare repo

 команда приведет к клонированию пустого репозитория.

cd repo

git status

git remote -v 

теперь у нас есть удаленный репозитория по адресам в repo.bare

date >> initial

git add initial

git commit -m "Initial commit"

git log

git push 

Запушили на "удаленный" репозиторий

далее происходит работа команды с удаленным репозиторием или это может быть работа с другого компьютера

чтобы синхронизироваться с удаленным репозиторием делаем

git pull


Правила оформления commit'ов:

  • одно изменение один commit
    • изменение это решение какой-то одной задачи, багфикс, новая фитча, редизайн
    • если задача слишком большая ее следует разбить на подзадачи в отдельных commits
    • если еще больше или одновременно с другими задачами, то ведется работа с отдельными ветками branches
  • commit не ломает уже имеющихся свойств, приложение продолжает работать не хуже чем раньше
    • если предполагается что ближайшая серия коммитов приведет к потере функциональности то лучше сделать отдельную ветку
Если мы еще не опубликовали, то переписывать историю можно как угодно (все что между HEAD и origin):

cp /bin/bash .
git add bash
git commit -m "False commit"
git log -p
закоммитили фигню, но пока не опубликовали

git status

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

git reset --hard [commit id] (либо git reset --hard HEAD^)

если не сказать --hard мы откатимся в репозитории на предыдущее состояние, а рабочая копия останется какая и была. Как если бы мы эти изменения сделали, но не закоммитили.

--hard синхронизирует рабочую копию тоже

 

commit id (sha-1) вычисляется от всего содержимого и даже от commit message, поэтому если отредактировать message это будет новый commit.

git commit -amend
используем если нужно исправить последний кормит. Фактически мы перекомичиваем тот же самый комит (последний). операция сохраняет время коммита

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


 

Git локальный репозиторий

https://git-scm.com/book/en/v2 
Нужно читать от начала и до конца




Система контроля версий: 
  • Хранение 
  • Версионирование 
    • Файлов/объектов
    • Состояний всего корпуса кода
  • История изменений 
    • Возможно, нелинейная (орграф, точнее — сеть)
  • Создание информационного пространства вокруг исходного текста


mkdir newrepo
git init
в рабочей копии нет ни одного файла (вообще), но есть каталог .git, который является локальным полноценным git хранилищем

cal > cal.txt
git status
git нам скажет что у нас в каталоге среди рабочей копии появился файл и он Untracked. 

git add cal.txt (git add -A)

git status

скажет нам что файл добавлен к отслеживанию (changes to be committed)  

git commit -m "new calendar file"

git status

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

git log 

отобразит commit id и commit message

git config core.editor 'ваш любимый редактор'
может поменять редактор

git config --list

отобразит список настроек которые могут быть local и global. git config --local ... // git config --global ... // git config --local --unset --user.name

echo "change file" >>  cal.txt

git status

знает что этот файл уже был добавлен в отслеживание и что он сейчас изменен но пока not staged for commit. Мы можем выбирать какие изменения в файле добавить в коммит. пока добавим все

git add cal.txt

git commit -m "new changes"

до этого момента мы делали модификацию своего собственного локального репозитория. чтобы опубликовать изменения нужно сделать git push



Историю коммитов можно посмотреть командой git log. Для того, чтобы отключить постраничный просмотр, используйте git -P log.

Все объекты имеют уникальный ID (это SHA-1 хеш). Объект с ID, допустим, 8dccc7a1d248ea923156b2e762e576b44e07886a, хранятся в файле с именем .git/objects/8d/ccc7a1d248ea923156b2e762e576b44e07886a
Посмотреть содержимое объектов можно (но непонятно, зачем ☺), например, так:

python3 -c "import sys; import zlib; print(zlib.decompress(sys.stdin.buffer.read()))" < .git/objects/8d/ccc7a1d248ea923156b2e762e576b44e07886a

В этих объектах мы найдем:
  • blob - это собственно наш файл в репозитории. Состояние файла на какой-то момент это не кусок файла не патч это именно файл целиком
  • состояния файлов на какой-то момент объединяются в дерево tree. Дерево говорит, в вашей ветке разработки на данный момент присутствовали вот такие файлы (размер, права, имя файла и id)
  • и собственно commit. Идентификатор дерева, id родительского коммита, имя автора и commit message

Эти три объекта создаются при каждом новом коммите. На каждый файл создается новый blob.
История разработки выглядит как цепочка коммитов.


  • для просмотра веток удобен qgit (или git log --graph --pretty=oneline --abbrev-commit --all)
  • не обязательно складывать в один коммит все что нахакали, можно разделить на разные

Для того чтобы сбросить коммиты (по правилу хорошего тона, те что не опубликовали чтобы избежать  git push --force) можно использовать git reset [commit id]. Эта команда откатит коммиты, но оставит рабочую копию не тронутой (изменения сохранятся).


Как разбить одну большую пачку изменений в несколько коммитов

git diff
смотрим все изменения

 git commit --patch 

показывает нам по одному hunk и спрашивает  Stage this hunk? Таким образом можно разбить изменения на разные коммиты

git status

покажет нам какие изменения еще остались, которые можно посмотреть с помощью git diff


еще есть  git add --interactive 

пятница, 19 мая 2023 г.

Git intra

Хорошая статья про устройство git - https://maryrosecook.com/blog/post/git-from-the-inside-out 
Интерактивный тур - https://githowto.com/ru
Книга by Scott Chacon and Ben Straub - https://git-scm.com/book/en/v2

Базовые команды git

git --version

В файле .gitconfig — в нём хранятся глобальные настройки программы
git config --global user.name "My Name" # вводите латиницей и в кавычках
git config --global user.email my@email.com # здесь e-mail 

git config --list # 
вывели в окно командной строки список всех свойств конфига

Создание, отображение, добавление 

# Сначала создаём папку для проекта, назовём её my-projects

mkdir my-project # создали папку my-project в текущей директории 
cd my-project # перешли в созданную папку my-project 
git init # инициализировали git в папке my-project


Создание локального репозитория 

в Git любой файл репозитория находится в одном из четырёх состояний: 
  • неотслеживаемый (англ. untracked), 
  • добавленный в индекс, индексируемый (англ. staged, «выдвинутый на плацдарм»),
  • изменённый (англ. modified), 
  • боевой, на жаргоне разработчиков «коммит» или «закоммиченный» (англ. committed, «брошенный в бой»).

Команды Git, выполняют одну из трёх задач:
  • изменяют состояние файла; 
  • отображают информацию о файле; 
  • показывают разницу между его версиями.

git add название_файла # команда для добавления файлов в индекс Staging Area 
git add --all # добавить все файлы к остлеживанию 

git commit -m "My first commit" # сделали первый коммит git clone [адрес, откуда копируем] [путь до папки, куда копируем] 

git push -u origin master #origin - имя сервера, master - имя ветки, u - связывает локальную ветку с веткой удалённого репозитория. Этот ключ нужен, только если вы публикуете новые ветки.


git pull # получает изменения с удаленного репозитория, Выполнение этой команды может привести к конфликтам, так как все изменения при получении сразу сливаются с вашей веткой. 

.gitignore

# комментарий — эта строка игнорируется 
# игнорировать файлы с расширением 
.pyc, 

# * - любое количество символов (ноль и больше), 
*.pyc 

# НО отслеживать файл main.pyc 
# несмотря на то, что мы игнорируем все .pyc файлы с помощью предыдущего правила 
!main.pyc 

# ? - ноль или один символ 
# Исключить файлы text.txt, test.txt, tet.txt и т.д. 
te?t.txt 

# игнорировать только файл main.py находящийся в корневом каталоге 
# не относится к файлам вида <папка>/main.py 
/main.py 

# игнорировать все файлы в каталоге .idea/ 
.idea/ 

# игнорировать файлы с расширением .txt, только в папке doc, но не в подпапках папки doc doc/*.txt 

# игнорировать все .txt файлы в каталоге doc/ и всех его подкаталогах 
doc/**/*.txt