Skip to content
This repository has been archived by the owner on Apr 26, 2021. It is now read-only.

Commit

Permalink
Get log for a project
Browse files Browse the repository at this point in the history
Return git log for a given repository.
  • Loading branch information
marcelometal committed Aug 19, 2014
1 parent d404674 commit 38d1b46
Show file tree
Hide file tree
Showing 7 changed files with 295 additions and 0 deletions.
29 changes: 29 additions & 0 deletions api/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -496,3 +496,32 @@ func Commit(w http.ResponseWriter, r *http.Request) {
}
w.Write(b)
}

func GetLog(w http.ResponseWriter, r *http.Request) {
repo := r.URL.Query().Get(":name")
ref := r.URL.Query().Get("ref")
total, err := strconv.Atoi(r.URL.Query().Get("total"))
if err != nil {
err := fmt.Errorf("Error when trying to obtain log for ref %s of repository %s (%s).", ref, repo, err)
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if repo == "" {
err := fmt.Errorf("Error when trying to obtain log for ref %s of repository %s (repository is required).", ref, repo)
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
logs, err := repository.GetLog(repo, ref, total)
if err != nil {
err := fmt.Errorf("Error when trying to obtain log for ref %s of repository %s (%s).", ref, repo, err)
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
b, err := json.Marshal(logs)
if err != nil {
err := fmt.Errorf("Error when trying to obtain log for ref %s of repository %s (%s).", ref, repo, err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Write(b)
}
42 changes: 42 additions & 0 deletions api/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1429,3 +1429,45 @@ func (s *S) TestPostNewCommitWithEmptyBranch(c *gocheck.C) {
Commit(recorder, request)
c.Assert(recorder.Code, gocheck.Equals, http.StatusBadRequest)
}

func (s *S) TestLog(c *gocheck.C) {
url := "/repository/repo/logs?&:name=repo&ref=HEAD&total=1"
objects := repository.GitHistory{}
parent := make([]string, 2)
parent[0] = "a367b5de5943632e47cb6f8bf5b2147bc0be5cf8"
parent[1] = "b267b5de5943632e47cb6f8bf5b2147bc0be5cf2"
commits := make([]repository.GitLog, 1)
commits[0] = repository.GitLog{
Ref: "a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9",
CreatedAt: "Mon Jul 28 10:13:27 2014 -0300",
Committer: &repository.GitUser{
Name: "doge",
Email: "much@email.com",
},
Author: &repository.GitUser{
Name: "doge",
Email: "much@email.com",
},
Subject: "will bark",
Parent: parent,
}
objects.Commits = commits
objects.Next = "b231c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9"
mockRetriever := repository.MockContentRetriever{
History: objects,
}
repository.Retriever = &mockRetriever
defer func() {
repository.Retriever = nil
}()
request, err := http.NewRequest("GET", url, nil)
c.Assert(err, gocheck.IsNil)
recorder := httptest.NewRecorder()
GetLog(recorder, request)
c.Assert(recorder.Code, gocheck.Equals, http.StatusOK)
var obj repository.GitHistory
json.Unmarshal(recorder.Body.Bytes(), &obj)
c.Assert(obj.Next, gocheck.Equals, "b231c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9")
c.Assert(obj.Commits, gocheck.HasLen, 1)
c.Assert(obj.Commits[0], gocheck.DeepEquals, commits[0])
}
43 changes: 43 additions & 0 deletions docs/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -297,3 +297,46 @@ Example result::
zipArchive: "/repository/myrepository/archive?ref=master&format=zip",
}
}

Logs
----

Returns a list of all commits into `repository`.

* Method: GET
* URI: /repository/`:name`/log?ref=:ref&total=:total
* Format: JSON

Where:

* `:name` is the name of the repository;
* `:ref` is the repository ref (commit, tag or branch);
* `:total` is the maximum number of items to retrieve

Example URL (http://gandalf-server omitted for clarity)::

$ curl /repository/myrepository/logs?ref=HEAD&total=1

Example result::

{
commits: [{
ref: "6767b5de5943632e47cb6f8bf5b2147bc0be5cf8",
subject: "much WOW",
createdAt: "Mon Jul 28 10:13:27 2014 -0300"
author: {
name: "Author name",
email: "author@email.com",
date: "Mon Jul 28 10:13:27 2014 -0300""
},
committer: {
name: "Committer name",
email: "committer@email.com",
date: "Tue Jul 29 13:43:57 2014 -0300"
},
parent: [
"a367b5de5943632e47cb6f8bf5b2147bc0be5cf8"
]
}],
next: "1267b5de5943632e47cb6f8bf5b2147bc0be5cf123"
}
11 changes: 11 additions & 0 deletions repository/mocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type MockContentRetriever struct {
OutputError error
ClonePath string
CleanUp func()
History GitHistory
}

func (r *MockContentRetriever) GetContents(repo, ref, path string) ([]byte, error) {
Expand Down Expand Up @@ -395,3 +396,13 @@ func (r *MockContentRetriever) CommitZip(repo string, z *multipart.FileHeader, c
}
return &r.Ref, nil
}

func (r *MockContentRetriever) GetLog(repo, hash string, total int) (*GitHistory, error) {
if r.LookPathError != nil {
return nil, r.LookPathError
}
if r.OutputError != nil {
return nil, r.OutputError
}
return &r.History, nil
}
108 changes: 108 additions & 0 deletions repository/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,20 @@ type Ref struct {
CreatedAt string `json:"createdAt"`
}

type GitLog struct {
Ref string `json:"ref"`
Author *GitUser `json:"author"`
Committer *GitUser `json:"committer"`
Subject string `json:"subject"`
CreatedAt string `json:"createdAt"`
Parent []string `json:"parent"`
}

type GitHistory struct {
Commits []GitLog `json:"commits"`
Next string `json:"next"`
}

// exists returns whether the given file or directory exists or not
func exists(path string) (bool, error) {
_, err := os.Stat(path)
Expand Down Expand Up @@ -308,6 +322,7 @@ type ContentRetriever interface {
Commit(cloneDir, message string, author GitUser) error
Push(cloneDir, branch string) error
CommitZip(repo string, z *multipart.FileHeader, c GitCommit) (*Ref, error)
GetLog(repo, hash string, total int) (*GitHistory, error)
}

var Retriever ContentRetriever
Expand Down Expand Up @@ -680,6 +695,95 @@ func (*GitContentRetriever) CommitZip(repo string, z *multipart.FileHeader, c Gi
return nil, fmt.Errorf("Error when trying to commit zip to repository %s, could not check branch: %s", repo, err)
}

func (*GitContentRetriever) GetLog(repo, hash string, total int) (*GitHistory, error) {
if total < 1 {
total = 1
}
totalPagination := total + 1
var last, ref, committerName, committerEmail, committerDate, authorName, authorEmail, authorDate, subject, parent string
gitPath, err := exec.LookPath("git")
if err != nil {
return nil, fmt.Errorf("Error when trying to obtain the log of repository %s (%s).", repo, err)
}
cwd := barePath(repo)
repoExists, err := exists(cwd)
if err != nil || !repoExists {
return nil, fmt.Errorf("Error when trying to obtain the log of repository %s (Repository does not exist).", repo)
}
format := "%H%x09%an%x09%ae%x09%ad%x09%cn%x09%ce%x09%cd%x09%P%x09%s"
cmd := exec.Command(gitPath, "--no-pager", "log", fmt.Sprintf("-n %d", totalPagination), fmt.Sprintf("--format=%s", format), hash)
cmd.Dir = cwd
out, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("Error when trying to obtain the log of repository %s (%s).", repo, err)
}
lines := strings.Split(strings.TrimSpace(string(out)), "\n")
objectCount := len(lines)
if len(lines) == 1 && len(lines[0]) == 0 {
objectCount = 0
}
if objectCount > total {
last = lines[objectCount-1]
lines = lines[0 : objectCount-1]
objectCount -= 1
}
history := GitHistory{}
commits := make([]GitLog, objectCount)
objectCount = 0
for _, line := range lines {
if strings.TrimSpace(line) == "" {
continue
}
fields := strings.Split(line, "\t")
if len(fields) > 8 { // let there be commits with empty subject
ref = fields[0]
authorName = fields[1]
authorEmail = fields[2]
authorDate = fields[3]
committerName = fields[4]
committerEmail = fields[5]
committerDate = fields[6]
parent = fields[7]
subject = strings.Join(fields[8:], "\t") // let there be subjects with \t
} else {
return nil, fmt.Errorf("Error when trying to obtain the log of repository %s (Invalid git log output [%s]).", repo, out)
}
commit := GitLog{}
commit.Ref = ref
commit.Subject = subject
commit.CreatedAt = authorDate
commit.Committer = &GitUser{
Name: committerName,
Email: committerEmail,
Date: committerDate,
}
commit.Author = &GitUser{
Name: authorName,
Email: authorEmail,
Date: authorDate,
}
parents := strings.Split(parent, " ")
parentCount := len(parents)
aux := make([]string, parentCount)
parentCount = 0
for _, item := range parents {
aux[parentCount] = item
parentCount++
}
commit.Parent = aux
commits[objectCount] = commit
objectCount++
}
history.Commits = commits
if last != "" {
fields := strings.Split(last, "\t")
history.Next = fields[0]
} else {
history.Next = ""
}
return &history, nil
}

func retriever() ContentRetriever {
if Retriever == nil {
Retriever = &GitContentRetriever{}
Expand Down Expand Up @@ -746,3 +850,7 @@ func Push(cloneDir, branch string) error {
func CommitZip(repo string, z *multipart.FileHeader, c GitCommit) (*Ref, error) {
return retriever().CommitZip(repo, z, c)
}

func GetLog(repo, hash string, total int) (*GitHistory, error) {
return retriever().GetLog(repo, hash, total)
}
61 changes: 61 additions & 0 deletions repository/repository_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1726,3 +1726,64 @@ func (s *S) TestCommitZipIntegrationWhenFileEmpty(c *gocheck.C) {
_, err = CommitZip(repo, file, commit)
c.Assert(err.Error(), gocheck.Equals, expectedErr)
}

func (s *S) TestGetLog(c *gocheck.C) {
oldBare := bare
bare = "/tmp"
repo := "gandalf-test-repo"
file := "README"
content := "will\tbark"
object1 := "You should read this README"
object2 := "Seriously, read this file!"
cleanUp, errCreate := CreateTestRepository(bare, repo, file, content)
defer func() {
cleanUp()
bare = oldBare
}()
c.Assert(errCreate, gocheck.IsNil)
errCreateCommit := CreateCommit(bare, repo, file, object1)
c.Assert(errCreateCommit, gocheck.IsNil)
errCreateCommit = CreateCommit(bare, repo, file, object2)
c.Assert(errCreateCommit, gocheck.IsNil)
history, err := GetLog(repo, "HEAD", 1)
c.Assert(err, gocheck.IsNil)
c.Assert(history.Commits, gocheck.HasLen, 1)
c.Assert(history.Commits[0].Ref, gocheck.Matches, "[a-f0-9]{40}")
c.Assert(history.Commits[0].Parent, gocheck.HasLen, 1)
c.Assert(history.Commits[0].Parent[0], gocheck.Matches, "[a-f0-9]{40}")
c.Assert(history.Commits[0].Committer.Name, gocheck.Equals, "doge")
c.Assert(history.Commits[0].Committer.Email, gocheck.Equals, "much@email.com")
c.Assert(history.Commits[0].Author.Name, gocheck.Equals, "doge")
c.Assert(history.Commits[0].Author.Email, gocheck.Equals, "much@email.com")
c.Assert(history.Commits[0].Subject, gocheck.Equals, "Seriously, read this file!")
c.Assert(history.Commits[0].CreatedAt, gocheck.Equals, history.Commits[0].Author.Date)
c.Assert(history.Next, gocheck.Matches, "[a-f0-9]{40}")
// Next
history, err = GetLog(repo, history.Next, 1)
c.Assert(err, gocheck.IsNil)
c.Assert(history.Commits, gocheck.HasLen, 1)
c.Assert(history.Commits[0].Ref, gocheck.Matches, "[a-f0-9]{40}")
c.Assert(history.Commits[0].Parent, gocheck.HasLen, 1)
c.Assert(history.Commits[0].Parent[0], gocheck.Matches, "[a-f0-9]{40}")
c.Assert(history.Commits[0].Committer.Name, gocheck.Equals, "doge")
c.Assert(history.Commits[0].Committer.Email, gocheck.Equals, "much@email.com")
c.Assert(history.Commits[0].Author.Name, gocheck.Equals, "doge")
c.Assert(history.Commits[0].Author.Email, gocheck.Equals, "much@email.com")
c.Assert(history.Commits[0].Subject, gocheck.Equals, "You should read this README")
c.Assert(history.Commits[0].CreatedAt, gocheck.Equals, history.Commits[0].Author.Date)
c.Assert(history.Next, gocheck.Matches, "[a-f0-9]{40}")
// Next
history, err = GetLog(repo, history.Next, 1)
c.Assert(err, gocheck.IsNil)
c.Assert(history.Commits, gocheck.HasLen, 1)
c.Assert(history.Commits[0].Ref, gocheck.Matches, "[a-f0-9]{40}")
c.Assert(history.Commits[0].Parent, gocheck.HasLen, 1)
c.Assert(history.Commits[0].Parent[0], gocheck.Equals, "")
c.Assert(history.Commits[0].Committer.Name, gocheck.Equals, "doge")
c.Assert(history.Commits[0].Committer.Email, gocheck.Equals, "much@email.com")
c.Assert(history.Commits[0].Author.Name, gocheck.Equals, "doge")
c.Assert(history.Commits[0].Author.Email, gocheck.Equals, "much@email.com")
c.Assert(history.Commits[0].Subject, gocheck.Equals, "will\tbark")
c.Assert(history.Commits[0].CreatedAt, gocheck.Equals, history.Commits[0].Author.Date)
c.Assert(history.Next, gocheck.Equals, "")
}
1 change: 1 addition & 0 deletions webserver/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ For an example conf check gandalf/etc/gandalf.conf file.\n %s`
router.Get("/repository/:name/tags", http.HandlerFunc(api.GetTags))
router.Get("/repository/:name/diff/commits", http.HandlerFunc(api.GetDiff))
router.Post("/repository/:name/commit", http.HandlerFunc(api.Commit))
router.Get("/repository/:name/logs", http.HandlerFunc(api.GetLog))
router.Get("/healthcheck/", http.HandlerFunc(api.HealthCheck))
router.Post("/hook/:name", http.HandlerFunc(api.AddHook))

Expand Down

0 comments on commit 38d1b46

Please sign in to comment.