Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Firedrill: Recreate central git repository from developers clones

Tags:

Say you have a scenario with a central master git repository, which developers and CI-engines clone from. I.e. very close to a traditional non-distributed version control system setup, with a central hub and a lot of nodes.

Now say that server was stolen or struck by lightning or any other thing that would result that the central repository was gone along with all its centralized backups. All you have left is the various clones, and by good fortune one of these was fully updated, so you create a blank git repository replacement server to be used as the future central repository and go to work on the clone.

The fully updated clone knows of all the "remotes/origin" branches with "git branch -a", but does only have a single local branch. (This is what worries me - losing branch information).

What would the steps be for reestablishing a new central git repository behaving in any way like the old one, branches and all?

like image 941
Thorbjørn Ravn Andersen Avatar asked Aug 20 '12 08:08

Thorbjørn Ravn Andersen


People also ask

How do you update a repository after cloning?

Update, then WorkUpdate your local repo from the central repo ( git pull upstream master ). Make edits, save, git add , and git commit all in your local repo. Push changes from local repo to your fork on github.com ( git push origin master ) Update the central repo from your fork ( Pull Request )

How do I clone a git developer branch?

In order to clone a specific branch, you have to execute “git branch” with the “-b” and specify the branch you want to clone. $ git clone -b dev https://github.com/username/project.git Cloning into 'project'... remote: Enumerating objects: 813, done.


2 Answers

Just create an empty repository and, from the fully updated clone, do:

git remote add new-origin url://to/new/origin

