|
@@ -0,0 +1,463 @@
|
|
|
+package gig
|
|
|
+
|
|
|
+import (
|
|
|
+ "bufio"
|
|
|
+ "bytes"
|
|
|
+ "fmt"
|
|
|
+ "io"
|
|
|
+ "io/ioutil"
|
|
|
+ "os"
|
|
|
+ "os/exec"
|
|
|
+ "path"
|
|
|
+ "path/filepath"
|
|
|
+ "strings"
|
|
|
+)
|
|
|
+
|
|
|
+
|
|
|
+type Repository struct {
|
|
|
+ Path string
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+func InitBareRepository(path string) (*Repository, error) {
|
|
|
+
|
|
|
+ path, err := filepath.Abs(path)
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("Could not determine absolute path: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ cmd := exec.Command("git", "init", "--bare", path)
|
|
|
+ err = cmd.Run()
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ return &Repository{Path: path}, nil
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+func IsBareRepository(path string) bool {
|
|
|
+
|
|
|
+ cmd := exec.Command("git", fmt.Sprintf("--git-dir=%s", path), "rev-parse", "--is-bare-repository")
|
|
|
+ body, err := cmd.Output()
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ status := strings.Trim(string(body), "\n ")
|
|
|
+ return status == "true"
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+func OpenRepository(path string) (*Repository, error) {
|
|
|
+
|
|
|
+ path, err := filepath.Abs(path)
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("git: could not determine absolute path")
|
|
|
+ }
|
|
|
+
|
|
|
+ if !IsBareRepository(path) {
|
|
|
+ return nil, fmt.Errorf("git: not a bare repository")
|
|
|
+ }
|
|
|
+
|
|
|
+ return &Repository{Path: path}, nil
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+func DiscoverRepository() (*Repository, error) {
|
|
|
+ cmd := exec.Command("git", "rev-parse", "--git-dir")
|
|
|
+ data, err := cmd.Output()
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ path := strings.Trim(string(data), "\n ")
|
|
|
+ return &Repository{Path: path}, nil
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+func (repo *Repository) ReadDescription() string {
|
|
|
+ path := filepath.Join(repo.Path, "description")
|
|
|
+
|
|
|
+ dat, err := ioutil.ReadFile(path)
|
|
|
+ if err != nil {
|
|
|
+ return ""
|
|
|
+ }
|
|
|
+
|
|
|
+ return string(dat)
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+func (repo *Repository) WriteDescription(description string) error {
|
|
|
+ path := filepath.Join(repo.Path, "description")
|
|
|
+
|
|
|
+
|
|
|
+ return ioutil.WriteFile(path, []byte(description), 0666)
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+func (repo *Repository) DeleteCollaborator(username string) error {
|
|
|
+ filePath := filepath.Join(repo.Path, "gin", "sharing", username)
|
|
|
+
|
|
|
+ return os.Remove(filePath)
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+func (repo *Repository) OpenObject(id SHA1) (Object, error) {
|
|
|
+ obj, err := repo.openRawObject(id)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ if IsStandardObject(obj.otype) {
|
|
|
+ return parseObject(obj)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ if !IsDeltaObject(obj.otype) {
|
|
|
+ return nil, fmt.Errorf("git: unsupported object")
|
|
|
+ }
|
|
|
+
|
|
|
+ delta, err := parseDelta(obj)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ chain, err := buildDeltaChain(delta, repo)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ return chain.resolve()
|
|
|
+}
|
|
|
+
|
|
|
+func (repo *Repository) openRawObject(id SHA1) (gitObject, error) {
|
|
|
+ idstr := id.String()
|
|
|
+ opath := filepath.Join(repo.Path, "objects", idstr[:2], idstr[2:])
|
|
|
+
|
|
|
+ obj, err := openRawObject(opath)
|
|
|
+
|
|
|
+ if err == nil {
|
|
|
+ return obj, nil
|
|
|
+ } else if err != nil && !os.IsNotExist(err) {
|
|
|
+ return obj, err
|
|
|
+ }
|
|
|
+
|
|
|
+ indicies := repo.loadPackIndices()
|
|
|
+
|
|
|
+ for _, f := range indicies {
|
|
|
+
|
|
|
+ idx, err := PackIndexOpen(f)
|
|
|
+ if err != nil {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ defer idx.Close()
|
|
|
+
|
|
|
+ off, err := idx.FindOffset(id)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ pf, err := idx.OpenPackFile()
|
|
|
+ if err != nil {
|
|
|
+ return gitObject{}, err
|
|
|
+ }
|
|
|
+
|
|
|
+ obj, err := pf.readRawObject(off)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return gitObject{}, err
|
|
|
+ }
|
|
|
+
|
|
|
+ return obj, nil
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ return gitObject{}, fmt.Errorf("git: object not found")
|
|
|
+}
|
|
|
+
|
|
|
+func (repo *Repository) loadPackIndices() []string {
|
|
|
+ target := filepath.Join(repo.Path, "objects", "pack", "*.idx")
|
|
|
+ files, err := filepath.Glob(target)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ panic(err)
|
|
|
+ }
|
|
|
+
|
|
|
+ return files
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+func (repo *Repository) OpenRef(name string) (Ref, error) {
|
|
|
+
|
|
|
+ if name == "HEAD" {
|
|
|
+ return repo.parseRef("HEAD")
|
|
|
+ }
|
|
|
+
|
|
|
+ matches := repo.listRefWithName(name)
|
|
|
+
|
|
|
+
|
|
|
+ var locals []Ref
|
|
|
+ for _, v := range matches {
|
|
|
+ if IsBranchRef(v) {
|
|
|
+ if name == v.Fullname() {
|
|
|
+ return v, nil
|
|
|
+ }
|
|
|
+ locals = append(locals, v)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ if len(locals) == 1 {
|
|
|
+ return locals[0], nil
|
|
|
+ }
|
|
|
+
|
|
|
+ switch len(matches) {
|
|
|
+ case 0:
|
|
|
+ return nil, fmt.Errorf("git: ref matching %q not found", name)
|
|
|
+ case 1:
|
|
|
+ return matches[0], nil
|
|
|
+ }
|
|
|
+ return nil, fmt.Errorf("git: ambiguous ref name, multiple matches")
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+func (repo *Repository) Readlink(id SHA1) (string, error) {
|
|
|
+
|
|
|
+ b, err := repo.OpenObject(id)
|
|
|
+ if err != nil {
|
|
|
+ return "", err
|
|
|
+ }
|
|
|
+
|
|
|
+ if b.Type() != ObjBlob {
|
|
|
+ return "", fmt.Errorf("id must point to a blob")
|
|
|
+ }
|
|
|
+
|
|
|
+ blob := b.(*Blob)
|
|
|
+
|
|
|
+
|
|
|
+ data, err := ioutil.ReadAll(blob)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return "", err
|
|
|
+ }
|
|
|
+
|
|
|
+ return string(data), nil
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+func (repo *Repository) ObjectForPath(root Object, pathstr string) (Object, error) {
|
|
|
+
|
|
|
+ var node Object
|
|
|
+ var err error
|
|
|
+
|
|
|
+ switch o := root.(type) {
|
|
|
+ case *Tree:
|
|
|
+ node = root
|
|
|
+ case *Commit:
|
|
|
+ node, err = repo.OpenObject(o.Tree)
|
|
|
+ case *Tag:
|
|
|
+ node, err = repo.OpenObject(o.Object)
|
|
|
+ default:
|
|
|
+ return nil, fmt.Errorf("unsupported root object type")
|
|
|
+ }
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("could not root tree object: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ cleaned := path.Clean(strings.Trim(pathstr, " /"))
|
|
|
+ comps := strings.Split(cleaned, "/")
|
|
|
+
|
|
|
+ var i int
|
|
|
+ for i = 0; i < len(comps); i++ {
|
|
|
+
|
|
|
+ tree, ok := node.(*Tree)
|
|
|
+ if !ok {
|
|
|
+ cwd := strings.Join(comps[:i+1], "/")
|
|
|
+ err := &os.PathError{
|
|
|
+ Op: "convert git.Object to git.Tree",
|
|
|
+ Path: cwd,
|
|
|
+ Err: fmt.Errorf("expected tree object, got %s", node.Type()),
|
|
|
+ }
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ if comps[i] == "." || comps[i] == "/" {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ var id *SHA1
|
|
|
+ for tree.Next() {
|
|
|
+ entry := tree.Entry()
|
|
|
+ if entry.Name == comps[i] {
|
|
|
+ id = &entry.ID
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if err = tree.Err(); err != nil {
|
|
|
+ cwd := strings.Join(comps[:i+1], "/")
|
|
|
+ return nil, &os.PathError{
|
|
|
+ Op: "find object",
|
|
|
+ Path: cwd,
|
|
|
+ Err: err}
|
|
|
+ } else if id == nil {
|
|
|
+ cwd := strings.Join(comps[:i+1], "/")
|
|
|
+ return nil, &os.PathError{
|
|
|
+ Op: "find object",
|
|
|
+ Path: cwd,
|
|
|
+ Err: os.ErrNotExist}
|
|
|
+ }
|
|
|
+
|
|
|
+ node, err = repo.OpenObject(*id)
|
|
|
+ if err != nil {
|
|
|
+ cwd := strings.Join(comps[:i+1], "/")
|
|
|
+ return nil, &os.PathError{
|
|
|
+ Op: "open object",
|
|
|
+ Path: cwd,
|
|
|
+ Err: err,
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return node, nil
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+const usefmt = `--pretty=format:
|
|
|
+Commit:=%H%n
|
|
|
+Committer:=%cn%n
|
|
|
+Author:=%an%n
|
|
|
+Date-iso:=%ai%n
|
|
|
+Date-rel:=%ar%n
|
|
|
+Subject:=%s%n
|
|
|
+Changes:=`
|
|
|
+
|
|
|
+
|
|
|
+type CommitSummary struct {
|
|
|
+ Commit string
|
|
|
+ Committer string
|
|
|
+ Author string
|
|
|
+ DateIso string
|
|
|
+ DateRelative string
|
|
|
+ Subject string
|
|
|
+ Changes []string
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+func (repo *Repository) CommitsForRef(ref string) ([]CommitSummary, error) {
|
|
|
+
|
|
|
+ raw, err := commitsForRef(repo.Path, ref, usefmt)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ sep := ":="
|
|
|
+ var comList []CommitSummary
|
|
|
+ r := bytes.NewReader(raw)
|
|
|
+ br := bufio.NewReader(r)
|
|
|
+
|
|
|
+ var changesFlag bool
|
|
|
+ for {
|
|
|
+
|
|
|
+ l, err := br.ReadString('\n')
|
|
|
+
|
|
|
+ if strings.Contains(l, sep) {
|
|
|
+ splitList := strings.SplitN(l, sep, 2)
|
|
|
+
|
|
|
+ key := splitList[0]
|
|
|
+ val := splitList[1]
|
|
|
+ switch key {
|
|
|
+ case "Commit":
|
|
|
+
|
|
|
+ changesFlag = false
|
|
|
+ newCommit := CommitSummary{Commit: val}
|
|
|
+ comList = append(comList, newCommit)
|
|
|
+ case "Committer":
|
|
|
+ comList[len(comList)-1].Committer = val
|
|
|
+ case "Author":
|
|
|
+ comList[len(comList)-1].Author = val
|
|
|
+ case "Date-iso":
|
|
|
+ comList[len(comList)-1].DateIso = val
|
|
|
+ case "Date-rel":
|
|
|
+ comList[len(comList)-1].DateRelative = val
|
|
|
+ case "Subject":
|
|
|
+ comList[len(comList)-1].Subject = val
|
|
|
+ case "Changes":
|
|
|
+
|
|
|
+ changesFlag = true
|
|
|
+ default:
|
|
|
+ fmt.Printf("[W] commits: unexpected key %q, value %q\n", key, strings.Trim(val, "\n"))
|
|
|
+ }
|
|
|
+ } else if changesFlag && strings.Contains(l, "\t") {
|
|
|
+ comList[len(comList)-1].Changes = append(comList[len(comList)-1].Changes, l)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if err != io.EOF && err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ return comList, nil
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+func commitsForRef(repoPath, ref, usefmt string) ([]byte, error) {
|
|
|
+ gdir := fmt.Sprintf("--git-dir=%s", repoPath)
|
|
|
+
|
|
|
+ cmd := exec.Command("git", gdir, "log", ref, usefmt, "--name-status")
|
|
|
+ body, err := cmd.Output()
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("failed running git log: %s\n", err.Error())
|
|
|
+ }
|
|
|
+ return body, nil
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+func (repo *Repository) BranchExists(branch string) (bool, error) {
|
|
|
+ gdir := fmt.Sprintf("--git-dir=%s", repo.Path)
|
|
|
+
|
|
|
+ cmd := exec.Command("git", gdir, "branch", branch, "--list")
|
|
|
+ body, err := cmd.Output()
|
|
|
+ if err != nil {
|
|
|
+ return false, err
|
|
|
+ } else if len(body) == 0 {
|
|
|
+ return false, nil
|
|
|
+ }
|
|
|
+
|
|
|
+ return true, nil
|
|
|
+}
|