Могу ли я написать конкретное правило о том, как git должен обрабатывать конкретный часто встречающийся конфликт слияния в конкретном файле?

У меня есть хранилище Obsidian под контролем версий, и один из плагинов синхронизируется с внешним источником, обновляя метку времени в файле конфигурации.

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

{
  "dateHighlightedFormat": "yyyy-MM-dd HH:mm:ss",
  ...
  "filter": "ALL",
<<<<<<< HEAD
  "syncAt": "2024-07-21T22:24:25",
=======
  "syncAt": "2024-07-22T09:35:14",
>>>>>>> 6630c5d461bcb5c4950e2694a67c60aee57acc7d
  ...
}

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

Есть ли способ настроить это для git?

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

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

п.с. Кажется, я припоминаю, что NPM смог настроить что-то специально для package-lock.json. Так что, если я не ошибаюсь, это должно быть технически возможно.

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

eftshift0 23.07.2024 09:45

если на самом деле это только один файл, вы можете просто восстановить его перед извлечением, например git restore my_file

john Smith 23.07.2024 09:49

@eftshift0 Я считаю, что rerere не решит мою проблему. Он записывает разрешение, т.е. вы сделали слияние, а затем преобразуете его в перебазирование, он может запомнить, как был разрешен каждый конфликт. В этом случае конфликты разные, т. е. разные временные метки. И вы никогда не можете быть уверены, какая версия является текущей, поэтому для ее обработки требуется некоторая «специфичная для предметной области» логика.

Pete 23.07.2024 09:53

@johnSmith Я не могу быть уверен, является ли локальная или удаленная версия самой последней. Обновил вопрос, чтобы сделать это явным.

Pete 23.07.2024 09:54

Может быть, собственный драйвер слияния?

knittl 23.07.2024 10:27
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
5
5
80
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы можете написать собственный драйвер слияния.

Предположим, у нас есть драйвер слияния /usr/bin/latest.py. Не забудьте сделать его исполняемым, если он не в Windows.

В gitconfig:

[merge."latest"]
        name = "choose the latest timestamp"
        driver = /usr/bin/latest.py %O %A %B %L %P

В .gitattributes:

package-lock.json    merge=latest

Вот грубая реализация latest.py на Python 3 (3.11.2 на моей машине). Если шебанг #!/usr/bin/env python не работает, попробуйте #!/usr/bin/env python3. Вы можете написать драйвер слияния на любом языке программирования, который вам нравится. Это немного грубо, так как на самом деле он не поддерживает JSON и полагается на тот факт, что в этом сценарии дата/время ISO сортируются в алфавитном порядке.

Сначала он создает два временных файла для двух объединяемых ветвей и заменяет строку syncAt последней временной меткой в ​​обоих. После этого он вызывает git merge-file -p, передавая их вместо оригинала. Таким образом, для этой конкретной строки git merge-file увидит то же самое изменение, которое не будет конфликтовать, но все остальное будет возвращаться к поведению git merge по умолчанию, гарантируя, что другие конфликты не будут выполнены, требуя разрешения вручную.

#!/usr/bin/env python3
import sys
import os
import subprocess
import tempfile
import logging
import traceback

ancestor = sys.argv[1]
current = sys.argv[2]
other = sys.argv[3]
KEYWORD = '"syncAt":'

def merge_file(current, ancestor, other):
    cmd = 'git merge-file -p "%s" "%s" "%s"' % (current, ancestor, other)
    status, output = subprocess.getstatusoutput(cmd)
    return status, output

def get_syncat_line(p):
    syncat = ''
    with open(p) as f:
        for line in f:
            if line.strip().startswith(KEYWORD):
                syncat = line
                break
    return syncat

def replace_syncat_line(p, replacement, output):
    with open(p) as f:
        for line in f:
            if line.strip().startswith(KEYWORD):
                output.write(replacement)
                output.write("\n")
            else:
                output.write(line)
        output.write("\n")

def write_output_exit(output, status):
    with open(current, 'w') as f:
        f.write(output)
    sys.exit(status)

current_syncat = get_syncat_line(current)
other_syncat = get_syncat_line(other)
ancestor_syncat = get_syncat_line(ancestor)

def create_tmp_file():
    return tempfile.NamedTemporaryFile('w', delete_on_close=False) 

if (current_syncat.strip() == other_syncat.strip()):
    status, output = merge_file(current, ancestor, other)
    write_output_exit(output, status)
