Pygit2: Почему слияние оставляет ветку в нечистом состоянии?

В настоящее время я использую Pygit 0.24.1 (вместе с libgit 0.24.1), работая над репозиторием, в котором у меня есть две ветки (скажем, prod и dev).

Каждое изменение сначала фиксируется в ветке dev и отправляется в удаленный репозиторий. Для этого у меня есть этот кусок кода:

repo = Repository('/foo/bar')
repo.checkout('refs/heads/dev')

index = repo.index
index.add('any_file')
index.write()

tree = index.write_tree()
author = Signature('foo', 'foo@bar')
committer = Signature('foo', 'foo@bar')
repo.create_commit('refs/heads/dev', author, committer, 'Just another commit', tree, [repo.head.get_object().hex])

up = UserPass('foo', '***')
rc = RemoteCallbacks(credentials=up)
repo.remotes['origin'].push(['refs/heads/dev'], rc)

Это работает отлично, я вижу локальную фиксацию, а также удаленную фиксацию, и локальное репо остается чистым:

ничего не коммитить, рабочий каталог чистый

Затем я возвращаюсь к ветке prod и хочу объединить фиксацию HEAD в dev. Для этого я использую этот другой фрагмент кода (при условии, что я всегда начинаю откат в ветку dev):

head_commit = repo.head
repo.checkout('refs/heads/prod')
prod_branch_tip = repo.lookup_reference('HEAD').resolve()
prod_branch_tip.set_target(head_commit.target)

rc = RemoteCallbacks(credentials=up)
repo.remotes['origin'].push(['refs/heads/prod'], rc)

repo.checkout('refs/heads/dev')

На самом деле я вижу, как ветка объединяется как локально, так и удаленно, но после выполнения этого фрагмента кода зафиксированный файл всегда остается в модифицированном состоянии в ветке dev.

В ветке разработки

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

изменено: любой_файл

Однако я полностью уверен, что никто не изменяет этот файл. На самом деле git diff ничего не показывает. Эта проблема возникает только с уже зафиксированными файлами (т. е. файлами, которые ранее были зафиксированы хотя бы один раз). Когда файлы новые, это работает отлично и оставляет файл в чистом состоянии.

Я уверен, что мне не хватает какой-то детали, но я не могу понять, что это такое. Почему файл остается измененным?

EDIT: Просто чтобы уточнить, моя цель - выполнить слияние FF (Fast-Forward). Я знаю, что в документации Pygit2 есть некоторая документация о выполнении слияния без FF, но я бы предпочел первый метод, потому что он сохраняет хэши коммитов через ветки.

EDIT 2: после комментария @Leon я дважды проверил, и действительно, git diff не показывает никаких результатов, а git diff --cached показывает содержимое, которое было в файле до фиксации. Это странно, поскольку я вижу, что изменение успешно зафиксировано в локальном и удаленном репозиториях, но похоже, что после этого файл снова меняется на предыдущее содержимое...

Пример этого:

  1. Имея файл с содержимым «12345», зафиксированный + отправленный, я заменяю эту строку на «54321».
  2. Я запускаю код выше
  3. git log показывает правильно зафиксированный файл, в удаленном репо я вижу файл с содержимым «54321», а локально git diff --cached показывает это:

    @@ -1 +1 @@
    -54321
    +12345
    

person nKn    schedule 15.07.2016    source источник
comment
На самом деле a git diff ничего не показывает. Вы имели в виду git diff --staged ничего не показывает?   -  person Leon    schedule 18.07.2016
comment
@ Леон, пожалуйста, взгляните на EDIT 2, так как я сделал несколько дополнительных тестов и помог мне продвинуться немного дальше.   -  person nKn    schedule 18.07.2016


Ответы (1)


Я бы объяснил наблюдаемую проблему следующим образом:

head_commit = repo.head

# This resets the index and the working tree to the old state
# and records that we are in a state corresponding to the commit
# pointed to by refs/heads/prod
repo.checkout('refs/heads/prod')

prod_branch_tip = repo.lookup_reference('HEAD').resolve()

# This changes where refs/heads/prod points. The index and
# the working tree are not updated, but (probably due to a bug in pygit2)
# they are not marked as gone-out-of-sync with refs/heads/prod
prod_branch_tip.set_target(head_commit.target)

rc = RemoteCallbacks(credentials=up)
repo.remotes['origin'].push(['refs/heads/prod'], rc)

# Now we must switch to a state corresponding to refs/heads/dev. It turns
# out that refs/heads/dev points to the same commit as refs/heads/prod.
# But we are already in the (clean) state corresponding to refs/heads/prod!
# Therefore there is no need to update the index and/or the working tree.
# So this simply changes HEAD to refs/heads/prod
repo.checkout('refs/heads/dev')

Решение состоит в том, чтобы перемотать ветку вперед, не проверяя ее. Следующий код лишен описанной проблемы:

head_commit = repo.head
prod_branch_tip = repo.lookup_branch('prod')
prod_branch_tip.set_target(head_commit.target)

rc = RemoteCallbacks(credentials=up)
repo.remotes['origin'].push(['refs/heads/prod'], rc)
person Leon    schedule 18.07.2016
comment
Это работает, спасибо. Таким образом, единственными существенными различиями между моим кодом и вашим являются проверки и способ разрешения prod_branch_tip. Довольно сложно понять, почему этот подход имеет значение, документация по этому поводу не совсем объяснительная, поэтому я проведу несколько дополнительных тестов, чтобы лучше понять, что происходит. В любом случае, ваш ответ правильный, поэтому я отмечаю его как таковой. - person nKn; 18.07.2016