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}