Improve support for binary file detection in diff views

https://bugzilla.gnome.org/show_bug.cgi?id=768650
This commit is contained in:
Jesse van den Kieboom 2016-08-16 17:59:27 +02:00
parent f15433ff1a
commit 411c5743ce
11 changed files with 407 additions and 91 deletions

View file

@ -132,7 +132,7 @@ GLIB_REQUIRED_VERSION=2.38
GTK_REQUIRED_VERSION=3.20.0
GTKSOURCEVIEW_REQUIRED_VERSION=3.10
INTROSPECTION_REQUIRED=0.10.1
LIBGIT2_GLIB_REQUIRED_VERSION=0.24.0
LIBGIT2_GLIB_REQUIRED_VERSION=0.24.1
LIBGIT2_GLIB_REQUIRED_MAX_VERSION=0.25.0
LIBXML_REQUIRED_VERSION=2.9.0

View file

@ -52,46 +52,48 @@ libgitg_libgitg_1_0_la_VALAFLAGS = \
--header libgitg/libgitg.h \
--gresources "$(top_srcdir)/libgitg/resources/resources.xml"
libgitg_libgitg_1_0_la_VALASOURCES = \
libgitg/gitg-assembly-info.vala \
libgitg/gitg-async.vala \
libgitg/gitg-authentication-dialog.vala \
libgitg/gitg-avatar-cache.vala \
libgitg/gitg-branch-base.vala \
libgitg/gitg-branch.vala \
libgitg/gitg-cell-renderer-lanes.vala \
libgitg/gitg-color.vala \
libgitg/gitg-commit-list-view.vala \
libgitg/gitg-commit-model.vala \
libgitg/gitg-commit.vala \
libgitg/gitg-credentials-manager.vala \
libgitg/gitg-date.vala \
libgitg/gitg-diff-stat.vala \
libgitg/gitg-diff-view.vala \
libgitg/gitg-diff-view-file.vala \
libgitg/gitg-diff-view-file-selectable.vala \
libgitg/gitg-diff-view-file-renderer.vala \
libgitg/gitg-diff-view-file-renderer-text.vala \
libgitg/gitg-diff-view-lines-renderer.vala \
libgitg/gitg-diff-selectable.vala \
libgitg/gitg-diff-view-commit-details.vala \
libgitg/gitg-diff-view-options.vala \
libgitg/gitg-hook.vala \
libgitg/gitg-init.vala \
libgitg/gitg-label-renderer.vala \
libgitg/gitg-lane.vala \
libgitg/gitg-lanes.vala \
libgitg/gitg-progress-bin.vala \
libgitg/gitg-ref-base.vala \
libgitg/gitg-ref.vala \
libgitg/gitg-remote.vala \
libgitg/gitg-repository-list-box.vala \
libgitg/gitg-repository.vala \
libgitg/gitg-resource.vala \
libgitg/gitg-sidebar.vala \
libgitg/gitg-stage-status-enumerator.vala \
libgitg/gitg-stage.vala \
libgitg/gitg-utils.vala \
libgitg_libgitg_1_0_la_VALASOURCES = \
libgitg/gitg-assembly-info.vala \
libgitg/gitg-async.vala \
libgitg/gitg-authentication-dialog.vala \
libgitg/gitg-avatar-cache.vala \
libgitg/gitg-branch-base.vala \
libgitg/gitg-branch.vala \
libgitg/gitg-cell-renderer-lanes.vala \
libgitg/gitg-color.vala \
libgitg/gitg-commit-list-view.vala \
libgitg/gitg-commit-model.vala \
libgitg/gitg-commit.vala \
libgitg/gitg-credentials-manager.vala \
libgitg/gitg-date.vala \
libgitg/gitg-diff-stat.vala \
libgitg/gitg-diff-view.vala \
libgitg/gitg-diff-view-file.vala \
libgitg/gitg-diff-view-file-info.vala \
libgitg/gitg-diff-view-file-selectable.vala \
libgitg/gitg-diff-view-file-renderer.vala \
libgitg/gitg-diff-view-file-renderer-binary.vala \
libgitg/gitg-diff-view-file-renderer-text.vala \
libgitg/gitg-diff-view-lines-renderer.vala \
libgitg/gitg-diff-selectable.vala \
libgitg/gitg-diff-view-commit-details.vala \
libgitg/gitg-diff-view-options.vala \
libgitg/gitg-hook.vala \
libgitg/gitg-init.vala \
libgitg/gitg-label-renderer.vala \
libgitg/gitg-lane.vala \
libgitg/gitg-lanes.vala \
libgitg/gitg-progress-bin.vala \
libgitg/gitg-ref-base.vala \
libgitg/gitg-ref.vala \
libgitg/gitg-remote.vala \
libgitg/gitg-repository-list-box.vala \
libgitg/gitg-repository.vala \
libgitg/gitg-resource.vala \
libgitg/gitg-sidebar.vala \
libgitg/gitg-stage-status-enumerator.vala \
libgitg/gitg-stage.vala \
libgitg/gitg-utils.vala \
libgitg/gitg-when-mapped.vala
libgitg_libgitg_1_0_la_SOURCES = \

