Изменить текущую ветку и сохранить локальные изменения - PullRequest
1 голос
/ 29 марта 2019

Я на ветке a. Я хочу сделать коммит на ветке b, чтобы тот, кто клонирует ветку b, имел тот же рабочий каталог, что и сейчас.

Сохранение текущих изменений не работает, поскольку в некоторых случаях это приводит к конфликту.

Я ищу способ сделать временную копию рабочего каталога, вызвать git checkout -f b, удалить все файлы, скопировать временный каталог в каталог проекта и сделать коммит.

Ответы [ 3 ]

1 голос
/ 29 марта 2019

На самом деле, git stash действительно сохраняет текущее рабочее дерево.Проблема в том, что способ , который он сохраняет, не соответствует вашим потребностям.Существует также вторичная проблема, которая может быть очень неприятной, но может быть довольно незначительной.Смотрите предостережение ниже.Но, в конце концов, вы можете использовать git stash, чтобы делать то, что вы хотите.

Что нужно знать

Во-первых, помните, что Git не делает коммитыиз вашего рабочего дерева (вашего рабочего каталога) вообще - и что фиксация завершена снимки всех файлов.Это снимки, сделанные из индекса , а не из рабочего дерева.Файлы в новом коммите - это те, которые сейчас находятся в индексе .

(Помните также, что индекс - это то, что другие части Git называют областью подготовки , или иногда кеш . Он содержит одну копию каждого файла, который будет добавлен в следующий коммит. Эта копия изначально является копией, взятой из текущей commit, за исключением некоторых крайних случаев, отмеченных в Извлечение другой ветки, если в текущей ветке есть незафиксированные изменения .)

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

Но именно поэтому git stash действительно делает два коммитов:

  • Один коммит сохраняет ваше текущее состояние индекса, как новый коммит, которого нет ни в одной ветви.Теперь безопасно уничтожить состояние индекса.
  • Второй коммит сохраняет ваше текущее рабочее дерево как коммит с двумя родителями: коммит индекса и текущий коммит.Чтобы получить , что сделанный коммит, Git заменяет всех файлов в индексе их вариантами рабочего дерева (потому что Git делает коммиты из индекса, а не из рабочего дерева). 1

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

Сделав эти два коммита, git stash обновляет refs/stash, чтобы запомнить хеш-идентификатор коммита рабочего дерева w.Этот коммит запоминает хеш-идентификатор индекса фиксации i, а также хеш-идентификатор текущего коммита:

...--o--o--T   <-- your-branch (HEAD)
 \         |\
  \        i-w   <-- refs/stash
   \
    o--A   <-- b

Затем git stash запускает git reset --hard, так что ваш индекс и рабочее дерево идутвернуться к соответствующему коммиту T.Я выделил еще один коммит A, на который указывает другая ветвь b.


1 Технически, git stash делает коммит w, используявторой, вспомогательный / временный индекс, на случай, если что-то пойдет не так.В этом случае он может просто отказаться от временного индекса.Выполнить фиксацию индекса i очень просто, так как сантехническая команда git write-tree выполняет всю работу.


Использование фиксации в Git

Запомните предостережение: git stash по сути просто делает git add для всех файлов, которые уже в индексе.Любые неотслеживаемые файлы, включая неотслеживаемые и игнорируемые файлы, вообще не находятся в коммите w.Они просто сидят на вашем рабочем дереве.Это правда , даже если , если бы вы сделали git checkout A, чтобы получить фиксацию A, некоторые из этих файлов были бы скопированы в ваш индекс.(Конечно, в этом случае вы, как правило, видели бы жалобу о том, что Git сначала нужно перезаписать какой-нибудь неотслеживаемый файл.)

В любом случае, за исключением одного большого предостережения, коммит stash w содержит снимок , именно такой снимок, который, как вы говорите, вы хотите добавить только после коммита A.

Теперь, когда этот снимок существует, вы можете сказать Git сделать новый коммит B у которого A в качестве родителя и w tree в качестве снимка.Для этого нужна одна команда Git:

git commit-tree -p refs/heads/b refs/stash^{tree}

То есть мы используем имя refs/heads/b (ветвь b, указывающее на коммит A), чтобы сообщить Git, каким должен быть хеш-идентификатор parent для нашего нового коммита. Мы используем refs/stash^{tree}, чтобы сообщить Git, каким должно быть дерево 1121 * (снимок) для нашего нового коммита. Git читает стандартный ввод для сбора сообщения журнала - если хотите, добавьте -m <message> или -F <file> для предоставления сообщения или отправьте его на стандартный ввод:

echo some message | git commit-tree -p refs/heads/b refs/stash^{tree}

Результат:

...--o--o--T   <-- your-branch (HEAD)
 \         |\
  \        i-w   <-- refs/stash
   \
    o--A   <-- b
        \
         B

, где новый коммит B имеет тот же моментальный снимок, что и stash commit w.

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

