Làm thế nào để sao lưu kho Git cục bộ?


155

Tôi đang sử dụng git trong một dự án tương đối nhỏ và tôi thấy rằng việc nén nội dung của thư mục .git có thể là một cách tốt để sao lưu dự án. Nhưng điều này thật kỳ lạ bởi vì, khi tôi khôi phục, điều đầu tiên tôi cần làm là git reset --hard.

Có bất kỳ vấn đề với việc sao lưu một repo git theo cách này? Ngoài ra, có cách nào tốt hơn để làm điều đó không (ví dụ: định dạng git di động hoặc một cái gì đó tương tự?)?


Tại sao không ai đưa ra câu trả lời rõ ràng về việc sử dụng gói git ???
gatopeich

@gatopeich họ đã làm. Cuộn xuống.
Dan Rosenstark

Tất cả các câu trả lời được nêu lên đều chứa một bức tường văn bản về các tập lệnh tùy chỉnh, ngay cả câu trả lời bắt đầu được đề cậpgit bundle
gatopeich

Câu trả lời:


23

Tôi bắt đầu hack đi một chút về kịch bản của Yar và kết quả là trên github, bao gồm các trang man và tập lệnh cài đặt:

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

Cài đặt :

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

Chào mừng tất cả các đề xuất và kéo yêu cầu trên 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 Kịch bản gói tuyệt vời, dựa trên gói git mà tôi ủng hộ trong câu trả lời của tôi dưới đây. +1.
VonC

1
Tôi đã cài đặt ứng dụng của bạn trong kho lưu trữ cục bộ của tôi .... làm thế nào để bạn sử dụng nó sau khi cài đặt .... không có thông tin nào liên quan đến tài liệu đó, bạn nên bao gồm một phần với một ví dụ về cách tạo bản sao lưu
JAF

Xin chào, xin lỗi bạn không làm cho nó hoạt động. Thông thường bạn chạy sudo install.sh, sau đó cấu hình nó (nó sử dụng hệ thống cấu hình git) để đặt thư mục đích (xem tệp readme trên github). Tiếp theo bạn chạy git backupbên trong kho lưu trữ của bạn. Là một sidenote, đây là một thử nghiệm với gói git và trả lời cho câu hỏi này, nhưng gói git không bao giờ tạo ra một bản sao chính xác tuyệt đối (ví dụ: nếu tôi nhớ tốt, đặc biệt là từ xa git), vì vậy cá nhân tôi thực sự sử dụng tar để sao lưu. thư mục git.

144

Cách chính thức khác là sử dụng gói git

Điều đó sẽ tạo ra một tệp hỗ trợ git fetchgit pullđể cập nhật repo thứ hai của bạn.
Hữu ích cho việc sao lưu và khôi phục gia tăng.

Nhưng nếu bạn cần sao lưu mọi thứ (vì bạn chưa có repo thứ hai với một số nội dung cũ hơn), thì bản sao lưu sẽ phức tạp hơn một chút, như đã đề cập trong câu trả lời khác của tôi, sau bình luận của 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

(Đây là một hoạt động nguyên tử , trái ngược với việc tạo một kho lưu trữ từ .gitthư mục, như được bình luận bởi fantabolous )


Cảnh báo: Tôi sẽ không đề xuất giải pháp của Pat Notz , đó là nhân bản repo. Sao lưu nhiều tệp luôn khó khăn hơn so với sao lưu hoặc cập nhật ... chỉ một.

Nếu bạn nhìn vào lịch sử các chỉnh sửa của câu trả lời OP Yar , bạn sẽ thấy rằng Yar được sử dụng đầu tiên , ... với chỉnh sửa:clone --mirror

Sử dụng điều này với Dropbox là một mớ hỗn độn .
Bạn sẽ có lỗi đồng bộ hóa và bạn KHÔNG THỂ KIẾM ĐƯỢC MỘT TRỰC TIẾP TRỞ LẠI TRONG DROPBOX.
Sử dụng git bundlenếu bạn muốn sao lưu vào dropbox của bạn.

Giải pháp hiện tại của Yar sử dụng git bundle.

I nghỉ ngơi trường hợp của tôi.


Tôi chỉ kiểm tra cái này và nó thực sự tuyệt vời. Tôi sẽ phải thử một số gói và giải nén và người đứng đầu danh sách để được thuyết phục ... nhưng tôi thích nó một chút. Cảm ơn một lần nữa, đặc biệt là các ghi chú trên công tắc --all.
Dan Rosenstark

