Changes
diff --git a/branch_filter.go b/branch_filter.go
new file mode 100644
index 0000000..19f6127
--- /dev/null
+++ b/branch_filter.go
@@ -0,0 +1,234 @@
+package main
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "log"
+ "os/exec"
+ "path/filepath"
+ "strings"
+ "time"
+)
+
+// Goes through list of branches and returns those that match whitelist.
+func branchFilter(repo string, options *options) ([]branch, error) {
+ cmd := exec.Command("git", "branch", "-a")
+ cmd.Dir = repo
+
+ whitelist := options.Branches
+
+ out, err := cmd.Output()
+
+ if err != nil {
+ return nil, err
+ }
+
+ var b = make(map[string]branch)
+ var m = make(map[string]bool)
+
+ scanner := bufio.NewScanner(bytes.NewReader(out))
+
+ for scanner.Scan() {
+ t := strings.TrimSpace(strings.TrimPrefix(scanner.Text(), "*"))
+ _, f := filepath.Split(t)
+
+ m[f] = true
+ }
+
+ if err := scanner.Err(); err != nil {
+ return nil, err
+ }
+
+ // Filter to match options, but return all if no branch flags given.
+ if len(whitelist) > 0 {
+ for k := range m {
+ m[k] = contains(whitelist, k)
+ }
+ } else {
+ // In git given order at this point.
+ for k := range m {
+ whitelist = append(whitelist, k)
+ }
+ }
+
+ for k, v := range m {
+ if v {
+ // TODO: Try a goroutine?
+ commits, err := commitParser(k, repo, options.Name)
+
+ if err != nil {
+ continue
+ }
+
+ b[k] = branch{commits, k, options.Name}
+ }
+ }
+
+ // Fill in resulting slice with desired branches in order.
+ var results []branch
+
+ for _, v := range whitelist {
+ results = append(results, b[v])
+ }
+
+ return results, nil
+}
+
+func commitParser(b string, repo string, name string) ([]commit, error) {
+ fst := strings.Join([]string{"%H", "%P", "%s", "%aN", "%aE", "%aD", "%h"}, SEP)
+ ref := fmt.Sprintf("origin/%s", b)
+
+ cmd := exec.Command("git", "log", fmt.Sprintf("--format=%s", fst), ref)
+ cmd.Dir = repo
+
+ out, err := cmd.Output()
+
+ if err != nil {
+ return nil, err
+ }
+
+ results := []commit{}
+ scanner := bufio.NewScanner(bytes.NewReader(out))
+
+ for scanner.Scan() {
+ text := strings.TrimSpace(scanner.Text())
+ data := strings.Split(text, SEP)
+
+ h := data[0]
+
+ var history []overview
+ var parents []string
+
+ if data[1] != "" {
+ parents = strings.Split(data[1], " ")
+ }
+
+ for _, parent := range parents {
+ diffstat, err := diffStatParser(h, parent, repo)
+
+ if err != nil {
+ log.Printf("unable to diffstat against parent: %s", err)
+
+ continue
+ }
+
+ history = append(history, overview{diffstat, h, parent})
+ }
+
+ a := author{data[4], data[3]}
+
+ date, err := time.Parse("Mon, 2 Jan 2006 15:04:05 -0700", data[5])
+
+ if err != nil {
+ log.Printf("unable to parse commit date: %s", err)
+
+ continue
+ }
+
+ body, err := bodyParser(h, repo)
+
+ if err != nil {
+ log.Printf("unable to parse commit body: %s", err)
+
+ continue
+ }
+
+ tree, err := treeParser(h, repo)
+
+ if err != nil {
+ log.Printf("unable to parse commit tree: %s", err)
+
+ continue
+ }
+
+ c := commit{
+ Abbr: data[6],
+ Author: a,
+ Body: body,
+ Branch: b,
+ Date: date,
+ Hash: h,
+ History: history,
+ Parents: parents,
+ Project: name,
+ Subject: data[2],
+ Tree: tree,
+ }
+
+ results = append(results, c)
+ }
+
+ if err := scanner.Err(); err != nil {
+ return nil, err
+ }
+
+ return results, nil
+}
+
+func treeParser(h string, repo string) ([]object, error) {
+ cmd := exec.Command("git", "ls-tree", "-r", "--format=%(objectname) %(path)", h)
+ cmd.Dir = repo
+
+ out, err := cmd.Output()
+
+ if err != nil {
+ return nil, err
+ }
+
+ var results []object
+ feed := strings.Split(strings.TrimSuffix(fmt.Sprintf("%s", out), "\n"), "\n")
+
+ for _, line := range feed {
+ w := strings.Split(line, " ")
+
+ results = append(results, object{
+ Hash: w[0],
+ Path: w[1],
+ })
+ }
+
+ return results, nil
+}
+
+func diffStatParser(h, parent string, repo string) (string, error) {
+ cmd := exec.Command("git", "diff", "--stat", fmt.Sprintf("%s..%s", parent, h))
+ cmd.Dir = repo
+
+ out, err := cmd.Output()
+
+ if err != nil {
+ return "", err
+ }
+
+ var results []string
+ feed := strings.Split(strings.TrimSuffix(fmt.Sprintf("%s", out), "\n"), "\n")
+
+ for _, line := range feed {
+ // NOTE: This is hackish I know, attach to project?
+ i := strings.Index(line, "|")
+
+ if i != -1 {
+ ext := filepath.Ext(strings.TrimSpace(line[:i]))
+ types[ext] = strings.Contains(line, "Bin")
+ }
+
+ results = append(results, strings.TrimSpace(line))
+ }
+
+ return strings.Join(results, "\n"), nil
+}
+
+func bodyParser(h string, repo string) (string, error) {
+ // Because the commit message body is multiline and is tripping the scanner.
+ cmd := exec.Command("git", "show", "--no-patch", "--format=%B", h)
+ cmd.Dir = repo
+
+ out, err := cmd.Output()
+
+ if err != nil {
+ return "", err
+ }
+
+ return strings.TrimSuffix(fmt.Sprintf("%s", out), "\n"), nil
+}
diff --git a/diff_parsers.go b/diff_parsers.go
new file mode 100644
index 0000000..eac48ab
--- /dev/null
+++ b/diff_parsers.go
@@ -0,0 +1,87 @@
+package main
+
+import (
+ "fmt"
+ "html/template"
+ "regexp"
+ "strings"
+)
+
+// Match diff body @@ del, ins line numbers.
+var aline = regexp.MustCompile(`\-(.*?),`)
+var bline = regexp.MustCompile(`\+(.*?),`)
+
+// Match diff body keywords.
+var xline = regexp.MustCompile(`^(deleted|index|new|rename|similarity)`)
+
+// Helps target file specific diff blocks.
+var diffanchor = regexp.MustCompile(`b\/(.*?)$`)
+
+func diffbodyparser(d diff) template.HTML {
+ var results []string
+ feed := strings.Split(strings.TrimSuffix(template.HTMLEscapeString(d.Body), "\n"), "\n")
+
+ var a, b string
+
+ for _, line := range feed {
+ if strings.HasPrefix(line, "diff") {
+ line = diffanchor.ReplaceAllString(line, `b/<a id="$1">$1</a>`)
+ line = fmt.Sprintf("<strong>%s</strong>", line)
+ }
+
+ line = xline.ReplaceAllString(line, "<em>$1</em>")
+
+ if strings.HasPrefix(line, "@@") {
+ if a != "" && !strings.HasPrefix(a, "---") {
+ repl := fmt.Sprintf(`<a href="commit/%s/%s.html#L$1">-$1</a>,`, d.Parent, a)
+ line = aline.ReplaceAllString(line, repl)
+ }
+
+ if b != "" && !strings.HasPrefix(b, "+++") {
+ repl := fmt.Sprintf(`<a href="commit/%s/%s.html#L$1">+$1</a>,`, d.Commit.Hash, b)
+ line = bline.ReplaceAllString(line, repl)
+ }
+ }
+
+ if strings.HasPrefix(line, "---") {
+ a = strings.TrimPrefix(line, "--- a/")
+ line = fmt.Sprintf("<mark>%s</mark>", line)
+ } else if strings.HasPrefix(line, "-") {
+ line = fmt.Sprintf("<del>%s</del>", line)
+ }
+
+ if strings.HasPrefix(line, "+++") {
+ b = strings.TrimPrefix(line, "+++ b/")
+ line = fmt.Sprintf("<mark>%s</mark>", line)
+ } else if strings.HasPrefix(line, "+") {
+ line = fmt.Sprintf("<ins>%s</ins>", line)
+ }
+
+ results = append(results, line)
+ }
+
+ return template.HTML(strings.Join(results, "\n"))
+}
+
+func diffstatbodyparser(o overview) template.HTML {
+ var results []string
+ feed := strings.Split(strings.TrimSuffix(o.Body, "\n"), "\n")
+
+ for i, line := range feed {
+ if i < len(feed)-1 {
+ // Link files to corresponding diff.
+ columns := strings.Split(line, "|")
+ files := strings.Split(columns[0], "=>")
+
+ a := strings.TrimSpace(files[len(files)-1])
+ b := fmt.Sprintf(`<a href="commit/%s/diff-%s.html#%s">%s</a>`, o.Hash, o.Parent, a, a)
+ l := strings.LastIndex(line, a)
+
+ line = line[:l] + strings.Replace(line[l:], a, b, 1)
+ }
+
+ results = append(results, line)
+ }
+
+ return template.HTML(strings.Join(results, "\n"))
+}