Как сделать коммит Git в прошлом? - PullRequest
196 голосов
/ 09 октября 2010

Я конвертирую все в Git для личного использования, и я нашел несколько старых версий файла, уже находящихся в репозитории. Как передать его в историю в правильном порядке в соответствии с «датой изменения файла», чтобы у меня была точная история файла?

Мне сказали, что-то вроде этого будет работать:

git filter-branch --env-filter="GIT_AUTHOR_DATE=... --index-filter "git commit path/to/file --date " --tag-name-filter cat -- --all  

Ответы [ 8 ]

178 голосов
/ 10 октября 2010

Совет, который вам дали, ошибочен. Безусловно, установка GIT_AUTHOR_DATE в --env-filter переписывает дату каждого коммита. Кроме того, было бы необычно использовать git commit внутри --index-filter.

Вы имеете дело с множеством независимых проблем здесь.

Указание дат, отличных от «сейчас»

Каждый коммит имеет две даты: дату автора и дату коммиттера. Вы можете переопределить каждое из них, передав значения через переменные окружения GIT_AUTHOR_DATE и GIT_COMMITTER_DATE для любой команды, которая пишет новый коммит. Смотрите «Форматы даты» в git-commit (1) или ниже:

Git internal format = <unix timestamp> <time zone offset>, e.g.  1112926393 +0200
RFC 2822            = e.g. Thu, 07 Apr 2005 22:13:13 +0200
ISO 8601            = e.g. 2005-04-07T22:13:13

