【问题标题】:How to backup a local Git repository?如何备份本地 Git 存储库?
【发布时间】:2011-01-08 21:49:59
【问题描述】:

我在一个相对较小的项目中使用 git,我发现压缩 .git 目录的内容可能是备份项目的好方法。但这有点奇怪,因为当我恢复时,我需要做的第一件事是git reset --hard

以这种方式备份 git repo 有什么问题吗?另外,有没有更好的方法(例如,可移植的 git 格式或类似的东西?)?

【问题讨论】:

  • 为什么没有人给出使用git bundle的明显答案???
  • @gatopeich 他们做到了。向下滚动。
  • 所有投票的答案都包含一堵关于自定义脚本的文字墙,即使是开始提到 git bundle

标签: git backup


【解决方案1】:

另一种官方方式是使用 git bundle

这将创建一个支持 git fetchgit pull 的文件来更新您的第二个 repo。
对增量备份和恢复很有用。

但是,如果您需要备份所有内容(因为您没有第二个包含一些旧内容的 repo),备份会更复杂一些,正如我的另一个在Kent Fredric的评论之后回答:

$ git bundle create /tmp/foo master
$ git bundle create /tmp/foo-all --all
$ git bundle list-heads /tmp/foo
$ git bundle list-heads /tmp/foo-all

(这是一个原子操作,而不是从.git 文件夹中存档,就像commented by fantabolous


警告:我不推荐 Pat Notzsolution,它正在克隆 repo。
备份许多文件总是比备份或更新更棘手......只有一个。

如果您查看OP Yar answer 中的history of edits,您会看到Yar 最初使用了clone --mirror,...与编辑:

将它与 Dropbox 一起使用是一团糟
您将遇到同步错误,并且您无法在 DROPBOX 中回滚目录。
如果您想备份到您的保管箱,请使用 git bundle

Yar 的current solution 使用git bundle

我休息一下。

【讨论】:

  • 我刚刚检查了这个,它实际上很棒。我将不得不尝试一些捆绑和拆分和列表头来说服......但我非常喜欢它。再次感谢,特别是关于 --all 开关的注释。
  • 有点相关,只是压缩我的本地存储库有什么问题吗?我需要一个备份文件,在外部驱动器上复制数千个文件非常慢。我只是想知道是否有更有效的方法,因为 zip 必须在 .git 文件夹中存档这么多文件。
  • @faB:唯一的区别是您可以使用git bundle 轻松进行增量备份。使用所有本地 repo 的全局 zip 是不可能的。
  • 回复旧评论,但捆绑和压缩目录之间的另一个区别是捆绑是原子的,因此如果有人在操作中间碰巧更新您的存储库,它不会搞砸。
  • @fantabolous 好点。我已将其包含在答案中以提高知名度。
【解决方案2】:

在翻过上面的文字墙后找到了简单的官方方法,这会让你觉得没有。

创建一个完整的包:

$ git bundle create <filename> --all

恢复它:

$ git clone <filename> <folder>

此操作是原子的 AFAIK。查看official docs 了解详细信息。

关于“zip”:与 .git 文件夹大小相比,git 包被压缩并且非常小。

【讨论】:

  • 这并不能回答有关 zip 的全部问题,并且还假设我们已经阅读了其他答案。请修复它,使其具有原子性并处理整个问题,我很高兴让它接受答案(10 年后)。谢谢
【解决方案3】:

通过谷歌来回答这个问题。

这是我以最简单的方式所做的。

git checkout branch_to_clone

然后从这个分支创建一个新的 git 分支

git checkout -b new_cloned_branch
Switched to branch 'new_cloned_branch'

回到原来的分支并继续:

git checkout branch_to_clone

假设你搞砸了,需要从备份分支恢复一些东西:

git checkout new_cloned_branch -- <filepath>  #notice the space before and after "--"

最好的部分,如果有任何事情搞砸了,你可以删除源分支并移回备份分支!

【讨论】:

  • 我喜欢这种方法 - 但我不确定这是否是最佳做法?我经常做“备份” git 分支,最终我会有很多备份分支。我不确定这是否可以(有大约 20 个来自不同日期的备份分支)。我想我最终总是可以删除较旧的备份 - 但如果我想保留所有备份 - 可以吗?到目前为止,它玩得很好 - 但很高兴知道这是好的还是坏的做法。
  • 这不是所谓的最佳实践,我认为它与个人做事的习惯有关。我通常只在一个分支中编码,直到工作完成,并为 adhoc 请求保留另一个分支。两者都有备份,一旦完成,删除主分支! :)
【解决方案4】:

这个问题的两个答案都是正确的,但我仍然缺少将 Github 存储库备份到本地文件的完整、简短的解决方案。 gist 可在此处获得,请随时分叉或适应您的需求。

backup.sh:

