我过去如何进行Git提交?


222

我将所有内容都转换为Git供个人使用,并且我已经在存储库中找到文件的一些旧版本。如何根据文件的“修改日期”以正确的顺序将其提交到历史记录中,以便获得准确的文件历史记录?

有人告诉我这样的方法会起作用:

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


33
我想知道是否正在寻找答案的人们是否只是想以这种方式保持他们的GitHub“贡献连胜”
ZitRo

1
@ZitRo是的。git commit --date="xxx day ago" -m "yyy"如果有人想知道,简单地设置就可以达到这个目的。
Vlas Sokolov

Answers:


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 commit。它还具有一个--date选项,可让您直接指定作者日期。您的预期用法还包括git filter-branch --env-filter使用上面提到的环境变量(这些是“ env”的一部分,在其后命名该选项;请参见git-filter-branch(1)中的“ Options”和基础的“ plumbing”命令git-commit) -tree(1)

将文件插入单个引用历史记录

如果您的存储库非常简单(即,您只有一个分支,没有标签),则可以使用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

只要您可以命名应为新提交的父提交的提交,上述方法就可以工作。如果您确实希望通过新的root提交(没有父项)添加新文件,那么您需要一些不同的东西:

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 filter-branch在使用git filter-branch之前,您应该制作整个存储库的备份副本。整个工作树(包括.git目录)的简单tar存档就足够了。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-filtergit 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

你也可以安排文件是在一个新的根先加入承诺:创建新的根通过从“赵氏孤儿”的方法提交git的变基节(捕获它new_commit),使用无条件的--index-filter,和--parent-filter喜欢"sed -e \"s/^$/-p $new_commit/\""


在用例的第一个示例中,“将文件插入到多引用历史记录中”:是否有一种方法可以--index-filter应用于由返回的提交git rev-list?目前,我看到将索引过滤器应用于rev-list的子集。赞赏任何见识。
刺猬

@Hedgehog:所有“ multi-ref”示例均用于-- --all处理任何ref均可到达的所有提交。最后一个示例显示了如何仅更改某些提交(只需测试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和类似于CVS的丑陋专有RCS派生词),所以不必担心与我交谈。我得到的印象是答案涉及“将文件插入到单个引用历史记录”部分的变体。正确?
史蒂夫

1
@Steve:如果您的历史记录来自单个开发行,则适用“单一引用”方案(即,如果将所有快照视为单个线性分支的连续点,将是有意义的)。如果您的历史记录中有(或曾经有)多个分支,则适用“多重引用”方案。除非您的历史很复杂(针对不同客户的“分叉”版本,在“开发”中发生新工作等时维护了“ bugfix”分支),否则您可能正在寻找“单一引用”情况。但是,您的情况似乎与问题的情况有所不同……
Chris Johnsen

1
@Steve:由于您具有一系列历史目录快照,并且还没有Git存储库,因此您可以仅使用import-directories.perlGit的contrib/(或import-tars.perlimport-zips.py…)从快照创建新的Git存储库(即使使用“旧”时间戳)。我的答案中的rebase/ filter-branch技术仅在您要插入“不存在”现有存储库历史记录的文件时才需要。
克里斯·约翰森

118

您可以像往常一样创建提交,但是在提交时,请设置环境变量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
我一直觉得这很好的答案,但随后不得不乱窜出发寻找日期格式,所以在这里它是下一次“周五7月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:10kernel.org/pub/software/scm/git/docs/…–
Marian

3
顺便说一下,“-0400”部分指定了时区偏移量。请注意正确选择它...因为否则,您的时间将因此而改变。例如,对于住在芝加哥的我自己,我必须选择“ -0600”(北美CST)。您可以在此处找到代码: timeanddate.com/time/zones
evaldeslacasa 2015年

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'
Frank Henard

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

因此,没有办法更改CommitDate或进行完全过期的提交吗?@ d4nyll
拉胡尔

35

就我而言,随着时间的流逝,我已经保存了许多版本的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 git提交--date,重复...

为了使之自动化,您可以使用shell-foo来获取文件的修改时间。我从ls -l和开始cut,但stat(1)更直接

git的承诺--date = “`STAT -c%Y MYFILE `” MYFILE


1
真好 我的git天以前肯定已经有文件想要使用文件修改时间。但是,这不是仅设置提交日期(不是作者日期吗?)
Xeoncross

在git-scm.com/docs/git-commit中:--date“覆盖提交中使用的作者日期。” git log的日期似乎是AuthorDate,同时git log --pretty=fuller显示AuthorDate和CommitDate。
skierpage 2015年

16
git commit选项--date只会修改GIT_AUTHOR_DATE,不会GIT_COMMITTER_DATE。正如Pro Git书中所解释的那样:“作者是最初撰写该作品的人,而提交者是最后应用该作品的人。” 在日期的上下文中,GIT_AUTHOR_DATE是文件更改GIT_COMMITTER_DATE的日期,而是文件提交的日期。重要的是要注意,默认情况下,将git log作者日期显示为“日期”,但在给定--since选项时使用提交日期进行过滤。
Christopher

OS X 10.11.1中没有用于统计的-c选项
thinsoldier

stat -c %y在macOS(和其他BSD变体)中的等效项是stat -f %m
胜利者

21

下面是我使用的更改提交fooN=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. Draheim

混合两种方法几乎可以完美地契合: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文件。因此,我手动从.git文件夹中删除了.lock文件,并执行了所有修改后的文件,这些文件将在过去的日期中提交,并且这次可以正常工作。谢谢这里的所有答案。

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

5
请参阅上面的评论git commit --date。另外,您的示例提交消息应该进行修改,以阻止像这样的不良单行消息@JstRoRR
Christopher

4
警告:git --date只会修改$ GIT_AUTHOR_DATE ... ...因此,根据情况,您会看到提交的当前日期($ GIT_COMMITTER_DATE)
Guido U. Draheim

9

要进行看起来像过去的提交,必须同时设置GIT_AUTHOR_DATEGIT_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

这也适用于合并提交:

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

2

您始终可以在计算机上更改日期,进行提交,然后更改日期并推入。


1
我认为这不是正确的做法,它可能会带来一些未知的问题
Vino

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.