mirror of
https://github.com/thomiceli/opengist.git
synced 2024-12-22 20:42:40 +00:00
Better log parsing
This commit is contained in:
parent
24f790fb9c
commit
527be16183
6 changed files with 158 additions and 103 deletions
|
@ -121,7 +121,7 @@ func GetFileContent(user string, gist string, revision string, filename string,
|
|||
return truncateCommandOutput(stdout, maxBytes)
|
||||
}
|
||||
|
||||
func GetLog(user string, gist string, skip string) (string, error) {
|
||||
func GetLog(user string, gist string, skip string) ([]*Commit, error) {
|
||||
repositoryPath := RepositoryPath(user, gist)
|
||||
|
||||
cmd := exec.Command(
|
||||
|
@ -130,20 +130,22 @@ func GetLog(user string, gist string, skip string) (string, error) {
|
|||
"log",
|
||||
"-n",
|
||||
"11",
|
||||
"--no-prefix",
|
||||
"--no-color",
|
||||
"-p",
|
||||
"--skip",
|
||||
skip,
|
||||
"--format=format:%n=commit %H:%aN:%at",
|
||||
"--format=format:c %H%na %aN%nt %at",
|
||||
"--shortstat",
|
||||
"--ignore-missing", // avoid errors if a wrong hash is given
|
||||
"HEAD",
|
||||
)
|
||||
cmd.Dir = repositoryPath
|
||||
stdout, _ := cmd.StdoutPipe()
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stdout, err := cmd.Output()
|
||||
return string(stdout), err
|
||||
return parseLog(stdout), nil
|
||||
}
|
||||
|
||||
func CloneTmp(user string, gist string, gistTmpId string) error {
|
||||
|
@ -213,7 +215,7 @@ func AddAll(gistTmpId string) error {
|
|||
return cmd.Run()
|
||||
}
|
||||
|
||||
func Commit(gistTmpId string) error {
|
||||
func CommitRepository(gistTmpId string) error {
|
||||
cmd := exec.Command("git", "commit", "--allow-empty", "-m", `"Opengist commit"`)
|
||||
tmpPath := TmpRepositoryPath(gistTmpId)
|
||||
cmd.Dir = tmpPath
|
||||
|
|
|
@ -1,10 +1,29 @@
|
|||
package git
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type File struct {
|
||||
Filename string `validate:"excludes=\x2f,excludes=\x5c,max=50"`
|
||||
OldFilename string `validate:"excludes=\x2f,excludes=\x5c,max=50"`
|
||||
Content string `validate:"required"`
|
||||
Truncated bool
|
||||
IsCreated bool
|
||||
IsDeleted bool
|
||||
}
|
||||
|
||||
type Commit struct {
|
||||
Hash string
|
||||
Author string
|
||||
Timestamp string
|
||||
Changed string
|
||||
Files []File
|
||||
}
|
||||
|
||||
func truncateCommandOutput(out io.Reader, maxBytes int64) (string, bool, error) {
|
||||
var buf []byte
|
||||
var err error
|
||||
|
@ -31,3 +50,96 @@ func truncateCommandOutput(out io.Reader, maxBytes int64) (string, bool, error)
|
|||
|
||||
return string(buf), truncated, nil
|
||||
}
|
||||
|
||||
func parseLog(out io.Reader) []*Commit {
|
||||
scanner := bufio.NewScanner(out)
|
||||
|
||||
var commits []*Commit
|
||||
var currentCommit *Commit
|
||||
var currentFile *File
|
||||
var isContent bool
|
||||
|
||||
for scanner.Scan() {
|
||||
// new commit found
|
||||
currentFile = nil
|
||||
currentCommit = &Commit{Hash: string(scanner.Bytes()[2:]), Files: []File{}}
|
||||
|
||||
scanner.Scan()
|
||||
currentCommit.Author = string(scanner.Bytes()[2:])
|
||||
|
||||
scanner.Scan()
|
||||
currentCommit.Timestamp = string(scanner.Bytes()[2:])
|
||||
|
||||
scanner.Scan()
|
||||
changed := scanner.Bytes()[1:]
|
||||
changed = bytes.ReplaceAll(changed, []byte("(+)"), []byte(""))
|
||||
changed = bytes.ReplaceAll(changed, []byte("(-)"), []byte(""))
|
||||
currentCommit.Changed = string(changed)
|
||||
|
||||
// twice because --shortstat adds a new line
|
||||
scanner.Scan()
|
||||
scanner.Scan()
|
||||
// commit header parsed
|
||||
|
||||
// files changes inside the commit
|
||||
for {
|
||||
line := scanner.Bytes()
|
||||
|
||||
// end of content of file
|
||||
if len(line) == 0 {
|
||||
isContent = false
|
||||
if currentFile != nil {
|
||||
currentCommit.Files = append(currentCommit.Files, *currentFile)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// new file found
|
||||
if bytes.HasPrefix(line, []byte("diff --git")) {
|
||||
// current file is finished, we can add it to the commit
|
||||
if currentFile != nil {
|
||||
currentCommit.Files = append(currentCommit.Files, *currentFile)
|
||||
}
|
||||
|
||||
// create a new file
|
||||
isContent = false
|
||||
currentFile = &File{}
|
||||
filenameRegex := regexp.MustCompile(`^diff --git a/(.+) b/(.+)$`)
|
||||
matches := filenameRegex.FindStringSubmatch(string(line))
|
||||
if len(matches) == 3 {
|
||||
currentFile.Filename = matches[2]
|
||||
if matches[1] != matches[2] {
|
||||
currentFile.OldFilename = matches[1]
|
||||
}
|
||||
}
|
||||
scanner.Scan()
|
||||
continue
|
||||
}
|
||||
|
||||
if bytes.HasPrefix(line, []byte("new")) {
|
||||
currentFile.IsCreated = true
|
||||
}
|
||||
|
||||
if bytes.HasPrefix(line, []byte("deleted")) {
|
||||
currentFile.IsDeleted = true
|
||||
}
|
||||
|
||||
// file content found
|
||||
if line[0] == '@' {
|
||||
isContent = true
|
||||
}
|
||||
|
||||
if isContent {
|
||||
currentFile.Content += string(line) + "\n"
|
||||
}
|
||||
|
||||
scanner.Scan()
|
||||
}
|
||||
|
||||
if currentCommit != nil {
|
||||
commits = append(commits, currentCommit)
|
||||
}
|
||||
}
|
||||
|
||||
return commits
|
||||
}
|
||||
|
|
|
@ -28,21 +28,6 @@ type Gist struct {
|
|||
ForkedID uint
|
||||
}
|
||||
|
||||
type File struct {
|
||||
Filename string `validate:"excludes=\x2f,excludes=\x5c,max=50"`
|
||||
OldFilename string `validate:"excludes=\x2f,excludes=\x5c,max=50"`
|
||||
Content string `validate:"required"`
|
||||
Truncated bool
|
||||
}
|
||||
|
||||
type Commit struct {
|
||||
Hash string
|
||||
Author string
|
||||
Timestamp string
|
||||
Changed string
|
||||
Files []File
|
||||
}
|
||||
|
||||
func (gist *Gist) BeforeDelete(tx *gorm.DB) error {
|
||||
// Decrement fork counter if the gist was forked
|
||||
err := tx.Model(&Gist{}).
|
||||
|
@ -195,8 +180,8 @@ func (gist *Gist) DeleteRepository() error {
|
|||
return git.DeleteRepository(gist.User.Username, gist.Uuid)
|
||||
}
|
||||
|
||||
func (gist *Gist) Files(revision string) ([]*File, error) {
|
||||
var files []*File
|
||||
func (gist *Gist) Files(revision string) ([]*git.File, error) {
|
||||
var files []*git.File
|
||||
filesStr, err := git.GetFilesOfRepository(gist.User.Username, gist.Uuid, revision)
|
||||
if err != nil {
|
||||
// if the revision or the file do not exist
|
||||
|
@ -218,7 +203,7 @@ func (gist *Gist) Files(revision string) ([]*File, error) {
|
|||
return files, err
|
||||
}
|
||||
|
||||
func (gist *Gist) File(revision string, filename string, truncate bool) (*File, error) {
|
||||
func (gist *Gist) File(revision string, filename string, truncate bool) (*git.File, error) {
|
||||
content, truncated, err := git.GetFileContent(gist.User.Username, gist.Uuid, revision, filename, truncate)
|
||||
|
||||
// if the revision or the file do not exist
|
||||
|
@ -226,24 +211,22 @@ func (gist *Gist) File(revision string, filename string, truncate bool) (*File,
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
return &File{
|
||||
return &git.File{
|
||||
Filename: filename,
|
||||
Content: content,
|
||||
Truncated: truncated,
|
||||
}, err
|
||||
}
|
||||
|
||||
func (gist *Gist) Log(skip string) error {
|
||||
_, err := git.GetLog(gist.User.Username, gist.Uuid, skip)
|
||||
|
||||
return err
|
||||
func (gist *Gist) Log(skip string) ([]*git.Commit, error) {
|
||||
return git.GetLog(gist.User.Username, gist.Uuid, skip)
|
||||
}
|
||||
|
||||
func (gist *Gist) NbCommits() (string, error) {
|
||||
return git.GetNumberOfCommitsOfRepository(gist.User.Username, gist.Uuid)
|
||||
}
|
||||
|
||||
func (gist *Gist) AddAndCommitFiles(files *[]File) error {
|
||||
func (gist *Gist) AddAndCommitFiles(files *[]git.File) error {
|
||||
if err := git.CloneTmp(gist.User.Username, gist.Uuid, gist.Uuid); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -258,7 +241,7 @@ func (gist *Gist) AddAndCommitFiles(files *[]File) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if err := git.Commit(gist.Uuid); err != nil {
|
||||
if err := git.CommitRepository(gist.Uuid); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -280,10 +263,10 @@ func (gist *Gist) RPC(service string) ([]byte, error) {
|
|||
// -- DTO -- //
|
||||
|
||||
type GistDTO struct {
|
||||
Title string `validate:"max=50" form:"title"`
|
||||
Description string `validate:"max=150" form:"description"`
|
||||
Private bool `form:"private"`
|
||||
Files []File `validate:"min=1,dive"`
|
||||
Title string `validate:"max=50" form:"title"`
|
||||
Description string `validate:"max=150" form:"description"`
|
||||
Private bool `form:"private"`
|
||||
Files []git.File `validate:"min=1,dive"`
|
||||
}
|
||||
|
||||
func (dto *GistDTO) ToGist() *Gist {
|
||||
|
|
|
@ -162,50 +162,9 @@ func revisions(ctx echo.Context) error {
|
|||
|
||||
pageInt := getPage(ctx)
|
||||
|
||||
nbCommits := getData(ctx, "nbCommits")
|
||||
commits := make([]*models.Commit, 0)
|
||||
if nbCommits != "0" {
|
||||
gitlogStr, err := git.GetLog(userName, gistName, strconv.Itoa((pageInt-1)*10))
|
||||
if err != nil {
|
||||
return errorRes(500, "Error fetching commits log", err)
|
||||
}
|
||||
|
||||
gitlog := strings.Split(gitlogStr, "\n=commit ")
|
||||
for _, commitStr := range gitlog[1:] {
|
||||
logContent := strings.SplitN(commitStr, "\n", 3)
|
||||
|
||||
header := strings.Split(logContent[0], ":")
|
||||
commitStruct := models.Commit{
|
||||
Hash: header[0],
|
||||
Author: header[1],
|
||||
Timestamp: header[2],
|
||||
Files: make([]models.File, 0),
|
||||
}
|
||||
|
||||
if len(logContent) > 2 {
|
||||
changed := strings.ReplaceAll(logContent[1], "(+)", "")
|
||||
changed = strings.ReplaceAll(changed, "(-)", "")
|
||||
commitStruct.Changed = changed
|
||||
}
|
||||
|
||||
files := strings.Split(logContent[len(logContent)-1], "diff --git ")
|
||||
if len(files) > 1 {
|
||||
for _, fileStr := range files {
|
||||
content := strings.SplitN(fileStr, "\n@@", 2)
|
||||
if len(content) > 1 {
|
||||
header := strings.Split(content[0], "\n")
|
||||
commitStruct.Files = append(commitStruct.Files, models.File{Content: "@@" + content[1], Filename: header[len(header)-1][4:], OldFilename: header[len(header)-2][4:]})
|
||||
} else {
|
||||
// in case there is no content but a file renamed
|
||||
header := strings.Split(content[0], "\n")
|
||||
if len(header) > 3 {
|
||||
commitStruct.Files = append(commitStruct.Files, models.File{Content: "", Filename: header[3][10:], OldFilename: header[2][12:]})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
commits = append(commits, &commitStruct)
|
||||
}
|
||||
commits, err := gist.Log(strconv.Itoa((pageInt - 1) * 10))
|
||||
if err != nil {
|
||||
return errorRes(500, "Error fetching commits log", err)
|
||||
}
|
||||
|
||||
if err := paginate(ctx, commits, pageInt, 10, "commits", userName+"/"+gistName+"/revisions", 2); err != nil {
|
||||
|
@ -249,7 +208,7 @@ func processCreate(ctx echo.Context) error {
|
|||
return errorRes(400, "Cannot bind data", err)
|
||||
}
|
||||
|
||||
dto.Files = make([]models.File, 0)
|
||||
dto.Files = make([]git.File, 0)
|
||||
for i := 0; i < len(ctx.Request().PostForm["content"]); i++ {
|
||||
name := ctx.Request().PostForm["name"][i]
|
||||
content := ctx.Request().PostForm["content"][i]
|
||||
|
@ -263,7 +222,7 @@ func processCreate(ctx echo.Context) error {
|
|||
return errorRes(400, "Invalid character unescaped", err)
|
||||
}
|
||||
|
||||
dto.Files = append(dto.Files, models.File{
|
||||
dto.Files = append(dto.Files, git.File{
|
||||
Filename: name,
|
||||
Content: escapedValue,
|
||||
})
|
||||
|
|
16
templates/pages/gist.html
vendored
16
templates/pages/gist.html
vendored
|
@ -26,13 +26,15 @@
|
|||
<div class="markdown markdown-body p-8">{{ $file.Content }}</div>
|
||||
{{ else }}
|
||||
{{ $fileslug := slug $file.Filename }}
|
||||
<table class="table-code w-full whitespace-pre" data-filename-slug="{{ $fileslug }}" data-filename="{{ $file.Filename }}" style="font-size: 0.8em; border-spacing: 0; border-collapse: collapse;">
|
||||
<tbody>
|
||||
{{ $ii := "1" }}
|
||||
{{ $i := toInt $ii }}
|
||||
{{ range $line := lines $file.Content }}<tr><td id="file-{{ $fileslug }}-{{$i}}" class="select-none line-num px-4">{{$i}}</td><td class="line-code">{{ $line }}</td></tr>{{ $i = inc $i }}{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
{{ if ne $file.Content "" }}
|
||||
<table class="table-code w-full whitespace-pre" data-filename-slug="{{ $fileslug }}" data-filename="{{ $file.Filename }}" style="font-size: 0.8em; border-spacing: 0; border-collapse: collapse;">
|
||||
<tbody>
|
||||
{{ $ii := "1" }}
|
||||
{{ $i := toInt $ii }}
|
||||
{{ range $line := lines $file.Content }}<tr><td id="file-{{ $fileslug }}-{{$i}}" class="select-none line-num px-4">{{$i}}</td><td class="line-code">{{ $line }}</td></tr>{{ $i = inc $i }}{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
25
templates/pages/revisions.html
vendored
25
templates/pages/revisions.html
vendored
|
@ -27,24 +27,22 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 flex text-slate-300" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
|
||||
</svg>
|
||||
{{ if eq $file.Filename $file.OldFilename }}
|
||||
<span class="flex text-sm ml-2 text-slate-300">{{ $file.Filename }}</span>
|
||||
{{ if $file.IsCreated }}
|
||||
<span class="flex text-sm ml-2 text-slate-300">{{ $file.Filename }}<span class="italic text-gray-400 ml-1">(file created)</span></span>
|
||||
{{ else if $file.IsDeleted }}
|
||||
<span class="flex text-sm ml-2 text-slate-300">{{ $file.Filename }} <span class="italic text-gray-400 ml-1">(file deleted)</span></span>
|
||||
{{ else if ne $file.OldFilename "" }}
|
||||
<span class="flex text-sm ml-2 text-slate-300">{{ $file.OldFilename }} <span class="italic text-gray-400 mx-1">renamed to</span> {{ $file.Filename }}</span>
|
||||
{{ else }}
|
||||
{{ if eq $file.OldFilename "/dev/null" }}
|
||||
<span class="flex text-sm ml-2 text-slate-300">{{ $file.Filename }}<span class="italic text-gray-400 ml-1">(file created)</span></span>
|
||||
{{ else if eq $file.Filename "/dev/null" }}
|
||||
<span class="flex text-sm ml-2 text-slate-300">{{ $file.OldFilename }} <span class="italic text-gray-400 ml-1">(file deleted)</span></span>
|
||||
{{ else }}
|
||||
<span class="flex text-sm ml-2 text-slate-300">{{ $file.OldFilename }} <span class="italic text-gray-400 mx-1">renamed to</span> {{ $file.Filename }}</span>
|
||||
{{ end }}
|
||||
<span class="flex text-sm ml-2 text-slate-300">{{ $file.Filename }}</span>
|
||||
{{ end }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="code overflow-auto">
|
||||
{{ if eq $file.Content "" }}
|
||||
<p class="m-2 ml-4 text-sm">
|
||||
File renamed without changes.
|
||||
</p>
|
||||
{{ if and (eq $file.Content "") (ne $file.OldFilename "") }}
|
||||
<p class="m-2 ml-4 text-xs">File renamed without changes</p>
|
||||
{{ else if eq $file.Content "" }}
|
||||
<p class="m-2 ml-4 text-xs">Empty file</p>
|
||||
{{ else }}
|
||||
<table class="table-code w-full whitespace-pre" data-filename="{{ $file.Filename }}" style="font-size: 0.8em; border-spacing: 0">
|
||||
<tbody>
|
||||
|
@ -52,7 +50,6 @@
|
|||
{{ $right := 0 }}
|
||||
{{ range $line := split $file.Content "\n" }}
|
||||
{{ if ne $line "" }}{{ if ne (index $line 0) 92 }}
|
||||
|
||||
{{ if eq (index $line 0) 64 }}
|
||||
{{ $left = toInt (index (splitGit (index (split $line "-") 1)) 0) }}
|
||||
{{ $right = toInt (index (splitGit (index (split $line "+") 1)) 0) }}
|
||||
|
|
Loading…
Reference in a new issue