else:
    try:
        if current_syncat.strip() > other_syncat.strip():
            latest_syncat = current_syncat.rstrip()
        else:
            latest_syncat = other_syncat.rstrip()

        with create_tmp_file() as tmp_current, \
            create_tmp_file() as tmp_other:

            replace_syncat_line(current, latest_syncat, tmp_current)
            replace_syncat_line(other, latest_syncat, tmp_other)
            tmp_current.close()
            tmp_other.close()
            status, output = merge_file(
                tmp_current.name, ancestor, tmp_other.name)

            write_output_exit(output, status)
    except Exception as e:
        logging.error(traceback.format_exc())
        status, output = merge_file(current, ancestor, other)
        write_output_exit(output, 1)

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

Pete 24.07.2024 09:26

питон 2 или 3? Или не имеет значения? /usr/bin/env python не работает в моей настройке, и мне нужно использовать /usr/bin/env python3. Но AFAIK, много настроек, python IS Python 3? (Я не разработчик Python, поэтому не очень хорошо осведомлен в этой теме)

Pete 24.07.2024 09:37

@Пит, я протестировал скрипт на Python 3 в git-bash. Я отредактировал ответ, чтобы было понятно. Python 2 вызывает исключение для subprocess.getstatusoutput. Вместо этого следует использовать некоторые другие методы. Кроме того, в Python 2 могут возникнуть и другие проблемы со строками и байтами.

ElpieKay 24.07.2024 10:59

Также: друзья не позволяют друзьям использовать Python 2 для чего-либо, что еще не является устаревшим проектом в 2024 году.

Joachim Sauer 24.07.2024 11:03

@JoachimSauer :D у меня сложилось такое впечатление, но, как уже упоминалось, я не разработчик Python. Но в целом, для любой достаточно актуальной заметки я должен быть в состоянии предположить, что любая «современная» ссылка на «pyhton», включая исполняемый файл, должна означать python3?

Pete 24.07.2024 11:23

Debian и Ubuntu внутренне стандартизированы и ссылаются только на двоичные файлы python2 и python3. Однако, поскольку ваше предположение вполне разумно, существует пакет python-is-python3, который по сути создает символическую ссылку. Он установлен во всех интересующих меня системах, но я думаю, что это добавление вручную, а не по умолчанию.

Joachim Sauer 24.07.2024 11:26

@ElpieKay Я немного удивлен, что ты это проверил. У меня вообще не сработало по разным причинам. Во-первых, когда я это тестировал, маркеры конфликта все еще были. Я исправил это, но затем увидел другие проблемы, потому что результат записывался не в тот файл, например. git merge --abort не работает. Он должен перезаписывать arg[2], а не arg[5]. В любом случае, я создал решение, которое создает два временных файла, каждый из которых имеет обновленный syncAt, прежде чем перейти к собственному инструменту слияния git, что устраняет необходимость иметь дело с маркерами слияния. Я взял на себя смелость обновить ваш ответ новой версией.

Pete 24.07.2024 13:14

Я тестировал в git-bash в Windows. Драйвер решает только конфликт "syncAt", остальные остаются без изменений. git merge --abort может работать в моем тесте. Как сказано в документе, вместо argv[2] следует писать argv[5]. argv[5] — имя (или путь) объединенного файла. Запись в него, по-видимому, приводит к тому же эффекту. Но, скорее всего, возникнут некоторые проблемы. Рад, что ваша обновленная версия работает. Спасибо, что сделали его лучше.

ElpieKay 24.07.2024 13:37

@ElpieKay — Рад помочь стать лучше. Я еще немного улучшил его: TemporaryFile обрабатывает очистку, поэтому обработка файлов становится намного проще. В любом случае, я бы не смог решить эту проблему, если бы вы не показали путь, включив в процесс инструмент слияния git.

Pete 24.07.2024 14:12

@ElpieKay Я также обновил описание кода, чтобы отразить измененную реализацию.

Pete 24.07.2024 14:43

Другие вопросы по теме

Показать объединенную разницу коммита слияния, включая файлы, добавленные любой родительской ветвью
Git не может создать запрос на включение | разные истории коммитов | bfg --удалить-файлы | git merge --allow-несвязанные-истории
Почему git pull изменил мой код, не вызывая конфликтов слияния для всех изменений?
Как разрешить конфликты слияния без слияния целевой ветки с моей веткой в ​​базовом репозитории с 3 ветками?
Как «пометить разрешенный» конфликт в IntelliJ при использовании git?
Git не может объединить несвязанные истории, мелкий клон
Проблема после слияния git: невозможно извлечь из-за незавершенного слияния и отсутствия промежуточных файлов
Как выделить конфликтные блоки слияния в монако-редакторе, таком как VSCode?
Как увидеть изменения в конфликте дерева ("удалено нами" или "удалено ими")?
Разрешить конфликт Git после слияния git upstream/master, используя как обновленные восходящие, так и спрятанные изменения в Sublime Text