hash=$(git commit-tree -p refs/heads/b refs/stash^{tree})
git update-ref -m "add stashed work-tree commit" refs/heads/b $hash

дает:

...--o--o--T   <-- your-branch (HEAD)
 \         |\
  \        i-w   <-- refs/stash
   \
    o--A--B   <-- b

То есть новый коммит b теперь является вершиной существующей ветви b. Снимок в B соответствует снимку w; они автоматически передаются. сообщение в B - это то, что вы дали git commit-tree. Хэш-идентификатор из B теперь хранится в b, а родительский элемент B - A, так что этот новый коммит находится на ветви b, как вы и хотели.

Теперь, когда все , что выполнено, вы захотите восстановить свой индекс и рабочее дерево, которое git stash выбросило, но сначала сохранило в этих двух коммитах. Для этого используйте git stash pop --index. --index важен: он сравнивает ваш текущий индекс с i и использует различия для восстановления вашего индекса. 2 Затем он сравнивает ваше текущее рабочее дерево с w и использует различия для восстановления твое рабочее дерево от w. Часть pop затем отбрасывает коммиты i-w и, если были другие спрятанные коммиты, заставляет refs/stash запомнить правильный.

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

git stash push   # and make sure it does something
hash=$(echo automatic commit of work-tree |
    git commit-tree -p refs/heads/b refs/stash^{tree})
git update-ref -m "add stashed work-tree commit" refs/heads/b $hash
git stash pop --index

Это полностью не проверено (и имеет некоторые плохие режимы сбоев, особенно тот, где git stash push говорит, что нечего сохранять, и вообще отказывается что-либо делать).


2 Это неэффективный способ простого чтения i непосредственно в индексе, но он достигает той же цели. То же самое относится к шагу w.

1 голос
/ 29 марта 2019

git reset --soft, если твой друг.Если вы хотите, чтобы ревизия после B была такой же, как у рабочего дерева (на данный момент не подтверждена), вы можете сделать это:

git checkout --detach # disconnect from A
git reset --soft b # set the branch pointer to whatever revision B is pointing to.... Your working tree will be unaffected
git add . # add everything to index
git commit -m "revision after B that made it look the way I had it where I was working"
# if you like everything, move b branch to this revision and push
git branch -f b
git checkout b
git push some-remote b

Это должно сделать.

1 голос
/ 29 марта 2019

Любая фиксация, по существу, - это «временная копия рабочего каталога».

«Изменения между», на которые вы ссылаетесь, фактически создаются для вас из снимков, когда вы git show <commit> например.

Вот почему немного сложно дать прямой ответ на ваш вопрос.Давайте попробуем эти два возможных пути:


С переписыванием истории ветви b

Если вы хотите зафиксировать в ветке b, отражающую точное состояниена ветке a в этот самый момент, почему бы не указать прямо ветку b там, где она должна быть.Например:

# get the uncommited changes on the branch
git commit -am "Useful message"

# point b where a is now
git branch -f b a

# instead of forcing the branch we could merge with a strategy ours
# but it's hard to tell what you need from your description alone

# reset a to its previous state
git reset --hard HEAD^

Исходное состояние:

C1---C2 <<< a <<< HEAD # with a dirty tree, uncommited changes

      ? <<< b # (you didn't mention where b was pointing)

Результат впоследствии

C1---C2 <<< a <<< HEAD
     \
      C3 <<< b # where your last (previously uncommited) changes are

Поскольку он переписывает историю ветви, его следует внимательно рассмотреть, ивероятно исключено, если ветка является общей.Тем не менее, он выполняет то, что вы просили: «у кого-то, кто клонирует ветку b, тот же рабочий каталог, что и у меня сейчас».


Без переписывания истории ветки b

Чтобы избежать переписывания истории ветки b, альтернативой является объединение a в b со стратегией, которая будет принимать все со стороны a, а не только конфликтующие части.Это будет выглядеть так:

# we work on a copy of branch a
git checkout -b a-copy a

# get the uncommited changes on the branch
git commit -am "Useful message"

# proceed with the merge, taking nothing from b
git merge -s ours b

# we now reflect the merge on b, and this is a fast-forward
git checkout b
git merge a-copy

# no further need for the working copy of a
git branch -D a-copy

И a не нужно сбрасывать, потому что он не двигался ни в какой точке.

После первого коммита:

C1---C2 <<< a
     \
      C3 <<< a-copy <<< HEAD

      o <<< b (anywhere in the tree)

После первого слияния:

C1---C2 <<< a
     \
      C3 (same state as a, plus the previously uncommited changes)
       \
        C4 <<< a-copy <<< HEAD (the merge taking only C3 side)
       /
      o <<< b (anywhere in the tree)

Конечное состояние:

C1---C2 <<< a
     \
      C3 (same state as a, plus the previously uncommited changes)
       \
        C4 <<< b <<< HEAD
       /
      o
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...