Git stash восстановление состояния индекса удаленных и переименованных файлов

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

В следующем примере:

$ git status s

A  file0
D  file1
R  file2 -> file3
?? file4

running git stash push -k -u, а затем git stash pop --index оставит меня в следующем состоянии:

$ git status s

A  file0
D  file1
R  file2 -> file3
?? file1
?? file2
?? file4

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

Как это обойти?

Изменить: Здесь script, который воссоздает проблему (протестирован в Mac OS X 10.13.2 с git 2.16.1)

#!/usr/bin/env bash

echo -e "\nInitializing a fresh git dir..."
mkdir gitStashIssue && cd $_
rm -rf * .*
git init


echo -e "\nPreparing git state for test..."
# Create files and commit them
echo 'abc' > file1
echo 'aabbcc' > file2
echo 'aaabbbccc' > file3
echo 'aaaabbbbcccc' > file4
git add .
git commit -m 'initial commit'

# Make changes and add them to stage
echo `cat file1` >> file1
echo `cat file2` >> file2
git add .

# Make another change to a staged file without
# staging it, making it partially staged
echo `cat file1` >> file1

# Delete and rename files
git rm file3
git mv file4 fileRenamed

# Add untracked file
echo "untracked" > untrackedFile

# git status -s should now show
# MM file1
# M  file2
# D  file3
# R  file4 -> fileREnamed
# ?? untrackedFile

echo -e "\nCurrent git status is:"
git status -s

echo -e "\nStasing changes..."
git stash save -u -k

# git status -s should now show
# M  file1
# M  file2
# D  file3
# R  file4 -> fileREnamed
# ?? file3
# ?? file4

echo -e "\ngit status after stashing files is:"
git status -s

echo -e "\ncleaning up deleted and renamed files..."
git clean ./ -f

echo -e "\ngit status after cleanup:"
git status -s

echo -e "\nCommiting unstashed changes..."
git commit -m 'commit unstashed changes'

# This causes a conflict in file1
# git status -s should now show
# UU file1
# ?? untrackedFile
git stash pop --index

echo -e "\ngit status after unstashing:"
git status -s

Ответ 1

Помните: git не отслеживает переименование файлов. Он отслеживает содержимое файла.

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

Попробуйте сами:

$ cp file1 boink
$ rm file1
$ git add .
$ git status

Имеет такой же эффект, как:

$ git mv file1 boink
$ git status

Это обнаружение переименования работает только на вещах, которые были добавлены в индекс. Сделайте git reset сейчас, и вы увидите, что я имею в виду:

$ git reset
$ git status
Changes not staged for commit:
        deleted:    file1

Untracked files:
        boink

Мы можем использовать команду git git ls-files для отображения файлов, присутствующих в индексе. По сути, это файлы git, которые считаются существующими на данный момент. Когда у нас есть переименование, поставленное, как указано выше, вывод из этого:

# git ls-files
boink
file2
file3
file4

Что касается индекса, file1 больше не существует. Мы удалили его, а затем создали boink. git status является дружественным и показывает нам результаты обнаружения переименования, но помните, что индекс не заботится об этом.

Теперь мы запускаем git stash -k -u. -k указывает, что прикосновение не прикасается к индексу, поэтому нет. Рассмотрим на мгновение первый абзац manpage для stash:

Используйте git stash, когда вы хотите записать текущее состояние рабочего каталога и индекс, но хотите вернуться в чистый рабочий каталог. Команда сохраняет локальные изменения и возвращает рабочий каталог в соответствии с фиксацией HEAD.

Итак: мы попросили тайник сохранить наши локальные изменения (не касаясь индекса) и вернуть рабочий каталог в соответствие с фиксацией HEAD. file1 существует в фиксации HEAD, поэтому он возвращается. Но file1 больше не находится в индексе, потому что мы его удалили. Следовательно, file1 теперь является необработанным файлом. Поскольку мы не касаемся индекса, новый файл boink и запись, которая была результатом переименования из file1, остаются в индексе. Следовательно, вы получаете потенциально неожиданный вывод из git status:

Changes to be committed:    
        renamed:    file1 -> boink

Untracked files:
        file1

Ответ 2

Появится вывод вашего script. Как видите, после неустановления нет file3 и file4. Эти файлы появляются после stash -k. Это нормальное поведение, потому что stash восстанавливает удаленные файлы. И -k, чтобы сохранить изменения индекса, который удаляет file3 из индекса. Таким образом, вы одновременно используете D file3 и ?? file3.

Initializing a fresh git dir...
rm: refusing to remove '.' or '..' directory: skipping '.'
rm: refusing to remove '.' or '..' directory: skipping '..'
Initialized empty Git repository in /home/azzel/projects/test/bash/gitstash/gitStashIssue/.git/

Preparing git state for test...
[master (root-commit) 7e891f6] initial commit
 4 files changed, 4 insertions(+)
 create mode 100644 file1
 create mode 100644 file2
 create mode 100644 file3
 create mode 100644 file4
rm 'file3'

Current git status is:
MM file1
M  file2
D  file3
R  file4 -> fileRenamed
?? untrackedFile

Stasing changes...
Saved working directory and index state WIP on master: 7e891f6 initial commit

git status after stashing files is:
M  file1
M  file2
D  file3
R  file4 -> fileRenamed
?? file3
?? file4

cleaning up deleted and renamed files...
Removing file3
Removing file4

git status after cleanup:
M  file1
M  file2
D  file3
R  file4 -> fileRenamed

Commiting unstashed changes...
[master 1156712] commit unstashed changes
 4 files changed, 2 insertions(+), 1 deletion(-)
 delete mode 100644 file3
 rename file4 => fileRenamed (100%)
Auto-merging file1
CONFLICT (content): Merge conflict in file1
Recorded preimage for 'file1'
Index was not unstashed.

git status after unstashing:
UU file1
?? untrackedFile