Hơi liên quan, có gì sai khi chỉ nén kho lưu trữ cục bộ của tôi? Tôi cần một tệp sao lưu duy nhất, sao chép hàng ngàn tệp trên ổ đĩa ngoài rất chậm. Tôi chỉ tự hỏi liệu có cái gì hiệu quả hơn không vì zip phải lưu trữ rất nhiều tệp trong thư mục .git.

@faB: sự khác biệt duy nhất là bạn có thể dễ dàng thực hiện sao lưu gia tăng với git bundle. Không thể có một zip toàn cầu của tất cả các repo địa phương.
VonC

2
Trả lời một nhận xét cũ, nhưng một sự khác biệt khác giữa bó và nén thư mục là gói nguyên tử, vì vậy nó sẽ không bị rối nếu ai đó tình cờ cập nhật repo của bạn ở giữa hoạt động.
tưởng tượng

1
@fantabolous điểm tốt. Tôi đã đưa nó vào câu trả lời để dễ nhìn hơn.
VonC

62

Cách tôi làm là tạo một kho lưu trữ từ xa (trần) (trên một ổ đĩa riêng, Khóa USB, máy chủ dự phòng hoặc thậm chí github) và sau đó sử dụng push --mirrorđể làm cho repo từ xa đó trông giống hệt như kho lưu trữ cục bộ của tôi (ngoại trừ điều khiển từ xa là trần kho).

Điều này sẽ đẩy tất cả các ref (chi nhánh và thẻ) bao gồm các bản cập nhật không chuyển tiếp nhanh. Tôi sử dụng điều này để tạo bản sao lưu của kho lưu trữ cục bộ của tôi.

Các trang người đàn ông mô tả nó như thế này:

Thay vì đặt tên mỗi ref để push, quy định cụ thể rằng tất cả các refs dưới $GIT_DIR/refs/(bao gồm nhưng không giới hạn refs/heads/, refs/remotes/refs/tags/) được phản ánh vào kho từ xa. Các ref mới được tạo cục bộ sẽ được đẩy đến đầu từ xa, các ref được cập nhật cục bộ sẽ được cập nhật ở đầu từ xa và các ref được xóa sẽ bị xóa khỏi đầu từ xa. Đây là mặc định nếu tùy chọn cấu hình remote.<remote>.mirrorđược đặt.

Tôi đã tạo một bí danh để thực hiện việc đẩy:

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

Sau đó, tôi chỉ chạy git bakbất cứ khi nào tôi muốn làm một bản sao lưu.


+1. Đã đồng ý. gói git là tốt để di chuyển một bản sao lưu xung quanh (một tệp). Nhưng với một ổ đĩa bạn có thể cắm bất cứ nơi nào, repo trần cũng tốt.
VonC

+1 awesme, tôi sẽ xem xét điều này. Cảm ơn các ví dụ, quá.
Dan Rosenstark

@Pat Notz, cuối cùng tôi quyết định thực hiện theo cách của bạn và tôi đặt câu trả lời bên dưới tại đây (điểm số được giữ ở mức 0 :)
Dan Rosenstark

Lưu ý rằng --mirrorkhông thực sự chạy bất kỳ loại xác minh nào trên các đối tượng mà nó nhận được. Bạn có thể nên chạy git fsckở một số điểm để ngăn ngừa tham nhũng.
docwhat

34

[Chỉ để lại đây để tham khảo của riêng tôi.]

Kịch bản gói của tôi được gọi là git-backupnhư thế này

#!/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

Đôi khi tôi sử dụng git backupvà đôi khi tôi sử dụng git backup different-namemang lại cho tôi hầu hết các khả năng tôi cần.


2
+1 Vì bạn không sử dụng --globaltùy chọn mà bí danh này sẽ chỉ được nhìn thấy trong dự án của bạn (nó được đặt trong .git/configtệp của bạn ) - đó có thể là những gì bạn muốn. Cảm ơn câu trả lời chi tiết và độc đáo hơn.
Pat Notz

1
@yar: bạn có biết làm thế nào để hoàn thành các nhiệm vụ này mà không cần dòng lệnh và thay vào đó chỉ sử dụng tortoisegit (đang tìm kiếm giải pháp cho người dùng không phải là dòng lệnh của tôi) không?
pastacool

@pastacool, xin lỗi tôi không biết gì về git mà không có dòng lệnh nào cả. Có lẽ kiểm tra một IDE có liên quan như RubyMine?
Dan Rosenstark

@intuited, bạn có thể quay lại GIÁM ĐỐC bằng spideroak hoặc chỉ các tệp (mà Dropbox làm và chúng cung cấp cho bạn 3 GB dung lượng)?
Dan Rosenstark

