1package git
2
3import (
4 "path/filepath"
5 "strings"
6
7 "github.com/gogs/git-module"
8)
9
10var (
11 // DiffMaxFile is the maximum number of files to show in a diff.
12 DiffMaxFiles = 1000
13 // DiffMaxFileLines is the maximum number of lines to show in a file diff.
14 DiffMaxFileLines = 1000
15 // DiffMaxLineChars is the maximum number of characters to show in a line diff.
16 DiffMaxLineChars = 1000
17)
18
19// Repository is a wrapper around git.Repository with helper methods.
20type Repository struct {
21 *git.Repository
22 Path string
23 IsBare bool
24}
25
26// Clone clones a repository.
27func Clone(src, dst string, opts ...git.CloneOptions) error {
28 return git.Clone(src, dst, opts...)
29}
30
31// Init initializes and opens a new git repository.
32func Init(path string, bare bool) (*Repository, error) {
33 if bare {
34 path = strings.TrimSuffix(path, ".git") + ".git"
35 }
36
37 err := git.Init(path, git.InitOptions{Bare: bare})
38 if err != nil {
39 return nil, err
40 }
41 return Open(path)
42}
43
44func isInsideWorkTree(r *git.Repository) bool {
45 out, err := r.RevParse("--is-inside-work-tree")
46 return err == nil && out == "true"
47}
48
49func isInsideGitDir(r *git.Repository) bool {
50 out, err := r.RevParse("--is-inside-git-dir")
51 return err == nil && out == "true"
52}
53
54func gitDir(r *git.Repository) (string, error) {
55 return r.RevParse("--git-dir")
56}
57
58// Open opens a git repository at the given path.
59func Open(path string) (*Repository, error) {
60 repo, err := git.Open(path)
61 if err != nil {
62 return nil, err
63 }
64 gp, err := gitDir(repo)
65 if err != nil || (gp != "." && gp != ".git") {
66 return nil, ErrNotAGitRepository
67 }
68 return &Repository{
69 Repository: repo,
70 Path: path,
71 IsBare: gp == ".",
72 }, nil
73}
74
75// Name returns the name of the repository.
76func (r *Repository) Name() string {
77 return filepath.Base(r.Path)
78}
79
80// HEAD returns the HEAD reference for a repository.
81func (r *Repository) HEAD() (*Reference, error) {
82 rn, err := r.Repository.SymbolicRef(git.SymbolicRefOptions{Name: "HEAD"})
83 if err != nil {
84 return nil, err
85 }
86 hash, err := r.ShowRefVerify(rn)
87 if err != nil {
88 return nil, err
89 }
90 return &Reference{
91 Reference: &git.Reference{
92 ID: hash,
93 Refspec: rn,
94 },
95 Hash: Hash(hash),
96 path: r.Path,
97 }, nil
98}
99
100// References returns the references for a repository.
101func (r *Repository) References() ([]*Reference, error) {
102 refs, err := r.ShowRef()
103 if err != nil {
104 return nil, err
105 }
106 rrefs := make([]*Reference, 0, len(refs))
107 for _, ref := range refs {
108 rrefs = append(rrefs, &Reference{
109 Reference: ref,
110 Hash: Hash(ref.ID),
111 path: r.Path,
112 })
113 }
114 return rrefs, nil
115}
116
117// LsTree returns the tree for the given reference.
118func (r *Repository) LsTree(ref string) (*Tree, error) {
119 tree, err := r.Repository.LsTree(ref)
120 if err != nil {
121 return nil, err
122 }
123 return &Tree{
124 Tree: tree,
125 Path: "",
126 Repository: r,
127 }, nil
128}
129
130// Tree returns the tree for the given reference.
131func (r *Repository) Tree(ref *Reference) (*Tree, error) {
132 if ref == nil {
133 rref, err := r.HEAD()
134 if err != nil {
135 return nil, err
136 }
137 ref = rref
138 }
139 return r.LsTree(ref.Hash.String())
140}
141
142// TreePath returns the tree for the given path.
143func (r *Repository) TreePath(ref *Reference, path string) (*Tree, error) {
144 path = filepath.Clean(path)
145 if path == "." {
146 path = ""
147 }
148 if path == "" {
149 return r.Tree(ref)
150 }
151 t, err := r.Tree(ref)
152 if err != nil {
153 return nil, err
154 }
155 return t.SubTree(path)
156}
157
158// Diff returns the diff for the given commit.
159func (r *Repository) Diff(commit *Commit) (*Diff, error) {
160 ddiff, err := r.Repository.Diff(commit.Hash.String(), DiffMaxFiles, DiffMaxFileLines, DiffMaxLineChars)
161 if err != nil {
162 return nil, err
163 }
164 files := make([]*DiffFile, 0, len(ddiff.Files))
165 for _, df := range ddiff.Files {
166 sections := make([]*DiffSection, 0, len(df.Sections))
167 for _, ds := range df.Sections {
168 sections = append(sections, &DiffSection{
169 DiffSection: ds,
170 })
171 }
172 files = append(files, &DiffFile{
173 DiffFile: df,
174 Sections: sections,
175 })
176 }
177 diff := &Diff{
178 Diff: ddiff,
179 Files: files,
180 }
181 return diff, nil
182}
183
184// Patch returns the patch for the given reference.
185func (r *Repository) Patch(commit *Commit) (string, error) {
186 diff, err := r.Diff(commit)
187 if err != nil {
188 return "", err
189 }
190 return diff.Patch(), err
191}
192
193// CountCommits returns the number of commits in the repository.
194func (r *Repository) CountCommits(ref *Reference) (int64, error) {
195 return r.Repository.RevListCount([]string{ref.Name().String()})
196}
197
198// CommitsByPage returns the commits for a given page and size.
199func (r *Repository) CommitsByPage(ref *Reference, page, size int) (Commits, error) {
200 cs, err := r.Repository.CommitsByPage(ref.Name().String(), page, size)
201 if err != nil {
202 return nil, err
203 }
204 commits := make(Commits, len(cs))
205 for i, c := range cs {
206 commits[i] = &Commit{
207 Commit: c,
208 Hash: Hash(c.ID.String()),
209 }
210 }
211 return commits, nil
212}
213
214// UpdateServerInfo updates the repository server info.
215func (r *Repository) UpdateServerInfo() error {
216 cmd := git.NewCommand("update-server-info")
217 _, err := cmd.RunInDir(r.Path)
218 return err
219}
220
221// Config returns the config value for the given key.
222func (r *Repository) Config(key string, opts ...ConfigOptions) (string, error) {
223 dir, err := gitDir(r.Repository)
224 if err != nil {
225 return "", err
226 }
227 var opt ConfigOptions
228 if len(opts) > 0 {
229 opt = opts[0]
230 }
231 opt.File = filepath.Join(dir, "config")
232 return Config(key, opt)
233}
234
235// SetConfig sets the config value for the given key.
236func (r *Repository) SetConfig(key, value string, opts ...ConfigOptions) error {
237 dir, err := gitDir(r.Repository)
238 if err != nil {
239 return err
240 }
241 var opt ConfigOptions
242 if len(opts) > 0 {
243 opt = opts[0]
244 }
245 opt.File = filepath.Join(dir, "config")
246 return SetConfig(key, value, opt)
247}
248
249// SymbolicRef returns or updates the symbolic reference for the given name.
250// Both name and ref can be empty.
251func (r *Repository) SymbolicRef(name string, ref string) (string, error) {
252 opt := git.SymbolicRefOptions{
253 Name: name,
254 Ref: ref,
255 }
256 return r.Repository.SymbolicRef(opt)
257}