#!/bin/bash
# Backup the repositories indicated in the command line
# Example:
# bin/backup user1/repo1 user1/repo2
set -e
for i in $@; do
  FILENAME=$(echo $i | sed 's/\//-/g')
  echo "== Backing up $i to $FILENAME.bak"
  git clone git@github.com:$i $FILENAME.git --mirror
  cd "$FILENAME.git"
  git bundle create ../$FILENAME.bak --all
  cd ..
  rm -rf $i.git
  echo "== Repository saved as $FILENAME.bak"
done

restore.sh:

#!/bin/bash
# Restore the repository indicated in the command line
# Example:
# bin/restore filename.bak
set -e

FOLDER_NAME=$(echo $1 | sed 's/.bak//')
git clone --bare $1 $FOLDER_NAME.git

【讨论】:

  • 有趣。比我的回答更准确。 +1
  • 谢谢,这对 Github 很有用。接受的答案是当前问题。
【解决方案5】:

您可以使用 git-copy 备份 git repo。 git-copy 将新项目保存为裸仓库,这意味着存储成本最低。

git copy /path/to/project /backup/project.backup

然后你可以用git clone恢复你的项目

git clone /backup/project.backup project

【讨论】:

  • 啊!这个答案让我相信“git copy”是官方的 git 命令。
【解决方案6】:

我开始修改 Yar 的脚本,结果在 github 上,包括手册页和安装脚本:

https://github.com/najamelan/git-backup

安装

git clone "https://github.com/najamelan/git-backup.git"
cd git-backup
sudo ./install.sh

欢迎 github 上的所有建议和拉取请求。

#!/usr/bin/env ruby
#
# For documentation please sea man git-backup(1)
#
# TODO:
# - make it a class rather than a function
# - check the standard format of git warnings to be conform
# - do better checking for git repo than calling git status
# - if multiple entries found in config file, specify which file
# - make it work with submodules
# - propose to make backup directory if it does not exists
# - depth feature in git config (eg. only keep 3 backups for a repo - like rotate...)
# - TESTING



# allow calling from other scripts
def git_backup


# constants:
git_dir_name    = '.git'          # just to avoid magic "strings"
filename_suffix = ".git.bundle"   # will be added to the filename of the created backup


# Test if we are inside a git repo
`git status 2>&1`

if $?.exitstatus != 0

   puts 'fatal: Not a git repository: .git or at least cannot get zero exit status from "git status"'
   exit 2


else # git status success

   until        File::directory?( Dir.pwd + '/' + git_dir_name )             \
            or  File::directory?( Dir.pwd                      ) == '/'


         Dir.chdir( '..' )
   end


   unless File::directory?( Dir.pwd + '/.git' )

      raise( 'fatal: Directory still not a git repo: ' + Dir.pwd )

   end

end


# git-config --get of version 1.7.10 does:
#
# if the key does not exist git config exits with 1
# if the key exists twice in the same file   with 2
# if the key exists exactly once             with 0
#
# if the key does not exist       , an empty string is send to stdin
# if the key exists multiple times, the last value  is send to stdin
# if exaclty one key is found once, it's value      is send to stdin
#


# get the setting for the backup directory
# ----------------------------------------

directory = `git config --get backup.directory`


# git config adds a newline, so remove it
directory.chomp!


# check exit status of git config
case $?.exitstatus

   when 1 : directory = Dir.pwd[ /(.+)\/[^\/]+/, 1]

            puts 'Warning: Could not find backup.directory in your git config file. Please set it. See "man git config" for more details on git configuration files. Defaulting to the same directroy your git repo is in: ' + directory

   when 2 : puts 'Warning: Multiple entries of backup.directory found in your git config file. Will use the last one: ' + directory

   else     unless $?.exitstatus == 0 then raise( 'fatal: unknown exit status from git-config: ' + $?.exitstatus ) end

end


# verify directory exists
unless File::directory?( directory )

   raise( 'fatal: backup directory does not exists: ' + directory )

end


# The date and time prefix
# ------------------------

prefix           = ''
prefix_date      = Time.now.strftime( '%F'       ) + ' - ' # %F = YYYY-MM-DD
prefix_time      = Time.now.strftime( '%H:%M:%S' ) + ' - '
add_date_default = true
add_time_default = false

prefix += prefix_date if git_config_bool( 'backup.prefix-date', add_date_default )
prefix += prefix_time if git_config_bool( 'backup.prefix-time', add_time_default )



# default bundle name is the name of the repo
bundle_name = Dir.pwd.split('/').last

# set the name of the file to the first command line argument if given
bundle_name = ARGV[0] if( ARGV[0] )


bundle_name = File::join( directory, prefix + bundle_name + filename_suffix )


puts "Backing up to bundle #{bundle_name.inspect}"


# git bundle will print it's own error messages if it fails
`git bundle create #{bundle_name.inspect} --all --remotes`


end # def git_backup