View file

@ -0,0 +1,104 @@
class Gitg.DiffViewFileInfo : Object
{
public Ggit.DiffDelta delta { get; construct set; }
public bool from_workdir { get; construct set; }
public Repository repository { get; construct set; }
public InputStream? new_file_input_stream { get; set; }
public string? new_file_content_type { get; private set; }
public DiffViewFileInfo(Repository repository, Ggit.DiffDelta delta, bool from_workdir)
{
Object(repository: repository, delta: delta, from_workdir: from_workdir);
}
public async void query(Cancellable? cancellable)
{
yield query_content(cancellable);
}
private async void query_content(Cancellable? cancellable)
{
var file = delta.get_new_file();
var id = file.get_oid();
var path = file.get_path();
if ((id.is_zero() && !from_workdir) || (path == null && from_workdir))
{
return;
}
var location = repository.get_workdir().get_child(path);
if (!from_workdir)
{
Ggit.Blob blob;
try
{
blob = repository.lookup<Ggit.Blob>(id);
}
catch (Error e)
{
return;
}
var bytes = new Bytes(blob.get_raw_content());
new_file_input_stream = new GLib.MemoryInputStream.from_bytes(bytes);
}
else
{
// Try to read from disk
try
{
new_file_input_stream = yield location.read_async(Priority.DEFAULT, cancellable);
}
catch
{
return;
}
}
var seekable = new_file_input_stream as Seekable;
if (seekable != null && seekable.can_seek())
{
// Read a little bit of content to guess the type
yield guess_content_type(new_file_input_stream, location.get_basename(), cancellable);
try
{
seekable.seek(0, SeekType.SET, cancellable);
}
catch (Error e)
{
stderr.printf("Failed to seek back to beginning of stream...\n");
new_file_input_stream = null;
}
}
}
private async void guess_content_type(InputStream stream, string basename, Cancellable? cancellable)
{
var buffer = new uint8[4096];
size_t bytes_read = 0;
try
{
yield stream.read_all_async(buffer, Priority.DEFAULT, cancellable, out bytes_read);
}
catch (Error e)
{
if (bytes_read <= 0)
{
return;
}
}
buffer.length = (int)bytes_read;
bool uncertain;
new_file_content_type = GLib.ContentType.guess(basename, buffer, out uncertain);
}
}

View file

@ -0,0 +1,28 @@
/*
* This file is part of gitg
*
* Copyright (C) 2016 - Jesse van den Kieboom
*
* gitg is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* gitg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with gitg. If not, see <http://www.gnu.org/licenses/>.
*/
[GtkTemplate (ui = "/org/gnome/gitg/ui/gitg-diff-view-file-renderer-binary.ui")]
class Gitg.DiffViewFileRendererBinary : Gtk.Grid, DiffViewFileRenderer
{
public void add_hunk(Ggit.DiffHunk hunk, Gee.ArrayList<Ggit.DiffLine> lines)
{
}
}
// ex:ts=4 noet

View file

