Firedrill: Recreate central git repository from developers clones

后端 未结 2 1777
日久生厌
日久生厌 2021-02-02 12:29

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 syste

2条回答
  •  借酒劲吻你
    2021-02-02 12:55

    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.

提交回复
热议问题