# helper function to call git config to retrieve a boolean setting
def git_config_bool( option, default_value )

   # get the setting for the prefix-time from git config
   config_value = `git config --get #{option.inspect}`

   # check exit status of git config
   case $?.exitstatus

      # when not set take default
      when 1 : return default_value

      when 0 : return true unless config_value =~ /(false|no|0)/i

      when 2 : puts 'Warning: Multiple entries of #{option.inspect} found in your git config file. Will use the last one: ' + config_value
               return true unless config_value =~ /(false|no|0)/i

      else     raise( 'fatal: unknown exit status from git-config: ' + $?.exitstatus )

   end
end

# function needs to be called if we are not included in another script
git_backup if __FILE__ == $0

【讨论】:

  • @Yar 很棒的捆绑脚本,基于我在下面的回答中提倡的 git 捆绑。 +1。
  • 我已经在我的本地裸存储库中安装了您的应用程序....安装后您如何使用它....文档中没有关于此的信息,您应该包含一个带有如何进行备份的示例
  • 嗨,对不起,你没有让它工作。通常你运行sudo install.sh,然后配置它(它使用 git config 系统)来设置目标目录(参见 github 上的自述文件)。接下来,您在存储库中运行 git backup。作为旁注,这是一个 git bundle 的实验和对这个问题的回应,但是 git bundle 从不制作绝对精确的副本(例如,如果我记得很好,尤其是关于 git 遥控器),所以我个人实际上使用 tar 来备份。 git 目录。
【解决方案7】:

[只是留在这里供我自己参考。]

我的名为 git-backup 的捆绑脚本看起来像这样

#!/usr/bin/env ruby
if __FILE__ == $0
        bundle_name = ARGV[0] if (ARGV[0])
        bundle_name = `pwd`.split('/').last.chomp if bundle_name.nil? 
        bundle_name += ".git.bundle"
        puts "Backing up to bundle #{bundle_name}"
        `git bundle create /data/Dropbox/backup/git-repos/#{bundle_name} --all`
end

有时我使用git backup,有时我使用git backup different-name,这给了我所需的大部分可能性。

【讨论】:

  • +1 因为你没有使用--global 选项,所以这个别名只会出现在你的项目中(它在你的.git/config 文件中定义)——这可能就是你想要的。感谢您提供更详细且格式正确的答案。
  • @yar:你知道如何在没有命令行的情况下完成这些任务,而是只使用 tortoisegit(我正在为我的非命令行窗口用户寻找解决方案)?
  • @pastacool,对不起,我根本不知道没有命令行的 git。或许可以看看像 RubyMine 这样的相关 IDE?
  • @intuited,您可以使用 spideroak 回滚 DIRECTORIES,也可以只回滚文件(Dropbox 会这样做,它们会为您提供 3GB 空间)?
  • @Yar:我不确定我是否理解.. 你的意思是如果我删除了一个 Dropbox 支持的目录,我会丢失其中包含的所有文件的先前版本吗?有关 spideroak 版本控制政策的更多信息是 here。 TBH 我并没有真正使用过 SpiderOak,也不能完全确定它的限制。看起来他们确实会为这些问题提供解决方案,但他们非常重视技术能力。另外:Dropbox 是否仍有 30 天的免费帐户回滚限制?
【解决方案8】:

我这样做的方法是创建一个远程(裸)存储库(在单独的驱动器、USB 密钥、备份服务器甚至 github 上),然后使用push --mirror 使远程存储库看起来与我的本地存储库完全相同(除了远程是一个 bare 存储库)。

这将推送所有引用(分支和标签),包括非快进更新。我用它来创建本地存储库的备份。

man page 是这样描述的:

指定将$GIT_DIR/refs/ 下的所有引用(包括但不限于refs/heads/refs/remotes/refs/tags/)镜像到远程存储库,而不是命名每个要推送的引用。新创建的本地 refs 会被推送到远端,本地更新的 refs 会在远端强制更新,删除的 refs 会从远端移除。如果设置了配置选项remote.&lt;remote&gt;.mirror,这是默认设置。

我做了一个别名来做推送:

git config --add alias.bak "push --mirror github"

然后,只要我想进行备份,我就运行git bak

【讨论】:

  • +1。同意。 git bundle 可以很好地移动备份(一个文件)。但是有了驱动器,你可以在任何地方插入,裸仓库也很好。
  • +1 太棒了,我会调查一下。也感谢您提供的示例。
  • @Pat Notz,最后我决定按照你的方式去做,我在下面写了一个答案(分数永久为零:)
  • 请注意,--mirror 实际上并没有对它获得的对象进行任何类型的验证。您可能应该在某个时候运行git fsck 以防止损坏。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-20
  • 1970-01-01
  • 1970-01-01
  • 2012-04-06
  • 2018-06-26
  • 1970-01-01
相关资源
最近更新 更多