-
Notifications
You must be signed in to change notification settings - Fork 0
/
convert_repository.go
144 lines (122 loc) · 3.38 KB
/
convert_repository.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package dumper
import (
"errors"
"fmt"
"os"
"path/filepath"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
)
var (
ErrNotImplemented = errors.New("not implemented yet")
ErrRepositoryIsNonBare = errors.New("repository is already non-bare")
ErrRepositoryIsBare = errors.New("repository is already bare")
)
type RepositoryType int
const (
RepositoryTypeBare RepositoryType = iota
RepositoryTypeNonBare
)
type Converter interface {
Convert(destination string, repoType RepositoryType) error
}
// Convert converts git repository from bare to non-bare and vice versa
func Convert(destination string, convertToRepoType RepositoryType) error {
repository, err := Repository(destination)
if err != nil {
return fmt.Errorf("convert repository: %w", err)
}
config, err := repository.Config()
if err != nil {
return fmt.Errorf("convert repository: %w", err)
}
switch convertToRepoType {
case RepositoryTypeBare:
if ok := config.Core.IsBare; ok {
return ErrRepositoryIsBare
}
return ErrNotImplemented
case RepositoryTypeNonBare:
if ok := config.Core.IsBare; !ok {
return ErrRepositoryIsNonBare
}
return convertToNonBare(repository, destination)
default:
return errors.New("unknown repository type")
}
}
func convertToNonBare(repository *git.Repository, destination string) error {
rConfig, err := repository.Config()
if err != nil {
return err
}
rConfig.Core.IsBare = false
repository.SetConfig(rConfig)
err = os.Mkdir(filepath.Join(destination, ".git"), 0755)
if err != nil {
return fmt.Errorf("init repo: %w", err)
}
// move all content of repository into .git
// we need this because after checkout files from
// branch will be placed into root directory
err = moveFolderContent(destination, filepath.Join(destination, ".git"))
if err != nil {
return fmt.Errorf("move bare repo content into .git: %w", err)
}
// once git-related files are moved into .git
// it won't have any knowledge about branches
// so we need to read .git folder again as current
// repository instance does nothing about new fs structure
repository, err = git.PlainOpen(destination)
if err != nil {
return fmt.Errorf("open repository: %w", err)
}
refs, err := repository.References()
if err != nil {
return err
}
err = refs.ForEach(func(ref *plumbing.Reference) error {
if ref.Type() == plumbing.HashReference {
branchName := ref.Name().Short()
branchRef := plumbing.NewBranchReferenceName(branchName)
worktree, err := repository.Worktree()
if err != nil {
return fmt.Errorf("create worktree %s: %s", branchName, err)
}
err = worktree.Checkout(&git.CheckoutOptions{
Branch: branchRef,
Create: false,
Force: true,
})
if err != nil {
return fmt.Errorf("checkout branch %s: %s", branchName, err)
}
}
return nil
})
if err != nil {
return fmt.Errorf("convert repository to non bare: %w", err)
}
return nil
}
func moveFolderContent(source, destination string) error {
files, err := os.ReadDir(source)
if err != nil {
return fmt.Errorf("read dir: %w", err)
}
for _, file := range files {
if file.Name() == ".git" {
continue
}
err := os.Rename(filepath.Join(source, file.Name()), filepath.Join(destination, file.Name()))
if err != nil {
return fmt.Errorf(
"move file/dir[%s] -> to [%s]: %w",
file.Name(),
filepath.Join(destination, file.Name()),
err,
)
}
}
return nil
}