Як я можу зробити Git в минулому?


222

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

Мені сказали, що щось подібне спрацює:

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

Короткі та прості відповіді: stackoverflow.com/a/34639957/2708266
Yash

33
Цікаво, чи люди, які шукають відповіді на це, просто хочуть таким чином продовжувати свою "
вкладку

1
@ZitRo так. І просто встановлення git commit --date="xxx day ago" -m "yyy"достатньо для цієї мети, якщо хтось цікавиться.
Влас Соколов

alexpeattie.com/blog/working-with-dates-in-git : якщо шукаєш ніжного пояснення
thepurpleowl

Відповіді:


198

Поради, які вам дали, є хибними. Безумовне встановлення 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 počin . Він також має --dateопцію, яка дозволяє безпосередньо вказати дату автора. Передбачуване використання включає git filter-branch --env-filterтакож використання згаданих вище змінних оточуючих середовищ (вони є частиною "env", після якої названа опція; див. "Параметри" у гіт-фільтр-гілці (1) та основна команда "сантехніка" git-commit -три (1) .

Вставлення файлу в єдину історію посилання

Якщо ваш сховище дуже просте (тобто у вас є лише одна гілка, немає тегів), ви, ймовірно, можете використовувати git rebase для виконання роботи.

У наступних командах використовуйте назву об'єкта (хеш SHA-1) комітки замість "A". Не забувайте використовувати один із методів “переопрацювання дат” під час запуску програми git .

---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.

Вставлення файлу в історію багаторазового використання

Якщо ваш репозиторій складніший (тобто він має більше одного посилання (гілки, теги тощо)), вам, ймовірно, доведеться використовувати фільтр-гілку git . Перш ніж використовувати гіт-фільтр-гілку , слід зробити резервну копію всього вашого сховища. Досить простого архіву 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

Крім того, можна організувати файл , щоб бути першим доданий в новому корені фіксації: створити новий корінь здійснювати через «сирітський» метод з мерзотник Rebase секції (захоплення його в new_commit), використовувати безумовний --index-filterі --parent-filterяк "sed -e \"s/^$/-p $new_commit/\"".


У першому прикладі випадку використання "Вставлення файлу в історію повторного перегляду": Чи є спосіб --index-filterзастосувати до повернених комітетів git rev-list? На даний момент я бачу фільтр індексів, застосований до підмножини rev-list. Цінуйте будь-яке розуміння.
Їжак

@ Hedgehog: Усі приклади "multi-ref" використовуються -- --allдля обробки всіх комісій, доступних з будь-якої посилання. Останній приклад показує, як можна змінити лише певні комісії (просто протестуйте GIT_COMMIT на те, що вам подобається). Щоб змінити лише певний список комітетів, ви можете зберегти список перед фільтруванням (наприклад git rev-list … >/tmp/commits_to_rewrite), а потім протестувати на приналежність до фільтра (наприклад if grep -qF "$GIT_COMMIT" /tmp/commits_to_rewrite; then git update-index …). Що саме ви намагаєтеся досягти? Можливо, ви захочете почати нове запитання, якщо занадто багато пояснювати в коментарях.
Кріс Джонсен

У моєму випадку у мене є невеликий проект, який я мав намір поставити під контроль джерел. (Я цього не робив, тому що це сольний проект, і я не вирішив, яку систему використовувати). Мій "контроль над джерелом" був лише питанням дублювання всього мого дерева проектів у новому каталозі та перейменування каталогу попередньої версії. Я новачок у Git (після використання SCCS, RCS та потворної фірмової похідної RCS, що нагадує CVS), тому не хвилюйтеся говорити зі мною. У мене складається враження, що відповідь передбачає зміну розділу "Вставлення файлу в єдину історію посилання". Правильно?
Стів

1
@Steve: Сценарій "єдиного відліку" застосовується, якщо ваша історія виходила з єдиної лінії розвитку (тобто було б сенс, якби всі знімки розглядалися як послідовні точки однієї лінійної гілки). Сценарій "багаторазового перегляду" застосовується, якщо у вашій історії (або у вас були) кілька відділень. Якщо ваша історія не є складною ("роздвоєні" версії для різних клієнтів, підтримували гілку "помилок", коли нова робота сталася ще в "розробці" тощо), ви, мабуть, дивитесь на ситуацію "однократного". Однак , схоже, ваша ситуація відрізняється від ситуації, що стосується питання ...
Кріс Джонсен

1
@Steve: Оскільки у вас є низка знімків з історичного каталогу - і у вас ще немає сховища Git - то ви, ймовірно, можете просто скористатися import-directories.perlз Git's contrib/(або import-tars.perl, або import-zips.py…) для створення нового сховища Git зі своїх знімків (навіть із "Старі" часові позначки). Методи rebase/ filter-branchвідповіді в моїй відповіді потрібні лише в тому випадку, якщо ви хочете вставити файл, який був «залишений» з історії існуючого сховища.
Кріс Джонсен

119

Ви можете створити фіксацію, як зазвичай, але коли ви здійснюєте, встановіть змінні середовища 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

25
Я завжди знаходжу цю гарну відповідь, але потім мені доведеться спішити, щоб знайти формат дати, тож ось наступний раз: «Пт, 26 липня 19:32:10 2013 -0400»
MeBigFatGuy

94
GIT_AUTHOR_DATE='Fri Jul 26 19:32:10 2013 -0400' GIT_COMMITTER_DATE='Fri Jul 26 19:32:10 2013 -0400' git commit
Xeoncross

15
Є ще, наприклад, досить мнемонічний 2013-07-26T19:32:10: kernel.org/pub/software/scm/git/docs/…
Marian

3
До речі, частина -0400 описує зміщення часового поясу. Будьте в курсі його правильного вибору ... адже якщо ні, то ваш час зміниться на основі цього. Наприклад, у випадку, коли я живу в Чикаго, мені довелося обрати "-0600" (Північна Америка CST). Ви можете знайти коди тут: timeanddate.com/time/zones
evaldeslacasa

2
оскільки дату потрібно повторити, мені стало легше створити ще одну змінну:THE_TIME='2019-03-30T8:20:00 -0500' GIT_AUTHOR_DATE=$THE_TIME GIT_COMMITTER_DATE=$THE_TIME git commit -m 'commit message here'
Френк Хенард

118

Я знаю, що це питання досить давнє, але ось що насправді працювало для мене:

git commit --date="10 day ago" -m "Your commit message" 

16
Це спрацювало чудово. Мені приємно, що --dateтакож підтримується читаний людиною формат відносної дати.
ZitRo

@ZitRo чому б не 10 днів?
Alex78191

10
Попередження: git --dateзмінить лише $ GIT_AUTHOR_DATE ... тому залежно від обставин ви побачите поточну дату, приєднану до комітету ($ GIT_COMMITTER_DATE)
Guido U. Draheim

3
Позначення того, що сказав @ GuidoU.Draheim. Ви можете перевірити повну інформацію про комісію за допомогою git show <commit-hash> --format=fuller. Там ви побачите AuthorDateдату, яку ви вказали, але CommitDateце фактична дата здійснення.
d4nyll

Тож немає ніякого способу змінити CommaDate або зробити повністю минулий датований комітет? @ d4nyll
Рахул

35

У моєму випадку я з часом зберігав купу версій myfile як myfile_bak, myfile_old, myfile_2010, резервні копії / myfile тощо. Я хотів перенести історію мого файлу в git, використовуючи дати їх модифікації. Тож перейменуйте найстаріший у мій файл, git add myfileа потім git commit --date=(modification date from ls -l) myfileперейменуйте наступний найстаріший у мійфайл, ще один git виконувати з --date, повторити ...

Щоб дещо автоматизувати це, ви можете використовувати shell-foo, щоб отримати час модифікації файлу. Я почав з ls -lі cut, але stat (1) є більш прямим

git commit --date = "` stat -c% y myfile `" мій файл


1
Приємно. У мене, звичайно, були файли до моїх днів git, для яких я хотів використовувати час, модифікований у файлі. Однак, це не визначає лише дату вчинення (не дату автора?)
Xeoncross

Від git-scm.com/docs/git-commit: --date" Перезазначте дату автора, яка використовується у коміті". git logДата, здається, авторська дата, git log --pretty=fullerпоказує як AuthorDate, так і CommitDate.
лижник

16
Цей git commitпараметр --dateзмінить лише GIT_AUTHOR_DATE, а не GIT_COMMITTER_DATE. Як пояснює Pro Git Book : "Автор - це людина, яка спочатку написала твір, тоді як виконавцем - особа, яка востаннє застосувала твір". У контексті дат GIT_AUTHOR_DATE- це дата, коли файл було змінено, тоді як GIT_COMMITTER_DATE- дата його введення. Тут важливо зауважити, що за замовчуванням git logдати автора відображаються як "Дата", але потім використовують дати фіксації для фільтрації, коли задається --sinceопція.
Крістофер

Немає опції -c для stat в OS X 10.11.1
thinsoldier

Еквівалент для stat -c %ymacOS (та інших варіантів BSD) є stat -f %m.
переможець

21

Далі - це те, що я використовую для внесення змін 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"

6
Попередження: git --dateзмінить лише $ GIT_AUTHOR_DATE ... тож залежно від обставин ви побачите поточну дату, приєднану до комітету ($ GIT_COMMITTER_DATE)
Guido U. Draham

Змішування двох підходів разом робить майже ідеальним:git commit --amend --date="$(stat -c %y fileToCopyMTimeFrom)"
andrej

Дивовижно! Та сама команда з людиною, читаною датоюgit commit --amend --date="$(date -R -d '2020-06-15 16:31')"
піксельні дужки

16

У моєму випадку, під час використання параметра --date, мій процес git завершився. Можливо, я зробив щось жахливе. І в результаті з’явився якийсь файл index.lock. Тож я вручну видалив файли .lock із папки .git і виконав усі файли, які були змінені, щоб зафіксуватись у минулі дати, і це спрацювало цього разу. Дякую за всі відповіді тут.

git commit --date="`date --date='2 day ago'`" -am "update"

5
Дивіться коментар вищеgit commit --date . Крім того, ваше прикладне повідомлення про введення зобов’язань має бути змінено, щоб відмовити від поганих одно рядкових повідомлень, таких як @JstRoRR
Крістофер,

4
Попередження: git --dateзмінить лише $ GIT_AUTHOR_DATE ... тож залежно від обставин ви побачите поточну дату, приєднану до комітету ($ GIT_COMMITTER_DATE)
Guido U. Draham

9

Щоб зробити зобов’язання таким чином, як це було зроблено в минулому, ви повинні встановити і те, 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:

git log --pretty=fuller

Це також працює для об'єднань:

GIT_AUTHOR_DATE=$(date -d'...') GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE" git merge <branchname> --no-ff

2

Ви завжди можете змінити дату на своєму комп’ютері, взяти на себе зобов’язання, потім змінити дату назад і натиснути.


1
Я думаю, що це не правильна практика, це може створити деякі невідомі проблеми
Vino

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.