appview/pages: initial support for split diffs #323

merged
opened by oppi.li targeting master from push-qkpqsrknozxs
Changed files
+170 -46
appview
types
+3
appview/pages/pages.go
···
Active string
EmailToDidOrHandle map[string]string
Pipeline *db.Pipeline
// singular because it's always going to be just one
VerifiedCommit commitverify.VerifiedCommits
···
Round int
Submission *db.PullSubmission
OrderedReactionKinds []db.ReactionKind
}
// this name is a mouthful
···
Base string
Head string
Diff *types.NiceDiff
Active string
}
···
Active string
EmailToDidOrHandle map[string]string
Pipeline *db.Pipeline
+
DiffOpts types.DiffOpts
// singular because it's always going to be just one
VerifiedCommit commitverify.VerifiedCommits
···
Round int
Submission *db.PullSubmission
OrderedReactionKinds []db.ReactionKind
+
DiffOpts types.DiffOpts
}
// this name is a mouthful
···
Base string
Head string
Diff *types.NiceDiff
+
DiffOpts types.DiffOpts
Active string
}
+1 -1
appview/pages/templates/repo/commit.html
···
{{ end }}
{{ define "contentAfter" }}
-
{{ template "repo/fragments/diff" (list .RepoInfo.FullName .Diff) }}
{{end}}
{{ define "contentAfterLeft" }}
···
{{ end }}
{{ define "contentAfter" }}
+
{{ template "repo/fragments/diff" (list .RepoInfo.FullName .Diff .DiffOpts) }}
{{end}}
{{ define "contentAfterLeft" }}
+1 -1
appview/pages/templates/repo/compare/compare.html
···
{{ end }}
{{ define "contentAfter" }}
-
{{ template "repo/fragments/diff" (list .RepoInfo.FullName .Diff) }}
{{end}}
{{ define "contentAfterLeft" }}
···
{{ end }}
{{ define "contentAfter" }}
+
{{ template "repo/fragments/diff" (list .RepoInfo.FullName .Diff .DiffOpts) }}
{{end}}
{{ define "contentAfterLeft" }}
+118 -39
appview/pages/templates/repo/fragments/diff.html
···
{{ define "repo/fragments/diff" }}
{{ $repo := index . 0 }}
{{ $diff := index . 1 }}
{{ $commit := $diff.Commit }}
{{ $diff := $diff.Diff }}
{{ $this := $commit.This }}
{{ $parent := $commit.Parent }}
···
This is a binary file and will not be displayed.
</p>
{{ else }}
-
{{ $name := .Name.New }}
-
<pre class="overflow-x-auto"><div class="overflow-x-auto"><div class="min-w-full inline-block">{{- range .TextFragments -}}<div class="bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400 select-none text-center">&middot;&middot;&middot;</div>
-
{{- $oldStart := .OldPosition -}}
-
{{- $newStart := .NewPosition -}}
-
{{- $lineNrStyle := "min-w-[3.5rem] flex-shrink-0 select-none text-right bg-white dark:bg-gray-800 scroll-mt-10 target:border target:border-amber-500 target:rounded " -}}
-
{{- $linkStyle := "text-gray-400 dark:text-gray-500 hover:underline" -}}
-
{{- $lineNrSepStyle1 := "" -}}
-
{{- $lineNrSepStyle2 := "pr-2" -}}
-
{{- range .Lines -}}
-
{{- if eq .Op.String "+" -}}
-
<div class="bg-green-100 dark:bg-green-800/30 text-green-700 dark:text-green-400 flex min-w-full items-center">
-
<div class="{{$lineNrStyle}} {{$lineNrSepStyle1}}"><span aria-hidden="true" class="invisible">{{$newStart}}</span></div>
-
<div class="{{$lineNrStyle}} {{$lineNrSepStyle2}}" id="{{$name}}-N{{$newStart}}"><a class="{{$linkStyle}}" href="#{{$name}}-N{{$newStart}}">{{ $newStart }}</a></div>
-
<div class="w-5 flex-shrink-0 select-none text-center">{{ .Op.String }}</div>
-
<div class="px-2">{{ .Line }}</div>
-
</div>
-
{{- $newStart = add64 $newStart 1 -}}
-
{{- end -}}
-
{{- if eq .Op.String "-" -}}
-
<div class="bg-red-100 dark:bg-red-800/30 text-red-700 dark:text-red-400 flex min-w-full items-center">
-
<div class="{{$lineNrStyle}} {{$lineNrSepStyle1}}" id="{{$name}}-O{{$oldStart}}"><a class="{{$linkStyle}}" href="#{{$name}}-O{{$oldStart}}">{{ $oldStart }}</a></div>
-
<div class="{{$lineNrStyle}} {{$lineNrSepStyle2}}"><span aria-hidden="true" class="invisible">{{$oldStart}}</span></div>
-
<div class="w-5 flex-shrink-0 select-none text-center">{{ .Op.String }}</div>
-
<div class="px-2">{{ .Line }}</div>
-
</div>
-
{{- $oldStart = add64 $oldStart 1 -}}
-
{{- end -}}
-
{{- if eq .Op.String " " -}}
-
<div class="bg-white dark:bg-gray-800 text-gray-500 dark:text-gray-400 flex min-w-full items-center">
-
<div class="{{$lineNrStyle}} {{$lineNrSepStyle1}}" id="{{$name}}-O{{$oldStart}}"><a class="{{$linkStyle}}" href="#{{$name}}-O{{$oldStart}}">{{ $oldStart }}</a></div>
-
<div class="{{$lineNrStyle}} {{$lineNrSepStyle2}}" id="{{$name}}-N{{$newStart}}"><a class="{{$linkStyle}}" href="#{{$name}}-N{{$newStart}}">{{ $newStart }}</a></div>
-
<div class="w-5 flex-shrink-0 select-none text-center">{{ .Op.String }}</div>
-
<div class="px-2">{{ .Line }}</div>
-
</div>
-
{{- $newStart = add64 $newStart 1 -}}
-
{{- $oldStart = add64 $oldStart 1 -}}
-
{{- end -}}
-
{{- end -}}
-
{{- end -}}</div></div></pre>
{{- end -}}
</div>
···
{{ end }}
{{ end }}
{{ define "statPill" }}
<div class="flex items-center font-mono text-sm">
{{ if and .Insertions .Deletions }}
···
{{ end }}
</div>
{{ end }}
···
{{ define "repo/fragments/diff" }}
{{ $repo := index . 0 }}
{{ $diff := index . 1 }}
+
{{ $opts := index . 2 }}
+
{{ $commit := $diff.Commit }}
{{ $diff := $diff.Diff }}
+
{{ $isSplit := $opts.Split }}
{{ $this := $commit.This }}
{{ $parent := $commit.Parent }}
···
This is a binary file and will not be displayed.
</p>
{{ else }}
+
{{ if $isSplit }}
+
{{- template "repo/fragments/splitDiff" .Split -}}
+
{{ else }}
+
{{- template "repo/fragments/unifiedDiff" . -}}
+
{{ end }}
{{- end -}}
</div>
···
{{ end }}
{{ end }}
+
{{ define "unifiedDiffLines" }}
+
{{ $name := .Name.New }}
+
<pre class="overflow-x-auto"><div class="overflow-x-auto"><div class="min-w-full inline-block">{{- range .TextFragments -}}<div class="bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400 select-none text-center">&middot;&middot;&middot;</div>
+
{{- $oldStart := .OldPosition -}}
+
{{- $newStart := .NewPosition -}}
+
{{- $lineNrStyle := "min-w-[3.5rem] flex-shrink-0 select-none text-right bg-white dark:bg-gray-800 target:bg-yellow-200 target:dark:bg-yellow-600" -}}
+
{{- $linkStyle := "text-gray-400 dark:text-gray-500 hover:underline" -}}
+
{{- $lineNrSepStyle1 := "" -}}
+
{{- $lineNrSepStyle2 := "pr-2 border-r border-gray-200 dark:border-gray-700" -}}
+
{{- $containerStyle := "flex min-w-full items-center target:border target:rounded-sm target:border-yellow-200 target:dark:border-yellow-700 scroll-mt-20" -}}
+
{{- $addStyle := "bg-green-100 dark:bg-green-800/30 text-green-700 dark:text-green-400 " -}}
+
{{- $delStyle := "bg-red-100 dark:bg-red-800/30 text-red-700 dark:text-red-400 " -}}
+
{{- $ctxStyle := "bg-white dark:bg-gray-800 text-gray-500 dark:text-gray-400" -}}
+
{{- $opStyle := "w-5 flex-shrink-0 select-none text-center" -}}
+
{{- range .Lines -}}
+
{{- if eq .Op.String "+" -}}
+
<div class="{{ $addStyle }} {{ $containerStyle }}" id="{{$name}}-N{{$newStart}}">
+
<div class="{{$lineNrStyle}} {{$lineNrSepStyle1}}"><span aria-hidden="true" class="invisible">{{$newStart}}</span></div>
+
<div class="{{$lineNrStyle}} {{$lineNrSepStyle2}}"><a class="{{$linkStyle}}" href="#{{$name}}-N{{$newStart}}">{{ $newStart }}</a></div>
+
<div class="{{ $opStyle }}">{{ .Op.String }}</div>
+
<div class="px-2">{{ .Line }}</div>
+
</div>
+
{{- $newStart = add64 $newStart 1 -}}
+
{{- end -}}
+
{{- if eq .Op.String "-" -}}
+
<div class="{{ $delStyle }} {{ $containerStyle }}" id="{{$name}}-O{{$oldStart}}">
+
<div class="{{$lineNrStyle}} {{$lineNrSepStyle1}}"><a class="{{$linkStyle}}" href="#{{$name}}-O{{$oldStart}}">{{ $oldStart }}</a></div>
+
<div class="{{$lineNrStyle}} {{$lineNrSepStyle2}}"><span aria-hidden="true" class="invisible">{{$oldStart}}</span></div>
+
<div class="{{ $opStyle }}">{{ .Op.String }}</div>
+
<div class="px-2">{{ .Line }}</div>
+
</div>
+
{{- $oldStart = add64 $oldStart 1 -}}
+
{{- end -}}
+
{{- if eq .Op.String " " -}}
+
<div class="{{ $ctxStyle }} {{ $containerStyle }}" id="{{$name}}-O{{$oldStart}}-N{{$newStart}}">
+
<div class="{{$lineNrStyle}} {{$lineNrSepStyle1}}"><a class="{{$linkStyle}}" href="#{{$name}}-O{{$oldStart}}-N{{$newStart}}">{{ $oldStart }}</a></div>
+
<div class="{{$lineNrStyle}} {{$lineNrSepStyle2}}"><a class="{{$linkStyle}}" href="#{{$name}}-O{{$oldStart}}-N{{$newStart}}">{{ $newStart }}</a></div>
+
<div class="{{ $opStyle }}">{{ .Op.String }}</div>
+
<div class="px-2">{{ .Line }}</div>
+
</div>
+
{{- $newStart = add64 $newStart 1 -}}
+
{{- $oldStart = add64 $oldStart 1 -}}
+
{{- end -}}
+
{{- end -}}
+
{{- end -}}</div></div></pre>
+
{{ end }}
+
+
{{ define "splitDiffLines" }}
+
{{ $name := .Name.New }}
+
{{- $lineNrStyle := "min-w-[3.5rem] flex-shrink-0 select-none text-right bg-white dark:bg-gray-800" -}}
+
{{- $linkStyle := "text-gray-400 dark:text-gray-500 hover:underline" -}}
+
{{- $lineNrSepStyle := "pr-2 border-r border-gray-200 dark:border-gray-700" -}}
+
{{- $containerStyle := "flex min-w-full items-center target:border target:rounded-sm target:border-yellow-200 target:dark:border-yellow-700 scroll-mt-20" -}}
+
{{- $emptyStyle := "bg-gray-200/30 dark:bg-gray-700/30" -}}
+
{{- $addStyle := "bg-green-100 dark:bg-green-800/30 text-green-700 dark:text-green-400" -}}
+
{{- $delStyle := "bg-red-100 dark:bg-red-800/30 text-red-700 dark:text-red-400 " -}}
+
{{- $ctxStyle := "bg-white dark:bg-gray-800 text-gray-500 dark:text-gray-400" -}}
+
{{- $opStyle := "w-5 flex-shrink-0 select-none text-center" -}}
+
<div class="grid grid-cols-2 divide-x divide-gray-200 dark:divide-gray-700">
+
<pre class="overflow-x-auto col-span-1"><div class="overflow-x-auto"><div class="min-w-full inline-block">{{- range .TextFragments -}}<div class="bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400 select-none text-center">&middot;&middot;&middot;</div>
+
{{- range .LeftLines -}}
+
{{- if .IsEmpty -}}
+
<div class="{{ $emptyStyle }} {{ $containerStyle }}">
+
<div class="{{$lineNrStyle}} {{$lineNrSepStyle}}"><span aria-hidden="true" class="invisible">{{.LineNumber}}</span></div>
+
<div class="{{ $opStyle }}"><span aria-hidden="true" class="invisible">{{ .Op.String }}</span></div>
+
<div class="px-2 invisible" aria-hidden="true">{{ .Content }}</div>
+
</div>
+
{{- else if eq .Op.String "-" -}}
+
<div class="{{ $delStyle }} {{ $containerStyle }}" id="{{$name}}-O{{.LineNumber}}">
+
<div class="{{ $lineNrStyle }} {{ $lineNrSepStyle }}"><a class="{{$linkStyle}}" href="#{{$name}}-O{{.LineNumber}}">{{ .LineNumber }}</a></div>
+
<div class="{{ $opStyle }}">{{ .Op.String }}</div>
+
<div class="px-2">{{ .Content }}</div>
+
</div>
+
{{- else if eq .Op.String " " -}}
+
<div class="{{ $ctxStyle }} {{ $containerStyle }}" id="{{$name}}-O{{.LineNumber}}">
+
<div class="{{ $lineNrStyle }} {{ $lineNrSepStyle }}"><a class="{{$linkStyle}}" href="#{{$name}}-O{{.LineNumber}}">{{ .LineNumber }}</a></div>
+
<div class="{{ $opStyle }}">{{ .Op.String }}</div>
+
<div class="px-2">{{ .Content }}</div>
+
</div>
+
{{- end -}}
+
{{- end -}}
+
{{- end -}}</div></div></pre>
+
+
<pre class="overflow-x-auto col-span-1"><div class="overflow-x-auto"><div class="min-w-full inline-block">{{- range .TextFragments -}}<div class="bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400 select-none text-center">&middot;&middot;&middot;</div>
+
{{- range .RightLines -}}
+
{{- if .IsEmpty -}}
+
<div class="{{ $emptyStyle }} {{ $containerStyle }}">
+
<div class="{{$lineNrStyle}} {{$lineNrSepStyle}}"><span aria-hidden="true" class="invisible">{{.LineNumber}}</span></div>
+
<div class="{{ $opStyle }}"><span aria-hidden="true" class="invisible">{{ .Op.String }}</span></div>
+
<div class="px-2 invisible" aria-hidden="true">{{ .Content }}</div>
+
</div>
+
{{- else if eq .Op.String "+" -}}
+
<div class="{{ $addStyle }} {{ $containerStyle }}" id="{{$name}}-N{{.LineNumber}}">
+
<div class="{{$lineNrStyle}} {{$lineNrSepStyle}}"><a class="{{$linkStyle}}" href="#{{$name}}-N{{.LineNumber}}">{{ .LineNumber }}</a></div>
+
<div class="{{ $opStyle }}">{{ .Op.String }}</div>
+
<div class="px-2" >{{ .Content }}</div>
+
</div>
+
{{- else if eq .Op.String " " -}}
+
<div class="{{ $ctxStyle }} {{ $containerStyle }}" id="{{$name}}-N{{.LineNumber}}">
+
<div class="{{$lineNrStyle}} {{$lineNrSepStyle}}"><a class="{{$linkStyle}}" href="#{{$name}}-N{{.LineNumber}}">{{ .LineNumber }}</a></div>
+
<div class="{{ $opStyle }}">{{ .Op.String }}</div>
+
<div class="px-2">{{ .Content }}</div>
+
</div>
+
{{- end -}}
+
{{- end -}}
+
{{- end -}}</div></div></pre>
+
</div>
+
{{ end }}
+
{{ define "statPill" }}
<div class="flex items-center font-mono text-sm">
{{ if and .Insertions .Deletions }}
···
{{ end }}
</div>
{{ end }}
+
+1 -1
appview/pages/templates/repo/fragments/diffChangedFiles.html
···
{{ $stat := .Stat }}
{{ $fileTree := fileTree .ChangedFiles }}
<div class="col-span-1 md:col-span-2 mt-4">
-
<section class="sticky top-0 overflow-x-auto px-6 py-4 border border-gray-200 dark:border-gray-700 w-full mx-auto rounded bg-white dark:bg-gray-800 drop-shadow-sm">
<div class="diff-stat">
<div class="flex gap-2 items-center">
<strong class="text-sm uppercase dark:text-gray-200">Changed files</strong>
···
{{ $stat := .Stat }}
{{ $fileTree := fileTree .ChangedFiles }}
<div class="col-span-1 md:col-span-2 mt-4">
+
<section class="sticky top-0 overflow-x-auto text-sm px-6 py-2 border border-gray-200 dark:border-gray-700 w-full mx-auto md:min-h-screen rounded bg-white dark:bg-gray-800 drop-shadow-sm">
<div class="diff-stat">
<div class="flex gap-2 items-center">
<strong class="text-sm uppercase dark:text-gray-200">Changed files</strong>
+1 -1
appview/pages/templates/repo/fragments/interdiffFiles.html
···
{{ define "repo/fragments/interdiffFiles" }}
{{ $fileTree := fileTree .AffectedFiles }}
-
<section class="mt-4 p-6 border border-gray-200 dark:border-gray-700 w-full mx-auto rounded bg-white dark:bg-gray-800 drop-shadow-sm">
<div class="diff-stat">
<div class="flex gap-2 items-center">
<strong class="text-sm uppercase dark:text-gray-200">files</strong>
···
{{ define "repo/fragments/interdiffFiles" }}
{{ $fileTree := fileTree .AffectedFiles }}
+
<section class="mt-4 px-6 py-2 border border-gray-200 dark:border-gray-700 w-full mx-auto rounded bg-white dark:bg-gray-800 drop-shadow-sm md:min-h-screen text-sm">
<div class="diff-stat">
<div class="flex gap-2 items-center">
<strong class="text-sm uppercase dark:text-gray-200">files</strong>
+1 -1
appview/pages/templates/repo/pulls/patch.html
···
{{ end }}
{{ define "contentAfter" }}
-
{{ template "repo/fragments/diff" (list .RepoInfo.FullName .Diff) }}
{{end}}
{{ define "contentAfterLeft" }}
···
{{ end }}
{{ define "contentAfter" }}
+
{{ template "repo/fragments/diff" (list .RepoInfo.FullName .Diff .DiffOpts) }}
{{end}}
{{ define "contentAfterLeft" }}
-1
appview/pages/templates/repo/pulls/pull.html
···
{{ end }}
</div>
</details>
-
<hr class="md:hidden border-t border-gray-300 dark:border-gray-600"/>
{{ end }}
{{ end }}
{{ end }}
···
{{ end }}
</div>
</details>
{{ end }}
{{ end }}
{{ end }}
+6
appview/pulls/pulls.go
···
return
}
pull, ok := r.Context().Value("pull").(*db.Pull)
if !ok {
log.Println("failed to get pull")
···
Round: roundIdInt,
Submission: pull.Submissions[roundIdInt],
Diff: &diff,
})
}
···
return
}
+
var diffOpts types.DiffOpts
+
if d := r.URL.Query().Get("diff"); d == "split" {
+
diffOpts.Split = true
+
}
+
pull, ok := r.Context().Value("pull").(*db.Pull)
if !ok {
log.Println("failed to get pull")
···
Round: roundIdInt,
Submission: pull.Submissions[roundIdInt],
Diff: &diff,
+
DiffOpts: diffOpts,
})
}
+12 -1
appview/repo/repo.go
···
protocol = "https"
}
if !plumbing.IsHash(ref) {
rp.pages.Error404(w)
return
···
EmailToDidOrHandle: emailToDidOrHandle(rp, emailToDidMap),
VerifiedCommit: vc,
Pipeline: pipeline,
})
-
return
}
func (rp *Repo) RepoTree(w http.ResponseWriter, r *http.Request) {
···
return
}
// if user is navigating to one of
// /compare/{base}/{head}
// /compare/{base}...{head}
···
Base: base,
Head: head,
Diff: &diff,
})
}
···
protocol = "https"
}
+
var diffOpts types.DiffOpts
+
if d := r.URL.Query().Get("diff"); d == "split" {
+
diffOpts.Split = true
+
}
+
if !plumbing.IsHash(ref) {
rp.pages.Error404(w)
return
···
EmailToDidOrHandle: emailToDidOrHandle(rp, emailToDidMap),
VerifiedCommit: vc,
Pipeline: pipeline,
+
DiffOpts: diffOpts,
})
}
func (rp *Repo) RepoTree(w http.ResponseWriter, r *http.Request) {
···
return
}
+
var diffOpts types.DiffOpts
+
if d := r.URL.Query().Get("diff"); d == "split" {
+
diffOpts.Split = true
+
}
+
// if user is navigating to one of
// /compare/{base}/{head}
// /compare/{base}...{head}
···
Base: base,
Head: head,
Diff: &diff,
+
DiffOpts: diffOpts,
})
}
+26
types/diff.go
···
"github.com/go-git/go-git/v5/plumbing/object"
)
type TextFragment struct {
Header string `json:"comment"`
Lines []gitdiff.Line `json:"lines"`
···
return files
}
···
"github.com/go-git/go-git/v5/plumbing/object"
)
+
type DiffOpts struct {
+
Split bool `json:"split"`
+
}
+
type TextFragment struct {
Header string `json:"comment"`
Lines []gitdiff.Line `json:"lines"`
···
return files
}
+
+
// used by html elements as a unique ID for hrefs
+
func (d *Diff) Id() string {
+
return d.Name.New
+
}
+
+
func (d *Diff) Split() *SplitDiff {
+
fragments := make([]SplitFragment, len(d.TextFragments))
+
for i, fragment := range d.TextFragments {
+
leftLines, rightLines := SeparateLines(&fragment)
+
fragments[i] = SplitFragment{
+
Header: fragment.Header(),
+
LeftLines: leftLines,
+
RightLines: rightLines,
+
}
+
}
+
+
return &SplitDiff{
+
Name: d.Id(),
+
TextFragments: fragments,
+
}
+
}