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 gitDir(r *git.Repository) (string, error) {
 45	return r.RevParse("--git-dir")
 46}
 47
 48// Open opens a git repository at the given path.
 49func Open(path string) (*Repository, error) {
 50	repo, err := git.Open(path)
 51	if err != nil {
 52		return nil, err
 53	}
 54	gp, err := gitDir(repo)
 55	if err != nil || (gp != "." && gp != ".git") {
 56		return nil, ErrNotAGitRepository
 57	}
 58	return &Repository{
 59		Repository: repo,
 60		Path:       path,
 61		IsBare:     gp == ".",
 62	}, nil
 63}
 64
 65// Name returns the name of the repository.
 66func (r *Repository) Name() string {
 67	return filepath.Base(r.Path)
 68}
 69
 70// HEAD returns the HEAD reference for a repository.
 71func (r *Repository) HEAD() (*Reference, error) {
 72	rn, err := r.Repository.SymbolicRef(git.SymbolicRefOptions{Name: "HEAD"})
 73	if err != nil {
 74		return nil, err
 75	}
 76	hash, err := r.ShowRefVerify(rn)
 77	if err != nil {
 78		return nil, err
 79	}
 80	return &Reference{
 81		Reference: &git.Reference{
 82			ID:      hash,
 83			Refspec: rn,
 84		},
 85		Hash: Hash(hash),
 86		path: r.Path,
 87	}, nil
 88}
 89
 90// References returns the references for a repository.
 91func (r *Repository) References() ([]*Reference, error) {
 92	refs, err := r.ShowRef()
 93	if err != nil {
 94		return nil, err
 95	}
 96	rrefs := make([]*Reference, 0, len(refs))
 97	for _, ref := range refs {
 98		rrefs = append(rrefs, &Reference{
 99			Reference: ref,
100			Hash:      Hash(ref.ID),
101			path:      r.Path,
102		})
103	}
104	return rrefs, nil
105}
106
107// LsTree returns the tree for the given reference.
108func (r *Repository) LsTree(ref string) (*Tree, error) {
109	tree, err := r.Repository.LsTree(ref)
110	if err != nil {
111		return nil, err
112	}
113	return &Tree{
114		Tree:       tree,
115		Path:       "",
116		Repository: r,
117	}, nil
118}
119
120// Tree returns the tree for the given reference.
121func (r *Repository) Tree(ref *Reference) (*Tree, error) {
122	if ref == nil {
123		rref, err := r.HEAD()
124		if err != nil {
125			return nil, err
126		}
127		ref = rref
128	}
129	return r.LsTree(ref.Hash.String())
130}
131
132// TreePath returns the tree for the given path.
133func (r *Repository) TreePath(ref *Reference, path string) (*Tree, error) {
134	path = filepath.Clean(path)
135	if path == "." {
136		path = ""
137	}
138	if path == "" {
139		return r.Tree(ref)
140	}
141	t, err := r.Tree(ref)
142	if err != nil {
143		return nil, err
144	}
145	return t.SubTree(path)
146}
147
148// Diff returns the diff for the given commit.
149func (r *Repository) Diff(commit *Commit) (*Diff, error) {
150	ddiff, err := r.Repository.Diff(commit.Hash.String(), DiffMaxFiles, DiffMaxFileLines, DiffMaxLineChars, git.DiffOptions{
151		CommandOptions: git.CommandOptions{
152			Envs: []string{"GIT_CONFIG_GLOBAL=/dev/null"},
153		},
154	})
155	if err != nil {
156		return nil, err
157	}
158	files := make([]*DiffFile, 0, len(ddiff.Files))
159	for _, df := range ddiff.Files {
160		sections := make([]*DiffSection, 0, len(df.Sections))
161		for _, ds := range df.Sections {
162			sections = append(sections, &DiffSection{
163				DiffSection: ds,
164			})
165		}
166		files = append(files, &DiffFile{
167			DiffFile: df,
168			Sections: sections,
169		})
170	}
171	diff := &Diff{
172		Diff:  ddiff,
173		Files: files,
174	}
175	return diff, nil
176}
177
178// Patch returns the patch for the given reference.
179func (r *Repository) Patch(commit *Commit) (string, error) {
180	diff, err := r.Diff(commit)
181	if err != nil {
182		return "", err
183	}
184	return diff.Patch(), err
185}
186
187// CountCommits returns the number of commits in the repository.
188func (r *Repository) CountCommits(ref *Reference) (int64, error) {
189	return r.RevListCount([]string{ref.Name().String()})
190}
191
192// CommitsByPage returns the commits for a given page and size.
193func (r *Repository) CommitsByPage(ref *Reference, page, size int) (Commits, error) {
194	cs, err := r.Repository.CommitsByPage(ref.Name().String(), page, size)
195	if err != nil {
196		return nil, err
197	}
198	commits := make(Commits, len(cs))
199	for i, c := range cs {
200		commits[i] = &Commit{
201			Commit: c,
202			Hash:   Hash(c.ID.String()),
203		}
204	}
205	return commits, nil
206}
207
208// UpdateServerInfo updates the repository server info.
209func (r *Repository) UpdateServerInfo() error {
210	cmd := git.NewCommand("update-server-info")
211	_, err := cmd.RunInDir(r.Path)
212	return err
213}
214
215// Config returns the config value for the given key.
216func (r *Repository) Config(key string, opts ...ConfigOptions) (string, error) {
217	dir, err := gitDir(r.Repository)
218	if err != nil {
219		return "", err
220	}
221	var opt ConfigOptions
222	if len(opts) > 0 {
223		opt = opts[0]
224	}
225	opt.File = filepath.Join(dir, "config")
226	return Config(key, opt)
227}
228
229// SetConfig sets the config value for the given key.
230func (r *Repository) SetConfig(key, value string, opts ...ConfigOptions) error {
231	dir, err := gitDir(r.Repository)
232	if err != nil {
233		return err
234	}
235	var opt ConfigOptions
236	if len(opts) > 0 {
237		opt = opts[0]
238	}
239	opt.File = filepath.Join(dir, "config")
240	return SetConfig(key, value, opt)
241}
242
243// SymbolicRef returns or updates the symbolic reference for the given name.
244// Both name and ref can be empty.
245func (r *Repository) SymbolicRef(name string, ref string) (string, error) {
246	opt := git.SymbolicRefOptions{
247		Name: name,
248		Ref:  ref,
249	}
250	return r.Repository.SymbolicRef(opt)
251}