Selali Adobor
Selali Adobor

Reputation: 2157

How can I set up Git branch for automatic backups

I realize there are already questions on how to back up a repository (and the answer is usually git bundle), but I'd aiming specific setup.

I'd trying to periodically commit a "snapshot" of the current branch to another branch for backup purposes. I'm using a batch file, and it looks like this (I've tried more variations than I can list here):

git stash
git checkout backup
git add .
git commit -m "Automatic Backup  %Time%"  
git push --all origin
git stash pop
git checkout -

The behavior I'm aiming for is that each commit should be an exact snapshot of the current state of the directory, regardless of what branch I have checked out when I run it. If a newer commit changes a file, I want it to take precedence if a merge conflict arises.

I'm not actually trying to merge branches, I'm just trying to get the files as they exist at the time of the snapshot on the disk. So if branch A has a change on file A, and branch B has a conflicting change on the same file, but branch B is checked out, the backup branch should end up getting branch B's changes, which are currently in the file on the disk, ignoring anything else.

And a practical(?) example of what I'm trying to do: Say Branch A has "myfile.txt" as "Hello World", and Branch B has "myfile.txt" as "Hello Dave".

When I checkout Branch A and open "myfile.txt" in a text editor I expect it'd have "Hello World". When I checkout B, I'd expect it to have "Hello Dave".

If within one branch commit 1 had "Hello World", and 2 had "Hello Dave" there wouldn't be a conflict. I want my back up branch to end up with commit 1 containing "Hello World", and commit 2 containing "Hello Dave" assuming commit 1 occurred while I had previously checked out branch A, and branch B when commit 2 occurred.

I believe git stash is the key to what I'm doing, but it simply isn't working. I tried several different combinations of those commands, and all of them returned different variations off errors, at different points while the repo was in various states, so it's really hard to summarize them. I'd say my approach is probably fundamentally wrong, so the commands listed are there just to give a picture of what I've tried so far.

But no matter what I do I either get merge conflicts or nothing gets committed. What am I missing? (If there's any additional information I can provide, please let me know)

Upvotes: 4

Views: 4241

Answers (3)

Mikko Rantalainen
Mikko Rantalainen

Reputation: 16015

It seems to me that you want to automatically backup the working directory contents as a new commit in the backup branch and the push all the branches to origin.

In that case, you do not want to checkout the backup branch because that would change your working directory contents. Instead, you'll want to use some combination of git read-tree, git update-index and git commit-tree to fabricate a new commit for your backup branch and then use git branch -f to add the newly fabricated commit to the backup branch. After that, you can continue to do the regular push to origin.

Links: http://alx.github.io/gitbook/7_raw_git.html

Update:

I think the easiest way to do this is like this (assuming you already have a branch called backup):

#!/bin/bash
BRANCH=backup
export GIT_INDEX_FILE=/tmp/git-backup-index.$$
git add .
git commit-tree $(git write-tree) -p $(git show-ref --hash --heads $BRANCH) -m "Automatic backup" | xargs git branch -f $BRANCH
rm -f "$GIT_INDEX_FILE"
#git push origin $BRANCH

(Note that I haven't tested the above script...)

Upvotes: 5

onionjake
onionjake

Reputation: 4045

Why you would ever need to do this is beyond me.

You can use git config core.logAllRefUpdates true. This will make git keep a perfect history of everywhere every branch has been. You can even tweak it to never expire and you will have a perfect history of every branch (and not just when you remember to back it up).


If you really want to do it you can just make the index contain the tree you want and then commit it.

  1. Switch to backup branch git stash; git checkout backup
  2. Clear out the index git rm -fr --cached .
  3. Copy into the index the tree of the commit you want to backup
    git ls-tree branchA | git update-index --index-info
  4. Commit the results git commit -m "Backup of branchA" (Don't use -a!)
  5. Go back to where you came from git checkout -f branchB; git stash pop. You need to use -f because the working copy will say it is dirty.

This will never have any merge conflicts, because you are not merging anything. Each commit in backup will exactly represent the branches that you backed up at the time.


Here is an example.

$ mkdir example
$ cd example
$ git init
Initialized empty Git repository in /tmp/example/.git/
$ echo "First change" > file1
$ git add file1
$ git commit -am "file1 first change"
[master (root-commit) f071d01] file1 first change
 1 file changed, 1 insertion(+)
 create mode 100644 file1
$ git branch master_two
$ git checkout master_two
Switched to branch 'master_two'
$ sed -i s/First/Two/g file1·
$ git commit -am "Two change"
[master_two b786d88] Two change
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git checkout master
Switched to branch 'master'
$ sed -i s/First/Second/g file1
$ git commit -am "Second change"
[master d88ca84] Second change
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git checkout --orphan backup
Switched to a new branch 'backup'
$ git rm -fr --cached .
rm 'file1'
$ git ls-tree master | git update-index --index-info
$ git commit -am "snapshot of master"
[backup (root-commit) 7af271d] snapshot of master
 1 file changed, 1 insertion(+)
 create mode 100644 file1
$ git rm -fr --cached .
rm 'file1'
$ git ls-tree master_two  | git update-index --index-info
$ git commit -m "snapshot of master_two"
[backup a3ddfdd] snapshot of master_two
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git checkout -f master
Switched to branch 'master'

Upvotes: 2

Lionel
Lionel

Reputation: 34844

The problem is that you're trying to get git to pretend all your branches can coexist in a single branch (that you call backup). So in effect, you are merging everything, and it's natural that you get conflicts!

Just to clarify, here is an example: You have branch a, with file f that contains the text "a", and branch b that has file f containing "b". You work on branch a, automatic backup occurs, and it store f="a" in the backup branch. Then you work on branch b for whatever reason, and automatic backup occurs, and now you are trying to store f="b". This is a merge conflict because there is no relationship between branch a and b (maybe they both inherited file f from the prod branch where file f contains "prod", and changed it to "a" and "b" respectively).

If you want to take snapshots of what's in your directory at a given time, you want to use tags, and you probably want to put the time the tag was taken as part of the tag name such as workdir-snapshot-20140818-1432.

Remember that branches are only pointers to commits, so using the term "backup" is really misleading, you are really not backuping data, the most you are keeping track of is what branch was checked out to your workdir at a given time, that's it.

Upvotes: -1

Related Questions