git push --tags new-origin refs/remotes/origin/*:refs/heads/*

Obviously, if the new origin is at the same url as the original one, you must be careful to not fetch from origin.

like image 51
CB Bailey Avatar answered Oct 26 '22 07:10

CB Bailey


First, please, don't install gitosis: go with the latest V3+ Gitolite, a much more complete authorization layer: see "gitosis vs gitolite?" and "Why do you need Gitosis or Gitolite?".

Second, you don't loose any branch information.

Here is another approach to the same issue, longer, but which illustrates how you still keep all the branches.

Any of your clone may not have all the local branches, true, but all those branches are still in the remote namespace of those clones, since they have cloned from the same (now gone) "central" repo.

Once you have cloned back (as a bare repo) on the new central server one of those repos, all you need is clean it up a little and you can reference it as your new blessed repo.

Note: if you cannot access to a local repo from your server, bundle said local repo, and copy the one file representing that bundle back to your server: you will be able to clone from said bundle.
See "git bundle: bundle tags and heads" to create the bundle properly.


Short Version

# Let's re-create a bare "blessed" repo on the server
git clone --mirror /path/to/a/local/repo repo.git
# or git clone --mirror /path/to/repo.bundle repo.git

# restore the local branches
remote=origin ; for brname in `git branch -r | grep $remote | grep -v HEAD | awk '{gsub(/[^\/]+\//,"",$1); print $1}'`; do git branch --set-upstream $brname  $remote/$brname ; done

# delete the remotes branches
# (your blessed repo doesn't track anything)
remote=origin ; for brname in `git branch -r | grep $remote | grep -v HEAD | awk '{gsub(/[^\/]+\//,"",$1); print $1}'`; do git branch -r -d origin/$brname ; done

# delete the remote 'origin'
# Not needed for your blessed repo
git remote rm origin

# Let's make sure master is the current branch
# for that bare repo:
git symbolic-ref HEAD refs/heads/master

That's it. Ready to go.


Long Version (Demo)

Let's create a repo, with 4 branches: master, b1, b2, b3, each with their own files:

C:\Users\VonC\prog\git\tests>mkdir c
C:\Users\VonC\prog\git\tests>cd c
C:\Users\VonC\prog\git\tests\c>git init r1
Initialized empty Git repository in C:/Users/VonC/prog/git/tests/c/r1/.git/
C:\Users\VonC\prog\git\tests\c>cd r1
C:\Users\VonC\prog\git\tests\c\r1>echo m > m.txt && git add . && git commit -m "first commit"
[master (root-commit) 1ffe5c1] first commit
 1 file changed, 1 insertion(+)
 create mode 100644 m.txt

C:\Users\VonC\prog\git\tests\c\r1>git checkout -b b1
Switched to a new branch 'b1'
C:\Users\VonC\prog\git\tests\c\r1>echo f1 > f1.txt && git add . && git commit -m "f1 in b1"
[b1 1e64d01] f1 in b1
 1 file changed, 1 insertion(+)
 create mode 100644 f1.txt

C:\Users\VonC\prog\git\tests\c\r1>git checkout -b b2 master
Switched to a new branch 'b2'
C:\Users\VonC\prog\git\tests\c\r1>echo f2 > f2.txt git add . && git commit -m "f2 in b2"
[b2 4462b8f] f2 in b2
 1 file changed, 1 insertion(+)
 create mode 100644 f2.txt

C:\Users\VonC\prog\git\tests\c\r1>git checkout -b b3 master
Switched to a new branch 'b3'
C:\Users\VonC\prog\git\tests\c\r1>echo f3 > f3.txt && git add . && git commit -m "f3 in b3"
[b3 7ada753] f3 in b3
 1 file changed, 1 insertion(+)
 create mode 100644 f3.txt

Now if I clone r1 into r2, and r2 into r3, yes, r3 would loose branch informations:

C:\Users\VonC\prog\git\tests\c>git clone r1 r2
Cloning into 'r2'...
done.

C:\Users\VonC\prog\git\tests\c>git clone r2 r3
Cloning into 'r3'...
done.

C:\Users\VonC\prog\git\tests\c>cd r3

C:\Users\VonC\prog\git\tests\c\r3>git br -a
* b3
  remotes/origin/HEAD -> origin/b3
  remotes/origin/b3

But in your case, most of the repos out there are the direct result of a clone from the blessed repo.

r2 has all the necessary branches (one local, 4 remote tracking branches, since there is no "local tracking branches"):

C:\Users\VonC\prog\git\tests\c\r2>git br -a
* b3
  remotes/origin/HEAD -> origin/b3
  remotes/origin/b1
  remotes/origin/b2
  remotes/origin/b3
  remotes/origin/master

If I can clone --mirror r2 into a bare repo r4, I will still get all the branches.
See "What's the difference between git clone --mirror and git clone --bare" for why.

C:\Users\VonC\prog\git\tests\c>git clone --mirror r2 r4
Cloning into bare repository 'r4'...
done.

C:\Users\VonC\prog\git\tests\c>cd r4

C:\Users\VonC\prog\git\tests\c\r4>git br -a
* b3
  remotes/origin/HEAD
  remotes/origin/b1
  remotes/origin/b2
  remotes/origin/b3
  remotes/origin/master

Its remote still points to r2

VonC@HOSTNAME ~/prog/git/tests/c/r4 (BARE:b3)
$ git remote -v
origin  C:/Users/VonC/prog/git/tests/c/r2 (fetch)
origin  C:/Users/VonC/prog/git/tests/c/r2 (push)

But that isn't needed anymore.
Let's make sure r2 (or r1 for that matter) is no longer accessible:

VonC@HOSTNAME ~/prog/git/tests/c/r4 (BARE:b3)
$ cd ..
VonC@HOSTNAME ~/prog/git/tests/c
$ mv r1 r1.old
VonC@HOSTNAME ~/prog/git/tests/c
$ mv r2 r2.old
VonC@HOSTNAME ~/prog/git/tests/c
$ cd r4

Now we can restore the local branches, by pointing them to the remote tracking branches:
See "Track all remote git branches as local branches":

VonC@HOSTNAME ~/prog/git/tests/c/r4 (BARE:b3)
$ remote=origin ; for brname in `git branch -r | grep $remote | grep -v HEAD | awk '{gsub(/[^\/]+\//,"",$1); print $1}'`; do git branch --set-upstream $brname  $remote/$brname ; done
Branch b1 set up to track remote ref refs/remotes/origin/b1.
Branch b2 set up to track remote ref refs/remotes/origin/b2.
Branch b3 set up to track remote ref refs/remotes/origin/b3.
Branch master set up to track remote ref refs/remotes/origin/master.

Let's make master the default branch for that bare repo:
See "Git: Correct way to change Active Branch in a bare repository?", and "How do I change a Git remote HEAD to point to something besides “master”".

VonC@HOSTNAME ~/prog/git/tests/c/r4 (BARE:b3)
$ git symbolic-ref HEAD refs/heads/master
VonC@HOSTNAME ~/prog/git/tests/c/r4 (BARE:master)

Anything referring to 'origin' is no longer needed.
Let's get rid of the remote tracking branches:
See "Delete branches listed by git branch -a" and "Deleting remote branches?"

VonC@HOSTNAME ~/prog/git/tests/c/r4 (BARE:master)
$ remote=origin ; for brname in `git branch -r | grep $remote | grep -v HEAD | awk '{gsub(/[^\/]+\//,"",$1); print $1}'`; do git branch -r -d origin/$brname ; done
Deleted remote branch origin/b1 (was 1e64d01).
Deleted remote branch origin/b2 (was 4462b8f).
Deleted remote branch origin/b3 (was 7ada753).
Deleted remote branch origin/master (was 1ffe5c1).

Let's panic and check if our local branches do still reference what we just "deleted":
(See "Display the most recent commit of each branch in git")

VonC@HOSTNAME ~/prog/git/tests/c/r4 (BARE:master)
$ git br -v
  b1     1e64d01 f1 in b1
  b2     4462b8f f2 in b2
  b3     7ada753 f3 in b3
* master 1ffe5c1 first commit

Yeap, all good.

like image 35
VonC Avatar answered Oct 26 '22 07:10

VonC