@ -83,8 +83,18 @@ class Gitg.DiffViewFileRendererText : Gtk.SourceView, DiffSelectable, DiffViewFi
}
public int maxlines { get; set; }
public Ggit.DiffDelta? delta { get; construct set; }
public Repository? repository { get; construct set; }
public DiffViewFileInfo info { get; construct set; }
public Ggit.DiffDelta? delta
{
get { return info.delta; }
}
public Repository? repository
{
get { return info.repository; }
}
public bool highlight
{
@ -92,8 +102,11 @@ class Gitg.DiffViewFileRendererText : Gtk.SourceView, DiffSelectable, DiffViewFi
construct set
{
d_highlight = value;
update_highlight();
if (d_highlight != value)
{
d_highlight = value;
update_highlight();
}
}
default = true;
@ -155,9 +168,9 @@ class Gitg.DiffViewFileRendererText : Gtk.SourceView, DiffSelectable, DiffViewFi
}
}
public DiffViewFileRendererText(Repository? repository, Ggit.DiffDelta delta, bool new_is_workdir, bool can_select)
public DiffViewFileRendererText(DiffViewFileInfo info, bool can_select)
{
Object(repository: repository, new_is_workdir: new_is_workdir, delta: delta, can_select: can_select);
Object(info: info, can_select: can_select);
}
construct
@ -234,28 +247,12 @@ class Gitg.DiffViewFileRendererText : Gtk.SourceView, DiffSelectable, DiffViewFi
var cancellable = new Cancellable();
d_higlight_cancellable = cancellable;
init_highlighting_buffer.begin(delta.get_old_file(), false, cancellable, (obj, res) => {
var buffer = init_highlighting_buffer.end(res);
if (!cancellable.is_cancelled())
{
d_old_highlight_buffer = buffer;
d_old_highlight_ready = true;
update_highlighting_ready();
}
init_highlighting_buffer_old.begin(cancellable, (obj, res) => {
init_highlighting_buffer_old.end(res);
});
init_highlighting_buffer.begin(delta.get_new_file(), new_is_workdir, cancellable, (obj, res) => {
var buffer = init_highlighting_buffer.end(res);
if (!cancellable.is_cancelled())
{
d_new_highlight_buffer = buffer;
d_new_highlight_ready = true;
update_highlighting_ready();
}
init_highlighting_buffer_new.begin(cancellable, (obj, res) => {
init_highlighting_buffer_new.end(res);
});
}
else
@ -264,20 +261,73 @@ class Gitg.DiffViewFileRendererText : Gtk.SourceView, DiffSelectable, DiffViewFi
}
}
private async Gtk.SourceBuffer? init_highlighting_buffer(Ggit.DiffFile file, bool from_workdir, Cancellable cancellable)
private async void init_highlighting_buffer_old(Cancellable cancellable)
{
var buffer = yield init_highlighting_buffer(delta.get_old_file(), false, cancellable);
if (!cancellable.is_cancelled())
{
d_old_highlight_buffer = buffer;
d_old_highlight_ready = true;
update_highlighting_ready();
}
}
private File? get_file_location(Ggit.DiffFile file)
{
var id = file.get_oid();
var path = file.get_path();
if ((id.is_zero() && !from_workdir) || (path == null && from_workdir))
if (path == null)
{
return null;
}
var sfile = new Gtk.SourceFile();
sfile.location = repository.get_workdir().get_child(path);
return repository.get_workdir().get_child(path);
}
private async void init_highlighting_buffer_new(Cancellable cancellable)
{
Gtk.SourceBuffer? buffer;
var file = delta.get_new_file();
if (info.new_file_input_stream != null)
{
// Use once
var stream = info.new_file_input_stream;
info.new_file_input_stream = null;
buffer = yield init_highlighting_buffer_from_stream(delta.get_new_file(),
get_file_location(file),
stream,
info.new_file_content_type,
cancellable);
}
else
{
buffer = yield init_highlighting_buffer(delta.get_new_file(), info.from_workdir, cancellable);
}
if (!cancellable.is_cancelled())
{
d_new_highlight_buffer = buffer;
d_new_highlight_ready = true;
update_highlighting_ready();
}
}
private async Gtk.SourceBuffer? init_highlighting_buffer(Ggit.DiffFile file, bool from_workdir, Cancellable cancellable)
{
var id = file.get_oid();
var location = get_file_location(file);
if ((id.is_zero() && !from_workdir) || (location == null && from_workdir))
{
return null;
}
var basename = sfile.location.get_basename();
uint8[] content;
if (!from_workdir)
@ -304,7 +354,7 @@ class Gitg.DiffViewFileRendererText : Gtk.SourceView, DiffSelectable, DiffViewFi
// Read it all into a buffer so we can guess the content type from
// it. This isn't really nice, but it's simple.
yield sfile.location.load_contents_async(cancellable, out content, out etag);
yield location.load_contents_async(cancellable, out content, out etag);
}
catch
{
@ -313,13 +363,17 @@ class Gitg.DiffViewFileRendererText : Gtk.SourceView, DiffSelectable, DiffViewFi
}
bool uncertain;
var content_type = GLib.ContentType.guess(basename, content, out uncertain);
var content_type = GLib.ContentType.guess(location.get_basename(), content, out uncertain);
var bytes = new Bytes(content);
var stream = new GLib.MemoryInputStream.from_bytes(bytes);
var stream = new GLib.MemoryInputStream.from_bytes(new Bytes(content));
return yield init_highlighting_buffer_from_stream(file, location, stream, content_type, cancellable);
}
private async Gtk.SourceBuffer? init_highlighting_buffer_from_stream(Ggit.DiffFile file, File location, InputStream stream, string content_type, Cancellable cancellable)
{
var manager = Gtk.SourceLanguageManager.get_default();
var language = manager.guess_language(basename, content_type);
var language = manager.guess_language(location.get_basename(), content_type);
if (language == null)
{
@ -334,6 +388,9 @@ class Gitg.DiffViewFileRendererText : Gtk.SourceView, DiffSelectable, DiffViewFi
buffer.highlight_syntax = true;
buffer.style_scheme = style_scheme_manager.get_scheme("classic");
var sfile = new Gtk.SourceFile();
sfile.location = location;
var loader = new Gtk.SourceFileLoader.from_stream(buffer, sfile, stream);
try

View file

@ -104,11 +104,11 @@ class Gitg.DiffViewFile : Gtk.Grid
Object(repository: repository, delta: delta);
}
public DiffViewFile.text(Repository? repository, Ggit.DiffDelta delta, bool new_is_workdir, bool handle_selection)
public DiffViewFile.text(DiffViewFileInfo info, bool handle_selection)
{
this(repository, delta);
this(info.repository, info.delta);
this.renderer = new DiffViewFileRendererText(repository, delta, new_is_workdir, handle_selection);
this.renderer = new DiffViewFileRendererText(info, handle_selection);
this.renderer.show();
this.renderer.bind_property("added", d_diff_stat_file, "added");
@ -118,6 +118,11 @@ class Gitg.DiffViewFile : Gtk.Grid
public DiffViewFile.binary(Repository? repository, Ggit.DiffDelta delta)
{
this(repository, delta);
this.renderer = new DiffViewFileRendererBinary();
this.renderer.show();
d_diff_stat_file.hide();
}
public DiffViewFile.image(Repository? repository, Ggit.DiffDelta delta)

View file

@ -265,8 +265,6 @@ public class Gitg.DiffView : Gtk.Grid
}
}
private delegate void Anon();
private void auto_change_expanded(bool expanded)
{
SignalHandler.block(d_commit_details, d_expanded_notify);
@ -307,13 +305,74 @@ public class Gitg.DiffView : Gtk.Grid
return path;
}
private delegate void Anon();
private string key_for_delta(Ggit.DiffDelta delta)
{
var new_file = delta.get_new_file();
var new_path = new_file.get_path();
if (new_path != null)
{
return @"path:$(new_path)";
}
var old_file = delta.get_old_file();
var old_path = old_file.get_path();
if (old_path != null)
{
return @"path:$(old_path)";
}
return "";
}
private void update_diff(Ggit.Diff diff, bool preserve_expanded, Cancellable? cancellable)
{
var nqueries = 0;
var finished = false;
var infomap = new Gee.HashMap<string, DiffViewFileInfo>();
Anon check_finish = () => {
if (nqueries == 0 && finished && (cancellable == null || !cancellable.is_cancelled()))
{
finished = false;
update_diff_hunks(diff, preserve_expanded, infomap, cancellable);
}
};
// Collect file info asynchronously first
for (var i = 0; i < diff.get_num_deltas(); i++)
{
var delta = diff.get_delta(i);
var info = new DiffViewFileInfo(repository, delta, new_is_workdir);
nqueries++;
info.query.begin(cancellable, (obj, res) => {
info.query.end(res);
infomap[key_for_delta(delta)] = info;
nqueries--;
check_finish();
});
}
finished = true;
check_finish();
}
private void update_diff_hunks(Ggit.Diff diff, bool preserve_expanded, Gee.HashMap<string, DiffViewFileInfo> infomap, Cancellable? cancellable)
{
var files = new Gee.ArrayList<Gitg.DiffViewFile>();
Gitg.DiffViewFile? current_file = null;
Ggit.DiffHunk? current_hunk = null;
Gee.ArrayList<Ggit.DiffLine>? current_lines = null;
var current_is_binary = false;
var maxlines = 0;
@ -352,8 +411,40 @@ public class Gitg.DiffView : Gtk.Grid
add_file();
current_file = new Gitg.DiffViewFile.text(repository, delta, new_is_workdir, handle_selection);
this.bind_property("highlight", current_file.renderer, "highlight", BindingFlags.SYNC_CREATE);
DiffViewFileInfo? info = null;
var deltakey = key_for_delta(delta);
if (infomap.has_key(deltakey))
{
info = infomap[deltakey];
}
else
{
info = new DiffViewFileInfo(repository, delta, new_is_workdir);
}
current_is_binary = ((delta.get_flags() & Ggit.DiffFlag.BINARY) != 0);
// List of known binary file types that may be wrongly classified by
// libgit2 because it does not contain any null bytes in the first N
// bytes. E.g. PDF
var known_binary_files_types = new string[] {"application/pdf"};
// Ignore binary based on content type
if (info != null && info.new_file_content_type in known_binary_files_types)
{
current_is_binary = true;
}
if (current_is_binary)
{
current_file = new Gitg.DiffViewFile.binary(repository, delta);
}
else
{
current_file = new Gitg.DiffViewFile.text(info, handle_selection);
this.bind_property("highlight", current_file.renderer, "highlight", BindingFlags.SYNC_CREATE);
}
return 0;
},
@ -374,13 +465,16 @@ public class Gitg.DiffView : Gtk.Grid
return 1;
}
maxlines = int.max(maxlines, hunk.get_old_start() + hunk.get_old_lines());
maxlines = int.max(maxlines, hunk.get_new_start() + hunk.get_new_lines());
if (!current_is_binary)
{
maxlines = int.max(maxlines, hunk.get_old_start() + hunk.get_old_lines());
maxlines = int.max(maxlines, hunk.get_new_start() + hunk.get_new_lines());
add_hunk();
add_hunk();
current_hunk = hunk;
current_lines = new Gee.ArrayList<Ggit.DiffLine>();
current_hunk = hunk;
current_lines = new Gee.ArrayList<Ggit.DiffLine>();
}
return 0;
},
@ -391,7 +485,7 @@ public class Gitg.DiffView : Gtk.Grid
return 1;
}
if ((delta.get_flags() & Ggit.DiffFlag.BINARY) == 0)
if (!current_is_binary)
{
current_lines.add(line);
}

