如何备份本地Git存储库?

时间:2010-01-24 22:40:59

标签: git backup

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

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

8 个答案:

答案 0 :(得分:140)

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

这将创建一个支持git fetchgit pull的文件,以便更新您的第二个回购。
对增量备份和还原很有用。

但是如果你需要备份所有内容(因为你没有第二个包含已经存在的旧内容的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文件夹中的归档,而commentedfantabolous


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

如果您查看history of edits OP Yaranswer,您会看到Yar首先使用clone --mirror,...使用编辑:

  

在Dropbox中使用它是一团糟   您将出现同步错误,并且无法在DROPBOX中返回目录   如果要备份到保管箱,请使用git bundle

Yar current solution使用git bundle

我休息一下。

答案 1 :(得分:61)

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

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

man page描述如下:

  

指定$GIT_DIR/refs/下的所有引用(包括但不限于refs/heads/refs/remotes/refs/tags/),而不是将每个引用命名为push。到远程存储库。新创建的本地引用将被推送到远程端,本地更新的引​​用将在远程端强制更新,并且已删除的引用将从远程端删除。如果设置了配置选项remote.<remote>.mirror,则这是默认值。

我做了一个别名来做推:

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

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

答案 2 :(得分:35)

[离开这里供我自己参考。]

我的名为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,这可以为我提供所需的大部分功能。

答案 3 :(得分:24)

我开始在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

答案 4 :(得分:9)

这两个问题的答案都是正确的,但我仍然缺少将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

答案 5 :(得分:4)

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

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

然后,您可以使用git clone

恢复项目
git clone /backup/project.backup project

答案 6 :(得分:2)

涉猎上方的文字墙后找到了一种简单的官方方法,这会让您觉得没有任何意义。

创建具有以下内容的完整捆绑包:

$ git bundle create <filename> --all

使用以下方法恢复它:

$ git clone <filename> <folder>

此操作是原子AFAIK。检查official docs以获得详细信息。

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

答案 7 :(得分:1)

通过谷歌来解决这个问题。

这是我用最简单的方式做的。

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

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