* Adds side-by-side diff for images Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Explain blank imports Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Use complete word for width and height labels on image compare Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Update index.css from master Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Moves ImageInfo to git commit file Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Assign ImageInfo function for template and sets correct target for BeforeSourcePath Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Adds missing comment Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Return error if ImageInfo failed Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Avoid template panic when ImageInfo failed for some reason Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Show file size on image diff Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Removes unused helper function Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Reverts copyright year change Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Close file reader Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Update commit.go Sets correct data key * Moves reader.Close() up a few lines * Updates index.css * Updates CSS file Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Transfers adjustments for image compare to compare.go file Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Adjusts variable name Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Apply lesshint recommendations Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Do not show old image on image compare if it is not in index of base commit Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Change file size text Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com>tags/v1.21.12.1
| @@ -10,6 +10,11 @@ import ( | |||
| "bytes" | |||
| "container/list" | |||
| "fmt" | |||
| "image" | |||
| "image/color" | |||
| _ "image/gif" // for processing gif images | |||
| _ "image/jpeg" // for processing jpeg images | |||
| _ "image/png" // for processing png images | |||
| "io" | |||
| "net/http" | |||
| "strconv" | |||
| @@ -158,6 +163,43 @@ func (c *Commit) IsImageFile(name string) bool { | |||
| return isImage | |||
| } | |||
| // ImageMetaData represents metadata of an image file | |||
| type ImageMetaData struct { | |||
| ColorModel color.Model | |||
| Width int | |||
| Height int | |||
| ByteSize int64 | |||
| } | |||
| // ImageInfo returns information about the dimensions of an image | |||
| func (c *Commit) ImageInfo(name string) (*ImageMetaData, error) { | |||
| if !c.IsImageFile(name) { | |||
| return nil, nil | |||
| } | |||
| blob, err := c.GetBlobByPath(name) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| reader, err := blob.DataAsync() | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| defer reader.Close() | |||
| config, _, err := image.DecodeConfig(reader) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| metadata := ImageMetaData{ | |||
| ColorModel: config.ColorModel, | |||
| Width: config.Width, | |||
| Height: config.Height, | |||
| ByteSize: blob.Size(), | |||
| } | |||
| return &metadata, nil | |||
| } | |||
| // GetCommitByPath return the commit of relative path object. | |||
| func (c *Commit) GetCommitByPath(relpath string) (*Commit, error) { | |||
| return c.repo.getCommitByPathWithID(c.ID, relpath) | |||
| @@ -310,6 +352,13 @@ func (c *Commit) FileChangedSinceCommit(filename, pastCommit string) (bool, erro | |||
| return c.repo.FileChangedBetweenCommits(filename, pastCommit, c.ID.String()) | |||
| } | |||
| // HasFile returns true if the file given exists on this commit | |||
| // This does only mean it's there - it does not mean the file was changed during the commit. | |||
| func (c *Commit) HasFile(filename string) (bool, error) { | |||
| result, err := c.repo.LsFiles(filename) | |||
| return result[0] == filename, err | |||
| } | |||
| // GetSubModules get all the sub modules of current revision git tree | |||
| func (c *Commit) GetSubModules() (*ObjectCache, error) { | |||
| if c.submoduleCache != nil { | |||
| @@ -1358,6 +1358,11 @@ diff.whitespace_ignore_at_eol = Ignore changes in whitespace at EOL | |||
| diff.stats_desc = <strong> %d changed files</strong> with <strong>%d additions</strong> and <strong>%d deletions</strong> | |||
| diff.bin = BIN | |||
| diff.view_file = View File | |||
| diff.file_before = Before | |||
| diff.file_after = After | |||
| diff.file_image_width = Width | |||
| diff.file_image_height = Height | |||
| diff.file_byte_size = Size | |||
| diff.file_suppressed = File diff suppressed because it is too large | |||
| diff.too_many_files = Some files were not shown because too many files changed in this diff | |||
| diff.comment.placeholder = Leave a comment | |||
| @@ -140,6 +140,16 @@ a{cursor:pointer} | |||
| .ui .migrate{color:#888!important;opacity:.5} | |||
| .ui .migrate a{color:#444!important} | |||
| .ui .migrate a:hover{color:#000!important} | |||
| .ui .border{border:1px solid} | |||
| .ui .border.red{border-color:#d95c5c!important} | |||
| .ui .border.blue{border-color:#428bca!important} | |||
| .ui .border.black{border-color:#444} | |||
| .ui .border.grey{border-color:#767676!important} | |||
| .ui .border.light.grey{border-color:#888!important} | |||
| .ui .border.green{border-color:#6cc644!important} | |||
| .ui .border.purple{border-color:#6e5494!important} | |||
| .ui .border.yellow{border-color:#fbbd08!important} | |||
| .ui .border.gold{border-color:#a1882b!important} | |||
| .ui .branch-tag-choice{line-height:20px} | |||
| @media only screen and (max-width:767px){.ui.pagination.menu .item.navigation span.navigation_label,.ui.pagination.menu .item:not(.active):not(.navigation){display:none} | |||
| } | |||
| @@ -670,6 +680,7 @@ footer .ui.left,footer .ui.right{line-height:40px} | |||
| .repository .diff-file-box .code-diff td{padding:0 0 0 10px!important;border-top:0} | |||
| .repository .diff-file-box .code-diff .lines-num{border-color:#d4d4d5;border-right-width:1px;border-right-style:solid;padding:0 5px!important} | |||
| .repository .diff-file-box .code-diff tbody tr td.halfwidth{width:49%} | |||
| .repository .diff-file-box .code-diff tbody tr td.center{text-align:center} | |||
| .repository .diff-file-box .code-diff tbody tr .removed-code{background-color:#f99} | |||
| .repository .diff-file-box .code-diff tbody tr .added-code{background-color:#9f9} | |||
| .repository .diff-file-box .code-diff tbody tr [data-line-num]::before{content:attr(data-line-num);text-align:right} | |||
| @@ -600,6 +600,45 @@ code, | |||
| } | |||
| } | |||
| .border { | |||
| border: 1px solid; | |||
| &.red { | |||
| border-color: #d95c5c !important; | |||
| } | |||
| &.blue { | |||
| border-color: #428bca !important; | |||
| } | |||
| &.black { | |||
| border-color: #444444; | |||
| } | |||
| &.grey { | |||
| border-color: #767676 !important; | |||
| } | |||
| &.light.grey { | |||
| border-color: #888888 !important; | |||
| } | |||
| &.green { | |||
| border-color: #6cc644 !important; | |||
| } | |||
| &.purple { | |||
| border-color: #6e5494 !important; | |||
| } | |||
| &.yellow { | |||
| border-color: #fbbd08 !important; | |||
| } | |||
| &.gold { | |||
| border-color: #a1882b !important; | |||
| } | |||
| } | |||
| .branch-tag-choice { | |||
| line-height: 20px; | |||
| } | |||
| @@ -1364,6 +1364,10 @@ | |||
| width: 49%; | |||
| } | |||
| td.center { | |||
| text-align: center; | |||
| } | |||
| .removed-code { | |||
| background-color: #ff9999; | |||
| } | |||
| @@ -240,6 +240,23 @@ func Diff(ctx *context.Context) { | |||
| ctx.Data["Username"] = userName | |||
| ctx.Data["Reponame"] = repoName | |||
| ctx.Data["IsImageFile"] = commit.IsImageFile | |||
| ctx.Data["ImageInfo"] = func(name string) *git.ImageMetaData { | |||
| result, err := commit.ImageInfo(name) | |||
| if err != nil { | |||
| log.Error("ImageInfo failed: %v", err) | |||
| return nil | |||
| } | |||
| return result | |||
| } | |||
| ctx.Data["ImageInfoBase"] = ctx.Data["ImageInfo"] | |||
| if commit.ParentCount() > 0 { | |||
| parentCommit, err := ctx.Repo.GitRepo.GetCommit(parents[0]) | |||
| if err != nil { | |||
| ctx.NotFound("GetParentCommit", err) | |||
| return | |||
| } | |||
| ctx.Data["ImageInfo"] = parentCommit.ImageInfo | |||
| } | |||
| ctx.Data["Title"] = commit.Summary() + " · " + base.ShortSha(commitID) | |||
| ctx.Data["Commit"] = commit | |||
| ctx.Data["Verification"] = models.ParseCommitWithSignature(commit) | |||
| @@ -248,6 +265,7 @@ func Diff(ctx *context.Context) { | |||
| ctx.Data["Parents"] = parents | |||
| ctx.Data["DiffNotAvailable"] = diff.NumFiles() == 0 | |||
| ctx.Data["SourcePath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "src", "commit", commitID) | |||
| ctx.Data["RawPath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "raw", "commit", commitID) | |||
| note := &git.Note{} | |||
| err = git.GetNote(ctx.Repo.GitRepo, commitID, note) | |||
| @@ -259,8 +277,8 @@ func Diff(ctx *context.Context) { | |||
| if commit.ParentCount() > 0 { | |||
| ctx.Data["BeforeSourcePath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "src", "commit", parents[0]) | |||
| ctx.Data["BeforeRawPath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "raw", "commit", parents[0]) | |||
| } | |||
| ctx.Data["RawPath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "raw", "commit", commitID) | |||
| ctx.Data["BranchName"], err = commit.GetBranchName() | |||
| if err != nil { | |||
| ctx.ServerError("commit.GetBranchName", err) | |||
| @@ -247,6 +247,26 @@ func PrepareCompareDiff( | |||
| return false | |||
| } | |||
| baseGitRepo := ctx.Repo.GitRepo | |||
| baseCommitID := baseBranch | |||
| if ctx.Data["BaseIsCommit"] == false { | |||
| if ctx.Data["BaseIsTag"] == true { | |||
| baseCommitID, err = baseGitRepo.GetTagCommitID(baseBranch) | |||
| } else { | |||
| baseCommitID, err = baseGitRepo.GetBranchCommitID(baseBranch) | |||
| } | |||
| if err != nil { | |||
| ctx.ServerError("GetRefCommitID", err) | |||
| return false | |||
| } | |||
| } | |||
| baseCommit, err := baseGitRepo.GetCommit(baseCommitID) | |||
| if err != nil { | |||
| ctx.ServerError("GetCommit", err) | |||
| return false | |||
| } | |||
| compareInfo.Commits = models.ValidateCommitsWithEmails(compareInfo.Commits) | |||
| compareInfo.Commits = models.ParseCommitsWithSignature(compareInfo.Commits) | |||
| compareInfo.Commits = models.ParseCommitsWithStatus(compareInfo.Commits, headRepo) | |||
| @@ -272,11 +292,43 @@ func PrepareCompareDiff( | |||
| ctx.Data["Username"] = headUser.Name | |||
| ctx.Data["Reponame"] = headRepo.Name | |||
| ctx.Data["IsImageFile"] = headCommit.IsImageFile | |||
| ctx.Data["ImageInfo"] = func(name string) *git.ImageMetaData { | |||
| result, err := headCommit.ImageInfo(name) | |||
| if err != nil { | |||
| log.Error("ImageInfo failed: %v", err) | |||
| return nil | |||
| } | |||
| return result | |||
| } | |||
| ctx.Data["FileExistsInBaseCommit"] = func(filename string) bool { | |||
| result, err := baseCommit.HasFile(filename) | |||
| if err != nil { | |||
| log.Error( | |||
| "Error while checking if file \"%s\" exists in base commit \"%s\" (repo: %s): %v", | |||
| filename, | |||
| baseCommit, | |||
| baseGitRepo.Path, | |||
| err) | |||
| return false | |||
| } | |||
| return result | |||
| } | |||
| ctx.Data["ImageInfoBase"] = func(name string) *git.ImageMetaData { | |||
| result, err := baseCommit.ImageInfo(name) | |||
| if err != nil { | |||
| log.Error("ImageInfo failed: %v", err) | |||
| return nil | |||
| } | |||
| return result | |||
| } | |||
| headTarget := path.Join(headUser.Name, repo.Name) | |||
| baseTarget := path.Join(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name) | |||
| ctx.Data["SourcePath"] = setting.AppSubURL + "/" + path.Join(headTarget, "src", "commit", headCommitID) | |||
| ctx.Data["BeforeSourcePath"] = setting.AppSubURL + "/" + path.Join(headTarget, "src", "commit", compareInfo.MergeBase) | |||
| ctx.Data["RawPath"] = setting.AppSubURL + "/" + path.Join(headTarget, "raw", "commit", headCommitID) | |||
| ctx.Data["BeforeSourcePath"] = setting.AppSubURL + "/" + path.Join(baseTarget, "src", "commit", baseCommitID) | |||
| ctx.Data["BeforeRawPath"] = setting.AppSubURL + "/" + path.Join(baseTarget, "raw", "commit", baseCommitID) | |||
| return false | |||
| } | |||
| @@ -535,6 +535,11 @@ func ViewPullFiles(ctx *context.Context) { | |||
| ctx.Data["Diff"] = diff | |||
| ctx.Data["DiffNotAvailable"] = diff.NumFiles() == 0 | |||
| baseCommit, err := ctx.Repo.GitRepo.GetCommit(startCommitID) | |||
| if err != nil { | |||
| ctx.ServerError("GetCommit", err) | |||
| return | |||
| } | |||
| commit, err := gitRepo.GetCommit(endCommitID) | |||
| if err != nil { | |||
| ctx.ServerError("GetCommit", err) | |||
| @@ -542,9 +547,29 @@ func ViewPullFiles(ctx *context.Context) { | |||
| } | |||
| ctx.Data["IsImageFile"] = commit.IsImageFile | |||
| ctx.Data["ImageInfoBase"] = func(name string) *git.ImageMetaData { | |||
| result, err := baseCommit.ImageInfo(name) | |||
| if err != nil { | |||
| log.Error("ImageInfo failed: %v", err) | |||
| return nil | |||
| } | |||
| return result | |||
| } | |||
| ctx.Data["ImageInfo"] = func(name string) *git.ImageMetaData { | |||
| result, err := commit.ImageInfo(name) | |||
| if err != nil { | |||
| log.Error("ImageInfo failed: %v", err) | |||
| return nil | |||
| } | |||
| return result | |||
| } | |||
| baseTarget := path.Join(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name) | |||
| ctx.Data["SourcePath"] = setting.AppSubURL + "/" + path.Join(headTarget, "src", "commit", endCommitID) | |||
| ctx.Data["BeforeSourcePath"] = setting.AppSubURL + "/" + path.Join(headTarget, "src", "commit", startCommitID) | |||
| ctx.Data["RawPath"] = setting.AppSubURL + "/" + path.Join(headTarget, "raw", "commit", endCommitID) | |||
| ctx.Data["BeforeSourcePath"] = setting.AppSubURL + "/" + path.Join(baseTarget, "src", "commit", startCommitID) | |||
| ctx.Data["BeforeRawPath"] = setting.AppSubURL + "/" + path.Join(baseTarget, "raw", "commit", startCommitID) | |||
| ctx.Data["RequireHighlightJS"] = true | |||
| ctx.Data["RequireTribute"] = true | |||
| if ctx.Data["Assignees"], err = ctx.Repo.Repository.GetAssignees(); err != nil { | |||
| @@ -107,14 +107,12 @@ | |||
| <div class="ui attached unstackable table segment"> | |||
| {{if ne $file.Type 4}} | |||
| {{$isImage := (call $.IsImageFile $file.Name)}} | |||
| {{if and $isImage}} | |||
| <div class="center"> | |||
| <img src="{{$.RawPath}}/{{EscapePound .Name}}"> | |||
| </div> | |||
| {{else}} | |||
| <div class="file-body file-code code-view code-diff {{if $.IsSplitStyle}}code-diff-split{{else}}code-diff-unified{{end}}"> | |||
| <table> | |||
| <tbody> | |||
| <div class="file-body file-code code-view code-diff {{if $.IsSplitStyle}}code-diff-split{{else}}code-diff-unified{{end}}"> | |||
| <table> | |||
| <tbody> | |||
| {{if $isImage}} | |||
| {{template "repo/diff/image_diff" dict "file" . "root" $}} | |||
| {{else}} | |||
| {{if $.IsSplitStyle}} | |||
| {{$highlightClass := $file.GetHighlightClass}} | |||
| {{range $j, $section := $file.Sections}} | |||
| @@ -164,10 +162,10 @@ | |||
| {{else}} | |||
| {{template "repo/diff/section_unified" dict "file" . "root" $}} | |||
| {{end}} | |||
| </tbody> | |||
| </table> | |||
| </div> | |||
| {{end}} | |||
| {{end}} | |||
| </tbody> | |||
| </table> | |||
| </div> | |||
| {{end}} | |||
| </div> | |||
| </div> | |||
| @@ -0,0 +1,46 @@ | |||
| {{ $imagePathOld := printf "%s/%s" .root.BeforeRawPath (EscapePound .file.OldName) }} | |||
| {{ $imagePathNew := printf "%s/%s" .root.RawPath (EscapePound .file.Name) }} | |||
| <tr> | |||
| <th class="halfwidth center"> | |||
| {{.root.i18n.Tr "repo.diff.file_before"}} | |||
| </th> | |||
| <th class="halfwidth center"> | |||
| {{.root.i18n.Tr "repo.diff.file_after"}} | |||
| </th> | |||
| </tr> | |||
| <tr> | |||
| <td class="halfwidth center"> | |||
| {{ $oldImageExists := (call .root.FileExistsInBaseCommit .file.OldName) }} | |||
| {{if $oldImageExists}} | |||
| <a href="{{$imagePathOld}}" target="_blank"> | |||
| <img src="{{$imagePathOld}}" class="border red" /> | |||
| </a> | |||
| {{end}} | |||
| </td> | |||
| <td class="halfwidth center"> | |||
| <a href="{{$imagePathNew}}" target="_blank"> | |||
| <img src="{{$imagePathNew}}" class="border green" /> | |||
| </a> | |||
| </td> | |||
| </tr> | |||
| {{ $imageInfoBase := (call .root.ImageInfoBase .file.OldName) }} | |||
| {{ $imageInfoHead := (call .root.ImageInfo .file.Name) }} | |||
| {{if and $imageInfoBase $imageInfoHead }} | |||
| <tr> | |||
| <td class="halfwidth center"> | |||
| {{.root.i18n.Tr "repo.diff.file_image_width"}}: <span class="text {{if not (eq $imageInfoBase.Width $imageInfoHead.Width)}}red{{end}}">{{$imageInfoBase.Width}}</span> | |||
| | | |||
| {{.root.i18n.Tr "repo.diff.file_image_height"}}: <span class="text {{if not (eq $imageInfoBase.Height $imageInfoHead.Height)}}red{{end}}">{{$imageInfoBase.Height}}</span> | |||
| | | |||
| {{.root.i18n.Tr "repo.diff.file_byte_size"}}: <span class="text {{if not (eq $imageInfoBase.ByteSize $imageInfoHead.ByteSize)}}red{{end}}">{{FileSize $imageInfoBase.ByteSize}}</span> | |||
| </td> | |||
| <td class="halfwidth center"> | |||
| {{.root.i18n.Tr "repo.diff.file_image_width"}}: <span class="text {{if not (eq $imageInfoBase.Width $imageInfoHead.Width)}}green{{end}}">{{$imageInfoHead.Width}}</span> | |||
| | | |||
| {{.root.i18n.Tr "repo.diff.file_image_height"}}: <span class="text {{if not (eq $imageInfoBase.Height $imageInfoHead.Height)}}green{{end}}">{{$imageInfoHead.Height}}</span> | |||
| | | |||
| {{.root.i18n.Tr "repo.diff.file_byte_size"}}: <span class="text {{if not (eq $imageInfoBase.ByteSize $imageInfoHead.ByteSize)}}green{{end}}">{{FileSize $imageInfoHead.ByteSize}}</span> | |||
| </td> | |||
| </tr> | |||
| {{end}} | |||