GIT: Как добавить файл к первому commit (и переписать историю в процессе)?

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

Я пробовал git filter-branch --tree-filter 'git add LICENSE.txt', но получил ошибку, что файл не найден.

Ответ 1

git filter-branch может сделать это, но, вероятно, намного тяжелее, чем нужно.

Насколько велика и ветка-у твоя история? Если это будет небольшим и коротким, самый простой способ сделать это - добавить новый файл, а затем использовать git rebase -i --root, чтобы переместить новую фиксацию во вторую позицию и выложить ее в корневой фиксации.

Например, скажем, у вас есть:

$ git log --oneline --graph --decorate --all
* e8719c9 (HEAD, master) umlaut
* b615ade finish
* e743479 initial

(ваши значения SHA-1 будут отличаться, конечно), и вы хотите добавить LICENSE.txt (уже в рабочем каталоге) в дерево как часть корневой фиксации. Вы можете просто сделать это сейчас:

$ git add LICENSE.txt && git commit -m 'add LICENSE, for fixup into root'
[master 924ccd9] add LICENSE, for fixup into root
 1 file changed, 1 insertion(+)
 create mode 100644 LICENSE.txt

затем запустите git rebase -i --root. Возьмите последнюю строку (pick ... add LICENSE, ...) и переместите ее во вторую строку, изменив pick на fixup, и напишите файл команд rebase-команд и выйдите из редактора:

".git/rebase-merge/git-rebase-todo" 22L, 705C written
[detached HEAD 7273593] initial
 2 files changed, 4 insertions(+)
 create mode 100644 LICENSE.txt
 create mode 100644 x.txt
Successfully rebased and updated refs/heads/master.

Теперь (новая, полностью переписанная) история выглядит примерно так:

git log --oneline --graph --decorate --all
* bb71dde (HEAD, master) umlaut
* 7785112 finish
* 7273593 initial

и LICENSE.txt во всех коммитах.


Если у вас есть более сложная (веткистая) история и вы хотите использовать git filter-branch, чтобы обновить ее, --tree-filter вам не нужно:

'git add LICENSE.txt'

а скорее:

'cp /somewhere/outside/the/repo/LICENSE.txt LICENSE.txt'

чтобы копировать новый файл в дерево каждый раз. (Более быстрый метод заключается в использовании --index-filter, но это сложнее.)

Ответ 2

--index-filter обеспечивает простое и быстрое решение:

git filter-branch --index-filter "cp /abs/path/to/LICENSE.txt . && git add LICENSE.txt"

Вот простой пример для других предложенных методов.

Первый столбец (large) показывает тайминги в секундах для одного прогона каждого фильтра в копиях репозитория проектов Git (45885 фиксирует, checkout ~ 30M). Метод rebase не применяется, поскольку он не обрабатывает слияния автоматически, даже с опцией -c.

Вторые столбцы (medium) показывают среднее время для трех прогонов каждого метода в копиях репозитория среднего размера с линейной историей и довольно большими деревьями (2430 транзакций, checkout ~ 80M).

Третий столбец (small) показывает среднее время для трех прогонов каждого метода в копиях небольшого репозитория (554 фиксации, checkout ~ 100K).

              large medium  small
index-filter   1064     38     10
tree-filter    4319     81     15
rebase            -    116     28

Также обратите внимание, что rebase не функционально эквивалентен вариантам filter-branch, так как он обновляет даты коммиттера.

Ответ 3

   --tree-filter <command>
       This is the filter for rewriting the tree and its contents. The
       argument is evaluated in shell with the working directory set to
       the root of the checked out tree. The new tree is then used as-is
       (new files are auto-added, disappeared files are auto-removed -
       neither .gitignore files nor any other ignore rules HAVE ANY
       EFFECT!).

Обратите внимание, что вы начинаете с новой проверки каждой версии. Итак, команда, которую вы хотите, похожа на --tree-filter 'cp $HOME/LICENSE.txt .' Мне нравится, что torek лучше отвечает (используя rebase), если у вас не так много версий, что это непрактично.