У меня есть хранилище 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 restore my_file
@eftshift0 Я считаю, что rerere не решит мою проблему. Он записывает разрешение, т.е. вы сделали слияние, а затем преобразуете его в перебазирование, он может запомнить, как был разрешен каждый конфликт. В этом случае конфликты разные, т. е. разные временные метки. И вы никогда не можете быть уверены, какая версия является текущей, поэтому для ее обработки требуется некоторая «специфичная для предметной области» логика.
@johnSmith Я не могу быть уверен, является ли локальная или удаленная версия самой последней. Обновил вопрос, чтобы сделать это явным.
Может быть, собственный драйвер слияния?
Вы можете написать собственный драйвер слияния.
Предположим, у нас есть драйвер слияния /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 Сегодня я займусь этим.
питон 2 или 3? Или не имеет значения? /usr/bin/env python
не работает в моей настройке, и мне нужно использовать /usr/bin/env python3
. Но AFAIK, много настроек, python
IS Python 3? (Я не разработчик Python, поэтому не очень хорошо осведомлен в этой теме)
@Пит, я протестировал скрипт на Python 3 в git-bash. Я отредактировал ответ, чтобы было понятно. Python 2 вызывает исключение для subprocess.getstatusoutput
. Вместо этого следует использовать некоторые другие методы. Кроме того, в Python 2 могут возникнуть и другие проблемы со строками и байтами.
Также: друзья не позволяют друзьям использовать Python 2 для чего-либо, что еще не является устаревшим проектом в 2024 году.
@JoachimSauer :D у меня сложилось такое впечатление, но, как уже упоминалось, я не разработчик Python. Но в целом, для любой достаточно актуальной заметки я должен быть в состоянии предположить, что любая «современная» ссылка на «pyhton», включая исполняемый файл, должна означать python3?
Debian и Ubuntu внутренне стандартизированы и ссылаются только на двоичные файлы python2
и python3
. Однако, поскольку ваше предположение вполне разумно, существует пакет python-is-python3
, который по сути создает символическую ссылку. Он установлен во всех интересующих меня системах, но я думаю, что это добавление вручную, а не по умолчанию.
@ElpieKay Я немного удивлен, что ты это проверил. У меня вообще не сработало по разным причинам. Во-первых, когда я это тестировал, маркеры конфликта все еще были. Я исправил это, но затем увидел другие проблемы, потому что результат записывался не в тот файл, например. git merge --abort
не работает. Он должен перезаписывать arg[2]
, а не arg[5]
. В любом случае, я создал решение, которое создает два временных файла, каждый из которых имеет обновленный syncAt
, прежде чем перейти к собственному инструменту слияния git, что устраняет необходимость иметь дело с маркерами слияния. Я взял на себя смелость обновить ваш ответ новой версией.
Я тестировал в git-bash в Windows. Драйвер решает только конфликт "syncAt"
, остальные остаются без изменений. git merge --abort
может работать в моем тесте. Как сказано в документе, вместо argv[2]
следует писать argv[5]
. argv[5]
— имя (или путь) объединенного файла. Запись в него, по-видимому, приводит к тому же эффекту. Но, скорее всего, возникнут некоторые проблемы. Рад, что ваша обновленная версия работает. Спасибо, что сделали его лучше.
@ElpieKay — Рад помочь стать лучше. Я еще немного улучшил его: TemporaryFile
обрабатывает очистку, поэтому обработка файлов становится намного проще. В любом случае, я бы не смог решить эту проблему, если бы вы не показали путь, включив в процесс инструмент слияния git.
@ElpieKay Я также обновил описание кода, чтобы отразить измененную реализацию.
Взгляните на
git help rerere
. Возможно, удастся решить проблему (хотя я никогда им не пользовался, поэтому не знаю, справится ли он с этой задачей).