repo.go
5589 bytes
1package git
2
3import (
4 gopath "path"
5 "path/filepath"
6 "strings"
7
8 "github.com/aymanbagabas/git-module"
9)
10
11var (
12 // DiffMaxFile is the maximum number of files to show in a diff.
13 DiffMaxFiles = 1000
14 // DiffMaxFileLines is the maximum number of lines to show in a file diff.
15 DiffMaxFileLines = 1000
16 // DiffMaxLineChars is the maximum number of characters to show in a line diff.
17 DiffMaxLineChars = 1000
18)
19
20// Repository is a wrapper around git.Repository with helper methods.
21type Repository struct {
22 *git.Repository
23 Path string
24 IsBare bool
25}
26
27// Clone clones a repository.
28func Clone(src, dst string, opts ...git.CloneOptions) error {
29 return git.Clone(src, dst, opts...)
30}
31
32// Init initializes and opens a new git repository.
33func Init(path string, bare bool) (*Repository, error) {
34 if bare {
35 path = strings.TrimSuffix(path, ".git") + ".git"
36 }
37
38 err := git.Init(path, git.InitOptions{Bare: bare})
39 if err != nil {
40 return nil, err
41 }
42 return Open(path)
43}
44
45func gitDir(r *git.Repository) (string, error) {
46 return r.RevParse("--git-dir")
47}
48
49// Open opens a git repository at the given path.
50func Open(path string) (*Repository, error) {
51 repo, err := git.Open(path)
52 if err != nil {
53 return nil, err
54 }
55 gp, err := gitDir(repo)
56 if err != nil || (gp != "." && gp != ".git") {
57 return nil, ErrNotAGitRepository
58 }
59 return &Repository{
60 Repository: repo,
61 Path: path,
62 IsBare: gp == ".",
63 }, nil
64}
65
66// HEAD returns the HEAD reference for a repository.
67func (r *Repository) HEAD() (*Reference, error) {
68 rn, err := r.Repository.SymbolicRef(git.SymbolicRefOptions{Name: "HEAD"})
69 if err != nil {
70 return nil, err
71 }
72 hash, err := r.ShowRefVerify(rn)
73 if err != nil {
74 return nil, err
75 }
76 return &Reference{
77 Reference: &git.Reference{
78 ID: hash,
79 Refspec: rn,
80 },
81 path: r.Path,
82 }, nil
83}
84
85// References returns the references for a repository.
86func (r *Repository) References() ([]*Reference, error) {
87 refs, err := r.ShowRef()
88 if err != nil {
89 return nil, err
90 }
91 rrefs := make([]*Reference, 0, len(refs))
92 for _, ref := range refs {
93 rrefs = append(rrefs, &Reference{
94 Reference: ref,
95 path: r.Path,
96 })
97 }
98 return rrefs, nil
99}
100
101// LsTree returns the tree for the given reference.
102func (r *Repository) LsTree(ref string) (*Tree, error) {
103 tree, err := r.Repository.LsTree(ref)
104 if err != nil {
105 return nil, err
106 }
107 return &Tree{
108 Tree: tree,
109 Path: "",
110 Repository: r,
111 }, nil
112}
113
114// Tree returns the tree for the given reference.
115func (r *Repository) Tree(ref *Reference) (*Tree, error) {
116 if ref == nil {
117 rref, err := r.HEAD()
118 if err != nil {
119 return nil, err
120 }
121 ref = rref
122 }
123 return r.LsTree(ref.ID)
124}
125
126// TreePath returns the tree for the given path.
127// Git paths always use forward slashes regardless of OS.
128func (r *Repository) TreePath(ref *Reference, path string) (*Tree, error) {
129 path = gopath.Clean(filepath.ToSlash(path))
130 if path == "." {
131 path = ""
132 }
133 if path == "" {
134 return r.Tree(ref)
135 }
136 t, err := r.Tree(ref)
137 if err != nil {
138 return nil, err
139 }
140 return t.SubTree(path)
141}
142
143// DiffLimits bounds the amount of diff data Git parses for one request.
144type DiffLimits struct {
145 MaxFiles int
146 MaxFileLines int
147 MaxLineChars int
148}
149
150// DiffWithLimits returns the diff for commit using request-local limits.
151// It does not modify the legacy package-level DiffMax* defaults.
152func (r *Repository) DiffWithLimits(commit *Commit, limits DiffLimits) (*Diff, error) {
153 diff, err := r.Repository.Diff(commit.ID.String(), limits.MaxFiles, limits.MaxFileLines, limits.MaxLineChars, git.DiffOptions{
154 CommandOptions: git.CommandOptions{
155 Envs: []string{"GIT_CONFIG_GLOBAL=/dev/null"},
156 },
157 })
158 if err != nil {
159 return nil, err
160 }
161 return toDiff(diff), nil
162}
163
164// Diff returns the diff for the given commit using the legacy default limits.
165func (r *Repository) Diff(commit *Commit) (*Diff, error) {
166 return r.DiffWithLimits(commit, DiffLimits{
167 MaxFiles: DiffMaxFiles,
168 MaxFileLines: DiffMaxFileLines,
169 MaxLineChars: DiffMaxLineChars,
170 })
171}
172
173// Patch returns the patch for the given reference.
174func (r *Repository) Patch(commit *Commit) (string, error) {
175 diff, err := r.Diff(commit)
176 if err != nil {
177 return "", err
178 }
179 return diff.Patch(), err
180}
181
182// CountCommits returns the number of commits in the repository.
183func (r *Repository) CountCommits(ref *Reference) (int64, error) {
184 return r.RevListCount([]string{ref.Name().String()})
185}
186
187// CommitsByPage returns the commits for a given page and size.
188func (r *Repository) CommitsByPage(ref *Reference, page, size int) (Commits, error) {
189 cs, err := r.Repository.CommitsByPage(ref.Name().String(), page, size)
190 if err != nil {
191 return nil, err
192 }
193 commits := make(Commits, len(cs))
194 copy(commits, cs)
195 return commits, nil
196}
197
198// CommitByPath returns the newest commit reachable from ref that affects treePath.
199// The revision is the resolved reference object ID, never a caller-provided revision.
200func (r *Repository) CommitByPath(ref *Reference, treePath string) (*Commit, error) {
201 return r.Repository.CommitByRevision(ref.ID, git.CommitByRevisionOptions{
202 Path: treePath,
203 CommandOptions: git.CommandOptions{
204 Envs: []string{"GIT_CONFIG_GLOBAL=/dev/null"},
205 },
206 })
207}
208
209// SymbolicRef returns or updates the symbolic reference for the given name.
210// Both name and ref can be empty.
211func (r *Repository) SymbolicRef(name string, ref string, opts ...git.SymbolicRefOptions) (string, error) {
212 var opt git.SymbolicRefOptions
213 if len(opts) > 0 {
214 opt = opts[0]
215 }
216
217 opt.Name = name
218 opt.Ref = ref
219 return r.Repository.SymbolicRef(opt)
220}