我搞砸了我的github仓库。我已经把我希望不要的东西推送到GitHub(而不仅仅是本地)。该存储库有20个“步骤”,其中Step1
合并到Step2
中,合并到Step3
中,依此类推,一直到Step20
,这很糟。 >
对于 全部 ,我希望我的 整个 存储库恢复到12月10日的状态整个存储库中的分支。
我已经看到了为给定分支执行此操作的多种方法,我想我可以做到二十次,对吧?
但是,我希望有一种方法可以不检出全部20个分支并将每个分支放回原处。
答案 0 :(得分:2)
除非您曾经删除提交或删除某些分支名称,或者删除并重新克隆了存储库,但是对于典型的设置而言,这实际上非常容易。部分原因是:
... 12月10日...
这只是十天前的事情。 30或90天后,由于reflog条目到期,事情变得更加艰难。 (这是默认配置,您没有告诉Git使用其他保留时间段。)
这里要记住的是,Git存储的不是更改,也不是文件,而是 commits 。 (每个提交都存储文件,因此Git间接存储文件,但是可见的级别是提交。)每个提交都由某个哈希ID唯一标识,该哈希ID是一个很大的丑陋字符串,如5d826e972970a784bd7a7bdf587512510097b8c7
。通常,您通常也只添加新的提交到Git存储库。即使对于似乎要删除提交的操作,例如git commit --amend
或git rebase
,也是如此。原始提交(其不变的哈希ID与提交本身完全一样的永久ID)仍在您的存储库中。
您的每个分支名称都只是充当指针。也就是说,每个存储一个哈希ID。当前存储在master
中的一个哈希ID可能是5d826e972970a784bd7a7bdf587512510097b8c7
。明天,进行新的提交后,将会有其他事情发生,但是master
中仍然只有一个哈希ID。
提交 new 提交时,Git:
打包完整快照(来自索引,暂存区或高速缓存)。这将使Git 树对象(通常不需要关心的东西)具有自己的哈希ID。
收集您的日志消息,您的姓名和电子邮件地址以及当前日期/时间,等等。为此,Git添加了来自步骤1的树哈希,在parent
行中记录了来自分支名称的 current 提交的哈希ID,以及Git要包含的任何其他合适的元数据。 Git从所有这些数据中创建一个 commit对象,该对象获得了自己的新的唯一哈希ID。
(时间戳记的一部分原因是确保哈希ID是唯一的。否则,如果您创建了一个与旧快照匹配的新快照,且快照具有相同的父代和其他元数据,则会得到 old 提交的哈希ID回来了!那会使两个分支折叠成一个分支,这实际上不是致命的—您可以通过欺骗和脚本使这种情况发生,从而每秒进行多个提交,例如实例,它确实可以工作,但是令人惊讶,并且有可能破坏某些工作流程。)
将步骤2中的哈希ID记录到当前分支名称中。瞧,分支现在指向 new 提交。新的提交指向上一个提交。
因此,在进行此新提交之前,如果名称master
指向其父为H
的某个提交G
(此处为实际哈希ID),则{ {1}}的父母是G
,依此类推,您有:
F
然后,如果我们有... <-F <-G <-H <--master
代表新提交,则图片为:
I
每当Git更新引用时,例如... <-F <-G <-H <-I <--master
之类的分支名称,Git都会将该分支名称的 previous 值记录到日志中。此日志称为 reflog 作为参考。每个日志条目上都有一个日期/时间戳,因此您可以运行master
使其溢出:
git reflog master
(这是我的Git Git存储库,在$ git reflog master
5d826e9729 master@{0}: merge refs/remotes/origin/master: Fast-forward
965798d1f2 master@{1}: merge refs/remotes/origin/master: Fast-forward
8a0ba68f6d master@{2}: merge refs/remotes/origin/master: Fast-forward
...
上发生了不太有趣的事情:基本上,我一直都在快速转发它。)
这些条目根据它们在日志中的新近度进行编号:master
是当前值,@{0}
是前一个值,@{1}
是@{2}
时的前一个值是@{1}
,依此类推。 @{0}
的默认设置是将编号打印出来,就像这样,但是使用git reflog
时,它将打印其时间戳:
--date=relative
以此类推。
您还可以使用5d826e9729 master@{11 days ago}: merge refs/remotes/origin/master: Fast-forward
965798d1f2 master@{12 days ago}: merge refs/remotes/origin/master: Fast-forward
和许多其他格式。要查看所有内容,请阅读--date=local
文档(git log
实际上是git reflog
)。现在,请尝试git log -g
。
那么,想象一下,我们有这样简单的东西:
--date=local
((我已经停止绘制内部箭头了,因为它太难了/令人讨厌,正如您稍后会看到的那样。请记住,它们必定指向向后:引用会转发到尚不存在的新提交,因为您直到进行之后才知道新提交的哈希ID,然后为时已晚,因为什么都没有任何提交中的em>或实际上在任何Git内部对象中的 都可以更改。)
要将...--F--G--H--I <--master
还原到昨天的样子,当它指向master
而不是H
时,您只需要告诉Git:设置名称I
指向现在提交master
。结果是:
H
提交 I
/
...--F--G--H <-- master
不再 。实际上,这只是在引用日志中添加了一个新条目,因此I
会记住指向master@{1}
时master
的哈希ID。 (该新reflog条目的日期和时间戳记为“现在”。)
最终(默认情况下至少为30天,大多数情况下为90天),Git将在reflog中扫描I
,并丢弃所有过旧的条目。但是直到现在至少30天,master
才会记住提交master@{<something>}
的哈希ID。
那么,我们要做的就是找出所有所有reflogs 关于所有分支机构的内容。 对于十天前存在的每个分支,该分支指向哪个提交?
您可以通过列举所有分支名称来做到这一点:
I
然后,对于每个名称,您可以运行$ git branch
<list of names spills out, one of them probably marked `*` as current branch>
并查找对应于12月10日的条目。在这种情况下,该日期可能是10日之前。
git reflog --date=local name
这意味着$ git reflog --date=local master
5d826e9729 master@{Sun Dec 9 11:03:46 2018}: merge refs/remotes/origin/master: Fast-forward
965798d1f2 master@{Sat Dec 8 07:53:19 2018}: merge refs/remotes/origin/master: Fast-forward
是第9位的值5d826e9729
,此后没有更新,因此它也必须是第10位的值master
。
您可以对每个分支名称重复此操作,并找出您希望每个名称标识的提交。如果该分支在10号之前不存在,则可能要完全删除该分支;请注意,如果您这样做,Git也会删除其reflog,因此没有回头路了。
但是,有一种更简单的方法。 reflog语法允许您编写:
master
或:
master@{10.days.ago}
让Git自己解决这个问题!通常,任何使用哈希ID的Git命令也将采用此方法。例如,master@{10.dec.2018}
将名称转换为相应的哈希ID:
git rev-parse
但是:
$ git rev-parse master
5d826e972970a784bd7a7bdf587512510097b8c7
我在这里使用$ git rev-parse master@{8.dec.2018}
965798d1f2992a4bdadb81eba195a7d465b6454a
是因为reflog之后没有任何有趣的东西。请注意,出来的是8 dec
,即96579...
,它是从8号开始的 。
但是要当心:如果您在选定的日期有多个值,Git将选择其中的一个。实际上,master@{1}
的意思是当天的午夜时间 ,如果您想要0900或1432或其他任何值,则可以添加更多的时间指示符。还要注意时区问题-确保选择正确的reflog条目!
您可以从以下内容开始:代替手动调用8.dec.2018
,复制名称,然后手动执行每个操作,
git branch
打印出将运行的命令(如果已取出git for-each-ref --format='%(refname:short)' refs/heads |
while read branch; do echo git branch -f $branch $branch@{10.dec}; done
。
如果它们看起来还不错,并且您确实确定这是正确的,则现在可以取出echo
来使命令实际发生。
(假定使用sh / bash兼容的命令行解释器。)