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

Get log for a project #153

Merged
merged 2 commits into from
Aug 19, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 32 additions & 3 deletions api/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ func GetTree(w http.ResponseWriter, r *http.Request) {
b, err := json.Marshal(tree)
if err != nil {
err := fmt.Errorf("Error when trying to obtain tree for path %s on ref %s of repository %s (%s).", path, ref, repo, err)
http.Error(w, err.Error(), http.StatusBadRequest)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Write(b)
Expand All @@ -392,7 +392,7 @@ func GetBranches(w http.ResponseWriter, r *http.Request) {
b, err := json.Marshal(branches)
if err != nil {
err := fmt.Errorf("Error when trying to obtain the branches of repository %s (%s).", repo, err)
http.Error(w, err.Error(), http.StatusBadRequest)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Write(b)
Expand All @@ -415,7 +415,7 @@ func GetTags(w http.ResponseWriter, r *http.Request) {
b, err := json.Marshal(tags)
if err != nil {
err := fmt.Errorf("Error when trying to obtain tags on ref %s of repository %s (%s).", ref, repo, err)
http.Error(w, err.Error(), http.StatusBadRequest)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Write(b)
Expand Down Expand Up @@ -491,8 +491,37 @@ func Commit(w http.ResponseWriter, r *http.Request) {
}
b, err := json.Marshal(ref)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
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