如何备份本地Git存储库?


155

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

这样备份git repo是否有任何问题?另外,还有更好的方法吗(例如,便携式git格式或类似的格式?)?


为什么没有人给出使用git bundle的明显答案?
gatopeich

@gatopeich他们做到了。向下滚动。
Dan Rosenstark

所有赞成的答案都包含有关自定义脚本的文字墙,甚至包括开始提及的文字git bundle
gatopeich

Answers:


23

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

1
@Yar Great bundle脚本,基于我在下面的答案中主张的git bundle。+1。
VonC 2014年

1
我已经在本地裸存储库中安装了您的应用程序。...安装后如何使用它
JAF

嗨,对不起,您无法使用它。通常,您运行sudo install.sh,然后对其进行配置(它使用git config系统)以设置目标目录(请参阅github上的自述文件)。接下来,您将git backup在存储库中运行。附带说明一下,这是一个使用git bundle和对此问题的回答的实验,但是git bundle从来没有做过绝对精确的复制(例如,如果我记得很好,尤其是关于git remotes),所以我个人实际上是使用tar备份的。 git目录。

144

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

这将创建一个支持git fetchgit pull更新第二个仓库的文件。
对于增量备份和还原很有用。

但是,如果您需要备份所有内容(因为您没有第二个存储库,其中已经包含一些较旧的内容),那么在肯特·弗雷德里克的评论之后,正如我在其他答案中所提到的那样,备份的工作就更加复杂了:

$ 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像通过fantabolous 注释那样从该文件夹进行归档)


警告:我不建议您使用Pat Notz解决方案,该解决方案正在克隆存储库。
备份许多文件总是比备份或更新要复杂得多。

如果你看一下编辑的历史的的OP亚尔 的答案,你会看到亚尔使用起初clone --mirror,......与编辑:

将其与Dropbox一起使用完全是一团糟
您将出现同步错误,并且无法在DROPBOX中回滚目录。
使用git bundle,如果你想备份到您的Dropbox。

Yar 当前的解决方案使用git bundle

我休息一下


我刚刚检查了一下,实际上很棒。我必须尝试进行一些捆绑和拆装以及标头的整理工作,但是我还是很喜欢它。再次感谢您,特别是--all开关上的注释。
丹·罗森斯塔克

有点相关,仅压缩本地存储库有什么问题吗?我只需要一个备份文件,就很难在外部驱动器上复制数千个文件。我只是想知道是否还有更有效的方法,因为zip必须将.git文件夹中的这么多文件存档。

@faB:唯一的区别是您可以使用轻松进行增量备份git bundle。使用所有本地存储库的全局压缩是不可能的。
VonC

2
回复旧评论,但bundle和压缩dir之间的另一个区别是bundle是原子的,因此,如果有人在操作过程中偶然更新您的存储库,则不会被弄乱。
惊人的2014年

1
@神奇的好点。我将其包含在答案中以提高可见性。
VonC 2014年

62

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

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

手册页描述它是这样的:

代替命名每个裁判推的,则指定下的所有参考文献$GIT_DIR/refs/(包括但不限于refs/heads/refs/remotes/refs/tags/)被镜像到远程存储库。新创建的本地裁判将被推送到远程端,本地更新的裁判将在远程端强制更新,而已删除的裁判将从远程端删除。如果remote.<remote>.mirror设置了配置选项,则这是默认设置。

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

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

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


+1。同意 git bundle非常适合移动备份(一个文件)。但是有了驱动器,您可以在任何地方插入,裸仓库也可以。
VonC

+1令人敬畏,我将对此进行调查。也感谢您的示例。
Dan Rosenstark 2010年

@Pat Notz,最后,我决定采用您的方式,在下面给出答案(分数永久保持在零:)
Dan Rosenstark 2010年

请注意,--mirror实际上并不会对其获取的对象进行任何形式的验证。您可能应该git fsck在某个时候运行以防止损坏。
docwhat

34

[仅将其留在这里供我参考。]

我的捆绑脚本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,这给了我所需的大多数可能性。


2
+1因为您没有使用该--global选项,所以该别名只会在您的项目中显示(在.git/config文件中定义)-这可能就是您想要的。感谢您提供更详细且格式正确的答案。
Pat Notz

1
@yar:您是否知道如何在不使用命令行的情况下完成这些任务,而仅使用tortoisegit(正在为非命令行窗口用户寻找解决方案)?
pastacool

@pastacool,对不起,我完全不了解git,而没有命令行。也许看看像RubyMine这样的相关IDE?
Dan Rosenstark 2010年

@intuited,您可以使用Spideroak或仅文件(Dropbox可以提供3GB的空间)来回滚目录吗?
Dan Rosenstark 2011年

@Yar:不确定我是否理解..你的意思是说,如果删除Dropbox支持的目录,则会丢失其中包含的文件的所有先前版本?有关Spideroak版本控制政策的更多信息,请点击此处。TBH我还没有真正使用过SpiderOak,也不确定它的局限性。看起来他们似乎已经为这些问题提供了解决方案,但是他们非常重视技术能力。另外:对于免费帐户,Dropbox是否仍具有30天的回滚限制?
直觉

9

这两个问题的答案都是正确的,但是我仍然缺少将Github存储库备份到本地文件的完整且简短的解决方案。该要点可在这里,随意叉子或适应您的需求。

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
有趣。比我的答案更精确。+1
VonC 2015年

谢谢,这对Github很有用。接受的答案是当前问题。
Dan Rosenstark 2015年

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命令。
gatopeich

2

在涉猎上方的文字墙后找到一种简单的官方方法,这会让您认为没有任何方法。

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

$ git bundle create <filename> --all

使用以下方法还原它:

$ git clone <filename> <folder>

此操作是原子AFAIK。检查官方文档以获取详细信息。

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


这并不能回答有关zip的整个问题,还假设我们已经阅读了其他答案。请修复它,使它原子化并处理整个问题,我很高兴能接受它的答案(十年后)。谢谢
丹·罗森斯塔克

0

通过谷歌来到这个问题。

这是我最简单的方法。

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

最好的办法是,如果您搞砸了什么,您可以删除源分支,然后移回备份分支!


1
我喜欢这种方法-但我不确定这是否是最佳做法?我经常创建“备份” git分支,最终我将有许多备份分支。我不确定这是否可以(有来自不同日期的〜20个备份分支)。我想我总是可以最终删除较旧的备份的-但是,如果我想保留它们,可以吗?到目前为止,它的表现不错-但很高兴知道它的好坏。
凯尔·瓦塞拉

它不是所谓的最佳实践,我认为它与个人做事的习惯有关。我通常只在一个分支中编写代码,直到完成工作,并为临时请求保留另一个分支。两者都有备份,完成后,删除主分支!:)
NoobEditor
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.