@Yar: không chắc chắn tôi hiểu .. bạn có nghĩa là nếu tôi xóa một thư mục được hỗ trợ Dropbox, tôi sẽ mất tất cả các phiên bản trước của các tệp có trong đó? Thông tin thêm về các chính sách phiên bản của spideroak có ở đây . TBH Tôi chưa thực sự sử dụng SpiderOak nhiều và không hoàn toàn chắc chắn về giới hạn của nó. Có vẻ như họ sẽ cung cấp một giải pháp cho những vấn đề như vậy, mặc dù vậy, họ rất chú trọng đến năng lực kỹ thuật. Ngoài ra: Dropbox có còn giới hạn 30 ngày đối với các lần quay lại đối với các tài khoản miễn phí không?
trực giác

9

Cả hai câu trả lời cho câu hỏi này đều đúng, nhưng tôi vẫn còn thiếu một giải pháp hoàn chỉnh, ngắn gọn để sao lưu kho lưu trữ Github vào một tệp cục bộ. Các ý chính có sẵn ở đây, cảm thấy thoải mái để ngã ba hoặc thích ứng với nhu cầu của bạn.

sao lưu.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

khôi phục.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
Hấp dẫn. Chính xác hơn câu trả lời của tôi. +1
VonC

Cảm ơn, điều này hữu ích cho Github. Câu trả lời được chấp nhận là câu hỏi hiện tại.
Dan Rosenstark 18/07/2015

5

Bạn có thể sao lưu git repo bằng git-copy . git-copy đã lưu dự án mới dưới dạng repo trần, điều đó có nghĩa là chi phí lưu trữ tối thiểu.

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

Sau đó, bạn có thể khôi phục dự án của bạn với git clone

git clone /backup/project.backup project

Argh! câu trả lời này khiến tôi tin rằng "git copy" là một lệnh git chính thức.
gatopeich

2

Tìm thấy cách chính thức đơn giản sau khi lội qua các bức tường văn bản ở trên sẽ khiến bạn nghĩ rằng không có gì.

Tạo một gói hoàn chỉnh với:

$ git bundle create <filename> --all

Khôi phục nó bằng:

$ git clone <filename> <folder>

Hoạt động này là AFAIK nguyên tử. Kiểm tra tài liệu chính thức cho các chi tiết gritty.

Về "zip": các gói git được nén và nhỏ đáng ngạc nhiên so với kích thước thư mục .git.


Điều này không trả lời toàn bộ câu hỏi về zip và cũng cho rằng chúng ta đã đọc các câu trả lời khác. Vui lòng sửa nó để nó nguyên tử và xử lý toàn bộ câu hỏi và tôi rất vui khi được chấp nhận câu trả lời (10 năm sau). Cảm ơn
Dan Rosenstark

0

đã đến câu hỏi này thông qua google.

Đây là những gì tôi đã làm theo cách đơn giản nhất.

git checkout branch_to_clone

sau đó tạo một nhánh git mới từ nhánh này

git checkout -b new_cloned_branch
Switched to branch 'new_cloned_branch'

trở lại chi nhánh ban đầu và tiếp tục:

git checkout branch_to_clone

Giả sử bạn đã làm hỏng và cần khôi phục một cái gì đó từ nhánh dự phòng:

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

Phần tốt nhất nếu bất cứ điều gì bị làm hỏng, bạn chỉ cần xóa nhánh nguồn và quay trở lại nhánh dự phòng !!


1
Tôi thích cách tiếp cận này - nhưng tôi không chắc liệu nó có tốt nhất không? Tôi tạo các nhánh git 'sao lưu' khá thường xuyên và cuối cùng tôi sẽ có nhiều nhánh dự phòng. Tôi không chắc điều này có ổn hay không (có ~ 20 nhánh dự phòng từ các ngày khác nhau). Tôi đoán cuối cùng tôi có thể xóa các bản sao lưu cũ hơn - nhưng nếu tôi muốn giữ tất cả chúng - điều đó có ổn không? Cho đến nay nó đang chơi độc đáo - nhưng sẽ rất tuyệt nếu biết đó là cách luyện tập tốt hay xấu.
Kyle Vassella

nó không phải là thứ gì đó sẽ được gọi là thực hành tốt nhất , tôi cho rằng nó liên quan nhiều hơn đến những thói quen cá nhân của họ khi làm công cụ. Tôi thường viết mã trong một nhánh cho đến khi công việc được thực hiện và giữ một nhánh khác cho các yêu cầu adhoc . Cả hai đều có bản sao lưu, sau khi thực hiện, xóa chi nhánh chính! :)
NoobEditor
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.