View file

@ -6,6 +6,7 @@
<file compressed="true" preprocess="xml-stripblanks">ui/gitg-diff-view.ui</file>
<file compressed="true" preprocess="xml-stripblanks">ui/gitg-diff-view-file.ui</file>
<file compressed="true" preprocess="xml-stripblanks">ui/gitg-diff-view-file-renderer-text.ui</file>
<file compressed="true" preprocess="xml-stripblanks">ui/gitg-diff-view-file-renderer-binary.ui</file>
<file compressed="true" preprocess="xml-stripblanks">ui/gitg-diff-view-options.ui</file>
<file compressed="true" preprocess="xml-stripblanks">ui/gitg-diff-view-options-spacing.ui</file>
<file compressed="true" preprocess="xml-stripblanks">ui/gitg-diff-view-commit-details.ui</file>

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.19.0 -->
<interface>
<requires lib="gtk+" version="3.16"/>
<template class="GitgDiffViewFileRendererBinary" parent="GtkGrid">
<property name="visible">True</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="label" translatable="yes">Unable to display changes for binary file</property>
<property name="margin-top">3</property>
<property name="margin-start">12</property>
<property name="margin-end">12</property>
<property name="margin-bottom">9</property>
<property name="sensitive">False</property>
<attributes>
<attribute name="weight" value="PANGO_WEIGHT_BOLD"/>
</attributes>
</object>
</child>
</template>
</interface>

View file

@ -44,6 +44,8 @@
<property name="hexpand">True</property>
<property name="label">the/file/header</property>
<property name="halign">start</property>
<property name="margin_top">6</property>
<property name="margin_bottom">6</property>
</object>
</child>
</object>

View file

@ -62,6 +62,7 @@ plugins/files/gitg-files.vala
[type: gettext/glade]gitg/resources/ui/gitg-window.ui
[type: gettext/glade]libgitg/resources/ui/gitg-authentication-dialog.ui
[type: gettext/glade]libgitg/resources/ui/gitg-diff-view-commit-details.ui
[type: gettext/glade]libgitg/resources/ui/gitg-diff-view-file-renderer-binary.ui
[type: gettext/glade]libgitg/resources/ui/gitg-diff-view-options-spacing.ui
[type: gettext/glade]libgitg/resources/ui/gitg-diff-view-options.ui
[type: gettext/glade]libgitg/resources/ui/gitg-repository-list-box-row.ui