Единственная команда, которая записывает новый коммит при обычном использовании, это git commit . Он также имеет опцию --date, которая позволяет напрямую указать дату автора. Предполагаемое использование включает в себя git filter-branch --env-filter, также используются переменные среды, упомянутые выше (они являются частью «env», после которого указывается опция; см. «Опции» в git-filter-branch (1) и базовая «сантехническая» команда git-commit-tree (1) .

Вставка файла в отдельный файл ref History

Если ваш репозиторий очень прост (т.е. у вас есть только одна ветвь, без тегов), то вы, вероятно, можете использовать git rebase для выполнения работы.

В следующих командах используйте имя объекта (хэш SHA-1) фиксации вместо «A». Не забудьте использовать один из методов «переопределения даты» при запуске git commit .

---A---B---C---o---o---o   master

git checkout master
git checkout A~0
git add path/to/file
git commit --date='whenever'
git tag ,new-commit -m'delete me later'
git checkout -
git rebase --onto ,new-commit A
git tag -d ,new-commit

---A---N                      (was ",new-commit", but we delete the tag)
        \
         B'---C'---o---o---o   master

Если вы хотите обновить A, чтобы включить новый файл (вместо создания нового коммита, в котором он был добавлен), тогда используйте git commit --amend вместо git commit. Результат будет выглядеть так:

---A'---B'---C'---o---o---o   master

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

B---C---o---o---o   master

git checkout master
git checkout --orphan new-root
git rm -rf .
git add path/to/file
GIT_AUTHOR_DATE='whenever' git commit
git checkout -
git rebase --root --onto new-root
git branch -d new-root

N                       (was new-root, but we deleted it)
 \
  B'---C'---o---o---o   master

git checkout --orphan является относительно новым (Git 1.7.2), но есть других способов сделать то же самое , которые работают на более старых версиях Git.

Вставка файла в мульти- ref History

Если ваш репозиторий более сложный (т. Е. Имеет несколько ссылок (ветки, теги и т. Д.)), То вам, вероятно, потребуется использовать git filter-branch . Перед использованием git filter-branch вы должны сделать резервную копию всего вашего хранилища. Простой tar архив всего вашего рабочего дерева (включая .git каталог) достаточно. git filter-branch делает резервные ссылки, но зачастую проще восстановить не совсем правильную фильтрацию, просто удалив каталог .git и восстановив его из резервной копии.

Примечание. В приведенных ниже примерах используется команда более низкого уровня git update-index --add вместо git add. Вы можете использовать git add , но сначала вам нужно будет скопировать файл из некоторого внешнего местоположения в ожидаемый путь (--index-filter запускает свою команду во временном пустом GIT_WORK_TREE).

Если вы хотите, чтобы ваш новый файл добавлялся в каждый существующий коммит, то вы можете сделать это:

new_file=$(git hash-object -w path/to/file)
git filter-branch \
  --index-filter \
    'git update-index --add --cacheinfo 100644 '"$new_file"' path/to/file' \
  --tag-name-filter cat \
  -- --all
git reset --hard

Я не вижу смысла менять даты существующих коммитов на --env-filter 'GIT_AUTHOR_DATE=…'. Если бы вы использовали его, вы бы сделали его условным, чтобы он переписывал дату для каждого коммита.

Если вы хотите, чтобы ваш новый файл появлялся только в коммитах после некоторого существующего коммита («A»), то вы можете сделать это:

file_path=path/to/file
before_commit=$(git rev-parse --verify A)
file_blob=$(git hash-object -w "$file_path")
git filter-branch \
  --index-filter '

    if x=$(git rev-list -1 "$GIT_COMMIT" --not '"$before_commit"') &&
       test -n "$x"; then
         git update-index --add --cacheinfo 100644 '"$file_blob $file_path"'
    fi

  ' \
  --tag-name-filter cat \
  -- --all
git reset --hard

Если вы хотите, чтобы файл был добавлен с помощью нового коммита, который должен быть вставлен в середину вашей истории, вам нужно будет сгенерировать новый коммит до использования git filter-branch и добавьте --parent-filter к git filter-branch :

file_path=path/to/file
before_commit=$(git rev-parse --verify A)

git checkout master
git checkout "$before_commit"
git add "$file_path"
git commit --date='whenever'
new_commit=$(git rev-parse --verify HEAD)
file_blob=$(git rev-parse --verify HEAD:"$file_path")
git checkout -

git filter-branch \
  --parent-filter "sed -e s/$before_commit/$new_commit/g" \
  --index-filter '

    if x=$(git rev-list -1 "$GIT_COMMIT" --not '"$new_commit"') &&
       test -n "$x"; then
         git update-index --add --cacheinfo 100644 '"$file_blob $file_path"'
    fi

  ' \
  --tag-name-filter cat \
  -- --all
git reset --hard

Вы также можете организоватьфайл, который будет сначала добавлен в новый корневой коммит: создайте новый корневой коммит с помощью метода «orphan» из раздела git rebase (захватите его в new_commit), используйте безусловный --index-filter, и --parent-filter как "sed -e \"s/^$/-p $new_commit/\"".

105 голосов
/ 09 октября 2010

Вы можете создать коммит как обычно, но когда вы фиксируете, установите переменные окружения GIT_AUTHOR_DATE и GIT_COMMITTER_DATE на соответствующие даты и времени.

Конечно, это сделает коммит на кончикеваша ветвь (т.е. перед текущим коммитом HEAD).Если вы хотите отодвинуть его дальше в репо, вам нужно немного придумать.Допустим, у вас есть эта история:

o--o--o--o--o

И вы хотите, чтобы ваш новый коммит (помеченный как "X") появился секунда :

o--X--o--o--o--o

Самый простойможно было бы перейти с первого коммита, добавить новый коммит, а затем перебазировать все остальные коммиты поверх нового.Вот так:

$ git checkout -b new_commit $desired_parent_of_new_commit
$ git add new_file
$ GIT_AUTHOR_DATE='your date' GIT_COMMITTER_DATE='your date' git commit -m 'new (old) files'
$ git checkout master
$ git rebase new_commit
$ git branch -d new_commit
83 голосов
/ 09 апреля 2017

Я знаю, что этот вопрос довольно старый, но это то, что на самом деле сработало для меня:

git commit --date="10 day ago" -m "Your commit message" 
31 голосов
/ 20 марта 2012

В моем случае со временем я сохранил несколько версий myfile как myfile_bak, myfile_old, myfile_2010, backups / myfile и т. Д. Я хотел поместить историю myfile в git, используя даты их изменения.Так что переименуйте самый старый в myfile, git add myfile, затем git commit --date=(modification date from ls -l) myfile, переименуйте следующий самый старый в myfile, другой git commit с --date, repeat ...

Чтобы автоматизировать это, вы можете использовать shell-foo, чтобы получить время модификации файла.Я начал с ls -l и cut, но stat (1) более прямой

git commit --date="&#x60;stat -c %y <i>myfile</i>&#x60;" <i>myfile</i>
16 голосов
/ 30 октября 2013

В моем случае при использовании параметра --date мой процесс git завершился сбоем. Может быть, я сделал что-то ужасное. И в результате появился файл index.lock. Поэтому я вручную удалил файлы .lock из папки .git и выполнил их, чтобы все измененные файлы были переданы в прошедшие даты, и на этот раз это сработало. Спасибо за все ответы здесь.

git commit --date="`date --date='2 day ago'`" -am "update"
15 голосов
/ 20 апреля 2017

Вот что я использую для фиксации изменений за foo до N=1 дней в прошлом:

git add foo
git commit -m "Update foo"
git commit --amend --date="$(date -v-1d)"

Если вы хотите зафиксировать более раннюю дату, скажем, 3 дня назад,просто измените аргумент date: date -v-3d.

Это действительно полезно, например, если вы забыли что-то зафиксировать вчера.

ОБНОВЛЕНИЕ : --date такжепринимает выражения типа --date "3 days ago" или даже --date "yesterday".Таким образом, мы можем сократить его до одной строчной команды:

git add foo ; git commit --date "yesterday" -m "Update"
3 голосов
/ 27 марта 2018

Вы всегда можете изменить дату на своем компьютере, сделать коммит, затем изменить дату назад и нажать.

2 голосов
/ 25 июня 2019

Чтобы сделать коммит, похожий на тот, что был сделан в прошлом, вы должны установить GIT_AUTHOR_DATE и GIT_COMMITTER_DATE:

GIT_AUTHOR_DATE=$(date -d'...') GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE" git commit -m '...'

, где date -d'...' может быть точной датой, такой как 2019-01-01 12:00:00или относительный, например 5 months ago 24 days ago.

Чтобы увидеть обе даты в git log, используйте:

git log --pretty=fuller
...