Allow to sign commits

This commit is contained in:
Adwait Rawat 2022-10-10 22:18:01 +02:00 committed by Alberto Fanjul
parent 32271214d3
commit 4ce63eeb37
12 changed files with 1822 additions and 11 deletions

View file

@ -371,5 +371,8 @@
<key name="sign-off" type="b">
<default>false</default>
</key>
<key name="sign-commit" type="b">
<default>false</default>
</key>
</schema>
</schemalist>

View file

@ -57,6 +57,9 @@ class Dialog : Gtk.Dialog
[GtkChild (name = "check_button_sign_off")]
private unowned Gtk.CheckButton d_check_button_sign_off;
[GtkChild (name = "check_button_sign_commit")]
private Gtk.CheckButton d_check_button_sign_commit;
[GtkChild (name = "image_avatar")]
private unowned Gtk.Image d_image_avatar;
@ -218,6 +221,8 @@ class Dialog : Gtk.Dialog
public bool sign_off { get; set; }
public bool sign_commit { get; set; }
public int max_number_commit_messages { get; set; }
public int max_number_days_commit_messages { get; set; }
@ -478,6 +483,11 @@ class Dialog : Gtk.Dialog
BindingFlags.BIDIRECTIONAL |
BindingFlags.SYNC_CREATE);
d_check_button_sign_commit.bind_property("active",
this, "sign-commit",
BindingFlags.BIDIRECTIONAL |
BindingFlags.SYNC_CREATE);
d_commit_settings = new Settings(Gitg.Config.APPLICATION_ID + ".state.commit");
d_commit_settings.bind("sign-off",
@ -486,6 +496,12 @@ class Dialog : Gtk.Dialog
SettingsBindFlags.GET |
SettingsBindFlags.SET);
d_commit_settings.bind("sign-commit",
this,
"sign-commit",
SettingsBindFlags.GET |
SettingsBindFlags.SET);
d_message_settings = new Settings(Gitg.Config.APPLICATION_ID + ".preferences.commit.message");
d_message_settings.bind("max-number-days-commit-messages",

View file

@ -1099,6 +1099,11 @@ namespace GitgCommit
opts |= Gitg.StageCommitOptions.SIGN_OFF;
}
if (dlg.sign_commit)
{
opts |= Gitg.StageCommitOptions.SIGN_COMMIT;
}
if (skip_hooks)
{
opts |= Gitg.StageCommitOptions.SKIP_HOOKS;

View file

@ -73,7 +73,6 @@ deps = [
libpeas_dep,
libdazzle_dep,
json_glib_dependency,
valac.find_library ('posix'),
]

View file

@ -246,6 +246,23 @@
<property name="height">1</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="check_button_sign_commit">
<property name="label" translatable="yes">Add si_gnature</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_underline">True</property>
<property name="halign">start</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">7</property>
<property name="width">2</property>
<property name="height">1</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
@ -280,7 +297,7 @@
</object>
<packing>
<property name="left_attach">2</property>
<property name="top_attach">6</property>
<property name="top_attach">7</property>
<property name="width">1</property>
<property name="height">1</property>
</packing>

View file

@ -0,0 +1,63 @@
/*
* This file is part of gitg
*
* Copyright (C) 2022 - Alberto Fanjul
*
* 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/>.
*/
using GPG;
namespace Gitg
{
public class GPGUtils
{
public static string sign_commit_object(string commit_content,
string signing_key) throws Error
{
check_version();
Data plain_data;
Data signed_data;
Data.create(out signed_data);
Data.create_from_memory(out plain_data, commit_content.data, false);
Context context;
Context.Context(out context);
context.set_armor(true);
Key key;
context.get_key(signing_key, out key, true);
if (key != null)
context.signers_add(key);
context.op_sign(plain_data, signed_data, SigMode.DETACH);
return get_string_from_data(signed_data);
}
private static string get_string_from_data(Data data) {
data.seek(0);
uint8[] buf = new uint8[256];
ssize_t? len = null;
string res = "";
do {
len = data.read(buf);
if (len > 0) {
string part = (string) buf;
part = part.substring(0, (long) len);
res += part;
}
} while (len > 0);
return res;
}
}
}

View file

@ -26,7 +26,8 @@ public enum StageCommitOptions
NONE = 0,
SIGN_OFF = 1 << 0,
AMEND = 1 << 1,
SKIP_HOOKS = 1 << 2
SKIP_HOOKS = 1 << 2,
SIGN_COMMIT= 1 << 3
}
public errordomain StageError
@ -34,7 +35,10 @@ public errordomain StageError
PRE_COMMIT_HOOK_FAILED,
COMMIT_MSG_HOOK_FAILED,
NOTHING_TO_COMMIT,
INDEX_ENTRY_NOT_FOUND
INDEX_ENTRY_NOT_FOUND,
SIGN_CONFIG_NOT_FOUND,
SIGN_CONFIG_ERROR,
UPDATE_REF_ERROR
}
public class PatchSet
@ -92,6 +96,8 @@ public class PatchSet
public class Stage : Object
{
private const string CONFIG_USER_SIGNINGKEY = "user.signingkey";
private weak Repository d_repository;
private Mutex d_index_mutex;
private Ggit.Tree? d_head_tree;
@ -400,6 +406,7 @@ public class Stage : Object
yield Async.thread(() => {
bool skip_hooks = (options & StageCommitOptions.SKIP_HOOKS) != 0;
bool amend = (options & StageCommitOptions.AMEND) != 0;
bool sign_commit = (options & StageCommitOptions.SIGN_COMMIT) != 0;
// Write tree from index
var conf = d_repository.get_config().snapshot();
@ -448,13 +455,63 @@ public class Stage : Object
pars = parents;
}
ret = d_repository.create_commit_from_ids(reference.get_name(),
author,
committer,
encoding,
emsg,
treeoid,
pars);
if (sign_commit)
{
string signing_key;
try {
signing_key = conf.get_string(CONFIG_USER_SIGNINGKEY);
} catch (Error e) {
throw new StageError.SIGN_CONFIG_NOT_FOUND(_("setup “%s” to do a signed commit"), CONFIG_USER_SIGNINGKEY);
}
Ggit.Commit[] parent_commits = new Ggit.Commit[pars.length];
for (int i = 0; i < pars.length; i++) {
Ggit.Commit commit = d_repository.lookup<Ggit.Commit>(pars[i]);
parent_commits[i] = commit;
}
var data = d_repository.create_commit_buffer(author,
committer,
encoding,
emsg,
d_repository.lookup_tree(treeoid),
parent_commits);
string signature;
try {
signature = Gitg.GPGUtils.sign_commit_object(data, signing_key);
} catch (Error e) {
throw new StageError.SIGN_CONFIG_ERROR(_("error signing the commit “%s”"), e.message);
}
ret = d_repository.create_commit_with_signature(data, signature, null);
Ggit.Ref? resolved;
if (reference.get_reference_type() == Ggit.RefType.SYMBOLIC)
{
try
{
resolved = reference.resolve();
} catch (Error e) {
throw new StageError.UPDATE_REF_ERROR(_("error updating current ref “%s”"), reference.get_name());
}
}
else
{
resolved = reference;
}
resolved.set_target(ret, null);
}
else
{
ret = d_repository.create_commit_from_ids(reference.get_name(),
author,
committer,
encoding,
emsg,
treeoid,
pars);
}
}
else
{

View file

@ -14,6 +14,7 @@ common_deps = [
gtk_dep,
libgit2_glib_dep,
libdazzle_dep,
gpgme_dependency,
]
sources = files(
@ -52,6 +53,7 @@ sources = files(
'gitg-diff-view-options.vala',
'gitg-diff-view.vala',
'gitg-font-manager.vala',
'gitg-gpg-utils.vala',
'gitg-hook.vala',
'gitg-init.vala',
'gitg-label-renderer.vala',

View file

@ -137,6 +137,7 @@ libsecret_dep = dependency('libsecret-1')
libxml_dep = dependency('libxml-2.0', version: '>= 2.9.0')
libdazzle_dep = dependency('libdazzle-1.0')
json_glib_dependency = dependency('json-glib-1.0')
gpgme_dependency = dependency('gpgme')
config_dep = valac.find_library('config', dirs: vapi_dir)
gitg_platform_support_dep = valac.find_library('gitg-platform-support', dirs: vapi_dir)

422
vapi/gpg-error.vapi Normal file
View file

@ -0,0 +1,422 @@
/* gpg-error.vapi
*
* Copyright (C) 2009 Sebastian Reichel <sre@ring0.de>
* Copyright (C) 2022 Itay Grudev <itay+git2022@grudev.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
[CCode (cheader_filename = "gpg-error.h")]
namespace GPGError {
/**
* The error value type gpg_error_t
*/
[SimpleType]
[CCode (cname = "gpg_error_t", has_type_id = false)]
public struct Error {
public GPGError.ErrorCode code {
[CCode (cname = "gpg_err_code")]
get;
}
}
/**
* The error code type gpg_err_code_t.
*/
[CCode (cname = "gpg_err_code_t", cprefix = "GPG_ERR_", has_type_id = false)]
public enum ErrorCode {
NO_ERROR,
GENERAL,
UNKNOWN_PACKET,
UNKNOWN_VERSION,
PUBKEY_ALGO,
DIGEST_ALGO,
BAD_PUBKEY,
BAD_SECKEY,
BAD_SIGNATURE,
NO_PUBKEY,
CHECKSUM,
BAD_PASSPHRASE,
CIPHER_ALGO,
KEYRING_OPEN,
INV_PACKET,
INV_ARMOR,
NO_USER_ID,
NO_SECKEY,
WRONG_SECKEY,
BAD_KEY,
COMPR_ALGO,
NO_PRIME,
NO_ENCODING_METHOD,
NO_ENCRYPTION_SCHEME,
NO_SIGNATURE_SCHEME,
INV_ATTR,
NO_VALUE,
NOT_FOUND,
VALUE_NOT_FOUND,
SYNTAX,
BAD_MPI,
INV_PASSPHRASE,
SIG_CLASS,
RESOURCE_LIMIT,
INV_KEYRING,
TRUSTDB,
BAD_CERT,
INV_USER_ID,
UNEXPECTED,
TIME_CONFLICT,
KEYSERVER,
WRONG_PUBKEY_ALGO,
TRIBUTE_TO_D_A,
WEAK_KEY,
INV_KEYLEN,
INV_ARG,
BAD_URI,
INV_URI,
NETWORK,
UNKNOWN_HOST,
SELFTEST_FAILED,
NOT_ENCRYPTED,
NOT_PROCESSED,
UNUSABLE_PUBKEY,
UNUSABLE_SECKEY,
INV_VALUE,
BAD_CERT_CHAIN,
MISSING_CERT,
NO_DATA,
BUG,
NOT_SUPPORTED,
INV_OP,
TIMEOUT,
INTERNAL,
EOF_GCRYPT,
INV_OBJ,
TOO_SHORT,
TOO_LARGE,
NO_OBJ,
NOT_IMPLEMENTED,
CONFLICT,
INV_CIPHER_MODE,
INV_FLAG,
INV_HANDLE,
TRUNCATED,
INCOMPLETE_LINE,
INV_RESPONSE,
NO_AGENT,
AGENT,
INV_DATA,
ASSUAN_SERVER_FAULT,
ASSUAN,
INV_SESSION_KEY,
INV_SEXP,
UNSUPPORTED_ALGORITHM,
NO_PIN_ENTRY,
PIN_ENTRY,
BAD_PIN,
INV_NAME,
BAD_DATA,
INV_PARAMETER,
WRONG_CARD,
NO_DIRMNGR,
DIRMNGR,
CERT_REVOKED,
NO_CRL_KNOWN,
CRL_TOO_OLD,
LINE_TOO_LONG,
NOT_TRUSTED,
CANCELED,
BAD_CA_CERT,
CERT_EXPIRED,
CERT_TOO_YOUNG,
UNSUPPORTED_CERT,
UNKNOWN_SEXP,
UNSUPPORTED_PROTECTION,
CORRUPTED_PROTECTION,
AMBIGUOUS_NAME,
CARD,
CARD_RESET,
CARD_REMOVED,
INV_CARD,
CARD_NOT_PRESENT,
NO_PKCS15_APP,
NOT_CONFIRMED,
CONFIGURATION,
NO_POLICY_MATCH,
INV_INDEX,
INV_ID,
NO_SCDAEMON,
SCDAEMON,
UNSUPPORTED_PROTOCOL,
BAD_PIN_METHOD,
CARD_NOT_INITIALIZED,
UNSUPPORTED_OPERATION,
WRONG_KEY_USAGE,
NOTHING_FOUND,
WRONG_BLOB_TYPE,
MISSING_VALUE,
HARDWARE,
PIN_BLOCKED,
USE_CONDITIONS,
PIN_NOT_SYNCED,
INV_CRL,
BAD_BER,
INV_BER,
ELEMENT_NOT_FOUND,
IDENTIFIER_NOT_FOUND,
INV_TAG,
INV_LENGTH,
INV_KEYINFO,
UNEXPECTED_TAG,
NOT_DER_ENCODED,
NO_CMS_OBJ,
INV_CMS_OBJ,
UNKNOWN_CMS_OBJ,
UNSUPPORTED_CMS_OBJ,
UNSUPPORTED_ENCODING,
UNSUPPORTED_CMS_VERSION,
UNKNOWN_ALGORITHM,
INV_ENGINE,
PUBKEY_NOT_TRUSTED,
DECRYPT_FAILED,
KEY_EXPIRED,
SIG_EXPIRED,
ENCODING_PROBLEM,
INV_STATE,
DUP_VALUE,
MISSING_ACTION,
MODULE_NOT_FOUND,
INV_OID_STRING,
INV_TIME,
INV_CRL_OBJ,
UNSUPPORTED_CRL_VERSION,
INV_CERT_OBJ,
UNKNOWN_NAME,
LOCALE_PROBLEM,
NOT_LOCKED,
PROTOCOL_VIOLATION,
INV_MAC,
INV_REQUEST,
UNKNOWN_EXTN,
UNKNOWN_CRIT_EXTN,
LOCKED,
UNKNOWN_OPTION,
UNKNOWN_COMMAND,
UNFINISHED,
BUFFER_TOO_SHORT,
SEXP_INV_LEN_SPEC,
SEXP_STRING_TOO_LONG,
SEXP_UNMATCHED_PAREN,
SEXP_NOT_CANONICAL,
SEXP_BAD_CHARACTER,
SEXP_BAD_QUOTATION,
SEXP_ZERO_PREFIX,
SEXP_NESTED_DH,
SEXP_UNMATCHED_DH,
SEXP_UNEXPECTED_PUNC,
SEXP_BAD_HEX_CHAR,
SEXP_ODD_HEX_NUMBERS,
SEXP_BAD_OCT_CHAR,
ASS_GENERAL,
ASS_ACCEPT_FAILED,
ASS_CONNECT_FAILED,
ASS_INV_RESPONSE,
ASS_INV_VALUE,
ASS_INCOMPLETE_LINE,
ASS_LINE_TOO_LONG,
ASS_NESTED_COMMANDS,
ASS_NO_DATA_CB,
ASS_NO_INQUIRE_CB,
ASS_NOT_A_SERVER,
ASS_NOT_A_CLIENT,
ASS_SERVER_START,
ASS_READ_ERROR,
ASS_WRITE_ERROR,
ASS_TOO_MUCH_DATA,
ASS_UNEXPECTED_CMD,
ASS_UNKNOWN_CMD,
ASS_SYNTAX,
ASS_CANCELED,
ASS_NO_INPUT,
ASS_NO_OUTPUT,
ASS_PARAMETER,
ASS_UNKNOWN_INQUIRE,
USER_1,
USER_2,
USER_3,
USER_4,
USER_5,
USER_6,
USER_7,
USER_8,
USER_9,
USER_10,
USER_11,
USER_12,
USER_13,
USER_14,
USER_15,
USER_16,
MISSING_ERRNO,
UNKNOWN_ERRNO,
EOF,
E2BIG,
EACCES,
EADDRINUSE,
EADDRNOTAVAIL,
EADV,
EAFNOSUPPORT,
EAGAIN,
EALREADY,
EAUTH,
EBACKGROUND,
EBADE,
EBADF,
EBADFD,
EBADMSG,
EBADR,
EBADRPC,
EBADRQC,
EBADSLT,
EBFONT,
EBUSY,
ECANCELED,
ECHILD,
ECHRNG,
ECOMM,
ECONNABORTED,
ECONNREFUSED,
ECONNRESET,
ED,
EDEADLK,
EDEADLOCK,
EDESTADDRREQ,
EDIED,
EDOM,
EDOTDOT,
EDQUOT,
EEXIST,
EFAULT,
EFBIG,
EFTYPE,
EGRATUITOUS,
EGREGIOUS,
EHOSTDOWN,
EHOSTUNREACH,
EIDRM,
EIEIO,
EILSEQ,
EINPROGRESS,
EINTR,
EINVAL,
EIO,
EISCONN,
EISDIR,
EISNAM,
EL2HLT,
EL2NSYNC,
EL3HLT,
EL3RST,
ELIBACC,
ELIBBAD,
ELIBEXEC,
ELIBMAX,
ELIBSCN,
ELNRNG,
ELOOP,
EMEDIUMTYPE,
EMFILE,
EMLINK,
EMSGSIZE,
EMULTIHOP,
ENAMETOOLONG,
ENAVAIL,
ENEEDAUTH,
ENETDOWN,
ENETRESET,
ENETUNREACH,
ENFILE,
ENOANO,
ENOBUFS,
ENOCSI,
ENODATA,
ENODEV,
ENOENT,
ENOEXEC,
ENOLCK,
ENOLINK,
ENOMEDIUM,
ENOMEM,
ENOMSG,
ENONET,
ENOPKG,
ENOPROTOOPT,
ENOSPC,
ENOSR,
ENOSTR,
ENOSYS,
ENOTBLK,
ENOTCONN,
ENOTDIR,
ENOTEMPTY,
ENOTNAM,
ENOTSOCK,
ENOTSUP,
ENOTTY,
ENOTUNIQ,
ENXIO,
EOPNOTSUPP,
EOVERFLOW,
EPERM,
EPFNOSUPPORT,
EPIPE,
EPROCLIM,
EPROCUNAVAIL,
EPROGMISMATCH,
EPROGUNAVAIL,
EPROTO,
EPROTONOSUPPORT,
EPROTOTYPE,
ERANGE,
EREMCHG,
EREMOTE,
EREMOTEIO,
ERESTART,
EROFS,
ERPCMISMATCH,
ESHUTDOWN,
ESOCKTNOSUPPORT,
ESPIPE,
ESRCH,
ESRMNT,
ESTALE,
ESTRPIPE,
ETIME,
ETIMEDOUT,
ETOOMANYREFS,
ETXTBSY,
EUCLEAN,
EUNATCH,
EUSERS,
EWOULDBLOCK,
EXDEV,
EXFULL,
CODE_DIM
}
}

1
vapi/gpgme.deps Normal file
View file

@ -0,0 +1 @@
gpg-error

1225
vapi/gpgme.vapi Normal file

File diff suppressed because it is too large Load diff