1package git
  2
  3import (
  4	"path/filepath"
  5
  6	"github.com/gogs/git-module"
  7)
  8
  9var (
 10	// DiffMaxFile is the maximum number of files to show in a diff.
 11	DiffMaxFiles = 1000
 12	// DiffMaxFileLines is the maximum number of lines to show in a file diff.
 13	DiffMaxFileLines = 1000
 14	// DiffMaxLineChars is the maximum number of characters to show in a line diff.
 15	DiffMaxLineChars = 1000
 16)
 17
 18// Repository is a wrapper around git.Repository with helper methods.
 19type Repository struct {
 20	*git.Repository
 21	Path   string
 22	IsBare bool
 23}
 24
 25// Clone clones a repository.
 26func Clone(src, dst string, opts ...git.CloneOptions) error {
 27	return git.Clone(src, dst, opts...)
 28}
 29
 30// Init initializes and opens a new git repository.
 31func Init(path string, bare bool) (*Repository, error) {
 32	err := git.Init(path, git.InitOptions{Bare: bare})
 33	if err != nil {
 34		return nil, err
 35	}
 36	return Open(path)
 37}
 38
 39func isInsideWorkTree(r *git.Repository) bool {
 40	out, err := r.RevParse("--is-inside-work-tree")
 41	return err == nil && out == "true"
 42}
 43
 44func isInsideGitDir(r *git.Repository) bool {
 45	out, err := r.RevParse("--is-inside-git-dir")
 46	return err == nil && out == "true"
 47}
 48
 49func gitDir(r *git.Repository) (string, error) {
 50	return r.RevParse("--git-dir")
 51}
 52
 53// Open opens a git repository at the given path.
 54func Open(path string) (*Repository, error) {
 55	repo, err := git.Open(path)
 56	if err != nil {
 57		return nil, err
 58	}
 59	gp, err := gitDir(repo)
 60	if err != nil || (gp != "." && gp != ".git") {
 61		return nil, ErrNotAGitRepository
 62	}
 63	return &Repository{
 64		Repository: repo,
 65		Path:       path,
 66		IsBare:     gp == ".",
 67	}, nil
 68}
 69
 70// Name returns the name of the repository.
 71func (r *Repository) Name() string {
 72	return filepath.Base(r.Path)
 73}
 74
 75// HEAD returns the HEAD reference for a repository.
 76func (r *Repository) HEAD() (*Reference, error) {
 77	rn, err := r.SymbolicRef(git.SymbolicRefOptions{Name: "HEAD"})
 78	if err != nil {
 79		return nil, err
 80	}
 81	hash, err := r.ShowRefVerify(rn)
 82	if err != nil {
 83		return nil, err
 84	}
 85	return &Reference{
 86		Reference: &git.Reference{
 87			ID:      hash,
 88			Refspec: rn,
 89		},
 90		Hash: Hash(hash),
 91		path: r.Path,
 92	}, nil
 93}
 94
 95// References returns the references for a repository.
 96func (r *Repository) References() ([]*Reference, error) {
 97	refs, err := r.ShowRef()
 98	if err != nil {
 99		return nil, err
100	}
101	rrefs := make([]*Reference, 0, len(refs))
102	for _, ref := range refs {
103		rrefs = append(rrefs, &Reference{
104			Reference: ref,
105			Hash:      Hash(ref.ID),
106			path:      r.Path,
107		})
108	}
109	return rrefs, nil
110}
111
112// Tree returns the tree for the given reference.
113func (r *Repository) Tree(ref *Reference) (*Tree, error) {
114	if ref == nil {
115		rref, err := r.HEAD()
116		if err != nil {
117			return nil, err
118		}
119		ref = rref
120	}
121	tree, err := r.LsTree(ref.Hash.String())
122	if err != nil {
123		return nil, err
124	}
125	return &Tree{
126		Tree:       tree,
127		Path:       "",
128		Repository: r,
129	}, nil
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)
151	if err != nil {
152		return nil, err
153	}
154	files := make([]*DiffFile, 0, len(ddiff.Files))
155	for _, df := range ddiff.Files {
156		sections := make([]*DiffSection, 0, len(df.Sections))
157		for _, ds := range df.Sections {
158			sections = append(sections, &DiffSection{
159				DiffSection: ds,
160			})
161		}
162		files = append(files, &DiffFile{
163			DiffFile: df,
164			Sections: sections,
165		})
166	}
167	diff := &Diff{
168		Diff:  ddiff,
169		Files: files,
170	}
171	return diff, nil
172}
173
174// Patch returns the patch for the given reference.
175func (r *Repository) Patch(commit *Commit) (string, error) {
176	diff, err := r.Diff(commit)
177	if err != nil {
178		return "", err
179	}
180	return diff.Patch(), err
181}
182
183// CountCommits returns the number of commits in the repository.
184func (r *Repository) CountCommits(ref *Reference) (int64, error) {
185	return r.Repository.RevListCount([]string{ref.Name().String()})
186}
187
188// CommitsByPage returns the commits for a given page and size.
189func (r *Repository) CommitsByPage(ref *Reference, page, size int) (Commits, error) {
190	cs, err := r.Repository.CommitsByPage(ref.Name().String(), page, size)
191	if err != nil {
192		return nil, err
193	}
194	commits := make(Commits, len(cs))
195	for i, c := range cs {
196		commits[i] = &Commit{
197			Commit: c,
198			Hash:   Hash(c.ID.String()),
199		}
200	}
201	return commits, nil
202}
203
204// UpdateServerInfo updates the repository server info.
205func (r *Repository) UpdateServerInfo() error {
206	cmd := git.NewCommand("update-server-info")
207	_, err := cmd.RunInDir(r.Path)
208	return err
209}