summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMario Lubenka <mario.lubenka@googlemail.com>2019-09-16 11:03:22 +0200
committerLunny Xiao <xiaolunwen@gmail.com>2019-09-16 17:03:22 +0800
commita37236314c88ea7990d4d7b1beeef74dd0aa112b (patch)
tree185fdc69c8a170a94510982a3310efa50b1857e7
parenta5f87feefd0622115611873da276d4725b6379bb (diff)
downloadgitea-a37236314c88ea7990d4d7b1beeef74dd0aa112b.tar.gz
gitea-a37236314c88ea7990d4d7b1beeef74dd0aa112b.zip
Adds side-by-side diff for images (#6784)
* 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>
-rw-r--r--modules/git/commit.go49
-rw-r--r--options/locale/locale_en-US.ini5
-rw-r--r--public/css/index.css11
-rw-r--r--public/less/_base.less39
-rw-r--r--public/less/_repository.less4
-rw-r--r--routers/repo/commit.go20
-rw-r--r--routers/repo/compare.go54
-rw-r--r--routers/repo/pull.go27
-rw-r--r--templates/repo/diff/box.tmpl22
-rw-r--r--templates/repo/diff/image_diff.tmpl46
10 files changed, 262 insertions, 15 deletions
diff --git a/modules/git/commit.go b/modules/git/commit.go
index 2f7f53e7ce..83e03c2795 100644
--- a/modules/git/commit.go
+++ b/modules/git/commit.go
@@ -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 {
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 45e71e0595..8a1dc95b9f 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -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
diff --git a/public/css/index.css b/public/css/index.css
index f58e56bf0a..113fdbf631 100644
--- a/public/css/index.css
+++ b/public/css/index.css
@@ -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}
diff --git a/public/less/_base.less b/public/less/_base.less
index 487779c137..e295be368d 100644
--- a/public/less/_base.less
+++ b/public/less/_base.less
@@ -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;
}
diff --git a/public/less/_repository.less b/public/less/_repository.less
index 8eb4a45bd3..74ca683c4e 100644
--- a/public/less/_repository.less
+++ b/public/less/_repository.less
@@ -1364,6 +1364,10 @@
width: 49%;
}
+ td.center {
+ text-align: center;
+ }
+
.removed-code {
background-color: #ff9999;
}
diff --git a/routers/repo/commit.go b/routers/repo/commit.go
index c3181cbe46..919ebabf42 100644
--- a/routers/repo/commit.go
+++ b/routers/repo/commit.go
@@ -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)
diff --git a/routers/repo/compare.go b/routers/repo/compare.go
index 4f9a918a7c..7c55dfca30 100644
--- a/routers/repo/compare.go
+++ b/routers/repo/compare.go
@@ -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
}
diff --git a/routers/repo/pull.go b/routers/repo/pull.go
index 14d2f50821..14b8670a20 100644
--- a/routers/repo/pull.go
+++ b/routers/repo/pull.go
@@ -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 {
diff --git a/templates/repo/diff/box.tmpl b/templates/repo/diff/box.tmpl
index 6c1ae2e447..edc04f9068 100644
--- a/templates/repo/diff/box.tmpl
+++ b/templates/repo/diff/box.tmpl
@@ -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>
diff --git a/templates/repo/diff/image_diff.tmpl b/templates/repo/diff/image_diff.tmpl
new file mode 100644
index 0000000000..8fa7f6b872
--- /dev/null
+++ b/templates/repo/diff/image_diff.tmpl
@@ -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>
+ &nbsp;|&nbsp;
+ {{.root.i18n.Tr "repo.diff.file_image_height"}}: <span class="text {{if not (eq $imageInfoBase.Height $imageInfoHead.Height)}}red{{end}}">{{$imageInfoBase.Height}}</span>
+ &nbsp;|&nbsp;
+ {{.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>
+ &nbsp;|&nbsp;
+ {{.root.i18n.Tr "repo.diff.file_image_height"}}: <span class="text {{if not (eq $imageInfoBase.Height $imageInfoHead.Height)}}green{{end}}">{{$imageInfoHead.Height}}</span>
+ &nbsp;|&nbsp;
+ {{.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}}